Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as path from 'node:path';

import type * as ts from 'typescript';

import { Path, Text } from '@rushstack/node-core-library';
import { Path } from '@rushstack/node-core-library';

export interface ISourceFileLocationFormatOptions {
sourceFileLine?: number;
Expand Down Expand Up @@ -47,7 +47,7 @@ export class SourceFileLocationFormatter {
}

// Convert it to a Unix-style path
scrubbedPath = Text.replaceAll(scrubbedPath, '\\', '/');
scrubbedPath = Path.convertToSlashes(scrubbedPath);
result += scrubbedPath;

if (options.sourceFileLine) {
Expand Down
5 changes: 2 additions & 3 deletions apps/api-extractor/src/api/ExtractorConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
PackageJsonLookup,
type INodePackageJson,
PackageName,
Text,
InternalError,
Path,
NewlineKind
Expand Down Expand Up @@ -1310,8 +1309,8 @@ function _expandStringWithTokens(
): string {
value = value ? value.trim() : '';
if (value !== '') {
value = Text.replaceAll(value, '<unscopedPackageName>', tokenContext.unscopedPackageName);
value = Text.replaceAll(value, '<packageName>', tokenContext.packageName);
value = value.replaceAll('<unscopedPackageName>', tokenContext.unscopedPackageName);
value = value.replaceAll('<packageName>', tokenContext.packageName);

const projectFolderToken: string = '<projectFolder>';
if (value.indexOf(projectFolderToken) === 0) {
Expand Down
16 changes: 7 additions & 9 deletions apps/lockfile-explorer/src/graph/lfxGraphLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import type * as lockfileTypes from '@pnpm/lockfile.types';
import type * as pnpmTypes from '@pnpm/types';

import { Text } from '@rushstack/node-core-library';

import {
type ILfxGraphDependencyOptions,
type ILfxGraphEntryOptions,
Expand Down Expand Up @@ -398,9 +396,9 @@ function createPackageLockfileEntry(options: {

// Rewrite to:
// "@rushstack/m@1.0.0; @rushstack/n@2.0.0"
suffix = Text.replaceAll(suffix, ')(', '; ');
suffix = Text.replaceAll(suffix, '(', '');
suffix = Text.replaceAll(suffix, ')', '');
suffix = suffix.replaceAll(')(', '; ');
suffix = suffix.replaceAll('(', '');
suffix = suffix.replaceAll(')', '');
result.entrySuffix = suffix;

// @rushstack/l@1.0.0(@rushstack/m@1.0.0)(@rushstack/n@2.0.0)
Expand All @@ -412,10 +410,10 @@ function createPackageLockfileEntry(options: {
// --> @rushstack+l@1.0.0_@rushstack+m@1.0.0_@rushstack+n@2.0.0

// @rushstack/l 1.0.0 (@rushstack/m@1.0.0)(@rushstack/n@2.0.0)
dotPnpmSubfolder = Text.replaceAll(slashlessRawEntryId, '/', '+');
dotPnpmSubfolder = Text.replaceAll(dotPnpmSubfolder, ')(', '_');
dotPnpmSubfolder = Text.replaceAll(dotPnpmSubfolder, '(', '_');
dotPnpmSubfolder = Text.replaceAll(dotPnpmSubfolder, ')', '');
dotPnpmSubfolder = slashlessRawEntryId.replaceAll('/', '+');
dotPnpmSubfolder = dotPnpmSubfolder.replaceAll(')(', '_');
dotPnpmSubfolder = dotPnpmSubfolder.replaceAll('(', '_');
dotPnpmSubfolder = dotPnpmSubfolder.replaceAll(')', '');
}

// Example:
Expand Down
6 changes: 1 addition & 5 deletions apps/lockfile-explorer/src/utils/PackageUpdateChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,8 @@ const CACHE_FOLDER: string = `${homedir()}/.rushstack/update-checks`;

async function _tryFetchLatestVersionAsync(packageName: string): Promise<string | undefined> {
const url: string = `${REGISTRY_BASE_URL}/${encodeURIComponent(packageName)}/latest`;
const controller: AbortController = new AbortController();
const timeout: NodeJS.Timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
try {
const response: Response = await fetch(url, { signal: controller.signal });
const response: Response = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
if (!response.ok) {
return undefined;
}
Expand All @@ -101,8 +99,6 @@ async function _tryFetchLatestVersionAsync(packageName: string): Promise<string
} catch {
// Network errors, timeouts, and parse failures are all silent.
return undefined;
} finally {
clearTimeout(timeout);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,37 @@ describe(PackageUpdateChecker.name, () => {
expect(saveSpy).not.toHaveBeenCalled();
});

it('returns undefined when the registry request times out', async () => {
loadSpy.mockRejectedValue(new Error('ENOENT'));

const nativeTimeout: typeof AbortSignal.timeout = AbortSignal.timeout.bind(AbortSignal);
const timeoutSpy: jest.SpyInstance = jest
.spyOn(AbortSignal, 'timeout')
.mockImplementation(() => nativeTimeout(1));

let fetchSignal: AbortSignal | undefined;
fetchSpy.mockImplementation(
async (input: Parameters<typeof fetch>[0], init?: RequestInit): Promise<Response> => {
void input;
fetchSignal = init?.signal as AbortSignal;
await new Promise<void>((resolve) => {
fetchSignal!.addEventListener('abort', () => resolve(), { once: true });
});
throw fetchSignal.reason;
}
);

const checker: PackageUpdateChecker = new PackageUpdateChecker({
packageName: PACKAGE_NAME,
currentVersion: CURRENT_VERSION
});
expect(await checker.tryGetUpdateAsync()).toBeUndefined();
expect(fetchSignal?.aborted).toBe(true);
expect((fetchSignal?.reason as DOMException).name).toBe('TimeoutError');
expect(timeoutSpy).toHaveBeenCalledWith(5000);
expect(saveSpy).not.toHaveBeenCalled();
});

it('returns undefined on non-ok HTTP response', async () => {
loadSpy.mockRejectedValue(new Error('ENOENT'));
fetchSpy.mockResolvedValue(makeFetchResponse('', false));
Expand Down
4 changes: 2 additions & 2 deletions apps/rundown/src/Rundown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as path from 'node:path';

import stringArgv from 'string-argv';

import { FileSystem, PackageJsonLookup, Sort, Text } from '@rushstack/node-core-library';
import { FileSystem, PackageJsonLookup, Path, Sort } from '@rushstack/node-core-library';

import type { IpcMessage } from './LauncherTypes';

Expand Down Expand Up @@ -57,7 +57,7 @@ export class Rundown {
importedPackageFolders.add(path.basename(importedPackageFolder));
} else {
const relativePath: string = path.relative(process.cwd(), importedPackageFolder);
importedPackageFolders.add(Text.replaceAll(relativePath, '\\', '/'));
importedPackageFolders.add(Path.convertToSlashes(relativePath));
}
} else {
// If the importedPath does not belong to an NPM package, then rundown-snapshot.log can ignore it.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@rushstack/node-core-library",
"comment": "Deprecate `Text.replaceAll()` in favor of the native `String.prototype.replaceAll()` function.",
"type": "minor"
}
],
"packageName": "@rushstack/node-core-library",
"email": "5100938+bmiddha@users.noreply.github.com"
}
1 change: 1 addition & 0 deletions common/reviews/api/node-core-library.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,7 @@ export class Text {
static padStart(s: string, minimumLength: number, paddingCharacter?: string): string;
static readLinesFromIterable(iterable: Iterable<string | Buffer | null>, options?: IReadLinesFromIterableOptions): Generator<string>;
static readLinesFromIterableAsync(iterable: AsyncIterable<string | Buffer>, options?: IReadLinesFromIterableOptions): AsyncGenerator<string>;
// @deprecated
static replaceAll(input: string, searchValue: string, replaceValue: string): string;
static reverse(s: string): string;
static splitByNewLines(s: undefined): undefined;
Expand Down
4 changes: 1 addition & 3 deletions libraries/node-core-library/src/JsonFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -570,8 +570,7 @@ function _formatJsonHeaderComment(headerComment: string): string {
if (headerComment === '') {
return '';
}
const lines: string[] = headerComment.split('\n');
const result: string[] = [];
const lines: string[] = Text.convertToLf(headerComment).split('\n');
for (const line of lines) {
if (!/^\s*$/.test(line) && !/^\s*\/\//.test(line)) {
throw new Error(
Expand All @@ -580,7 +579,6 @@ function _formatJsonHeaderComment(headerComment: string): string {
JSON.stringify(line)
);
}
result.push(Text.replaceAll(line, '\r', ''));
}
return lines.join('\n') + '\n';
}
Expand Down
13 changes: 10 additions & 3 deletions libraries/node-core-library/src/Text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,21 @@ function* readLinesFromChunk(
*/
export class Text {
/**
* Returns the same thing as targetString.replace(searchValue, replaceValue), except that
* all matches are replaced, rather than just the first match.
* Replaces every occurrence of `searchValue` with `replaceValue`.
*
* @remarks
* This method has the same behavior as {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll | String.prototype.replaceAll}.
* In particular, if `searchValue` is an empty string, `replaceValue` is inserted before the first
* UTF-16 code unit, between each code unit, and after the last code unit.
*
* @param input - The string to be modified
* @param searchValue - The value to search for
* @param replaceValue - The replacement text
*
* @deprecated Use `String.prototype.replaceAll()` instead.
*/
public static replaceAll(input: string, searchValue: string, replaceValue: string): string {
return input.split(searchValue).join(replaceValue);
return input.replaceAll(searchValue, replaceValue);
}

/**
Expand Down
6 changes: 3 additions & 3 deletions libraries/node-core-library/src/test/Executable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as os from 'node:os';
import * as path from 'node:path';
import type * as child_process from 'node:child_process';
import { once } from 'node:events';
import { Readable } from 'node:stream';

import {
Executable,
Expand All @@ -16,9 +17,8 @@ import {
type IWaitForExitResultWithoutOutput
} from '../Executable';
import { FileSystem } from '../FileSystem';
import { Path } from '../Path';
import { PosixModeBits } from '../PosixModeBits';
import { Text } from '../Text';
import { Readable } from 'node:stream';

describe('Executable process tests', () => {
// The PosixModeBits are intended to be used with bitwise operations.
Expand Down Expand Up @@ -101,7 +101,7 @@ describe('Executable process tests', () => {
test('Executable.tryResolve() pathless', () => {
const resolved: string | undefined = Executable.tryResolve('npm-binary-wrapper', options);
expect(resolved).toBeDefined();
const resolvedRelative: string = Text.replaceAll(path.relative(executableFolder, resolved!), '\\', '/');
const resolvedRelative: string = Path.convertToSlashes(path.relative(executableFolder, resolved!));

if (os.platform() === 'win32') {
// On Windows, we should find npm-binary-wrapper.cmd instead of npm-binary-wrapper
Expand Down
4 changes: 2 additions & 2 deletions libraries/rush-lib/src/api/test/RushConfiguration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

import * as path from 'node:path';

import { JsonFile, Path, Text } from '@rushstack/node-core-library';
import { JsonFile, Path } from '@rushstack/node-core-library';
import { RushConfiguration } from '../RushConfiguration';
import type { ApprovedPackagesPolicy } from '../ApprovedPackagesPolicy';
import { RushConfigurationProject } from '../RushConfigurationProject';
import { EnvironmentConfiguration } from '../EnvironmentConfiguration';
import { DependencyType } from '../PackageJsonEditor';

function normalizePathForComparison(pathToNormalize: string): string {
return Text.replaceAll(pathToNormalize, '\\', '/').toUpperCase();
return Path.convertToSlashes(pathToNormalize).toUpperCase();
}

function assertPathProperty(validatedPropertyName: string, absolutePath: string, relativePath: string): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
type IPackageJson,
JsonFile,
AlreadyReportedError,
Text
} from '@rushstack/node-core-library';
import { Colorize } from '@rushstack/terminal';

Expand Down Expand Up @@ -264,7 +263,7 @@ export class GlobalScriptAction extends BaseScriptAction<IGlobalCommandConfig> {
): string {
let expandedShellCommand: string = shellCommand;
for (const [token, tokenReplacement] of Object.entries(tokenContext)) {
expandedShellCommand = Text.replaceAll(expandedShellCommand, `<${token}>`, tokenReplacement);
expandedShellCommand = expandedShellCommand.replaceAll(`<${token}>`, tokenReplacement);
}
return expandedShellCommand;
}
Expand Down
3 changes: 1 addition & 2 deletions libraries/rush-lib/src/logic/PublishUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
type IPackageJson,
JsonFile,
FileConstants,
Text,
Enum,
InternalError,
Executable
Expand Down Expand Up @@ -282,7 +281,7 @@ export class PublishUtilities {

if (secretSubstring && secretSubstring.length > 0) {
// Avoid printing the NPM publish token on the console when displaying the commandArgs
commandArgs = Text.replaceAll(commandArgs, secretSubstring, '<<SECRET>>');
commandArgs = commandArgs.replaceAll(secretSubstring, '<<SECRET>>');
}

// eslint-disable-next-line no-console
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import * as ssri from 'ssri';

import {
JsonFile,
Text,
FileSystem,
FileConstants,
Sort,
InternalError,
AlreadyReportedError
AlreadyReportedError,
Path
} from '@rushstack/node-core-library';
import { Colorize, PrintUtilities } from '@rushstack/terminal';

Expand Down Expand Up @@ -558,11 +558,8 @@ export class RushInstallManager extends BaseInstallManager {
// eslint-disable-next-line no-console
console.log(`Deleting ${pathToDeleteWithoutStar}\\*`);
// Glob can't handle Windows paths
const normalizedPathToDeleteWithoutStar: string = Text.replaceAll(
pathToDeleteWithoutStar,
'\\',
'/'
);
const normalizedPathToDeleteWithoutStar: string =
Path.convertToSlashes(pathToDeleteWithoutStar);

const { default: glob } = await import('fast-glob');
const tempModulePaths: string[] = await glob(
Expand Down Expand Up @@ -693,7 +690,7 @@ export class RushInstallManager extends BaseInstallManager {
RushConstants.rushTempNpmScope
);
// Glob can't handle Windows paths
const normalizedPathToDeleteWithoutStar: string = Text.replaceAll(pathToDeleteWithoutStar, '\\', '/');
const normalizedPathToDeleteWithoutStar: string = Path.convertToSlashes(pathToDeleteWithoutStar);

let anyChanges: boolean = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import type * as child_process from 'node:child_process';

import { Text } from '@rushstack/node-core-library';
import { Path } from '@rushstack/node-core-library';
import { type ITerminal, type ITerminalProvider, TerminalProviderSeverity } from '@rushstack/terminal';
Comment thread
bmiddha marked this conversation as resolved.

import type { IPhase } from '../../api/CommandLineConfiguration';
Expand Down Expand Up @@ -200,7 +200,7 @@ export function convertSlashesForWindows(command: string): string {
// "bin\blarg --path ./config/blah.json && a/b"
//
// NOTE: we don't attempt to process the path parameter or stuff after "&&"
return Text.replaceAll(commandPart, '/', '\\') + remainder;
return Path.convertToBackslashes(commandPart) + remainder;
}
}

Expand Down
4 changes: 2 additions & 2 deletions libraries/rush-lib/src/utilities/AsyncRecycler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as child_process from 'node:child_process';
import * as fs from 'node:fs';
import * as path from 'node:path';

import { Text, Path, FileSystem, type FolderItem } from '@rushstack/node-core-library';
import { Path, FileSystem, type FolderItem } from '@rushstack/node-core-library';

import { Utilities } from './Utilities';
import { IS_WINDOWS } from './executionUtilities';
Expand Down Expand Up @@ -131,7 +131,7 @@ export class AsyncRecycler {
command = 'cmd.exe';

// In PowerShell single-quote literals, single quotes are escaped by doubling them
const escapedRecyclerFolder: string = Text.replaceAll(this.recyclerFolder, "'", "''");
const escapedRecyclerFolder: string = this.recyclerFolder.replaceAll("'", "''");

// As of PowerShell 3.0, the "\\?" prefix can be used for paths that exceed MAX_PATH.
// (This prefix does not seem to work for cmd.exe's "rd" command.)
Expand Down
4 changes: 2 additions & 2 deletions libraries/terminal/src/test/TextRewriterTransform.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import { NewlineKind, Text } from '@rushstack/node-core-library';
import { NewlineKind } from '@rushstack/node-core-library';

import { Colorize } from '../Colorize';
import { TerminalChunkKind } from '../ITerminalChunk';
Expand Down Expand Up @@ -32,7 +32,7 @@ describe(TextRewriterTransform.name, () => {
expect(
mockWritable.chunks.map((x) => ({
kind: x.kind,
text: Text.replaceAll(x.text, '\n', '[n]')
text: x.text.replaceAll('\n', '[n]')
}))
).toMatchSnapshot();
});
Expand Down