Skip to content

Commit 99e0e61

Browse files
authored
Forbid private static members and refactor existing usages to module scope (#5884)
* Refactor private static members to module scope; enforce no-restricted-syntax rule Enforce the new ESLint rule in decoupled-local-node-rig that forbids `private static` methods and properties (kept at "error" severity alongside the existing `export *` restriction). Convert every `private static` violation across all rig-consuming projects to module-scoped functions and variables: - private static method -> module-scoped function - private static property -> module-scoped const (or let if reassigned) Edge cases handled without changing public behavior: - Helpers needing private instance members / private constructors kept as private instance methods, or return data for an in-class caller to construct (e.g. JsonSchema, LockFile, RigConfig, MinimalRushConfiguration). - Stateful singletons (e.g. EnvironmentConfiguration) moved to module state. - Async lazy-init caches suppressed with require-atomic-updates where needed. - Module declarations placed to preserve TSDoc/API-Extractor doc association and avoid no-use-before-define. - Test seams exported where tests referenced moved members. Full `rush build` passes with zero errors and zero warnings. * Fix OperationBuildCache test broken by private static refactor Moving `_tarUtilityPromise` from a `private static` field to a module-scoped variable silently invalidated the test's `Reflect.set(OperationBuildCache, '_tarUtilityPromise', ...)` mock injection, causing the restore test to run the real tar/untar path (EACCES on mkdir '/repo'). Add an exported `@internal` test seam `_setTarUtilityPromiseForTesting` and use it from the test instead of poking the class internals. * Rush change.
1 parent 5e32c62 commit 99e0e61

128 files changed

Lines changed: 7024 additions & 6966 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/api-documenter/src/nodes/CustomDocNodeKind.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ export enum CustomDocNodeKind {
2222
TableRow = 'TableRow'
2323
}
2424

25-
export class CustomDocNodes {
26-
private static _configuration: TSDocConfiguration | undefined;
25+
let _configuration: TSDocConfiguration | undefined;
2726

27+
export class CustomDocNodes {
2828
public static get configuration(): TSDocConfiguration {
29-
if (CustomDocNodes._configuration === undefined) {
29+
if (_configuration === undefined) {
3030
const configuration: TSDocConfiguration = new TSDocConfiguration();
3131

3232
configuration.docNodeManager.registerDocNodes('@micrososft/api-documenter', [
@@ -53,8 +53,8 @@ export class CustomDocNodes {
5353
CustomDocNodeKind.EmphasisSpan
5454
]);
5555

56-
CustomDocNodes._configuration = configuration;
56+
_configuration = configuration;
5757
}
58-
return CustomDocNodes._configuration;
58+
return _configuration;
5959
}
6060
}

apps/api-documenter/src/utils/Utilities.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33

44
import { ApiParameterListMixin, type ApiItem } from '@microsoft/api-extractor-model';
55

6+
const _badFilenameCharsRegExp: RegExp = /[^a-z0-9_\-\.]/gi;
7+
68
export class Utilities {
7-
private static readonly _badFilenameCharsRegExp: RegExp = /[^a-z0-9_\-\.]/gi;
89
/**
910
* Generates a concise signature for a function. Example: "getArea(width, height)"
1011
*/
@@ -21,6 +22,6 @@ export class Utilities {
2122
public static getSafeFilenameForName(name: string): string {
2223
// TODO: This can introduce naming collisions.
2324
// We will fix that as part of https://github.com/microsoft/rushstack/issues/1308
24-
return name.replace(Utilities._badFilenameCharsRegExp, '_').toLowerCase();
25+
return name.replace(_badFilenameCharsRegExp, '_').toLowerCase();
2526
}
2627
}

apps/api-extractor/src/analyzer/ExportAnalyzer.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,7 @@ export class ExportAnalyzer {
657657
importKind: AstImportKind.StarImport,
658658
exportName: declarationSymbol.name,
659659
modulePath: externalModulePath,
660-
isTypeOnly: ExportAnalyzer._getIsTypeOnly(importDeclaration)
660+
isTypeOnly: _getIsTypeOnly(importDeclaration)
661661
});
662662
}
663663

@@ -690,7 +690,7 @@ export class ExportAnalyzer {
690690
importKind: AstImportKind.NamedImport,
691691
modulePath: externalModulePath,
692692
exportName: exportName,
693-
isTypeOnly: ExportAnalyzer._getIsTypeOnly(importDeclaration)
693+
isTypeOnly: _getIsTypeOnly(importDeclaration)
694694
});
695695
}
696696

@@ -724,7 +724,7 @@ export class ExportAnalyzer {
724724
importKind: AstImportKind.DefaultImport,
725725
modulePath: externalModulePath,
726726
exportName,
727-
isTypeOnly: ExportAnalyzer._getIsTypeOnly(importDeclaration)
727+
isTypeOnly: _getIsTypeOnly(importDeclaration)
728728
});
729729
}
730730

@@ -794,13 +794,6 @@ export class ExportAnalyzer {
794794
return namespaceImport;
795795
}
796796

797-
private static _getIsTypeOnly(importDeclaration: ts.ImportDeclaration): boolean {
798-
if (importDeclaration.importClause) {
799-
return !!importDeclaration.importClause.isTypeOnly;
800-
}
801-
return false;
802-
}
803-
804797
private _getExportOfSpecifierAstModule(
805798
exportName: string,
806799
importOrExportDeclaration: ts.ImportDeclaration | ts.ExportDeclaration,
@@ -1012,3 +1005,10 @@ export class ExportAnalyzer {
10121005
return moduleSpecifier;
10131006
}
10141007
}
1008+
1009+
function _getIsTypeOnly(importDeclaration: ts.ImportDeclaration): boolean {
1010+
if (importDeclaration.importClause) {
1011+
return !!importDeclaration.importClause.isTypeOnly;
1012+
}
1013+
return false;
1014+
}

apps/api-extractor/src/analyzer/PackageMetadataManager.ts

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,33 @@ function _tryResolveTsdocMetadataFromMainField({ main }: INodePackageJson): stri
174174
}
175175
}
176176

177+
/**
178+
* This feature is still being standardized: https://github.com/microsoft/tsdoc/issues/7
179+
* In the future we will use the @microsoft/tsdoc library to read this file.
180+
*/
181+
function _resolveTsdocMetadataPathFromPackageJson(
182+
packageFolder: string,
183+
packageJson: INodePackageJson
184+
): string {
185+
const tsdocMetadataRelativePath: string =
186+
_tryResolveTsdocMetadataFromTsdocMetadataField(packageJson) ??
187+
_tryResolveTsdocMetadataFromExportsField(packageJson) ??
188+
_tryResolveTsdocMetadataFromTypesVersionsField(packageJson) ??
189+
_tryResolveTsdocMetadataFromTypesOrTypingsFields(packageJson) ??
190+
_tryResolveTsdocMetadataFromMainField(packageJson) ??
191+
// As a final fallback, place the file in the root of the package.
192+
TSDOC_METADATA_FILENAME;
193+
194+
// Always resolve relative to the package folder.
195+
const tsdocMetadataPath: string = path.resolve(
196+
packageFolder,
197+
// This non-null assertion is safe because the last entry in TSDOC_METADATA_RESOLUTION_FUNCTIONS
198+
// returns a non-undefined value.
199+
tsdocMetadataRelativePath!
200+
);
201+
return tsdocMetadataPath;
202+
}
203+
177204
/**
178205
* This class maintains a cache of analyzed information obtained from package.json
179206
* files. It is built on top of the PackageJsonLookup class.
@@ -202,33 +229,6 @@ export class PackageMetadataManager {
202229
this._messageRouter = messageRouter;
203230
}
204231

205-
/**
206-
* This feature is still being standardized: https://github.com/microsoft/tsdoc/issues/7
207-
* In the future we will use the @microsoft/tsdoc library to read this file.
208-
*/
209-
private static _resolveTsdocMetadataPathFromPackageJson(
210-
packageFolder: string,
211-
packageJson: INodePackageJson
212-
): string {
213-
const tsdocMetadataRelativePath: string =
214-
_tryResolveTsdocMetadataFromTsdocMetadataField(packageJson) ??
215-
_tryResolveTsdocMetadataFromExportsField(packageJson) ??
216-
_tryResolveTsdocMetadataFromTypesVersionsField(packageJson) ??
217-
_tryResolveTsdocMetadataFromTypesOrTypingsFields(packageJson) ??
218-
_tryResolveTsdocMetadataFromMainField(packageJson) ??
219-
// As a final fallback, place the file in the root of the package.
220-
TSDOC_METADATA_FILENAME;
221-
222-
// Always resolve relative to the package folder.
223-
const tsdocMetadataPath: string = path.resolve(
224-
packageFolder,
225-
// This non-null assertion is safe because the last entry in TSDOC_METADATA_RESOLUTION_FUNCTIONS
226-
// returns a non-undefined value.
227-
tsdocMetadataRelativePath!
228-
);
229-
return tsdocMetadataPath;
230-
}
231-
232232
/**
233233
* @param tsdocMetadataPath - An explicit path that can be configured in api-extractor.json.
234234
* If this parameter is not an empty string, it overrides the normal path calculation.
@@ -243,7 +243,7 @@ export class PackageMetadataManager {
243243
return path.resolve(packageFolder, tsdocMetadataPath);
244244
}
245245

246-
return PackageMetadataManager._resolveTsdocMetadataPathFromPackageJson(packageFolder, packageJson);
246+
return _resolveTsdocMetadataPathFromPackageJson(packageFolder, packageJson);
247247
}
248248

249249
/**
@@ -292,7 +292,7 @@ export class PackageMetadataManager {
292292

293293
let aedocSupported: boolean = false;
294294

295-
const tsdocMetadataPath: string = PackageMetadataManager._resolveTsdocMetadataPathFromPackageJson(
295+
const tsdocMetadataPath: string = _resolveTsdocMetadataPathFromPackageJson(
296296
packageJsonFolder,
297297
packageJson
298298
);

apps/api-extractor/src/analyzer/TypeScriptHelpers.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ import { InternalError } from '@rushstack/node-core-library';
1010
import { SourceFileLocationFormatter } from './SourceFileLocationFormatter';
1111
import { TypeScriptInternals } from './TypeScriptInternals';
1212

13-
export class TypeScriptHelpers {
14-
// Matches TypeScript's encoded names for well-known ECMAScript symbols like
15-
// "__@iterator" or "__@toStringTag".
16-
private static readonly _wellKnownSymbolNameRegExp: RegExp = /^__@(\w+)$/;
13+
// Matches TypeScript's encoded names for well-known ECMAScript symbols like
14+
// "__@iterator" or "__@toStringTag".
15+
const _wellKnownSymbolNameRegExp: RegExp = /^__@(\w+)$/;
1716

18-
// Matches TypeScript's encoded names for late-bound symbols derived from `unique symbol` declarations
19-
// which have the form of "__@<variableName>@<symbolId>", i.e. "__@someSymbol@12345".
20-
private static readonly _uniqueSymbolNameRegExp: RegExp = /^__@.*@\d+$/;
17+
// Matches TypeScript's encoded names for late-bound symbols derived from `unique symbol` declarations
18+
// which have the form of "__@<variableName>@<symbolId>", i.e. "__@someSymbol@12345".
19+
const _uniqueSymbolNameRegExp: RegExp = /^__@.*@\d+$/;
2120

21+
export class TypeScriptHelpers {
2222
/**
2323
* This traverses any symbol aliases to find the original place where an item was defined.
2424
* For example, suppose a class is defined as "export default class MyClass { }"
@@ -268,7 +268,7 @@ export class TypeScriptHelpers {
268268
* If the string does not start with `__@` then `undefined` is returned.
269269
*/
270270
public static tryDecodeWellKnownSymbolName(name: ts.__String): string | undefined {
271-
const match: RegExpExecArray | null = TypeScriptHelpers._wellKnownSymbolNameRegExp.exec(name as string);
271+
const match: RegExpExecArray | null = _wellKnownSymbolNameRegExp.exec(name as string);
272272
if (match) {
273273
const identifier: string = match[1];
274274
return `[Symbol.${identifier}]`;
@@ -280,7 +280,7 @@ export class TypeScriptHelpers {
280280
* Returns whether the provided name was generated for a TypeScript `unique symbol`.
281281
*/
282282
public static isUniqueSymbolName(name: ts.__String): boolean {
283-
return TypeScriptHelpers._uniqueSymbolNameRegExp.test(name as string);
283+
return _uniqueSymbolNameRegExp.test(name as string);
284284
}
285285

286286
/**

0 commit comments

Comments
 (0)