This commit is contained in:
2024-02-07 01:33:07 -05:00
commit c1af19d441
4088 changed files with 1260170 additions and 0 deletions

View File

@ -0,0 +1,117 @@
import * as ts from 'typescript';
import type { AstSymbol } from './AstSymbol';
import type { AstEntity } from './AstEntity';
/**
* Constructor options for AstDeclaration
*/
export interface IAstDeclarationOptions {
readonly declaration: ts.Declaration;
readonly astSymbol: AstSymbol;
readonly parent: AstDeclaration | undefined;
}
/**
* The AstDeclaration and AstSymbol classes are API Extractor's equivalent of the compiler's
* ts.Declaration and ts.Symbol objects. They are created by the `AstSymbolTable` class.
*
* @remarks
* The AstDeclaration represents one or more syntax components of a symbol. Usually there is
* only one AstDeclaration per AstSymbol, but certain TypeScript constructs can have multiple
* declarations (e.g. overloaded functions, merged declarations, etc.).
*
* Because of this, the `AstDeclaration` manages the parent/child nesting hierarchy (e.g. with
* declaration merging, each declaration has its own children) and becomes the main focus
* of analyzing AEDoc and emitting *.d.ts files.
*
* The AstDeclarations correspond to items from the compiler's ts.Node hierarchy, but
* omitting/skipping any nodes that don't match the AstDeclaration.isSupportedSyntaxKind()
* criteria. This simplification makes the other API Extractor stages easier to implement.
*/
export declare class AstDeclaration {
readonly declaration: ts.Declaration;
readonly astSymbol: AstSymbol;
/**
* The parent, if this object is nested inside another AstDeclaration.
*/
readonly parent: AstDeclaration | undefined;
/**
* A bit set of TypeScript modifiers such as "private", "protected", etc.
*/
readonly modifierFlags: ts.ModifierFlags;
/**
* Additional information that is calculated later by the `Collector`. The actual type is `DeclarationMetadata`,
* but we declare it as `unknown` because consumers must obtain this object by calling
* `Collector.fetchDeclarationMetadata()`.
*/
declarationMetadata: unknown;
/**
* Additional information that is calculated later by the `Collector`. The actual type is `ApiItemMetadata`,
* but we declare it as `unknown` because consumers must obtain this object by calling
* `Collector.fetchApiItemMetadata()`.
*/
apiItemMetadata: unknown;
private readonly _analyzedChildren;
private readonly _analyzedReferencedAstEntitiesSet;
private _childrenByName;
constructor(options: IAstDeclarationOptions);
/**
* Returns the children for this AstDeclaration.
* @remarks
* The collection will be empty until AstSymbol.analyzed is true.
*/
get children(): ReadonlyArray<AstDeclaration>;
/**
* Returns the AstEntity objects referenced by this node.
* @remarks
* NOTE: The collection will be empty until AstSymbol.analyzed is true.
*
* Since we assume references are always collected by a traversal starting at the
* root of the nesting declarations, this array omits the following items because they
* would be redundant:
* - symbols corresponding to parents of this declaration (e.g. a method that returns its own class)
* - symbols already listed in the referencedAstSymbols property for parents of this declaration
* (e.g. a method that returns its own class's base class)
* - symbols that are referenced only by nested children of this declaration
* (e.g. if a method returns an enum, this doesn't imply that the method's class references that enum)
*/
get referencedAstEntities(): ReadonlyArray<AstEntity>;
/**
* This is an internal callback used when the AstSymbolTable attaches a new
* child AstDeclaration to this object.
* @internal
*/
_notifyChildAttach(child: AstDeclaration): void;
/**
* Returns a diagnostic dump of the tree, which reports the hierarchy of
* AstDefinition objects.
*/
getDump(indent?: string): string;
/**
* Returns a diagnostic dump using Span.getDump(), which reports the detailed
* compiler structure.
*/
getSpanDump(indent?: string): string;
/**
* This is an internal callback used when AstSymbolTable.analyze() discovers a new
* type reference associated with this declaration.
* @internal
*/
_notifyReferencedAstEntity(referencedAstEntity: AstEntity): void;
/**
* Visits all the current declaration and all children recursively in a depth-first traversal,
* and performs the specified action for each one.
*/
forEachDeclarationRecursive(action: (astDeclaration: AstDeclaration) => void): void;
/**
* Returns the list of child declarations whose `AstSymbol.localName` matches the provided `name`.
*
* @remarks
* This is an efficient O(1) lookup.
*/
findChildrenWithName(name: string): ReadonlyArray<AstDeclaration>;
/**
* This function determines which ts.Node kinds will generate an AstDeclaration.
* These correspond to the definitions that we can add AEDoc to.
*/
static isSupportedSyntaxKind(kind: ts.SyntaxKind): boolean;
}
//# sourceMappingURL=AstDeclaration.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"AstDeclaration.d.ts","sourceRoot":"","sources":["../../src/analyzer/AstDeclaration.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,CAAC;CAC7C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,cAAc;IACzB,SAAgB,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC;IAE5C,SAAgB,SAAS,EAAE,SAAS,CAAC;IAErC;;OAEG;IACH,SAAgB,MAAM,EAAE,cAAc,GAAG,SAAS,CAAC;IAEnD;;OAEG;IACH,SAAgB,aAAa,EAAE,EAAE,CAAC,aAAa,CAAC;IAEhD;;;;OAIG;IACI,mBAAmB,EAAE,OAAO,CAAC;IAEpC;;;;OAIG;IACI,eAAe,EAAE,OAAO,CAAC;IAGhC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAwB;IAE1D,OAAO,CAAC,QAAQ,CAAC,iCAAiC,CAAwC;IAG1F,OAAO,CAAC,eAAe,CAAwD;gBAE5D,OAAO,EAAE,sBAAsB;IA0BlD;;;;OAIG;IACH,IAAW,QAAQ,IAAI,aAAa,CAAC,cAAc,CAAC,CAEnD;IAED;;;;;;;;;;;;;OAaG;IACH,IAAW,qBAAqB,IAAI,aAAa,CAAC,SAAS,CAAC,CAE3D;IAED;;;;OAIG;IACI,kBAAkB,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAYtD;;;OAGG;IACI,OAAO,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM;IAmB3C;;;OAGG;IACI,WAAW,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM;IAK/C;;;;OAIG;IACI,0BAA0B,CAAC,mBAAmB,EAAE,SAAS,GAAG,IAAI;IAmBvE;;;OAGG;IACI,2BAA2B,CAAC,MAAM,EAAE,CAAC,cAAc,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI;IAO1F;;;;;OAKG;IACI,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,cAAc,CAAC;IA4BxE;;;OAGG;WACW,qBAAqB,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,OAAO;CAmClE"}

View File

@ -0,0 +1,241 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AstDeclaration = void 0;
const ts = __importStar(require("typescript"));
const Span_1 = require("./Span");
const node_core_library_1 = require("@rushstack/node-core-library");
/**
* The AstDeclaration and AstSymbol classes are API Extractor's equivalent of the compiler's
* ts.Declaration and ts.Symbol objects. They are created by the `AstSymbolTable` class.
*
* @remarks
* The AstDeclaration represents one or more syntax components of a symbol. Usually there is
* only one AstDeclaration per AstSymbol, but certain TypeScript constructs can have multiple
* declarations (e.g. overloaded functions, merged declarations, etc.).
*
* Because of this, the `AstDeclaration` manages the parent/child nesting hierarchy (e.g. with
* declaration merging, each declaration has its own children) and becomes the main focus
* of analyzing AEDoc and emitting *.d.ts files.
*
* The AstDeclarations correspond to items from the compiler's ts.Node hierarchy, but
* omitting/skipping any nodes that don't match the AstDeclaration.isSupportedSyntaxKind()
* criteria. This simplification makes the other API Extractor stages easier to implement.
*/
class AstDeclaration {
constructor(options) {
// NOTE: This array becomes immutable after astSymbol.analyze() sets astSymbol.analyzed=true
this._analyzedChildren = [];
this._analyzedReferencedAstEntitiesSet = new Set();
// Reverse lookup used by findChildrenWithName()
this._childrenByName = undefined;
this.declaration = options.declaration;
this.astSymbol = options.astSymbol;
this.parent = options.parent;
this.astSymbol._notifyDeclarationAttach(this);
if (this.parent) {
this.parent._notifyChildAttach(this);
}
this.modifierFlags = ts.getCombinedModifierFlags(this.declaration);
// Check for ECMAScript private fields, for example:
//
// class Person { #name: string; }
//
const declarationName = ts.getNameOfDeclaration(this.declaration);
if (declarationName) {
if (ts.isPrivateIdentifier(declarationName)) {
// eslint-disable-next-line no-bitwise
this.modifierFlags |= ts.ModifierFlags.Private;
}
}
}
/**
* Returns the children for this AstDeclaration.
* @remarks
* The collection will be empty until AstSymbol.analyzed is true.
*/
get children() {
return this.astSymbol.analyzed ? this._analyzedChildren : [];
}
/**
* Returns the AstEntity objects referenced by this node.
* @remarks
* NOTE: The collection will be empty until AstSymbol.analyzed is true.
*
* Since we assume references are always collected by a traversal starting at the
* root of the nesting declarations, this array omits the following items because they
* would be redundant:
* - symbols corresponding to parents of this declaration (e.g. a method that returns its own class)
* - symbols already listed in the referencedAstSymbols property for parents of this declaration
* (e.g. a method that returns its own class's base class)
* - symbols that are referenced only by nested children of this declaration
* (e.g. if a method returns an enum, this doesn't imply that the method's class references that enum)
*/
get referencedAstEntities() {
return this.astSymbol.analyzed ? [...this._analyzedReferencedAstEntitiesSet] : [];
}
/**
* This is an internal callback used when the AstSymbolTable attaches a new
* child AstDeclaration to this object.
* @internal
*/
_notifyChildAttach(child) {
if (child.parent !== this) {
throw new node_core_library_1.InternalError('Invalid call to notifyChildAttach()');
}
if (this.astSymbol.analyzed) {
throw new node_core_library_1.InternalError('_notifyChildAttach() called after analysis is already complete');
}
this._analyzedChildren.push(child);
}
/**
* Returns a diagnostic dump of the tree, which reports the hierarchy of
* AstDefinition objects.
*/
getDump(indent = '') {
const declarationKind = ts.SyntaxKind[this.declaration.kind];
let result = indent + `+ ${this.astSymbol.localName} (${declarationKind})`;
if (this.astSymbol.nominalAnalysis) {
result += ' (nominal)';
}
result += '\n';
for (const referencedAstEntity of this._analyzedReferencedAstEntitiesSet.values()) {
result += indent + ` ref: ${referencedAstEntity.localName}\n`;
}
for (const child of this.children) {
result += child.getDump(indent + ' ');
}
return result;
}
/**
* Returns a diagnostic dump using Span.getDump(), which reports the detailed
* compiler structure.
*/
getSpanDump(indent = '') {
const span = new Span_1.Span(this.declaration);
return span.getDump(indent);
}
/**
* This is an internal callback used when AstSymbolTable.analyze() discovers a new
* type reference associated with this declaration.
* @internal
*/
_notifyReferencedAstEntity(referencedAstEntity) {
if (this.astSymbol.analyzed) {
throw new node_core_library_1.InternalError('_notifyReferencedAstEntity() called after analysis is already complete');
}
for (let current = this; current; current = current.parent) {
// Don't add references to symbols that are already referenced by a parent
if (current._analyzedReferencedAstEntitiesSet.has(referencedAstEntity)) {
return;
}
// Don't add the symbols of parents either
if (referencedAstEntity === current.astSymbol) {
return;
}
}
this._analyzedReferencedAstEntitiesSet.add(referencedAstEntity);
}
/**
* Visits all the current declaration and all children recursively in a depth-first traversal,
* and performs the specified action for each one.
*/
forEachDeclarationRecursive(action) {
action(this);
for (const child of this.children) {
child.forEachDeclarationRecursive(action);
}
}
/**
* Returns the list of child declarations whose `AstSymbol.localName` matches the provided `name`.
*
* @remarks
* This is an efficient O(1) lookup.
*/
findChildrenWithName(name) {
// The children property returns:
//
// return this.astSymbol.analyzed ? this._analyzedChildren : [];
//
if (!this.astSymbol.analyzed || this._analyzedChildren.length === 0) {
return [];
}
if (this._childrenByName === undefined) {
// Build the lookup table
const childrenByName = new Map();
for (const child of this._analyzedChildren) {
const childName = child.astSymbol.localName;
let array = childrenByName.get(childName);
if (array === undefined) {
array = [];
childrenByName.set(childName, array);
}
array.push(child);
}
this._childrenByName = childrenByName;
}
return this._childrenByName.get(name) || [];
}
/**
* This function determines which ts.Node kinds will generate an AstDeclaration.
* These correspond to the definitions that we can add AEDoc to.
*/
static isSupportedSyntaxKind(kind) {
// (alphabetical order)
switch (kind) {
case ts.SyntaxKind.CallSignature:
case ts.SyntaxKind.ClassDeclaration:
case ts.SyntaxKind.ConstructSignature: // Example: "new(x: number): IMyClass"
case ts.SyntaxKind.Constructor: // Example: "constructor(x: number)"
case ts.SyntaxKind.EnumDeclaration:
case ts.SyntaxKind.EnumMember:
case ts.SyntaxKind.FunctionDeclaration: // Example: "(x: number): number"
case ts.SyntaxKind.GetAccessor:
case ts.SyntaxKind.SetAccessor:
case ts.SyntaxKind.IndexSignature: // Example: "[key: string]: string"
case ts.SyntaxKind.InterfaceDeclaration:
case ts.SyntaxKind.MethodDeclaration:
case ts.SyntaxKind.MethodSignature:
case ts.SyntaxKind.ModuleDeclaration: // Used for both "module" and "namespace" declarations
case ts.SyntaxKind.PropertyDeclaration:
case ts.SyntaxKind.PropertySignature:
case ts.SyntaxKind.TypeAliasDeclaration: // Example: "type Shape = Circle | Square"
case ts.SyntaxKind.VariableDeclaration:
return true;
// NOTE: Prior to TypeScript 3.7, in the emitted .d.ts files, the compiler would merge a GetAccessor/SetAccessor
// pair into a single PropertyDeclaration.
// NOTE: In contexts where a source file is treated as a module, we do create "nominal analysis"
// AstSymbol objects corresponding to a ts.SyntaxKind.SourceFile node. However, a source file
// is NOT considered a nesting structure, and it does NOT act as a root for the declarations
// appearing in the file. This is because the *.d.ts generator is in the business of rolling up
// source files, and thus wants to ignore them in general.
}
return false;
}
}
exports.AstDeclaration = AstDeclaration;
//# sourceMappingURL=AstDeclaration.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,44 @@
/**
* `AstEntity` is the abstract base class for analyzer objects that can become a `CollectorEntity`.
*
* @remarks
*
* The subclasses are:
* ```
* - AstEntity
* - AstSymbol
* - AstSyntheticEntity
* - AstImport
* - AstNamespaceImport
* ```
*/
export declare abstract class AstEntity {
/**
* The original name of the symbol, as exported from the module (i.e. source file)
* containing the original TypeScript definition. Constructs such as
* `import { X as Y } from` may introduce other names that differ from the local name.
*
* @remarks
* For the most part, `localName` corresponds to `followedSymbol.name`, but there
* are some edge cases. For example, the ts.Symbol.name for `export default class X { }`
* is actually `"default"`, not `"X"`.
*/
abstract readonly localName: string;
}
/**
* `AstSyntheticEntity` is the abstract base class for analyzer objects whose emitted declarations
* are not text transformations performed by the `Span` helper.
*
* @remarks
* Most of API Extractor's output is produced by using the using the `Span` utility to regurgitate strings from
* the input .d.ts files. If we need to rename an identifier, the `Span` visitor can pick out an interesting
* node and rewrite its string, but otherwise the transformation operates on dumb text and not compiler concepts.
* (Historically we did this because the compiler's emitter was an internal API, but it still has some advantages,
* for example preserving syntaxes generated by an older compiler to avoid incompatibilities.)
*
* This strategy does not work for cases where the output looks very different from the input. Today these
* cases are always kinds of `import` statements, but that may change in the future.
*/
export declare abstract class AstSyntheticEntity extends AstEntity {
}
//# sourceMappingURL=AstEntity.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"AstEntity.d.ts","sourceRoot":"","sources":["../../src/analyzer/AstEntity.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AACH,8BAAsB,SAAS;IAC7B;;;;;;;;;OASG;IACH,kBAAyB,SAAS,EAAE,MAAM,CAAC;CAC5C;AAED;;;;;;;;;;;;;GAaG;AACH,8BAAsB,kBAAmB,SAAQ,SAAS;CAAG"}

View File

@ -0,0 +1,40 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.AstSyntheticEntity = exports.AstEntity = void 0;
/**
* `AstEntity` is the abstract base class for analyzer objects that can become a `CollectorEntity`.
*
* @remarks
*
* The subclasses are:
* ```
* - AstEntity
* - AstSymbol
* - AstSyntheticEntity
* - AstImport
* - AstNamespaceImport
* ```
*/
class AstEntity {
}
exports.AstEntity = AstEntity;
/**
* `AstSyntheticEntity` is the abstract base class for analyzer objects whose emitted declarations
* are not text transformations performed by the `Span` helper.
*
* @remarks
* Most of API Extractor's output is produced by using the using the `Span` utility to regurgitate strings from
* the input .d.ts files. If we need to rename an identifier, the `Span` visitor can pick out an interesting
* node and rewrite its string, but otherwise the transformation operates on dumb text and not compiler concepts.
* (Historically we did this because the compiler's emitter was an internal API, but it still has some advantages,
* for example preserving syntaxes generated by an older compiler to avoid incompatibilities.)
*
* This strategy does not work for cases where the output looks very different from the input. Today these
* cases are always kinds of `import` statements, but that may change in the future.
*/
class AstSyntheticEntity extends AstEntity {
}
exports.AstSyntheticEntity = AstSyntheticEntity;
//# sourceMappingURL=AstEntity.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"AstEntity.js","sourceRoot":"","sources":["../../src/analyzer/AstEntity.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;AAE3D;;;;;;;;;;;;;GAaG;AACH,MAAsB,SAAS;CAY9B;AAZD,8BAYC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAsB,kBAAmB,SAAQ,SAAS;CAAG;AAA7D,gDAA6D","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\n/**\n * `AstEntity` is the abstract base class for analyzer objects that can become a `CollectorEntity`.\n *\n * @remarks\n *\n * The subclasses are:\n * ```\n * - AstEntity\n * - AstSymbol\n * - AstSyntheticEntity\n * - AstImport\n * - AstNamespaceImport\n * ```\n */\nexport abstract class AstEntity {\n /**\n * The original name of the symbol, as exported from the module (i.e. source file)\n * containing the original TypeScript definition. Constructs such as\n * `import { X as Y } from` may introduce other names that differ from the local name.\n *\n * @remarks\n * For the most part, `localName` corresponds to `followedSymbol.name`, but there\n * are some edge cases. For example, the ts.Symbol.name for `export default class X { }`\n * is actually `\"default\"`, not `\"X\"`.\n */\n public abstract readonly localName: string;\n}\n\n/**\n * `AstSyntheticEntity` is the abstract base class for analyzer objects whose emitted declarations\n * are not text transformations performed by the `Span` helper.\n *\n * @remarks\n * Most of API Extractor's output is produced by using the using the `Span` utility to regurgitate strings from\n * the input .d.ts files. If we need to rename an identifier, the `Span` visitor can pick out an interesting\n * node and rewrite its string, but otherwise the transformation operates on dumb text and not compiler concepts.\n * (Historically we did this because the compiler's emitter was an internal API, but it still has some advantages,\n * for example preserving syntaxes generated by an older compiler to avoid incompatibilities.)\n *\n * This strategy does not work for cases where the output looks very different from the input. Today these\n * cases are always kinds of `import` statements, but that may change in the future.\n */\nexport abstract class AstSyntheticEntity extends AstEntity {}\n"]}

View File

@ -0,0 +1,112 @@
import type { AstSymbol } from './AstSymbol';
import { AstSyntheticEntity } from './AstEntity';
/**
* Indicates the import kind for an `AstImport`.
*/
export declare enum AstImportKind {
/**
* An import statement such as `import X from "y";`.
*/
DefaultImport = 0,
/**
* An import statement such as `import { X } from "y";`.
*/
NamedImport = 1,
/**
* An import statement such as `import * as x from "y";`.
*/
StarImport = 2,
/**
* An import statement such as `import x = require("y");`.
*/
EqualsImport = 3,
/**
* An import statement such as `interface foo { foo: import("bar").a.b.c }`.
*/
ImportType = 4
}
/**
* Constructor parameters for AstImport
*
* @privateRemarks
* Our naming convention is to use I____Parameters for constructor options and
* I____Options for general function options. However the word "parameters" is
* confusingly similar to the terminology for function parameters modeled by API Extractor,
* so we use I____Options for both cases in this code base.
*/
export interface IAstImportOptions {
readonly importKind: AstImportKind;
readonly modulePath: string;
readonly exportName: string;
readonly isTypeOnly: boolean;
}
/**
* For a symbol that was imported from an external package, this tracks the import
* statement that was used to reach it.
*/
export declare class AstImport extends AstSyntheticEntity {
readonly importKind: AstImportKind;
/**
* The name of the external package (and possibly module path) that this definition
* was imported from.
*
* Example: "@rushstack/node-core-library/lib/FileSystem"
*/
readonly modulePath: string;
/**
* The name of the symbol being imported.
*
* @remarks
*
* The name depends on the type of import:
*
* ```ts
* // For AstImportKind.DefaultImport style, exportName would be "X" in this example:
* import X from "y";
*
* // For AstImportKind.NamedImport style, exportName would be "X" in this example:
* import { X } from "y";
*
* // For AstImportKind.StarImport style, exportName would be "x" in this example:
* import * as x from "y";
*
* // For AstImportKind.EqualsImport style, exportName would be "x" in this example:
* import x = require("y");
*
* // For AstImportKind.ImportType style, exportName would be "a.b.c" in this example:
* interface foo { foo: import('bar').a.b.c };
* ```
*/
readonly exportName: string;
/**
* Whether it is a type-only import, for example:
*
* ```ts
* import type { X } from "y";
* ```
*
* This is set to true ONLY if the type-only form is used in *every* reference to this AstImport.
*/
isTypeOnlyEverywhere: boolean;
/**
* If this import statement refers to an API from an external package that is tracked by API Extractor
* (according to `PackageMetadataManager.isAedocSupportedFor()`), then this property will return the
* corresponding AstSymbol. Otherwise, it is undefined.
*/
astSymbol: AstSymbol | undefined;
/**
* If modulePath and exportName are defined, then this is a dictionary key
* that combines them with a colon (":").
*
* Example: "@rushstack/node-core-library/lib/FileSystem:FileSystem"
*/
readonly key: string;
constructor(options: IAstImportOptions);
/** {@inheritdoc} */
get localName(): string;
/**
* Calculates the lookup key used with `AstImport.key`
*/
static getKey(options: IAstImportOptions): string;
}
//# sourceMappingURL=AstImport.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"AstImport.d.ts","sourceRoot":"","sources":["../../src/analyzer/AstImport.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD;;GAEG;AACH,oBAAY,aAAa;IACvB;;OAEG;IACH,aAAa,IAAA;IAEb;;OAEG;IACH,WAAW,IAAA;IAEX;;OAEG;IACH,UAAU,IAAA;IAEV;;OAEG;IACH,YAAY,IAAA;IAEZ;;OAEG;IACH,UAAU,IAAA;CACX;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED;;;GAGG;AACH,qBAAa,SAAU,SAAQ,kBAAkB;IAC/C,SAAgB,UAAU,EAAE,aAAa,CAAC;IAE1C;;;;;OAKG;IACH,SAAgB,UAAU,EAAE,MAAM,CAAC;IAEnC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,SAAgB,UAAU,EAAE,MAAM,CAAC;IAEnC;;;;;;;;OAQG;IACI,oBAAoB,EAAE,OAAO,CAAC;IAErC;;;;OAIG;IACI,SAAS,EAAE,SAAS,GAAG,SAAS,CAAC;IAExC;;;;;OAKG;IACH,SAAgB,GAAG,EAAE,MAAM,CAAC;gBAET,OAAO,EAAE,iBAAiB;IAa7C,oBAAoB;IACpB,IAAW,SAAS,IAAI,MAAM,CAG7B;IAED;;OAEG;WACW,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM;CAsBzD"}

View File

@ -0,0 +1,80 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.AstImport = exports.AstImportKind = void 0;
const node_core_library_1 = require("@rushstack/node-core-library");
const AstEntity_1 = require("./AstEntity");
/**
* Indicates the import kind for an `AstImport`.
*/
var AstImportKind;
(function (AstImportKind) {
/**
* An import statement such as `import X from "y";`.
*/
AstImportKind[AstImportKind["DefaultImport"] = 0] = "DefaultImport";
/**
* An import statement such as `import { X } from "y";`.
*/
AstImportKind[AstImportKind["NamedImport"] = 1] = "NamedImport";
/**
* An import statement such as `import * as x from "y";`.
*/
AstImportKind[AstImportKind["StarImport"] = 2] = "StarImport";
/**
* An import statement such as `import x = require("y");`.
*/
AstImportKind[AstImportKind["EqualsImport"] = 3] = "EqualsImport";
/**
* An import statement such as `interface foo { foo: import("bar").a.b.c }`.
*/
AstImportKind[AstImportKind["ImportType"] = 4] = "ImportType";
})(AstImportKind = exports.AstImportKind || (exports.AstImportKind = {}));
/**
* For a symbol that was imported from an external package, this tracks the import
* statement that was used to reach it.
*/
class AstImport extends AstEntity_1.AstSyntheticEntity {
constructor(options) {
super();
this.importKind = options.importKind;
this.modulePath = options.modulePath;
this.exportName = options.exportName;
// We start with this assumption, but it may get changed later if non-type-only import is encountered.
this.isTypeOnlyEverywhere = options.isTypeOnly;
this.key = AstImport.getKey(options);
}
/** {@inheritdoc} */
get localName() {
// abstract
return this.exportName;
}
/**
* Calculates the lookup key used with `AstImport.key`
*/
static getKey(options) {
switch (options.importKind) {
case AstImportKind.DefaultImport:
return `${options.modulePath}:${options.exportName}`;
case AstImportKind.NamedImport:
return `${options.modulePath}:${options.exportName}`;
case AstImportKind.StarImport:
return `${options.modulePath}:*`;
case AstImportKind.EqualsImport:
return `${options.modulePath}:=`;
case AstImportKind.ImportType: {
const subKey = !options.exportName
? '*' // Equivalent to StarImport
: options.exportName.includes('.') // Equivalent to a named export
? options.exportName.split('.')[0]
: options.exportName;
return `${options.modulePath}:${subKey}`;
}
default:
throw new node_core_library_1.InternalError('Unknown AstImportKind');
}
}
}
exports.AstImport = AstImport;
//# sourceMappingURL=AstImport.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,61 @@
import type * as ts from 'typescript';
import type { AstEntity } from './AstEntity';
/**
* Represents information collected by {@link AstSymbolTable.fetchAstModuleExportInfo}
*/
export declare class AstModuleExportInfo {
readonly exportedLocalEntities: Map<string, AstEntity>;
readonly starExportedExternalModules: Set<AstModule>;
}
/**
* Constructor parameters for AstModule
*
* @privateRemarks
* Our naming convention is to use I____Parameters for constructor options and
* I____Options for general function options. However the word "parameters" is
* confusingly similar to the terminology for function parameters modeled by API Extractor,
* so we use I____Options for both cases in this code base.
*/
export interface IAstModuleOptions {
sourceFile: ts.SourceFile;
moduleSymbol: ts.Symbol;
externalModulePath: string | undefined;
}
/**
* An internal data structure that represents a source file that is analyzed by AstSymbolTable.
*/
export declare class AstModule {
/**
* The source file that declares this TypeScript module. In most cases, the source file's
* top-level exports constitute the module.
*/
readonly sourceFile: ts.SourceFile;
/**
* The symbol for the module. Typically this corresponds to ts.SourceFile itself, however
* in some cases the ts.SourceFile may contain multiple modules declared using the `module` keyword.
*/
readonly moduleSymbol: ts.Symbol;
/**
* Example: "@rushstack/node-core-library/lib/FileSystem"
* but never: "./FileSystem"
*/
readonly externalModulePath: string | undefined;
/**
* A list of other `AstModule` objects that appear in `export * from "___";` statements.
*/
readonly starExportedModules: Set<AstModule>;
/**
* A partial map of entities exported by this module. The key is the exported name.
*/
readonly cachedExportedEntities: Map<string, AstEntity>;
/**
* Additional state calculated by `AstSymbolTable.fetchWorkingPackageModule()`.
*/
astModuleExportInfo: AstModuleExportInfo | undefined;
constructor(options: IAstModuleOptions);
/**
* If false, then this source file is part of the working package being processed by the `Collector`.
*/
get isExternal(): boolean;
}
//# sourceMappingURL=AstModule.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"AstModule.d.ts","sourceRoot":"","sources":["../../src/analyzer/AstModule.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAGtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,SAAgB,qBAAqB,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAgC;IAC7F,SAAgB,2BAA2B,EAAE,GAAG,CAAC,SAAS,CAAC,CAAwB;CACpF;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC;IAC1B,YAAY,EAAE,EAAE,CAAC,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AAED;;GAEG;AACH,qBAAa,SAAS;IACpB;;;OAGG;IACH,SAAgB,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC;IAE1C;;;OAGG;IACH,SAAgB,YAAY,EAAE,EAAE,CAAC,MAAM,CAAC;IAExC;;;OAGG;IACH,SAAgB,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;IAEvD;;OAEG;IACH,SAAgB,mBAAmB,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC;IAEpD;;OAEG;IACH,SAAgB,sBAAsB,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAE/D;;OAEG;IACI,mBAAmB,EAAE,mBAAmB,GAAG,SAAS,CAAC;gBAEzC,OAAO,EAAE,iBAAiB;IAa7C;;OAEG;IACH,IAAW,UAAU,IAAI,OAAO,CAE/B;CACF"}

View File

@ -0,0 +1,36 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.AstModule = exports.AstModuleExportInfo = void 0;
/**
* Represents information collected by {@link AstSymbolTable.fetchAstModuleExportInfo}
*/
class AstModuleExportInfo {
constructor() {
this.exportedLocalEntities = new Map();
this.starExportedExternalModules = new Set();
}
}
exports.AstModuleExportInfo = AstModuleExportInfo;
/**
* An internal data structure that represents a source file that is analyzed by AstSymbolTable.
*/
class AstModule {
constructor(options) {
this.sourceFile = options.sourceFile;
this.moduleSymbol = options.moduleSymbol;
this.externalModulePath = options.externalModulePath;
this.starExportedModules = new Set();
this.cachedExportedEntities = new Map();
this.astModuleExportInfo = undefined;
}
/**
* If false, then this source file is part of the working package being processed by the `Collector`.
*/
get isExternal() {
return this.externalModulePath !== undefined;
}
}
exports.AstModule = AstModule;
//# sourceMappingURL=AstModule.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"AstModule.js","sourceRoot":"","sources":["../../src/analyzer/AstModule.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;AAO3D;;GAEG;AACH,MAAa,mBAAmB;IAAhC;QACkB,0BAAqB,GAA2B,IAAI,GAAG,EAAqB,CAAC;QAC7E,gCAA2B,GAAmB,IAAI,GAAG,EAAa,CAAC;IACrF,CAAC;CAAA;AAHD,kDAGC;AAiBD;;GAEG;AACH,MAAa,SAAS;IAkCpB,YAAmB,OAA0B;QAC3C,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;QAEzC,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC;QAErD,IAAI,CAAC,mBAAmB,GAAG,IAAI,GAAG,EAAa,CAAC;QAEhD,IAAI,CAAC,sBAAsB,GAAG,IAAI,GAAG,EAAqB,CAAC;QAE3D,IAAI,CAAC,mBAAmB,GAAG,SAAS,CAAC;IACvC,CAAC;IAED;;OAEG;IACH,IAAW,UAAU;QACnB,OAAO,IAAI,CAAC,kBAAkB,KAAK,SAAS,CAAC;IAC/C,CAAC;CACF;AArDD,8BAqDC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type * as ts from 'typescript';\n\nimport type { AstSymbol } from './AstSymbol';\nimport type { AstEntity } from './AstEntity';\n\n/**\n * Represents information collected by {@link AstSymbolTable.fetchAstModuleExportInfo}\n */\nexport class AstModuleExportInfo {\n public readonly exportedLocalEntities: Map<string, AstEntity> = new Map<string, AstEntity>();\n public readonly starExportedExternalModules: Set<AstModule> = new Set<AstModule>();\n}\n\n/**\n * Constructor parameters for AstModule\n *\n * @privateRemarks\n * Our naming convention is to use I____Parameters for constructor options and\n * I____Options for general function options. However the word \"parameters\" is\n * confusingly similar to the terminology for function parameters modeled by API Extractor,\n * so we use I____Options for both cases in this code base.\n */\nexport interface IAstModuleOptions {\n sourceFile: ts.SourceFile;\n moduleSymbol: ts.Symbol;\n externalModulePath: string | undefined;\n}\n\n/**\n * An internal data structure that represents a source file that is analyzed by AstSymbolTable.\n */\nexport class AstModule {\n /**\n * The source file that declares this TypeScript module. In most cases, the source file's\n * top-level exports constitute the module.\n */\n public readonly sourceFile: ts.SourceFile;\n\n /**\n * The symbol for the module. Typically this corresponds to ts.SourceFile itself, however\n * in some cases the ts.SourceFile may contain multiple modules declared using the `module` keyword.\n */\n public readonly moduleSymbol: ts.Symbol;\n\n /**\n * Example: \"@rushstack/node-core-library/lib/FileSystem\"\n * but never: \"./FileSystem\"\n */\n public readonly externalModulePath: string | undefined;\n\n /**\n * A list of other `AstModule` objects that appear in `export * from \"___\";` statements.\n */\n public readonly starExportedModules: Set<AstModule>;\n\n /**\n * A partial map of entities exported by this module. The key is the exported name.\n */\n public readonly cachedExportedEntities: Map<string, AstEntity>; // exportName --> entity\n\n /**\n * Additional state calculated by `AstSymbolTable.fetchWorkingPackageModule()`.\n */\n public astModuleExportInfo: AstModuleExportInfo | undefined;\n\n public constructor(options: IAstModuleOptions) {\n this.sourceFile = options.sourceFile;\n this.moduleSymbol = options.moduleSymbol;\n\n this.externalModulePath = options.externalModulePath;\n\n this.starExportedModules = new Set<AstModule>();\n\n this.cachedExportedEntities = new Map<string, AstSymbol>();\n\n this.astModuleExportInfo = undefined;\n }\n\n /**\n * If false, then this source file is part of the working package being processed by the `Collector`.\n */\n public get isExternal(): boolean {\n return this.externalModulePath !== undefined;\n }\n}\n"]}

View File

@ -0,0 +1,70 @@
import type * as ts from 'typescript';
import type { AstModule, AstModuleExportInfo } from './AstModule';
import { AstSyntheticEntity } from './AstEntity';
import type { Collector } from '../collector/Collector';
export interface IAstNamespaceImportOptions {
readonly astModule: AstModule;
readonly namespaceName: string;
readonly declaration: ts.Declaration;
readonly symbol: ts.Symbol;
}
/**
* `AstNamespaceImport` represents a namespace that is created implicitly by a statement
* such as `import * as example from "./file";`
*
* @remarks
*
* A typical input looks like this:
* ```ts
* // Suppose that example.ts exports two functions f1() and f2().
* import * as example from "./file";
* export { example };
* ```
*
* API Extractor's .d.ts rollup will transform it into an explicit namespace, like this:
* ```ts
* declare f1(): void;
* declare f2(): void;
*
* declare namespace example {
* export {
* f1,
* f2
* }
* }
* ```
*
* The current implementation does not attempt to relocate f1()/f2() to be inside the `namespace`
* because other type signatures may reference them directly (without using the namespace qualifier).
* The `declare namespace example` is a synthetic construct represented by `AstNamespaceImport`.
*/
export declare class AstNamespaceImport extends AstSyntheticEntity {
/**
* Returns true if the AstSymbolTable.analyze() was called for this object.
* See that function for details.
*/
analyzed: boolean;
/**
* For example, if the original statement was `import * as example from "./file";`
* then `astModule` refers to the `./file.d.ts` file.
*/
readonly astModule: AstModule;
/**
* For example, if the original statement was `import * as example from "./file";`
* then `namespaceName` would be `example`.
*/
readonly namespaceName: string;
/**
* The original `ts.SyntaxKind.NamespaceImport` which can be used as a location for error messages.
*/
readonly declaration: ts.Declaration;
/**
* The original `ts.SymbolFlags.Namespace` symbol.
*/
readonly symbol: ts.Symbol;
constructor(options: IAstNamespaceImportOptions);
/** {@inheritdoc} */
get localName(): string;
fetchAstModuleExportInfo(collector: Collector): AstModuleExportInfo;
}
//# sourceMappingURL=AstNamespaceImport.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"AstNamespaceImport.d.ts","sourceRoot":"","sources":["../../src/analyzer/AstNamespaceImport.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,kBAAmB,SAAQ,kBAAkB;IACxD;;;OAGG;IACI,QAAQ,EAAE,OAAO,CAAS;IAEjC;;;OAGG;IACH,SAAgB,SAAS,EAAE,SAAS,CAAC;IAErC;;;OAGG;IACH,SAAgB,aAAa,EAAE,MAAM,CAAC;IAEtC;;OAEG;IACH,SAAgB,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC;IAE5C;;OAEG;IACH,SAAgB,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC;gBAEf,OAAO,EAAE,0BAA0B;IAQtD,oBAAoB;IACpB,IAAW,SAAS,IAAI,MAAM,CAG7B;IAEM,wBAAwB,CAAC,SAAS,EAAE,SAAS,GAAG,mBAAmB;CAM3E"}

View File

@ -0,0 +1,61 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.AstNamespaceImport = void 0;
const AstEntity_1 = require("./AstEntity");
/**
* `AstNamespaceImport` represents a namespace that is created implicitly by a statement
* such as `import * as example from "./file";`
*
* @remarks
*
* A typical input looks like this:
* ```ts
* // Suppose that example.ts exports two functions f1() and f2().
* import * as example from "./file";
* export { example };
* ```
*
* API Extractor's .d.ts rollup will transform it into an explicit namespace, like this:
* ```ts
* declare f1(): void;
* declare f2(): void;
*
* declare namespace example {
* export {
* f1,
* f2
* }
* }
* ```
*
* The current implementation does not attempt to relocate f1()/f2() to be inside the `namespace`
* because other type signatures may reference them directly (without using the namespace qualifier).
* The `declare namespace example` is a synthetic construct represented by `AstNamespaceImport`.
*/
class AstNamespaceImport extends AstEntity_1.AstSyntheticEntity {
constructor(options) {
super();
/**
* Returns true if the AstSymbolTable.analyze() was called for this object.
* See that function for details.
*/
this.analyzed = false;
this.astModule = options.astModule;
this.namespaceName = options.namespaceName;
this.declaration = options.declaration;
this.symbol = options.symbol;
}
/** {@inheritdoc} */
get localName() {
// abstract
return this.namespaceName;
}
fetchAstModuleExportInfo(collector) {
const astModuleExportInfo = collector.astSymbolTable.fetchAstModuleExportInfo(this.astModule);
return astModuleExportInfo;
}
}
exports.AstNamespaceImport = AstNamespaceImport;
//# sourceMappingURL=AstNamespaceImport.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"AstNamespaceImport.js","sourceRoot":"","sources":["../../src/analyzer/AstNamespaceImport.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;AAK3D,2CAAiD;AAUjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAa,kBAAmB,SAAQ,8BAAkB;IA6BxD,YAAmB,OAAmC;QACpD,KAAK,EAAE,CAAC;QA7BV;;;WAGG;QACI,aAAQ,GAAY,KAAK,CAAC;QA0B/B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;QAC3C,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;QACvC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC/B,CAAC;IAED,oBAAoB;IACpB,IAAW,SAAS;QAClB,WAAW;QACX,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAEM,wBAAwB,CAAC,SAAoB;QAClD,MAAM,mBAAmB,GAAwB,SAAS,CAAC,cAAc,CAAC,wBAAwB,CAChG,IAAI,CAAC,SAAS,CACf,CAAC;QACF,OAAO,mBAAmB,CAAC;IAC7B,CAAC;CACF;AAjDD,gDAiDC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type * as ts from 'typescript';\n\nimport type { AstModule, AstModuleExportInfo } from './AstModule';\nimport { AstSyntheticEntity } from './AstEntity';\nimport type { Collector } from '../collector/Collector';\n\nexport interface IAstNamespaceImportOptions {\n readonly astModule: AstModule;\n readonly namespaceName: string;\n readonly declaration: ts.Declaration;\n readonly symbol: ts.Symbol;\n}\n\n/**\n * `AstNamespaceImport` represents a namespace that is created implicitly by a statement\n * such as `import * as example from \"./file\";`\n *\n * @remarks\n *\n * A typical input looks like this:\n * ```ts\n * // Suppose that example.ts exports two functions f1() and f2().\n * import * as example from \"./file\";\n * export { example };\n * ```\n *\n * API Extractor's .d.ts rollup will transform it into an explicit namespace, like this:\n * ```ts\n * declare f1(): void;\n * declare f2(): void;\n *\n * declare namespace example {\n * export {\n * f1,\n * f2\n * }\n * }\n * ```\n *\n * The current implementation does not attempt to relocate f1()/f2() to be inside the `namespace`\n * because other type signatures may reference them directly (without using the namespace qualifier).\n * The `declare namespace example` is a synthetic construct represented by `AstNamespaceImport`.\n */\nexport class AstNamespaceImport extends AstSyntheticEntity {\n /**\n * Returns true if the AstSymbolTable.analyze() was called for this object.\n * See that function for details.\n */\n public analyzed: boolean = false;\n\n /**\n * For example, if the original statement was `import * as example from \"./file\";`\n * then `astModule` refers to the `./file.d.ts` file.\n */\n public readonly astModule: AstModule;\n\n /**\n * For example, if the original statement was `import * as example from \"./file\";`\n * then `namespaceName` would be `example`.\n */\n public readonly namespaceName: string;\n\n /**\n * The original `ts.SyntaxKind.NamespaceImport` which can be used as a location for error messages.\n */\n public readonly declaration: ts.Declaration;\n\n /**\n * The original `ts.SymbolFlags.Namespace` symbol.\n */\n public readonly symbol: ts.Symbol;\n\n public constructor(options: IAstNamespaceImportOptions) {\n super();\n this.astModule = options.astModule;\n this.namespaceName = options.namespaceName;\n this.declaration = options.declaration;\n this.symbol = options.symbol;\n }\n\n /** {@inheritdoc} */\n public get localName(): string {\n // abstract\n return this.namespaceName;\n }\n\n public fetchAstModuleExportInfo(collector: Collector): AstModuleExportInfo {\n const astModuleExportInfo: AstModuleExportInfo = collector.astSymbolTable.fetchAstModuleExportInfo(\n this.astModule\n );\n return astModuleExportInfo;\n }\n}\n"]}

View File

@ -0,0 +1,43 @@
import * as tsdoc from '@microsoft/tsdoc';
import type { AstDeclaration } from './AstDeclaration';
import type { Collector } from '../collector/Collector';
/**
* Used by `AstReferenceResolver` to report a failed resolution.
*
* @privateRemarks
* This class is similar to an `Error` object, but the intent of `ResolverFailure` is to describe
* why a reference could not be resolved. This information could be used to throw an actual `Error` object,
* but normally it is handed off to the `MessageRouter` instead.
*/
export declare class ResolverFailure {
/**
* Details about why the failure occurred.
*/
readonly reason: string;
constructor(reason: string);
}
/**
* This resolves a TSDoc declaration reference by walking the `AstSymbolTable` compiler state.
*
* @remarks
*
* This class is analogous to `ModelReferenceResolver` from the `@microsoft/api-extractor-model` project,
* which resolves declaration references by walking the hierarchy loaded from an .api.json file.
*/
export declare class AstReferenceResolver {
private readonly _collector;
private readonly _astSymbolTable;
private readonly _workingPackage;
constructor(collector: Collector);
resolve(declarationReference: tsdoc.DocDeclarationReference): AstDeclaration | ResolverFailure;
private _getMemberReferenceIdentifier;
private _selectDeclaration;
private _selectUsingSystemSelector;
private _selectUsingIndexSelector;
/**
* This resolves an ambiguous match in the case where the extra matches are all ancillary declarations,
* except for one match that is the main declaration.
*/
private _tryDisambiguateAncillaryMatches;
}
//# sourceMappingURL=AstReferenceResolver.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"AstReferenceResolver.d.ts","sourceRoot":"","sources":["../../src/analyzer/AstReferenceResolver.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,kBAAkB,CAAC;AAI1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAIxD;;;;;;;GAOG;AACH,qBAAa,eAAe;IAC1B;;OAEG;IACH,SAAgB,MAAM,EAAE,MAAM,CAAC;gBAEZ,MAAM,EAAE,MAAM;CAGlC;AAED;;;;;;;GAOG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;IACvC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiB;IACjD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiB;gBAE9B,SAAS,EAAE,SAAS;IAMhC,OAAO,CAAC,oBAAoB,EAAE,KAAK,CAAC,uBAAuB,GAAG,cAAc,GAAG,eAAe;IAoFrG,OAAO,CAAC,6BAA6B;IAUrC,OAAO,CAAC,kBAAkB;IAoC1B,OAAO,CAAC,0BAA0B;IA2DlC,OAAO,CAAC,yBAAyB;IAqCjC;;;OAGG;IACH,OAAO,CAAC,gCAAgC;CAgBzC"}

View File

@ -0,0 +1,230 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AstReferenceResolver = exports.ResolverFailure = void 0;
const ts = __importStar(require("typescript"));
const tsdoc = __importStar(require("@microsoft/tsdoc"));
const AstSymbol_1 = require("./AstSymbol");
/**
* Used by `AstReferenceResolver` to report a failed resolution.
*
* @privateRemarks
* This class is similar to an `Error` object, but the intent of `ResolverFailure` is to describe
* why a reference could not be resolved. This information could be used to throw an actual `Error` object,
* but normally it is handed off to the `MessageRouter` instead.
*/
class ResolverFailure {
constructor(reason) {
this.reason = reason;
}
}
exports.ResolverFailure = ResolverFailure;
/**
* This resolves a TSDoc declaration reference by walking the `AstSymbolTable` compiler state.
*
* @remarks
*
* This class is analogous to `ModelReferenceResolver` from the `@microsoft/api-extractor-model` project,
* which resolves declaration references by walking the hierarchy loaded from an .api.json file.
*/
class AstReferenceResolver {
constructor(collector) {
this._collector = collector;
this._astSymbolTable = collector.astSymbolTable;
this._workingPackage = collector.workingPackage;
}
resolve(declarationReference) {
// Is it referring to the working package?
if (declarationReference.packageName !== undefined &&
declarationReference.packageName !== this._workingPackage.name) {
return new ResolverFailure('External package references are not supported');
}
// Is it a path-based import?
if (declarationReference.importPath) {
return new ResolverFailure('Import paths are not supported');
}
const astModule = this._astSymbolTable.fetchAstModuleFromWorkingPackage(this._workingPackage.entryPointSourceFile);
if (declarationReference.memberReferences.length === 0) {
return new ResolverFailure('Package references are not supported');
}
const rootMemberReference = declarationReference.memberReferences[0];
const exportName = this._getMemberReferenceIdentifier(rootMemberReference);
if (exportName instanceof ResolverFailure) {
return exportName;
}
const rootAstEntity = this._astSymbolTable.tryGetExportOfAstModule(exportName, astModule);
if (rootAstEntity === undefined) {
return new ResolverFailure(`The package "${this._workingPackage.name}" does not have an export "${exportName}"`);
}
if (!(rootAstEntity instanceof AstSymbol_1.AstSymbol)) {
return new ResolverFailure('This type of declaration is not supported yet by the resolver');
}
let currentDeclaration = this._selectDeclaration(rootAstEntity.astDeclarations, rootMemberReference, rootAstEntity.localName);
if (currentDeclaration instanceof ResolverFailure) {
return currentDeclaration;
}
for (let index = 1; index < declarationReference.memberReferences.length; ++index) {
const memberReference = declarationReference.memberReferences[index];
const memberName = this._getMemberReferenceIdentifier(memberReference);
if (memberName instanceof ResolverFailure) {
return memberName;
}
const matchingChildren = currentDeclaration.findChildrenWithName(memberName);
if (matchingChildren.length === 0) {
return new ResolverFailure(`No member was found with name "${memberName}"`);
}
const selectedDeclaration = this._selectDeclaration(matchingChildren, memberReference, memberName);
if (selectedDeclaration instanceof ResolverFailure) {
return selectedDeclaration;
}
currentDeclaration = selectedDeclaration;
}
return currentDeclaration;
}
_getMemberReferenceIdentifier(memberReference) {
if (memberReference.memberSymbol !== undefined) {
return new ResolverFailure('ECMAScript symbol selectors are not supported');
}
if (memberReference.memberIdentifier === undefined) {
return new ResolverFailure('The member identifier is missing in the root member reference');
}
return memberReference.memberIdentifier.identifier;
}
_selectDeclaration(astDeclarations, memberReference, astSymbolName) {
const memberSelector = memberReference.selector;
if (memberSelector === undefined) {
if (astDeclarations.length === 1) {
return astDeclarations[0];
}
else {
// If we found multiple matches, but the extra ones are all ancillary declarations,
// then return the main declaration.
const nonAncillaryMatch = this._tryDisambiguateAncillaryMatches(astDeclarations);
if (nonAncillaryMatch) {
return nonAncillaryMatch;
}
return new ResolverFailure(`The reference is ambiguous because "${astSymbolName}"` +
` has more than one declaration; you need to add a TSDoc member reference selector`);
}
}
switch (memberSelector.selectorKind) {
case tsdoc.SelectorKind.System:
return this._selectUsingSystemSelector(astDeclarations, memberSelector, astSymbolName);
case tsdoc.SelectorKind.Index:
return this._selectUsingIndexSelector(astDeclarations, memberSelector, astSymbolName);
}
return new ResolverFailure(`The selector "${memberSelector.selector}" is not a supported selector type`);
}
_selectUsingSystemSelector(astDeclarations, memberSelector, astSymbolName) {
const selectorName = memberSelector.selector;
let selectorSyntaxKind;
switch (selectorName) {
case 'class':
selectorSyntaxKind = ts.SyntaxKind.ClassDeclaration;
break;
case 'enum':
selectorSyntaxKind = ts.SyntaxKind.EnumDeclaration;
break;
case 'function':
selectorSyntaxKind = ts.SyntaxKind.FunctionDeclaration;
break;
case 'interface':
selectorSyntaxKind = ts.SyntaxKind.InterfaceDeclaration;
break;
case 'namespace':
selectorSyntaxKind = ts.SyntaxKind.ModuleDeclaration;
break;
case 'type':
selectorSyntaxKind = ts.SyntaxKind.TypeAliasDeclaration;
break;
case 'variable':
selectorSyntaxKind = ts.SyntaxKind.VariableDeclaration;
break;
default:
return new ResolverFailure(`Unsupported system selector "${selectorName}"`);
}
const matches = astDeclarations.filter((x) => x.declaration.kind === selectorSyntaxKind);
if (matches.length === 0) {
return new ResolverFailure(`A declaration for "${astSymbolName}" was not found that matches the` +
` TSDoc selector "${selectorName}"`);
}
if (matches.length > 1) {
// If we found multiple matches, but the extra ones are all ancillary declarations,
// then return the main declaration.
const nonAncillaryMatch = this._tryDisambiguateAncillaryMatches(matches);
if (nonAncillaryMatch) {
return nonAncillaryMatch;
}
return new ResolverFailure(`More than one declaration "${astSymbolName}" matches the TSDoc selector "${selectorName}"`);
}
return matches[0];
}
_selectUsingIndexSelector(astDeclarations, memberSelector, astSymbolName) {
const selectorOverloadIndex = parseInt(memberSelector.selector, 10);
const matches = [];
for (const astDeclaration of astDeclarations) {
const overloadIndex = this._collector.getOverloadIndex(astDeclaration);
if (overloadIndex === selectorOverloadIndex) {
matches.push(astDeclaration);
}
}
if (matches.length === 0) {
return new ResolverFailure(`An overload for "${astSymbolName}" was not found that matches the` +
` TSDoc selector ":${selectorOverloadIndex}"`);
}
if (matches.length > 1) {
// If we found multiple matches, but the extra ones are all ancillary declarations,
// then return the main declaration.
const nonAncillaryMatch = this._tryDisambiguateAncillaryMatches(matches);
if (nonAncillaryMatch) {
return nonAncillaryMatch;
}
return new ResolverFailure(`More than one declaration for "${astSymbolName}" matches the` +
` TSDoc selector ":${selectorOverloadIndex}"`);
}
return matches[0];
}
/**
* This resolves an ambiguous match in the case where the extra matches are all ancillary declarations,
* except for one match that is the main declaration.
*/
_tryDisambiguateAncillaryMatches(matches) {
let result = undefined;
for (const match of matches) {
const declarationMetadata = this._collector.fetchDeclarationMetadata(match);
if (!declarationMetadata.isAncillary) {
if (result) {
return undefined; // more than one match
}
result = match;
}
}
return result;
}
}
exports.AstReferenceResolver = AstReferenceResolver;
//# sourceMappingURL=AstReferenceResolver.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,133 @@
import type * as ts from 'typescript';
import type { AstDeclaration } from './AstDeclaration';
import { AstEntity } from './AstEntity';
/**
* Constructor options for AstSymbol
*/
export interface IAstSymbolOptions {
readonly followedSymbol: ts.Symbol;
readonly localName: string;
readonly isExternal: boolean;
readonly nominalAnalysis: boolean;
readonly parentAstSymbol: AstSymbol | undefined;
readonly rootAstSymbol: AstSymbol | undefined;
}
/**
* The AstDeclaration and AstSymbol classes are API Extractor's equivalent of the compiler's
* ts.Declaration and ts.Symbol objects. They are created by the `AstSymbolTable` class.
*
* @remarks
* The AstSymbol represents the ts.Symbol information for an AstDeclaration. For example,
* if a method has 3 overloads, each overloaded signature will have its own AstDeclaration,
* but they will all share a common AstSymbol.
*
* For nested definitions, the AstSymbol has a unique parent (i.e. AstSymbol.rootAstSymbol),
* but the parent/children for each AstDeclaration may be different. Consider this example:
*
* ```ts
* export namespace N {
* export function f(): void { }
* }
*
* export interface N {
* g(): void;
* }
* ```
*
* Note how the parent/child relationships are different for the symbol tree versus
* the declaration tree, and the declaration tree has two roots:
*
* ```
* AstSymbol tree: AstDeclaration tree:
* - N - N (namespace)
* - f - f
* - g - N (interface)
* - g
* ```
*/
export declare class AstSymbol extends AstEntity {
/** {@inheritdoc} */
readonly localName: string;
/**
* If true, then the `followedSymbol` (i.e. original declaration) of this symbol
* is not part of the working package. The working package may still export this symbol,
* but if so it should be emitted as an alias such as `export { X } from "package1";`.
*/
readonly isExternal: boolean;
/**
* The compiler symbol where this type was defined, after following any aliases.
*
* @remarks
* This is a normal form that can be reached from any symbol alias by calling
* `TypeScriptHelpers.followAliases()`. It can be compared to determine whether two
* symbols refer to the same underlying type.
*/
readonly followedSymbol: ts.Symbol;
/**
* If true, then this AstSymbol represents a foreign object whose structure will be
* ignored. The AstDeclaration objects will not have any parent or children, and its references
* will not be analyzed.
*
* Nominal symbols are tracked e.g. when they are reexported by the working package.
*/
readonly nominalAnalysis: boolean;
/**
* Returns the symbol of the parent of this AstSymbol, or undefined if there is no parent.
* @remarks
* If a symbol has multiple declarations, we assume (as an axiom) that their parent
* declarations will belong to the same symbol. This means that the "parent" of a
* symbol is a well-defined concept. However, the "children" of a symbol are not very
* meaningful, because different declarations may have different nested members,
* so we usually need to traverse declarations to find children.
*/
readonly parentAstSymbol: AstSymbol | undefined;
/**
* Returns the symbol of the root of the AstDeclaration hierarchy.
* @remarks
* NOTE: If this AstSymbol is the root, then rootAstSymbol will point to itself.
*/
readonly rootAstSymbol: AstSymbol;
/**
* Additional information that is calculated later by the `Collector`. The actual type is `SymbolMetadata`,
* but we declare it as `unknown` because consumers must obtain this object by calling
* `Collector.fetchSymbolMetadata()`.
*/
symbolMetadata: unknown;
private readonly _astDeclarations;
private _analyzed;
constructor(options: IAstSymbolOptions);
/**
* The one or more declarations for this symbol.
* @remarks
* For example, if this symbol is a method, then the declarations might be
* various method overloads. If this symbol is a namespace, then the declarations
* might be separate namespace blocks with the same name that get combined via
* declaration merging.
*/
get astDeclarations(): ReadonlyArray<AstDeclaration>;
/**
* Returns true if the AstSymbolTable.analyze() was called for this object.
* See that function for details.
* @remarks
* AstSymbolTable.analyze() is always performed on the root AstSymbol. This function
* returns true if-and-only-if the root symbol was analyzed.
*/
get analyzed(): boolean;
/**
* This is an internal callback used when the AstSymbolTable attaches a new
* AstDeclaration to this object.
* @internal
*/
_notifyDeclarationAttach(astDeclaration: AstDeclaration): void;
/**
* This is an internal callback used when the AstSymbolTable.analyze()
* has processed this object.
* @internal
*/
_notifyAnalyzed(): void;
/**
* Helper that calls AstDeclaration.forEachDeclarationRecursive() for each AstDeclaration.
*/
forEachDeclarationRecursive(action: (astDeclaration: AstDeclaration) => void): void;
}
//# sourceMappingURL=AstSymbol.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"AstSymbol.d.ts","sourceRoot":"","sources":["../../src/analyzer/AstSymbol.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,cAAc,EAAE,EAAE,CAAC,MAAM,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,eAAe,EAAE,SAAS,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,aAAa,EAAE,SAAS,GAAG,SAAS,CAAC;CAC/C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qBAAa,SAAU,SAAQ,SAAS;IACtC,oBAAoB;IACpB,SAAgB,SAAS,EAAE,MAAM,CAAC;IAElC;;;;OAIG;IACH,SAAgB,UAAU,EAAE,OAAO,CAAC;IAEpC;;;;;;;OAOG;IACH,SAAgB,cAAc,EAAE,EAAE,CAAC,MAAM,CAAC;IAE1C;;;;;;OAMG;IACH,SAAgB,eAAe,EAAE,OAAO,CAAC;IAEzC;;;;;;;;OAQG;IACH,SAAgB,eAAe,EAAE,SAAS,GAAG,SAAS,CAAC;IAEvD;;;;OAIG;IACH,SAAgB,aAAa,EAAE,SAAS,CAAC;IAEzC;;;;OAIG;IACI,cAAc,EAAE,OAAO,CAAC;IAE/B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IAIpD,OAAO,CAAC,SAAS,CAAkB;gBAEhB,OAAO,EAAE,iBAAiB;IAY7C;;;;;;;OAOG;IACH,IAAW,eAAe,IAAI,aAAa,CAAC,cAAc,CAAC,CAE1D;IAED;;;;;;OAMG;IACH,IAAW,QAAQ,IAAI,OAAO,CAE7B;IAED;;;;OAIG;IACI,wBAAwB,CAAC,cAAc,EAAE,cAAc,GAAG,IAAI;IAOrE;;;;OAIG;IACI,eAAe,IAAI,IAAI;IAO9B;;OAEG;IACI,2BAA2B,CAAC,MAAM,EAAE,CAAC,cAAc,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI;CAK3F"}

View File

@ -0,0 +1,108 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.AstSymbol = void 0;
const node_core_library_1 = require("@rushstack/node-core-library");
const AstEntity_1 = require("./AstEntity");
/**
* The AstDeclaration and AstSymbol classes are API Extractor's equivalent of the compiler's
* ts.Declaration and ts.Symbol objects. They are created by the `AstSymbolTable` class.
*
* @remarks
* The AstSymbol represents the ts.Symbol information for an AstDeclaration. For example,
* if a method has 3 overloads, each overloaded signature will have its own AstDeclaration,
* but they will all share a common AstSymbol.
*
* For nested definitions, the AstSymbol has a unique parent (i.e. AstSymbol.rootAstSymbol),
* but the parent/children for each AstDeclaration may be different. Consider this example:
*
* ```ts
* export namespace N {
* export function f(): void { }
* }
*
* export interface N {
* g(): void;
* }
* ```
*
* Note how the parent/child relationships are different for the symbol tree versus
* the declaration tree, and the declaration tree has two roots:
*
* ```
* AstSymbol tree: AstDeclaration tree:
* - N - N (namespace)
* - f - f
* - g - N (interface)
* - g
* ```
*/
class AstSymbol extends AstEntity_1.AstEntity {
constructor(options) {
super();
// This flag is unused if this is not the root symbol.
// Being "analyzed" is a property of the root symbol.
this._analyzed = false;
this.followedSymbol = options.followedSymbol;
this.localName = options.localName;
this.isExternal = options.isExternal;
this.nominalAnalysis = options.nominalAnalysis;
this.parentAstSymbol = options.parentAstSymbol;
this.rootAstSymbol = options.rootAstSymbol || this;
this._astDeclarations = [];
}
/**
* The one or more declarations for this symbol.
* @remarks
* For example, if this symbol is a method, then the declarations might be
* various method overloads. If this symbol is a namespace, then the declarations
* might be separate namespace blocks with the same name that get combined via
* declaration merging.
*/
get astDeclarations() {
return this._astDeclarations;
}
/**
* Returns true if the AstSymbolTable.analyze() was called for this object.
* See that function for details.
* @remarks
* AstSymbolTable.analyze() is always performed on the root AstSymbol. This function
* returns true if-and-only-if the root symbol was analyzed.
*/
get analyzed() {
return this.rootAstSymbol._analyzed;
}
/**
* This is an internal callback used when the AstSymbolTable attaches a new
* AstDeclaration to this object.
* @internal
*/
_notifyDeclarationAttach(astDeclaration) {
if (this.analyzed) {
throw new node_core_library_1.InternalError('_notifyDeclarationAttach() called after analysis is already complete');
}
this._astDeclarations.push(astDeclaration);
}
/**
* This is an internal callback used when the AstSymbolTable.analyze()
* has processed this object.
* @internal
*/
_notifyAnalyzed() {
if (this.parentAstSymbol) {
throw new node_core_library_1.InternalError('_notifyAnalyzed() called for an AstSymbol which is not the root');
}
this._analyzed = true;
}
/**
* Helper that calls AstDeclaration.forEachDeclarationRecursive() for each AstDeclaration.
*/
forEachDeclarationRecursive(action) {
for (const astDeclaration of this.astDeclarations) {
astDeclaration.forEachDeclarationRecursive(action);
}
}
}
exports.AstSymbol = AstSymbol;
//# sourceMappingURL=AstSymbol.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,136 @@
import * as ts from 'typescript';
import { type PackageJsonLookup } from '@rushstack/node-core-library';
import { AstDeclaration } from './AstDeclaration';
import type { AstModule, AstModuleExportInfo } from './AstModule';
import type { AstEntity } from './AstEntity';
import type { MessageRouter } from '../collector/MessageRouter';
/**
* Options for `AstSymbolTable._fetchAstSymbol()`
*/
export interface IFetchAstSymbolOptions {
/**
* The symbol after any symbol aliases have been followed using TypeScriptHelpers.followAliases()
*/
followedSymbol: ts.Symbol;
/**
* True if followedSymbol is not part of the working package
*/
isExternal: boolean;
/**
* If true, symbols with AstSymbol.nominalAnalysis=true will be returned.
* Otherwise `undefined` will be returned for such symbols.
*/
includeNominalAnalysis: boolean;
/**
* True while populating the `AstSymbolTable`; false if we're doing a passive lookup
* without adding anything new to the table
*/
addIfMissing: boolean;
/**
* A hint to help `_fetchAstSymbol()` determine the `AstSymbol.localName`.
*/
localName?: string;
}
/**
* AstSymbolTable is the workhorse that builds AstSymbol and AstDeclaration objects.
* It maintains a cache of already constructed objects. AstSymbolTable constructs
* AstModule objects, but otherwise the state that it maintains is agnostic of
* any particular entry point. (For example, it does not track whether a given AstSymbol
* is "exported" or not.)
*
* Internally, AstSymbolTable relies on ExportAnalyzer to crawl import statements and determine where symbols
* are declared (i.e. the AstImport information needed to import them).
*/
export declare class AstSymbolTable {
private readonly _program;
private readonly _typeChecker;
private readonly _messageRouter;
private readonly _globalVariableAnalyzer;
private readonly _packageMetadataManager;
private readonly _exportAnalyzer;
private readonly _alreadyWarnedGlobalNames;
/**
* A mapping from ts.Symbol --> AstSymbol
* NOTE: The AstSymbol.followedSymbol will always be a lookup key, but additional keys
* are possible.
*
* After following type aliases, we use this map to look up the corresponding AstSymbol.
*/
private readonly _astSymbolsBySymbol;
/**
* A mapping from ts.Declaration --> AstDeclaration
*/
private readonly _astDeclarationsByDeclaration;
private readonly _entitiesByNode;
constructor(program: ts.Program, typeChecker: ts.TypeChecker, packageJsonLookup: PackageJsonLookup, bundledPackageNames: ReadonlySet<string>, messageRouter: MessageRouter);
/**
* Used to analyze an entry point that belongs to the working package.
*/
fetchAstModuleFromWorkingPackage(sourceFile: ts.SourceFile): AstModule;
/**
* This crawls the specified entry point and collects the full set of exported AstSymbols.
*/
fetchAstModuleExportInfo(astModule: AstModule): AstModuleExportInfo;
/**
* Attempts to retrieve an export by name from the specified `AstModule`.
* Returns undefined if no match was found.
*/
tryGetExportOfAstModule(exportName: string, astModule: AstModule): AstEntity | undefined;
/**
* Ensures that AstSymbol.analyzed is true for the provided symbol. The operation
* starts from the root symbol and then fills out all children of all declarations, and
* also calculates AstDeclaration.referencedAstSymbols for all declarations.
* If the symbol is not imported, any non-imported references are also analyzed.
*
* @remarks
* This is an expensive operation, so we only perform it for top-level exports of an
* the AstModule. For example, if some code references a nested class inside
* a namespace from another library, we do not analyze any of that class's siblings
* or members. (We do always construct its parents however, since AstDefinition.parent
* is immutable, and needed e.g. to calculate release tag inheritance.)
*/
analyze(astEntity: AstEntity): void;
/**
* For a given astDeclaration, this efficiently finds the child corresponding to the
* specified ts.Node. It is assumed that AstDeclaration.isSupportedSyntaxKind() would return true for
* that node type, and that the node is an immediate child of the provided AstDeclaration.
*/
getChildAstDeclarationByNode(node: ts.Node, parentAstDeclaration: AstDeclaration): AstDeclaration;
/**
* For a given ts.Identifier that is part of an AstSymbol that we analyzed, return the AstEntity that
* it refers to. Returns undefined if it doesn't refer to anything interesting.
* @remarks
* Throws an Error if the ts.Identifier is not part of node tree that was analyzed.
*/
tryGetEntityForNode(identifier: ts.Identifier | ts.ImportTypeNode): AstEntity | undefined;
/**
* Builds an AstSymbol.localName for a given ts.Symbol. In the current implementation, the localName is
* a TypeScript-like expression that may be a string literal or ECMAScript symbol expression.
*
* ```ts
* class X {
* // localName="identifier"
* public identifier: number = 1;
* // localName="\"identifier\""
* public "quoted string!": number = 2;
* // localName="[MyNamespace.MySymbol]"
* public [MyNamespace.MySymbol]: number = 3;
* }
* ```
*/
static getLocalNameForSymbol(symbol: ts.Symbol): string;
private _analyzeAstNamespaceImport;
private _analyzeAstSymbol;
/**
* Used by analyze to recursively analyze the entire child tree.
*/
private _analyzeChildTree;
private _fetchEntityForNode;
private _fetchAstDeclaration;
private _fetchAstSymbol;
/**
* Returns the first parent satisfying isAstDeclaration(), or undefined if none is found.
*/
private _tryFindFirstAstDeclarationParent;
}
//# sourceMappingURL=AstSymbolTable.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"AstSymbolTable.d.ts","sourceRoot":"","sources":["../../src/analyzer/AstSymbolTable.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,KAAK,iBAAiB,EAAiB,MAAM,8BAA8B,CAAC;AAErF,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGlD,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGlE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAKhE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,cAAc,EAAE,EAAE,CAAC,MAAM,CAAC;IAC1B;;OAEG;IACH,UAAU,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,sBAAsB,EAAE,OAAO,CAAC;IAEhC;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAa;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiB;IAC9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAA0B;IAClE,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAyB;IACjE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiB;IACjD,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAc;IAExD;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAA8D;IAElG;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,6BAA6B,CAG1C;IAIJ,OAAO,CAAC,QAAQ,CAAC,eAAe,CAG5B;gBAGF,OAAO,EAAE,EAAE,CAAC,OAAO,EACnB,WAAW,EAAE,EAAE,CAAC,WAAW,EAC3B,iBAAiB,EAAE,iBAAiB,EACpC,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAC,EACxC,aAAa,EAAE,aAAa;IAgB9B;;OAEG;IACI,gCAAgC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,GAAG,SAAS;IAI7E;;OAEG;IACI,wBAAwB,CAAC,SAAS,EAAE,SAAS,GAAG,mBAAmB;IAI1E;;;OAGG;IACI,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS;IAI/F;;;;;;;;;;;;OAYG;IACI,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAU1C;;;;OAIG;IAEI,4BAA4B,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,oBAAoB,EAAE,cAAc,GAAG,cAAc;IAgBxG;;;;;OAKG;IACI,mBAAmB,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,cAAc,GAAG,SAAS,GAAG,SAAS;IAOhG;;;;;;;;;;;;;;OAcG;WACW,qBAAqB,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,GAAG,MAAM;IAmE9D,OAAO,CAAC,0BAA0B;IAiBlC,OAAO,CAAC,iBAAiB;IA4CzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAsIzB,OAAO,CAAC,mBAAmB;IA4B3B,OAAO,CAAC,oBAAoB;IAiC5B,OAAO,CAAC,eAAe;IA2JvB;;OAEG;IACH,OAAO,CAAC,iCAAiC;CAU1C"}

View File

@ -0,0 +1,551 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AstSymbolTable = void 0;
/* eslint-disable no-bitwise */ // for ts.SymbolFlags
const ts = __importStar(require("typescript"));
const node_core_library_1 = require("@rushstack/node-core-library");
const AstDeclaration_1 = require("./AstDeclaration");
const TypeScriptHelpers_1 = require("./TypeScriptHelpers");
const AstSymbol_1 = require("./AstSymbol");
const PackageMetadataManager_1 = require("./PackageMetadataManager");
const ExportAnalyzer_1 = require("./ExportAnalyzer");
const AstNamespaceImport_1 = require("./AstNamespaceImport");
const TypeScriptInternals_1 = require("./TypeScriptInternals");
const SyntaxHelpers_1 = require("./SyntaxHelpers");
const SourceFileLocationFormatter_1 = require("./SourceFileLocationFormatter");
/**
* AstSymbolTable is the workhorse that builds AstSymbol and AstDeclaration objects.
* It maintains a cache of already constructed objects. AstSymbolTable constructs
* AstModule objects, but otherwise the state that it maintains is agnostic of
* any particular entry point. (For example, it does not track whether a given AstSymbol
* is "exported" or not.)
*
* Internally, AstSymbolTable relies on ExportAnalyzer to crawl import statements and determine where symbols
* are declared (i.e. the AstImport information needed to import them).
*/
class AstSymbolTable {
constructor(program, typeChecker, packageJsonLookup, bundledPackageNames, messageRouter) {
/**
* A mapping from ts.Symbol --> AstSymbol
* NOTE: The AstSymbol.followedSymbol will always be a lookup key, but additional keys
* are possible.
*
* After following type aliases, we use this map to look up the corresponding AstSymbol.
*/
this._astSymbolsBySymbol = new Map();
/**
* A mapping from ts.Declaration --> AstDeclaration
*/
this._astDeclarationsByDeclaration = new Map();
// Note that this is a mapping from specific AST nodes that we analyzed, based on the underlying symbol
// for that node.
this._entitiesByNode = new Map();
this._program = program;
this._typeChecker = typeChecker;
this._messageRouter = messageRouter;
this._globalVariableAnalyzer = TypeScriptInternals_1.TypeScriptInternals.getGlobalVariableAnalyzer(program);
this._packageMetadataManager = new PackageMetadataManager_1.PackageMetadataManager(packageJsonLookup, messageRouter);
this._exportAnalyzer = new ExportAnalyzer_1.ExportAnalyzer(this._program, this._typeChecker, bundledPackageNames, {
analyze: this.analyze.bind(this),
fetchAstSymbol: this._fetchAstSymbol.bind(this)
});
this._alreadyWarnedGlobalNames = new Set();
}
/**
* Used to analyze an entry point that belongs to the working package.
*/
fetchAstModuleFromWorkingPackage(sourceFile) {
return this._exportAnalyzer.fetchAstModuleFromSourceFile(sourceFile, undefined, false);
}
/**
* This crawls the specified entry point and collects the full set of exported AstSymbols.
*/
fetchAstModuleExportInfo(astModule) {
return this._exportAnalyzer.fetchAstModuleExportInfo(astModule);
}
/**
* Attempts to retrieve an export by name from the specified `AstModule`.
* Returns undefined if no match was found.
*/
tryGetExportOfAstModule(exportName, astModule) {
return this._exportAnalyzer.tryGetExportOfAstModule(exportName, astModule);
}
/**
* Ensures that AstSymbol.analyzed is true for the provided symbol. The operation
* starts from the root symbol and then fills out all children of all declarations, and
* also calculates AstDeclaration.referencedAstSymbols for all declarations.
* If the symbol is not imported, any non-imported references are also analyzed.
*
* @remarks
* This is an expensive operation, so we only perform it for top-level exports of an
* the AstModule. For example, if some code references a nested class inside
* a namespace from another library, we do not analyze any of that class's siblings
* or members. (We do always construct its parents however, since AstDefinition.parent
* is immutable, and needed e.g. to calculate release tag inheritance.)
*/
analyze(astEntity) {
if (astEntity instanceof AstSymbol_1.AstSymbol) {
return this._analyzeAstSymbol(astEntity);
}
if (astEntity instanceof AstNamespaceImport_1.AstNamespaceImport) {
return this._analyzeAstNamespaceImport(astEntity);
}
}
/**
* For a given astDeclaration, this efficiently finds the child corresponding to the
* specified ts.Node. It is assumed that AstDeclaration.isSupportedSyntaxKind() would return true for
* that node type, and that the node is an immediate child of the provided AstDeclaration.
*/
// NOTE: This could be a method of AstSymbol if it had a backpointer to its AstSymbolTable.
getChildAstDeclarationByNode(node, parentAstDeclaration) {
if (!parentAstDeclaration.astSymbol.analyzed) {
throw new Error('getChildDeclarationByNode() cannot be used for an AstSymbol that was not analyzed');
}
const childAstDeclaration = this._astDeclarationsByDeclaration.get(node);
if (!childAstDeclaration) {
throw new Error('Child declaration not found for the specified node');
}
if (childAstDeclaration.parent !== parentAstDeclaration) {
throw new node_core_library_1.InternalError('The found child is not attached to the parent AstDeclaration');
}
return childAstDeclaration;
}
/**
* For a given ts.Identifier that is part of an AstSymbol that we analyzed, return the AstEntity that
* it refers to. Returns undefined if it doesn't refer to anything interesting.
* @remarks
* Throws an Error if the ts.Identifier is not part of node tree that was analyzed.
*/
tryGetEntityForNode(identifier) {
if (!this._entitiesByNode.has(identifier)) {
throw new node_core_library_1.InternalError('tryGetEntityForIdentifier() called for an identifier that was not analyzed');
}
return this._entitiesByNode.get(identifier);
}
/**
* Builds an AstSymbol.localName for a given ts.Symbol. In the current implementation, the localName is
* a TypeScript-like expression that may be a string literal or ECMAScript symbol expression.
*
* ```ts
* class X {
* // localName="identifier"
* public identifier: number = 1;
* // localName="\"identifier\""
* public "quoted string!": number = 2;
* // localName="[MyNamespace.MySymbol]"
* public [MyNamespace.MySymbol]: number = 3;
* }
* ```
*/
static getLocalNameForSymbol(symbol) {
// TypeScript binds well-known ECMAScript symbols like "[Symbol.iterator]" as "__@iterator".
// Decode it back into "[Symbol.iterator]".
const wellKnownSymbolName = TypeScriptHelpers_1.TypeScriptHelpers.tryDecodeWellKnownSymbolName(symbol.escapedName);
if (wellKnownSymbolName) {
return wellKnownSymbolName;
}
const isUniqueSymbol = TypeScriptHelpers_1.TypeScriptHelpers.isUniqueSymbolName(symbol.escapedName);
// We will try to obtain the name from a declaration; otherwise we'll fall back to the symbol name.
let unquotedName = symbol.name;
for (const declaration of symbol.declarations || []) {
// Handle cases such as "export default class X { }" where the symbol name is "default"
// but the local name is "X".
const localSymbol = TypeScriptInternals_1.TypeScriptInternals.tryGetLocalSymbol(declaration);
if (localSymbol) {
unquotedName = localSymbol.name;
}
// If it is a non-well-known symbol, then return the late-bound name. For example, "X.Y.z" in this example:
//
// namespace X {
// export namespace Y {
// export const z: unique symbol = Symbol("z");
// }
// }
//
// class C {
// public [X.Y.z](): void { }
// }
//
if (isUniqueSymbol) {
const declarationName = ts.getNameOfDeclaration(declaration);
if (declarationName && ts.isComputedPropertyName(declarationName)) {
const lateBoundName = TypeScriptHelpers_1.TypeScriptHelpers.tryGetLateBoundName(declarationName);
if (lateBoundName) {
// Here the string may contain an expression such as "[X.Y.z]". Names starting with "[" are always
// expressions. If a string literal contains those characters, the code below will JSON.stringify() it
// to avoid a collision.
return lateBoundName;
}
}
}
}
// Otherwise that name may come from a quoted string or pseudonym like `__constructor`.
// If the string is not a safe identifier, then we must add quotes.
// Note that if it was quoted but did not need to be quoted, here we will remove the quotes.
if (!SyntaxHelpers_1.SyntaxHelpers.isSafeUnquotedMemberIdentifier(unquotedName)) {
// For API Extractor's purposes, a canonical form is more appropriate than trying to reflect whatever
// appeared in the source code. The code is not even guaranteed to be consistent, for example:
//
// class X {
// public "f1"(x: string): void;
// public f1(x: boolean): void;
// public 'f1'(x: string | boolean): void { }
// }
return JSON.stringify(unquotedName);
}
return unquotedName;
}
_analyzeAstNamespaceImport(astNamespaceImport) {
if (astNamespaceImport.analyzed) {
return;
}
// mark before actual analyzing, to handle module cyclic reexport
astNamespaceImport.analyzed = true;
const exportedLocalEntities = this.fetchAstModuleExportInfo(astNamespaceImport.astModule).exportedLocalEntities;
for (const exportedEntity of exportedLocalEntities.values()) {
this.analyze(exportedEntity);
}
}
_analyzeAstSymbol(astSymbol) {
if (astSymbol.analyzed) {
return;
}
if (astSymbol.nominalAnalysis) {
// We don't analyze nominal symbols
astSymbol._notifyAnalyzed();
return;
}
// Start at the root of the tree
const rootAstSymbol = astSymbol.rootAstSymbol;
// Calculate the full child tree for each definition
for (const astDeclaration of rootAstSymbol.astDeclarations) {
this._analyzeChildTree(astDeclaration.declaration, astDeclaration);
}
rootAstSymbol._notifyAnalyzed();
if (!astSymbol.isExternal) {
// If this symbol is non-external (i.e. it belongs to the working package), then we also analyze any
// referencedAstSymbols that are non-external. For example, this ensures that forgotten exports
// get analyzed.
rootAstSymbol.forEachDeclarationRecursive((astDeclaration) => {
for (const referencedAstEntity of astDeclaration.referencedAstEntities) {
// Walk up to the root of the tree, looking for any imports along the way
if (referencedAstEntity instanceof AstSymbol_1.AstSymbol) {
if (!referencedAstEntity.isExternal) {
this._analyzeAstSymbol(referencedAstEntity);
}
}
if (referencedAstEntity instanceof AstNamespaceImport_1.AstNamespaceImport) {
if (!referencedAstEntity.astModule.isExternal) {
this._analyzeAstNamespaceImport(referencedAstEntity);
}
}
}
});
}
}
/**
* Used by analyze to recursively analyze the entire child tree.
*/
_analyzeChildTree(node, governingAstDeclaration) {
switch (node.kind) {
case ts.SyntaxKind.JSDocComment: // Skip JSDoc comments - TS considers @param tags TypeReference nodes
return;
// Is this a reference to another AstSymbol?
case ts.SyntaxKind.TypeReference: // general type references
case ts.SyntaxKind.ExpressionWithTypeArguments: // special case for e.g. the "extends" keyword
case ts.SyntaxKind.ComputedPropertyName: // used for EcmaScript "symbols", e.g. "[toPrimitive]".
case ts.SyntaxKind.TypeQuery: // represents for "typeof X" as a type
{
// Sometimes the type reference will involve multiple identifiers, e.g. "a.b.C".
// In this case, we only need to worry about importing the first identifier,
// so do a depth-first search for it:
const identifierNode = TypeScriptHelpers_1.TypeScriptHelpers.findFirstChildNode(node, ts.SyntaxKind.Identifier);
if (identifierNode) {
let referencedAstEntity = this._entitiesByNode.get(identifierNode);
if (!referencedAstEntity) {
const symbol = this._typeChecker.getSymbolAtLocation(identifierNode);
if (!symbol) {
throw new Error('Symbol not found for identifier: ' + identifierNode.getText());
}
// Normally we expect getSymbolAtLocation() to take us to a declaration within the same source
// file, or else to an explicit "import" statement within the same source file. But in certain
// situations (e.g. a global variable) the symbol will refer to a declaration in some other
// source file. We'll call that case a "displaced symbol".
//
// For more info, see this discussion:
// https://github.com/microsoft/rushstack/issues/1765#issuecomment-595559849
let displacedSymbol = true;
for (const declaration of symbol.declarations || []) {
if (declaration.getSourceFile() === identifierNode.getSourceFile()) {
displacedSymbol = false;
break;
}
}
if (displacedSymbol) {
if (this._globalVariableAnalyzer.hasGlobalName(identifierNode.text)) {
// If the displaced symbol is a global variable, then API Extractor simply ignores it.
// Ambient declarations typically describe the runtime environment (provided by an API consumer),
// so we don't bother analyzing them as an API contract. (There are probably some packages
// that include interesting global variables in their API, but API Extractor doesn't support
// that yet; it would be a feature request.)
if (this._messageRouter.showDiagnostics) {
if (!this._alreadyWarnedGlobalNames.has(identifierNode.text)) {
this._alreadyWarnedGlobalNames.add(identifierNode.text);
this._messageRouter.logDiagnostic(`Ignoring reference to global variable "${identifierNode.text}"` +
` in ` +
SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(identifierNode));
}
}
}
else {
// If you encounter this, please report a bug with a repro. We're interested to know
// how it can occur.
throw new node_core_library_1.InternalError(`Unable to follow symbol for "${identifierNode.text}"`);
}
}
else {
referencedAstEntity = this._exportAnalyzer.fetchReferencedAstEntity(symbol, governingAstDeclaration.astSymbol.isExternal);
this._entitiesByNode.set(identifierNode, referencedAstEntity);
}
}
if (referencedAstEntity) {
governingAstDeclaration._notifyReferencedAstEntity(referencedAstEntity);
}
}
}
break;
// Is this the identifier for the governingAstDeclaration?
case ts.SyntaxKind.Identifier:
{
const identifierNode = node;
if (!this._entitiesByNode.has(identifierNode)) {
const symbol = this._typeChecker.getSymbolAtLocation(identifierNode);
let referencedAstEntity = undefined;
if (symbol === governingAstDeclaration.astSymbol.followedSymbol) {
referencedAstEntity = this._fetchEntityForNode(identifierNode, governingAstDeclaration);
}
this._entitiesByNode.set(identifierNode, referencedAstEntity);
}
}
break;
case ts.SyntaxKind.ImportType:
{
const importTypeNode = node;
let referencedAstEntity = this._entitiesByNode.get(importTypeNode);
if (!this._entitiesByNode.has(importTypeNode)) {
referencedAstEntity = this._fetchEntityForNode(importTypeNode, governingAstDeclaration);
if (!referencedAstEntity) {
// This should never happen
throw new Error('Failed to fetch entity for import() type node: ' + importTypeNode.getText());
}
this._entitiesByNode.set(importTypeNode, referencedAstEntity);
}
if (referencedAstEntity) {
governingAstDeclaration._notifyReferencedAstEntity(referencedAstEntity);
}
}
break;
}
// Is this node declaring a new AstSymbol?
const newGoverningAstDeclaration = this._fetchAstDeclaration(node, governingAstDeclaration.astSymbol.isExternal);
for (const childNode of node.getChildren()) {
this._analyzeChildTree(childNode, newGoverningAstDeclaration || governingAstDeclaration);
}
}
_fetchEntityForNode(node, governingAstDeclaration) {
let referencedAstEntity = this._entitiesByNode.get(node);
if (!referencedAstEntity) {
if (node.kind === ts.SyntaxKind.ImportType) {
referencedAstEntity = this._exportAnalyzer.fetchReferencedAstEntityFromImportTypeNode(node, governingAstDeclaration.astSymbol.isExternal);
}
else {
const symbol = this._typeChecker.getSymbolAtLocation(node);
if (!symbol) {
throw new Error('Symbol not found for identifier: ' + node.getText());
}
referencedAstEntity = this._exportAnalyzer.fetchReferencedAstEntity(symbol, governingAstDeclaration.astSymbol.isExternal);
}
this._entitiesByNode.set(node, referencedAstEntity);
}
return referencedAstEntity;
}
_fetchAstDeclaration(node, isExternal) {
if (!AstDeclaration_1.AstDeclaration.isSupportedSyntaxKind(node.kind)) {
return undefined;
}
const symbol = TypeScriptHelpers_1.TypeScriptHelpers.getSymbolForDeclaration(node, this._typeChecker);
if (!symbol) {
throw new node_core_library_1.InternalError('Unable to find symbol for node');
}
const astSymbol = this._fetchAstSymbol({
followedSymbol: symbol,
isExternal: isExternal,
includeNominalAnalysis: true,
addIfMissing: true
});
if (!astSymbol) {
return undefined;
}
const astDeclaration = this._astDeclarationsByDeclaration.get(node);
if (!astDeclaration) {
throw new node_core_library_1.InternalError('Unable to find constructed AstDeclaration');
}
return astDeclaration;
}
_fetchAstSymbol(options) {
const followedSymbol = options.followedSymbol;
// Filter out symbols representing constructs that we don't care about
const arbitraryDeclaration = TypeScriptHelpers_1.TypeScriptHelpers.tryGetADeclaration(followedSymbol);
if (!arbitraryDeclaration) {
return undefined;
}
if (followedSymbol.flags &
(ts.SymbolFlags.TypeParameter | ts.SymbolFlags.TypeLiteral | ts.SymbolFlags.Transient)) {
if (!TypeScriptInternals_1.TypeScriptInternals.isLateBoundSymbol(followedSymbol)) {
return undefined;
}
}
// API Extractor doesn't analyze ambient declarations at all
if (TypeScriptHelpers_1.TypeScriptHelpers.isAmbient(followedSymbol, this._typeChecker)) {
// We make a special exemption for ambient declarations that appear in a source file containing
// an "export=" declaration that allows them to be imported as non-ambient.
if (!this._exportAnalyzer.isImportableAmbientSourceFile(arbitraryDeclaration.getSourceFile())) {
return undefined;
}
}
// Make sure followedSymbol isn't an alias for something else
if (TypeScriptHelpers_1.TypeScriptHelpers.isFollowableAlias(followedSymbol, this._typeChecker)) {
// We expect the caller to have already followed any aliases
throw new node_core_library_1.InternalError('AstSymbolTable._fetchAstSymbol() cannot be called with a symbol alias');
}
let astSymbol = this._astSymbolsBySymbol.get(followedSymbol);
if (!astSymbol) {
// None of the above lookups worked, so create a new entry...
let nominalAnalysis = false;
if (options.isExternal) {
// If the file is from an external package that does not support AEDoc, normally we ignore it completely.
// But in some cases (e.g. checking star exports of an external package) we need an AstSymbol to
// represent it, but we don't need to analyze its sibling/children.
const followedSymbolSourceFileName = arbitraryDeclaration.getSourceFile().fileName;
if (!this._packageMetadataManager.isAedocSupportedFor(followedSymbolSourceFileName)) {
nominalAnalysis = true;
if (!options.includeNominalAnalysis) {
return undefined;
}
}
}
let parentAstSymbol = undefined;
if (!nominalAnalysis) {
for (const declaration of followedSymbol.declarations || []) {
if (!AstDeclaration_1.AstDeclaration.isSupportedSyntaxKind(declaration.kind)) {
throw new node_core_library_1.InternalError(`The "${followedSymbol.name}" symbol has a` +
` ts.SyntaxKind.${ts.SyntaxKind[declaration.kind]} declaration which is not (yet?)` +
` supported by API Extractor`);
}
}
// We always fetch the entire chain of parents for each declaration.
// (Children/siblings are only analyzed on demand.)
// Key assumptions behind this squirrely logic:
//
// IF a given symbol has two declarations D1 and D2; AND
// If D1 has a parent P1, then
// - D2 will also have a parent P2; AND
// - P1 and P2's symbol will be the same
// - but P1 and P2 may be different (e.g. merged namespaces containing merged interfaces)
// Is there a parent AstSymbol? First we check to see if there is a parent declaration:
if (arbitraryDeclaration) {
const arbitraryParentDeclaration = this._tryFindFirstAstDeclarationParent(arbitraryDeclaration);
if (arbitraryParentDeclaration) {
const parentSymbol = TypeScriptHelpers_1.TypeScriptHelpers.getSymbolForDeclaration(arbitraryParentDeclaration, this._typeChecker);
parentAstSymbol = this._fetchAstSymbol({
followedSymbol: parentSymbol,
isExternal: options.isExternal,
includeNominalAnalysis: false,
addIfMissing: true
});
if (!parentAstSymbol) {
throw new node_core_library_1.InternalError('Unable to construct a parent AstSymbol for ' + followedSymbol.name);
}
}
}
}
const localName = options.localName || AstSymbolTable.getLocalNameForSymbol(followedSymbol);
astSymbol = new AstSymbol_1.AstSymbol({
followedSymbol: followedSymbol,
localName: localName,
isExternal: options.isExternal,
nominalAnalysis: nominalAnalysis,
parentAstSymbol: parentAstSymbol,
rootAstSymbol: parentAstSymbol ? parentAstSymbol.rootAstSymbol : undefined
});
this._astSymbolsBySymbol.set(followedSymbol, astSymbol);
// Okay, now while creating the declarations we will wire them up to the
// their corresponding parent declarations
for (const declaration of followedSymbol.declarations || []) {
let parentAstDeclaration = undefined;
if (parentAstSymbol) {
const parentDeclaration = this._tryFindFirstAstDeclarationParent(declaration);
if (!parentDeclaration) {
throw new node_core_library_1.InternalError('Missing parent declaration');
}
parentAstDeclaration = this._astDeclarationsByDeclaration.get(parentDeclaration);
if (!parentAstDeclaration) {
throw new node_core_library_1.InternalError('Missing parent AstDeclaration');
}
}
const astDeclaration = new AstDeclaration_1.AstDeclaration({
declaration,
astSymbol,
parent: parentAstDeclaration
});
this._astDeclarationsByDeclaration.set(declaration, astDeclaration);
}
}
if (options.isExternal !== astSymbol.isExternal) {
throw new node_core_library_1.InternalError(`Cannot assign isExternal=${options.isExternal} for` +
` the symbol ${astSymbol.localName} because it was previously registered` +
` with isExternal=${astSymbol.isExternal}`);
}
return astSymbol;
}
/**
* Returns the first parent satisfying isAstDeclaration(), or undefined if none is found.
*/
_tryFindFirstAstDeclarationParent(node) {
let currentNode = node.parent;
while (currentNode) {
if (AstDeclaration_1.AstDeclaration.isSupportedSyntaxKind(currentNode.kind)) {
return currentNode;
}
currentNode = currentNode.parent;
}
return undefined;
}
}
exports.AstSymbolTable = AstSymbolTable;
//# sourceMappingURL=AstSymbolTable.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,113 @@
import * as ts from 'typescript';
import { AstSymbol } from './AstSymbol';
import { AstModule, AstModuleExportInfo } from './AstModule';
import type { IFetchAstSymbolOptions } from './AstSymbolTable';
import type { AstEntity } from './AstEntity';
/**
* Exposes the minimal APIs from AstSymbolTable that are needed by ExportAnalyzer.
*
* In particular, we want ExportAnalyzer to be able to call AstSymbolTable._fetchAstSymbol() even though it
* is a very private API that should not be exposed to any other components.
*/
export interface IAstSymbolTable {
fetchAstSymbol(options: IFetchAstSymbolOptions): AstSymbol | undefined;
analyze(astEntity: AstEntity): void;
}
/**
* Used with ExportAnalyzer.fetchAstModuleBySourceFile() to provide contextual information about how the source file
* was imported.
*/
interface IAstModuleReference {
/**
* For example, if we are following a statement like `import { X } from 'some-package'`, this will be the
* string `"some-package"`.
*/
moduleSpecifier: string;
/**
* For example, if we are following a statement like `import { X } from 'some-package'`, this will be the
* symbol for `X`.
*/
moduleSpecifierSymbol: ts.Symbol;
}
/**
* The ExportAnalyzer is an internal part of AstSymbolTable that has been moved out into its own source file
* because it is a complex and mostly self-contained algorithm.
*
* Its job is to build up AstModule objects by crawling import statements to discover where declarations come from.
* This is conceptually the same as the compiler's own TypeChecker.getExportsOfModule(), except that when
* ExportAnalyzer encounters a declaration that was imported from an external package, it remembers how it was imported
* (i.e. the AstImport object). Today the compiler API does not expose this information, which is crucial for
* generating .d.ts rollups.
*/
export declare class ExportAnalyzer {
private readonly _program;
private readonly _typeChecker;
private readonly _bundledPackageNames;
private readonly _astSymbolTable;
private readonly _astModulesByModuleSymbol;
private readonly _importableAmbientSourceFiles;
private readonly _astImportsByKey;
private readonly _astNamespaceImportByModule;
constructor(program: ts.Program, typeChecker: ts.TypeChecker, bundledPackageNames: ReadonlySet<string>, astSymbolTable: IAstSymbolTable);
/**
* For a given source file, this analyzes all of its exports and produces an AstModule object.
*
* @param moduleReference - contextual information about the import statement that took us to this source file.
* or `undefined` if this source file is the initial entry point
* @param isExternal - whether the given `moduleReference` is external.
*/
fetchAstModuleFromSourceFile(sourceFile: ts.SourceFile, moduleReference: IAstModuleReference | undefined, isExternal: boolean): AstModule;
/**
* Retrieves the symbol for the module corresponding to the ts.SourceFile that is being imported/exported.
*
* @remarks
* The `module` keyword can be used to declare multiple TypeScript modules inside a single source file.
* (This is a deprecated construct and mainly used for typings such as `@types/node`.) In this situation,
* `moduleReference` helps us to fish out the correct module symbol.
*/
private _getModuleSymbolFromSourceFile;
/**
* Implementation of {@link AstSymbolTable.fetchAstModuleExportInfo}.
*/
fetchAstModuleExportInfo(entryPointAstModule: AstModule): AstModuleExportInfo;
/**
* Returns true if the module specifier refers to an external package. Ignores packages listed in the
* "bundledPackages" setting from the api-extractor.json config file.
*/
private _isExternalModulePath;
/**
* Returns true if when we analyzed sourceFile, we found that it contains an "export=" statement that allows
* it to behave /either/ as an ambient module /or/ as a regular importable module. In this case,
* `AstSymbolTable._fetchAstSymbol()` will analyze its symbols even though `TypeScriptHelpers.isAmbient()`
* returns true.
*/
isImportableAmbientSourceFile(sourceFile: ts.SourceFile): boolean;
private _collectAllExportsRecursive;
/**
* For a given symbol (which was encountered in the specified sourceFile), this fetches the AstEntity that it
* refers to. For example, if a particular interface describes the return value of a function, this API can help
* us determine a TSDoc declaration reference for that symbol (if the symbol is exported).
*/
fetchReferencedAstEntity(symbol: ts.Symbol, referringModuleIsExternal: boolean): AstEntity | undefined;
fetchReferencedAstEntityFromImportTypeNode(node: ts.ImportTypeNode, referringModuleIsExternal: boolean): AstEntity | undefined;
private _tryMatchExportDeclaration;
private _tryMatchImportDeclaration;
private static _getIsTypeOnly;
private _getExportOfSpecifierAstModule;
private _getExportOfAstModule;
/**
* Implementation of {@link AstSymbolTable.tryGetExportOfAstModule}.
*/
tryGetExportOfAstModule(exportName: string, astModule: AstModule): AstEntity | undefined;
private _tryGetExportOfAstModule;
private _tryGetExternalModulePath;
/**
* Given an ImportDeclaration of the form `export { X } from "___";`, this interprets the module specifier (`"___"`)
* and fetches the corresponding AstModule object.
*/
private _fetchSpecifierAstModule;
private _fetchAstImport;
private _getModuleSpecifier;
}
export {};
//# sourceMappingURL=ExportAnalyzer.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"ExportAnalyzer.d.ts","sourceRoot":"","sources":["../../src/analyzer/ExportAnalyzer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAIjC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAG7D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,OAAO,EAAE,sBAAsB,GAAG,SAAS,GAAG,SAAS,CAAC;IAEvE,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;CACrC;AAED;;;GAGG;AACH,UAAU,mBAAmB;IAC3B;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,qBAAqB,EAAE,EAAE,CAAC,MAAM,CAAC;CAClC;AAED;;;;;;;;;GASG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAa;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiB;IAC9C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAsB;IAC3D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAElD,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAA8D;IAGxG,OAAO,CAAC,QAAQ,CAAC,6BAA6B,CAAgD;IAE9F,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAwD;IACzF,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAAiD;gBAG3F,OAAO,EAAE,EAAE,CAAC,OAAO,EACnB,WAAW,EAAE,EAAE,CAAC,WAAW,EAC3B,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAC,EACxC,cAAc,EAAE,eAAe;IAQjC;;;;;;OAMG;IACI,4BAA4B,CACjC,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,eAAe,EAAE,mBAAmB,GAAG,SAAS,EAChD,UAAU,EAAE,OAAO,GAClB,SAAS;IAoFZ;;;;;;;OAOG;IACH,OAAO,CAAC,8BAA8B;IAiDtC;;OAEG;IACI,wBAAwB,CAAC,mBAAmB,EAAE,SAAS,GAAG,mBAAmB;IAepF;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IA4C7B;;;;;OAKG;IACI,6BAA6B,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,GAAG,OAAO;IAIxE,OAAO,CAAC,2BAA2B;IAgDnC;;;;OAIG;IACI,wBAAwB,CAC7B,MAAM,EAAE,EAAE,CAAC,MAAM,EACjB,yBAAyB,EAAE,OAAO,GACjC,SAAS,GAAG,SAAS;IA4DjB,0CAA0C,CAC/C,IAAI,EAAE,EAAE,CAAC,cAAc,EACvB,yBAAyB,EAAE,OAAO,GACjC,SAAS,GAAG,SAAS;IA0FxB,OAAO,CAAC,0BAA0B;IA4ElC,OAAO,CAAC,0BAA0B;IAqKlC,OAAO,CAAC,MAAM,CAAC,cAAc;IAO7B,OAAO,CAAC,8BAA8B;IAatC,OAAO,CAAC,qBAAqB;IAe7B;;OAEG;IACI,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS;IAK/F,OAAO,CAAC,wBAAwB;IAoDhC,OAAO,CAAC,yBAAyB;IAWjC;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IA4DhC,OAAO,CAAC,eAAe;IA8BvB,OAAO,CAAC,mBAAmB;CAgB5B"}

View File

@ -0,0 +1,732 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExportAnalyzer = void 0;
const ts = __importStar(require("typescript"));
const node_core_library_1 = require("@rushstack/node-core-library");
const TypeScriptHelpers_1 = require("./TypeScriptHelpers");
const AstSymbol_1 = require("./AstSymbol");
const AstImport_1 = require("./AstImport");
const AstModule_1 = require("./AstModule");
const TypeScriptInternals_1 = require("./TypeScriptInternals");
const SourceFileLocationFormatter_1 = require("./SourceFileLocationFormatter");
const AstNamespaceImport_1 = require("./AstNamespaceImport");
const SyntaxHelpers_1 = require("./SyntaxHelpers");
/**
* The ExportAnalyzer is an internal part of AstSymbolTable that has been moved out into its own source file
* because it is a complex and mostly self-contained algorithm.
*
* Its job is to build up AstModule objects by crawling import statements to discover where declarations come from.
* This is conceptually the same as the compiler's own TypeChecker.getExportsOfModule(), except that when
* ExportAnalyzer encounters a declaration that was imported from an external package, it remembers how it was imported
* (i.e. the AstImport object). Today the compiler API does not expose this information, which is crucial for
* generating .d.ts rollups.
*/
class ExportAnalyzer {
constructor(program, typeChecker, bundledPackageNames, astSymbolTable) {
this._astModulesByModuleSymbol = new Map();
// Used with isImportableAmbientSourceFile()
this._importableAmbientSourceFiles = new Set();
this._astImportsByKey = new Map();
this._astNamespaceImportByModule = new Map();
this._program = program;
this._typeChecker = typeChecker;
this._bundledPackageNames = bundledPackageNames;
this._astSymbolTable = astSymbolTable;
}
/**
* For a given source file, this analyzes all of its exports and produces an AstModule object.
*
* @param moduleReference - contextual information about the import statement that took us to this source file.
* or `undefined` if this source file is the initial entry point
* @param isExternal - whether the given `moduleReference` is external.
*/
fetchAstModuleFromSourceFile(sourceFile, moduleReference, isExternal) {
const moduleSymbol = this._getModuleSymbolFromSourceFile(sourceFile, moduleReference);
// Don't traverse into a module that we already processed before:
// The compiler allows m1 to have "export * from 'm2'" and "export * from 'm3'",
// even if m2 and m3 both have "export * from 'm4'".
let astModule = this._astModulesByModuleSymbol.get(moduleSymbol);
if (!astModule) {
// (If moduleReference === undefined, then this is the entry point of the local project being analyzed.)
const externalModulePath = moduleReference !== undefined && isExternal ? moduleReference.moduleSpecifier : undefined;
astModule = new AstModule_1.AstModule({ sourceFile, moduleSymbol, externalModulePath });
this._astModulesByModuleSymbol.set(moduleSymbol, astModule);
if (astModule.isExternal) {
// It's an external package, so do the special simplified analysis that doesn't crawl into referenced modules
for (const exportedSymbol of this._typeChecker.getExportsOfModule(moduleSymbol)) {
if (externalModulePath === undefined) {
throw new node_core_library_1.InternalError('Failed assertion: externalModulePath=undefined but astModule.isExternal=true');
}
const followedSymbol = TypeScriptHelpers_1.TypeScriptHelpers.followAliases(exportedSymbol, this._typeChecker);
// Ignore virtual symbols that don't have any declarations
const arbitraryDeclaration = TypeScriptHelpers_1.TypeScriptHelpers.tryGetADeclaration(followedSymbol);
if (arbitraryDeclaration) {
const astSymbol = this._astSymbolTable.fetchAstSymbol({
followedSymbol: followedSymbol,
isExternal: astModule.isExternal,
includeNominalAnalysis: true,
addIfMissing: true
});
if (!astSymbol) {
throw new Error(`Unsupported export ${JSON.stringify(exportedSymbol.name)}:\n` +
SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(arbitraryDeclaration));
}
astModule.cachedExportedEntities.set(exportedSymbol.name, astSymbol);
}
}
}
else {
// The module is part of the local project, so do the full analysis
if (moduleSymbol.exports) {
// The "export * from 'module-name';" declarations are all attached to a single virtual symbol
// whose name is InternalSymbolName.ExportStar
const exportStarSymbol = moduleSymbol.exports.get(ts.InternalSymbolName.ExportStar);
if (exportStarSymbol) {
for (const exportStarDeclaration of exportStarSymbol.getDeclarations() || []) {
if (ts.isExportDeclaration(exportStarDeclaration)) {
const starExportedModule = this._fetchSpecifierAstModule(exportStarDeclaration, exportStarSymbol);
if (starExportedModule !== undefined) {
astModule.starExportedModules.add(starExportedModule);
}
}
else {
// Ignore ExportDeclaration nodes that don't match the expected pattern
// TODO: Should we report a warning?
}
}
}
}
}
}
return astModule;
}
/**
* Retrieves the symbol for the module corresponding to the ts.SourceFile that is being imported/exported.
*
* @remarks
* The `module` keyword can be used to declare multiple TypeScript modules inside a single source file.
* (This is a deprecated construct and mainly used for typings such as `@types/node`.) In this situation,
* `moduleReference` helps us to fish out the correct module symbol.
*/
_getModuleSymbolFromSourceFile(sourceFile, moduleReference) {
const moduleSymbol = TypeScriptInternals_1.TypeScriptInternals.tryGetSymbolForDeclaration(sourceFile, this._typeChecker);
if (moduleSymbol !== undefined) {
// This is the normal case. The SourceFile acts is a module and has a symbol.
return moduleSymbol;
}
if (moduleReference !== undefined) {
// But there is also an elaborate case where the source file contains one or more "module" declarations,
// and our moduleReference took us to one of those.
// eslint-disable-next-line no-bitwise
if ((moduleReference.moduleSpecifierSymbol.flags & ts.SymbolFlags.Alias) !== 0) {
// Follow the import/export declaration to one hop the exported item inside the target module
let followedSymbol = TypeScriptInternals_1.TypeScriptInternals.getImmediateAliasedSymbol(moduleReference.moduleSpecifierSymbol, this._typeChecker);
if (followedSymbol === undefined) {
// This is a workaround for a compiler bug where getImmediateAliasedSymbol() sometimes returns undefined
followedSymbol = this._typeChecker.getAliasedSymbol(moduleReference.moduleSpecifierSymbol);
}
if (followedSymbol !== undefined && followedSymbol !== moduleReference.moduleSpecifierSymbol) {
// The parent of the exported symbol will be the module that we're importing from
const parent = TypeScriptInternals_1.TypeScriptInternals.getSymbolParent(followedSymbol);
if (parent !== undefined) {
// Make sure the thing we found is a module
// eslint-disable-next-line no-bitwise
if ((parent.flags & ts.SymbolFlags.ValueModule) !== 0) {
// Record that that this is an ambient module that can also be imported from
this._importableAmbientSourceFiles.add(sourceFile);
return parent;
}
}
}
}
}
throw new node_core_library_1.InternalError('Unable to determine module for: ' + sourceFile.fileName);
}
/**
* Implementation of {@link AstSymbolTable.fetchAstModuleExportInfo}.
*/
fetchAstModuleExportInfo(entryPointAstModule) {
if (entryPointAstModule.isExternal) {
throw new Error('fetchAstModuleExportInfo() is not supported for external modules');
}
if (entryPointAstModule.astModuleExportInfo === undefined) {
const astModuleExportInfo = new AstModule_1.AstModuleExportInfo();
this._collectAllExportsRecursive(astModuleExportInfo, entryPointAstModule, new Set());
entryPointAstModule.astModuleExportInfo = astModuleExportInfo;
}
return entryPointAstModule.astModuleExportInfo;
}
/**
* Returns true if the module specifier refers to an external package. Ignores packages listed in the
* "bundledPackages" setting from the api-extractor.json config file.
*/
_isExternalModulePath(importOrExportDeclaration, moduleSpecifier) {
var _a;
const specifier = ts.isImportTypeNode(importOrExportDeclaration)
? importOrExportDeclaration.argument
: importOrExportDeclaration.moduleSpecifier;
const mode = specifier && ts.isStringLiteralLike(specifier)
? TypeScriptInternals_1.TypeScriptInternals.getModeForUsageLocation(importOrExportDeclaration.getSourceFile(), specifier)
: undefined;
const resolvedModule = TypeScriptInternals_1.TypeScriptInternals.getResolvedModule(this._program, importOrExportDeclaration.getSourceFile(), moduleSpecifier, mode);
if (resolvedModule === undefined) {
// The TS compiler API `getResolvedModule` cannot resolve ambient modules. Thus, to match API Extractor's
// previous behavior, simply treat all ambient modules as external. This bug is tracked by
// https://github.com/microsoft/rushstack/issues/3335.
return true;
}
// Either something like `jquery` or `@microsoft/api-extractor`.
const packageName = (_a = resolvedModule.packageId) === null || _a === void 0 ? void 0 : _a.name;
if (packageName !== undefined && this._bundledPackageNames.has(packageName)) {
return false;
}
if (resolvedModule.isExternalLibraryImport === undefined) {
// This presumably means the compiler couldn't figure out whether the module was external, but we're not
// sure how this can happen.
throw new node_core_library_1.InternalError(`Cannot determine whether the module ${JSON.stringify(moduleSpecifier)} is external\n` +
SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(importOrExportDeclaration));
}
return resolvedModule.isExternalLibraryImport;
}
/**
* Returns true if when we analyzed sourceFile, we found that it contains an "export=" statement that allows
* it to behave /either/ as an ambient module /or/ as a regular importable module. In this case,
* `AstSymbolTable._fetchAstSymbol()` will analyze its symbols even though `TypeScriptHelpers.isAmbient()`
* returns true.
*/
isImportableAmbientSourceFile(sourceFile) {
return this._importableAmbientSourceFiles.has(sourceFile);
}
_collectAllExportsRecursive(astModuleExportInfo, astModule, visitedAstModules) {
if (visitedAstModules.has(astModule)) {
return;
}
visitedAstModules.add(astModule);
if (astModule.isExternal) {
astModuleExportInfo.starExportedExternalModules.add(astModule);
}
else {
// Fetch each of the explicit exports for this module
if (astModule.moduleSymbol.exports) {
astModule.moduleSymbol.exports.forEach((exportSymbol, exportName) => {
switch (exportName) {
case ts.InternalSymbolName.ExportStar:
case ts.InternalSymbolName.ExportEquals:
break;
default:
// Don't collect the "export default" symbol unless this is the entry point module
if (exportName !== ts.InternalSymbolName.Default || visitedAstModules.size === 1) {
if (!astModuleExportInfo.exportedLocalEntities.has(exportSymbol.name)) {
const astEntity = this._getExportOfAstModule(exportSymbol.name, astModule);
if (astEntity instanceof AstSymbol_1.AstSymbol && !astEntity.isExternal) {
this._astSymbolTable.analyze(astEntity);
}
if (astEntity instanceof AstNamespaceImport_1.AstNamespaceImport && !astEntity.astModule.isExternal) {
this._astSymbolTable.analyze(astEntity);
}
astModuleExportInfo.exportedLocalEntities.set(exportSymbol.name, astEntity);
}
}
break;
}
});
}
for (const starExportedModule of astModule.starExportedModules) {
this._collectAllExportsRecursive(astModuleExportInfo, starExportedModule, visitedAstModules);
}
}
}
/**
* For a given symbol (which was encountered in the specified sourceFile), this fetches the AstEntity that it
* refers to. For example, if a particular interface describes the return value of a function, this API can help
* us determine a TSDoc declaration reference for that symbol (if the symbol is exported).
*/
fetchReferencedAstEntity(symbol, referringModuleIsExternal) {
// eslint-disable-next-line no-bitwise
if ((symbol.flags & ts.SymbolFlags.FunctionScopedVariable) !== 0) {
// If a symbol refers back to part of its own definition, don't follow that rabbit hole
// Example:
//
// function f(x: number): typeof x {
// return 123;
// }
return undefined;
}
let current = symbol;
if (referringModuleIsExternal) {
current = TypeScriptHelpers_1.TypeScriptHelpers.followAliases(symbol, this._typeChecker);
}
else {
for (;;) {
// Is this symbol an import/export that we need to follow to find the real declaration?
for (const declaration of current.declarations || []) {
let matchedAstEntity;
matchedAstEntity = this._tryMatchExportDeclaration(declaration, current);
if (matchedAstEntity !== undefined) {
return matchedAstEntity;
}
matchedAstEntity = this._tryMatchImportDeclaration(declaration, current);
if (matchedAstEntity !== undefined) {
return matchedAstEntity;
}
}
// eslint-disable-next-line no-bitwise
if (!(current.flags & ts.SymbolFlags.Alias)) {
break;
}
const currentAlias = TypeScriptInternals_1.TypeScriptInternals.getImmediateAliasedSymbol(current, this._typeChecker);
// Stop if we reach the end of the chain
if (!currentAlias || currentAlias === current) {
break;
}
current = currentAlias;
}
}
// Otherwise, assume it is a normal declaration
const astSymbol = this._astSymbolTable.fetchAstSymbol({
followedSymbol: current,
isExternal: referringModuleIsExternal,
includeNominalAnalysis: false,
addIfMissing: true
});
return astSymbol;
}
fetchReferencedAstEntityFromImportTypeNode(node, referringModuleIsExternal) {
const externalModulePath = this._tryGetExternalModulePath(node);
if (externalModulePath) {
let exportName;
if (node.qualifier) {
// Example input:
// import('api-extractor-lib1-test').Lib1GenericType<number>
//
// Extracted qualifier:
// Lib1GenericType
exportName = node.qualifier.getText().trim();
}
else {
// Example input:
// import('api-extractor-lib1-test')
//
// Extracted qualifier:
// apiExtractorLib1Test
exportName = SyntaxHelpers_1.SyntaxHelpers.makeCamelCaseIdentifier(externalModulePath);
}
return this._fetchAstImport(undefined, {
importKind: AstImport_1.AstImportKind.ImportType,
exportName: exportName,
modulePath: externalModulePath,
isTypeOnly: false
});
}
// Internal reference: AstSymbol
const rightMostToken = node.qualifier
? node.qualifier.kind === ts.SyntaxKind.QualifiedName
? node.qualifier.right
: node.qualifier
: node;
// There is no symbol property in a ImportTypeNode, obtain the associated export symbol
const exportSymbol = this._typeChecker.getSymbolAtLocation(rightMostToken);
if (!exportSymbol) {
throw new node_core_library_1.InternalError(`Symbol not found for identifier: ${node.getText()}\n` +
SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(node));
}
let followedSymbol = exportSymbol;
for (;;) {
const referencedAstEntity = this.fetchReferencedAstEntity(followedSymbol, referringModuleIsExternal);
if (referencedAstEntity) {
return referencedAstEntity;
}
const followedSymbolNode = followedSymbol.declarations && followedSymbol.declarations[0];
if (followedSymbolNode && followedSymbolNode.kind === ts.SyntaxKind.ImportType) {
return this.fetchReferencedAstEntityFromImportTypeNode(followedSymbolNode, referringModuleIsExternal);
}
// eslint-disable-next-line no-bitwise
if (!(followedSymbol.flags & ts.SymbolFlags.Alias)) {
break;
}
const currentAlias = this._typeChecker.getAliasedSymbol(followedSymbol);
if (!currentAlias || currentAlias === followedSymbol) {
break;
}
followedSymbol = currentAlias;
}
const astSymbol = this._astSymbolTable.fetchAstSymbol({
followedSymbol: followedSymbol,
isExternal: referringModuleIsExternal,
includeNominalAnalysis: false,
addIfMissing: true
});
return astSymbol;
}
_tryMatchExportDeclaration(declaration, declarationSymbol) {
const exportDeclaration = TypeScriptHelpers_1.TypeScriptHelpers.findFirstParent(declaration, ts.SyntaxKind.ExportDeclaration);
if (exportDeclaration) {
let exportName = undefined;
if (declaration.kind === ts.SyntaxKind.ExportSpecifier) {
// EXAMPLE:
// "export { A } from './file-a';"
//
// ExportDeclaration:
// ExportKeyword: pre=[export] sep=[ ]
// NamedExports:
// FirstPunctuation: pre=[{] sep=[ ]
// SyntaxList:
// ExportSpecifier: <------------- declaration
// Identifier: pre=[A] sep=[ ]
// CloseBraceToken: pre=[}] sep=[ ]
// FromKeyword: pre=[from] sep=[ ]
// StringLiteral: pre=['./file-a']
// SemicolonToken: pre=[;]
// Example: " ExportName as RenamedName"
const exportSpecifier = declaration;
exportName = (exportSpecifier.propertyName || exportSpecifier.name).getText().trim();
}
else if (declaration.kind === ts.SyntaxKind.NamespaceExport) {
// EXAMPLE:
// "export * as theLib from 'the-lib';"
//
// ExportDeclaration:
// ExportKeyword: pre=[export] sep=[ ]
// NamespaceExport:
// AsteriskToken: pre=[*] sep=[ ]
// AsKeyword: pre=[as] sep=[ ]
// Identifier: pre=[theLib] sep=[ ]
// FromKeyword: pre=[from] sep=[ ]
// StringLiteral: pre=['the-lib']
// SemicolonToken: pre=[;]
// Issue tracking this feature: https://github.com/microsoft/rushstack/issues/2780
throw new Error(`The "export * as ___" syntax is not supported yet; as a workaround,` +
` use "import * as ___" with a separate "export { ___ }" declaration\n` +
SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(declaration));
}
else {
throw new node_core_library_1.InternalError(`Unimplemented export declaration kind: ${declaration.getText()}\n` +
SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(declaration));
}
// Ignore "export { A }" without a module specifier
if (exportDeclaration.moduleSpecifier) {
const externalModulePath = this._tryGetExternalModulePath(exportDeclaration);
if (externalModulePath !== undefined) {
return this._fetchAstImport(declarationSymbol, {
importKind: AstImport_1.AstImportKind.NamedImport,
modulePath: externalModulePath,
exportName: exportName,
isTypeOnly: false
});
}
return this._getExportOfSpecifierAstModule(exportName, exportDeclaration, declarationSymbol);
}
}
return undefined;
}
_tryMatchImportDeclaration(declaration, declarationSymbol) {
const importDeclaration = TypeScriptHelpers_1.TypeScriptHelpers.findFirstParent(declaration, ts.SyntaxKind.ImportDeclaration);
if (importDeclaration) {
const externalModulePath = this._tryGetExternalModulePath(importDeclaration);
if (declaration.kind === ts.SyntaxKind.NamespaceImport) {
// EXAMPLE:
// "import * as theLib from 'the-lib';"
//
// ImportDeclaration:
// ImportKeyword: pre=[import] sep=[ ]
// ImportClause:
// NamespaceImport: <------------- declaration
// AsteriskToken: pre=[*] sep=[ ]
// AsKeyword: pre=[as] sep=[ ]
// Identifier: pre=[theLib] sep=[ ]
// FromKeyword: pre=[from] sep=[ ]
// StringLiteral: pre=['the-lib']
// SemicolonToken: pre=[;]
if (externalModulePath === undefined) {
const astModule = this._fetchSpecifierAstModule(importDeclaration, declarationSymbol);
let namespaceImport = this._astNamespaceImportByModule.get(astModule);
if (namespaceImport === undefined) {
namespaceImport = new AstNamespaceImport_1.AstNamespaceImport({
namespaceName: declarationSymbol.name,
astModule: astModule,
declaration: declaration,
symbol: declarationSymbol
});
this._astNamespaceImportByModule.set(astModule, namespaceImport);
}
return namespaceImport;
}
// Here importSymbol=undefined because {@inheritDoc} and such are not going to work correctly for
// a package or source file.
return this._fetchAstImport(undefined, {
importKind: AstImport_1.AstImportKind.StarImport,
exportName: declarationSymbol.name,
modulePath: externalModulePath,
isTypeOnly: ExportAnalyzer._getIsTypeOnly(importDeclaration)
});
}
if (declaration.kind === ts.SyntaxKind.ImportSpecifier) {
// EXAMPLE:
// "import { A, B } from 'the-lib';"
//
// ImportDeclaration:
// ImportKeyword: pre=[import] sep=[ ]
// ImportClause:
// NamedImports:
// FirstPunctuation: pre=[{] sep=[ ]
// SyntaxList:
// ImportSpecifier: <------------- declaration
// Identifier: pre=[A]
// CommaToken: pre=[,] sep=[ ]
// ImportSpecifier:
// Identifier: pre=[B] sep=[ ]
// CloseBraceToken: pre=[}] sep=[ ]
// FromKeyword: pre=[from] sep=[ ]
// StringLiteral: pre=['the-lib']
// SemicolonToken: pre=[;]
// Example: " ExportName as RenamedName"
const importSpecifier = declaration;
const exportName = (importSpecifier.propertyName || importSpecifier.name).getText().trim();
if (externalModulePath !== undefined) {
return this._fetchAstImport(declarationSymbol, {
importKind: AstImport_1.AstImportKind.NamedImport,
modulePath: externalModulePath,
exportName: exportName,
isTypeOnly: ExportAnalyzer._getIsTypeOnly(importDeclaration)
});
}
return this._getExportOfSpecifierAstModule(exportName, importDeclaration, declarationSymbol);
}
else if (declaration.kind === ts.SyntaxKind.ImportClause) {
// EXAMPLE:
// "import A, { B } from './A';"
//
// ImportDeclaration:
// ImportKeyword: pre=[import] sep=[ ]
// ImportClause: <------------- declaration (referring to A)
// Identifier: pre=[A]
// CommaToken: pre=[,] sep=[ ]
// NamedImports:
// FirstPunctuation: pre=[{] sep=[ ]
// SyntaxList:
// ImportSpecifier:
// Identifier: pre=[B] sep=[ ]
// CloseBraceToken: pre=[}] sep=[ ]
// FromKeyword: pre=[from] sep=[ ]
// StringLiteral: pre=['./A']
// SemicolonToken: pre=[;]
const importClause = declaration;
const exportName = importClause.name
? importClause.name.getText().trim()
: ts.InternalSymbolName.Default;
if (externalModulePath !== undefined) {
return this._fetchAstImport(declarationSymbol, {
importKind: AstImport_1.AstImportKind.DefaultImport,
modulePath: externalModulePath,
exportName,
isTypeOnly: ExportAnalyzer._getIsTypeOnly(importDeclaration)
});
}
return this._getExportOfSpecifierAstModule(ts.InternalSymbolName.Default, importDeclaration, declarationSymbol);
}
else {
throw new node_core_library_1.InternalError(`Unimplemented import declaration kind: ${declaration.getText()}\n` +
SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(declaration));
}
}
if (ts.isImportEqualsDeclaration(declaration)) {
// EXAMPLE:
// import myLib = require('my-lib');
//
// ImportEqualsDeclaration:
// ImportKeyword: pre=[import] sep=[ ]
// Identifier: pre=[myLib] sep=[ ]
// FirstAssignment: pre=[=] sep=[ ]
// ExternalModuleReference:
// RequireKeyword: pre=[require]
// OpenParenToken: pre=[(]
// StringLiteral: pre=['my-lib']
// CloseParenToken: pre=[)]
// SemicolonToken: pre=[;]
if (ts.isExternalModuleReference(declaration.moduleReference)) {
if (ts.isStringLiteralLike(declaration.moduleReference.expression)) {
const variableName = TypeScriptInternals_1.TypeScriptInternals.getTextOfIdentifierOrLiteral(declaration.name);
const externalModuleName = TypeScriptInternals_1.TypeScriptInternals.getTextOfIdentifierOrLiteral(declaration.moduleReference.expression);
return this._fetchAstImport(declarationSymbol, {
importKind: AstImport_1.AstImportKind.EqualsImport,
modulePath: externalModuleName,
exportName: variableName,
isTypeOnly: false
});
}
}
}
return undefined;
}
static _getIsTypeOnly(importDeclaration) {
if (importDeclaration.importClause) {
return !!importDeclaration.importClause.isTypeOnly;
}
return false;
}
_getExportOfSpecifierAstModule(exportName, importOrExportDeclaration, exportSymbol) {
const specifierAstModule = this._fetchSpecifierAstModule(importOrExportDeclaration, exportSymbol);
const astEntity = this._getExportOfAstModule(exportName, specifierAstModule);
return astEntity;
}
_getExportOfAstModule(exportName, astModule) {
const visitedAstModules = new Set();
const astEntity = this._tryGetExportOfAstModule(exportName, astModule, visitedAstModules);
if (astEntity === undefined) {
throw new node_core_library_1.InternalError(`Unable to analyze the export ${JSON.stringify(exportName)} in\n` + astModule.sourceFile.fileName);
}
return astEntity;
}
/**
* Implementation of {@link AstSymbolTable.tryGetExportOfAstModule}.
*/
tryGetExportOfAstModule(exportName, astModule) {
const visitedAstModules = new Set();
return this._tryGetExportOfAstModule(exportName, astModule, visitedAstModules);
}
_tryGetExportOfAstModule(exportName, astModule, visitedAstModules) {
if (visitedAstModules.has(astModule)) {
return undefined;
}
visitedAstModules.add(astModule);
let astEntity = astModule.cachedExportedEntities.get(exportName);
if (astEntity !== undefined) {
return astEntity;
}
// Try the explicit exports
const escapedExportName = ts.escapeLeadingUnderscores(exportName);
if (astModule.moduleSymbol.exports) {
const exportSymbol = astModule.moduleSymbol.exports.get(escapedExportName);
if (exportSymbol) {
astEntity = this.fetchReferencedAstEntity(exportSymbol, astModule.isExternal);
if (astEntity !== undefined) {
astModule.cachedExportedEntities.set(exportName, astEntity); // cache for next time
return astEntity;
}
}
}
// Try each of the star imports
for (const starExportedModule of astModule.starExportedModules) {
astEntity = this._tryGetExportOfAstModule(exportName, starExportedModule, visitedAstModules);
if (astEntity !== undefined) {
if (starExportedModule.externalModulePath !== undefined) {
// This entity was obtained from an external module, so return an AstImport instead
const astSymbol = astEntity;
return this._fetchAstImport(astSymbol.followedSymbol, {
importKind: AstImport_1.AstImportKind.NamedImport,
modulePath: starExportedModule.externalModulePath,
exportName: exportName,
isTypeOnly: false
});
}
return astEntity;
}
}
return undefined;
}
_tryGetExternalModulePath(importOrExportDeclaration) {
const moduleSpecifier = this._getModuleSpecifier(importOrExportDeclaration);
if (this._isExternalModulePath(importOrExportDeclaration, moduleSpecifier)) {
return moduleSpecifier;
}
return undefined;
}
/**
* Given an ImportDeclaration of the form `export { X } from "___";`, this interprets the module specifier (`"___"`)
* and fetches the corresponding AstModule object.
*/
_fetchSpecifierAstModule(importOrExportDeclaration, exportSymbol) {
const moduleSpecifier = this._getModuleSpecifier(importOrExportDeclaration);
const mode = importOrExportDeclaration.moduleSpecifier &&
ts.isStringLiteralLike(importOrExportDeclaration.moduleSpecifier)
? TypeScriptInternals_1.TypeScriptInternals.getModeForUsageLocation(importOrExportDeclaration.getSourceFile(), importOrExportDeclaration.moduleSpecifier)
: undefined;
const resolvedModule = TypeScriptInternals_1.TypeScriptInternals.getResolvedModule(this._program, importOrExportDeclaration.getSourceFile(), moduleSpecifier, mode);
if (resolvedModule === undefined) {
// Encountered in https://github.com/microsoft/rushstack/issues/1914.
//
// It's also possible for this to occur with ambient modules. However, in practice this doesn't happen
// as API Extractor treats all ambient modules as external per the logic in `_isExternalModulePath`, and
// thus this code path is never reached for ambient modules.
throw new node_core_library_1.InternalError(`getResolvedModule() could not resolve module name ${JSON.stringify(moduleSpecifier)}\n` +
SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(importOrExportDeclaration));
}
// Map the filename back to the corresponding SourceFile. This circuitous approach is needed because
// we have no way to access the compiler's internal resolveExternalModuleName() function
const moduleSourceFile = this._program.getSourceFile(resolvedModule.resolvedFileName);
if (!moduleSourceFile) {
// This should not happen, since getResolvedModule() specifically looks up names that the compiler
// found in export declarations for this source file
throw new node_core_library_1.InternalError(`getSourceFile() failed to locate ${JSON.stringify(resolvedModule.resolvedFileName)}\n` +
SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(importOrExportDeclaration));
}
const isExternal = this._isExternalModulePath(importOrExportDeclaration, moduleSpecifier);
const moduleReference = {
moduleSpecifier: moduleSpecifier,
moduleSpecifierSymbol: exportSymbol
};
const specifierAstModule = this.fetchAstModuleFromSourceFile(moduleSourceFile, moduleReference, isExternal);
return specifierAstModule;
}
_fetchAstImport(importSymbol, options) {
const key = AstImport_1.AstImport.getKey(options);
let astImport = this._astImportsByKey.get(key);
if (!astImport) {
astImport = new AstImport_1.AstImport(options);
this._astImportsByKey.set(key, astImport);
if (importSymbol) {
const followedSymbol = TypeScriptHelpers_1.TypeScriptHelpers.followAliases(importSymbol, this._typeChecker);
astImport.astSymbol = this._astSymbolTable.fetchAstSymbol({
followedSymbol: followedSymbol,
isExternal: true,
includeNominalAnalysis: false,
addIfMissing: true
});
}
}
else {
// If we encounter at least one import that does not use the type-only form,
// then the .d.ts rollup will NOT use "import type".
if (!options.isTypeOnly) {
astImport.isTypeOnlyEverywhere = false;
}
}
return astImport;
}
_getModuleSpecifier(importOrExportDeclaration) {
// The name of the module, which could be like "./SomeLocalFile' or like 'external-package/entry/point'
const moduleSpecifier = TypeScriptHelpers_1.TypeScriptHelpers.getModuleSpecifier(importOrExportDeclaration);
if (!moduleSpecifier) {
throw new node_core_library_1.InternalError('Unable to parse module specifier\n' +
SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(importOrExportDeclaration));
}
return moduleSpecifier;
}
}
exports.ExportAnalyzer = ExportAnalyzer;
//# sourceMappingURL=ExportAnalyzer.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,65 @@
import { type PackageJsonLookup, type NewlineKind, type INodePackageJson } from '@rushstack/node-core-library';
import type { MessageRouter } from '../collector/MessageRouter';
/**
* Represents analyzed information for a package.json file.
* This object is constructed and returned by PackageMetadataManager.
*/
export declare class PackageMetadata {
/**
* The absolute path to the package.json file being analyzed.
*/
readonly packageJsonPath: string;
/**
* The parsed contents of package.json. Note that PackageJsonLookup
* only includes essential fields.
*/
readonly packageJson: INodePackageJson;
/**
* If true, then the package's documentation comments can be assumed
* to contain API Extractor compatible TSDoc tags.
*/
readonly aedocSupported: boolean;
constructor(packageJsonPath: string, packageJson: INodePackageJson, aedocSupported: boolean);
}
/**
* This class maintains a cache of analyzed information obtained from package.json
* files. It is built on top of the PackageJsonLookup class.
*
* @remarks
*
* IMPORTANT: Don't use PackageMetadataManager to analyze source files from the current project:
* 1. Files such as tsdoc-metadata.json may not have been built yet, and thus may contain incorrect information.
* 2. The current project is not guaranteed to have a package.json file at all. For example, API Extractor can
* be invoked on a bare .d.ts file.
*
* Use ts.program.isSourceFileFromExternalLibrary() to test source files before passing the to PackageMetadataManager.
*/
export declare class PackageMetadataManager {
static tsdocMetadataFilename: string;
private readonly _packageJsonLookup;
private readonly _messageRouter;
private readonly _packageMetadataByPackageJsonPath;
constructor(packageJsonLookup: PackageJsonLookup, messageRouter: MessageRouter);
private static _resolveTsdocMetadataPathFromPackageJson;
/**
* @param tsdocMetadataPath - An explicit path that can be configured in api-extractor.json.
* If this parameter is not an empty string, it overrides the normal path calculation.
* @returns the absolute path to the TSDoc metadata file
*/
static resolveTsdocMetadataPath(packageFolder: string, packageJson: INodePackageJson, tsdocMetadataPath?: string): string;
/**
* Writes the TSDoc metadata file to the specified output file.
*/
static writeTsdocMetadataFile(tsdocMetadataPath: string, newlineKind: NewlineKind): void;
/**
* Finds the package.json in a parent folder of the specified source file, and
* returns a PackageMetadata object. If no package.json was found, then undefined
* is returned. The results are cached.
*/
tryFetchPackageMetadata(sourceFilePath: string): PackageMetadata | undefined;
/**
* Returns true if the source file is part of a package whose .d.ts files support AEDoc annotations.
*/
isAedocSupportedFor(sourceFilePath: string): boolean;
}
//# sourceMappingURL=PackageMetadataManager.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"PackageMetadataManager.d.ts","sourceRoot":"","sources":["../../src/analyzer/PackageMetadataManager.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,iBAAiB,EAGtB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EAEtB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAGhE;;;GAGG;AACH,qBAAa,eAAe;IAC1B;;OAEG;IACH,SAAgB,eAAe,EAAE,MAAM,CAAC;IACxC;;;OAGG;IACH,SAAgB,WAAW,EAAE,gBAAgB,CAAC;IAC9C;;;OAGG;IACH,SAAgB,cAAc,EAAE,OAAO,CAAC;gBAErB,eAAe,EAAE,MAAM,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,OAAO;CAKnG;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,sBAAsB;IACjC,OAAc,qBAAqB,EAAE,MAAM,CAAyB;IAEpE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoB;IACvD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,iCAAiC,CAG9C;gBAEe,iBAAiB,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa;IAOrF,OAAO,CAAC,MAAM,CAAC,wCAAwC;IAgCvD;;;;OAIG;WACW,wBAAwB,CACpC,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,gBAAgB,EAC7B,iBAAiB,CAAC,EAAE,MAAM,GACzB,MAAM;IAOT;;OAEG;WACW,sBAAsB,CAAC,iBAAiB,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,GAAG,IAAI;IAsB/F;;;;OAIG;IACI,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAqCnF;;OAEG;IACI,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO;CAO5D"}

View File

@ -0,0 +1,164 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PackageMetadataManager = exports.PackageMetadata = void 0;
const path = __importStar(require("path"));
const node_core_library_1 = require("@rushstack/node-core-library");
const Extractor_1 = require("../api/Extractor");
/**
* Represents analyzed information for a package.json file.
* This object is constructed and returned by PackageMetadataManager.
*/
class PackageMetadata {
constructor(packageJsonPath, packageJson, aedocSupported) {
this.packageJsonPath = packageJsonPath;
this.packageJson = packageJson;
this.aedocSupported = aedocSupported;
}
}
exports.PackageMetadata = PackageMetadata;
/**
* This class maintains a cache of analyzed information obtained from package.json
* files. It is built on top of the PackageJsonLookup class.
*
* @remarks
*
* IMPORTANT: Don't use PackageMetadataManager to analyze source files from the current project:
* 1. Files such as tsdoc-metadata.json may not have been built yet, and thus may contain incorrect information.
* 2. The current project is not guaranteed to have a package.json file at all. For example, API Extractor can
* be invoked on a bare .d.ts file.
*
* Use ts.program.isSourceFileFromExternalLibrary() to test source files before passing the to PackageMetadataManager.
*/
class PackageMetadataManager {
constructor(packageJsonLookup, messageRouter) {
this._packageMetadataByPackageJsonPath = new Map();
this._packageJsonLookup = packageJsonLookup;
this._messageRouter = messageRouter;
}
// This feature is still being standardized: https://github.com/microsoft/tsdoc/issues/7
// In the future we will use the @microsoft/tsdoc library to read this file.
static _resolveTsdocMetadataPathFromPackageJson(packageFolder, packageJson) {
const tsdocMetadataFilename = PackageMetadataManager.tsdocMetadataFilename;
let tsdocMetadataRelativePath;
if (packageJson.tsdocMetadata) {
// 1. If package.json contains a field such as "tsdocMetadata": "./path1/path2/tsdoc-metadata.json",
// then that takes precedence. This convention will be rarely needed, since the other rules below generally
// produce a good result.
tsdocMetadataRelativePath = packageJson.tsdocMetadata;
}
else if (packageJson.typings) {
// 2. If package.json contains a field such as "typings": "./path1/path2/index.d.ts", then we look
// for the file under "./path1/path2/tsdoc-metadata.json"
tsdocMetadataRelativePath = path.join(path.dirname(packageJson.typings), tsdocMetadataFilename);
}
else if (packageJson.main) {
// 3. If package.json contains a field such as "main": "./path1/path2/index.js", then we look for
// the file under "./path1/path2/tsdoc-metadata.json"
tsdocMetadataRelativePath = path.join(path.dirname(packageJson.main), tsdocMetadataFilename);
}
else {
// 4. If none of the above rules apply, then by default we look for the file under "./tsdoc-metadata.json"
// since the default entry point is "./index.js"
tsdocMetadataRelativePath = tsdocMetadataFilename;
}
// Always resolve relative to the package folder.
const tsdocMetadataPath = path.resolve(packageFolder, tsdocMetadataRelativePath);
return tsdocMetadataPath;
}
/**
* @param tsdocMetadataPath - An explicit path that can be configured in api-extractor.json.
* If this parameter is not an empty string, it overrides the normal path calculation.
* @returns the absolute path to the TSDoc metadata file
*/
static resolveTsdocMetadataPath(packageFolder, packageJson, tsdocMetadataPath) {
if (tsdocMetadataPath) {
return path.resolve(packageFolder, tsdocMetadataPath);
}
return PackageMetadataManager._resolveTsdocMetadataPathFromPackageJson(packageFolder, packageJson);
}
/**
* Writes the TSDoc metadata file to the specified output file.
*/
static writeTsdocMetadataFile(tsdocMetadataPath, newlineKind) {
const fileObject = {
tsdocVersion: '0.12',
toolPackages: [
{
packageName: '@microsoft/api-extractor',
packageVersion: Extractor_1.Extractor.version
}
]
};
const fileContent = '// This file is read by tools that parse documentation comments conforming to the TSDoc standard.\n' +
'// It should be published with your NPM package. It should not be tracked by Git.\n' +
node_core_library_1.JsonFile.stringify(fileObject);
node_core_library_1.FileSystem.writeFile(tsdocMetadataPath, fileContent, {
convertLineEndings: newlineKind,
ensureFolderExists: true
});
}
/**
* Finds the package.json in a parent folder of the specified source file, and
* returns a PackageMetadata object. If no package.json was found, then undefined
* is returned. The results are cached.
*/
tryFetchPackageMetadata(sourceFilePath) {
const packageJsonFilePath = this._packageJsonLookup.tryGetPackageJsonFilePathFor(sourceFilePath);
if (!packageJsonFilePath) {
return undefined;
}
let packageMetadata = this._packageMetadataByPackageJsonPath.get(packageJsonFilePath);
if (!packageMetadata) {
const packageJson = this._packageJsonLookup.loadNodePackageJson(packageJsonFilePath);
const packageJsonFolder = path.dirname(packageJsonFilePath);
let aedocSupported = false;
const tsdocMetadataPath = PackageMetadataManager._resolveTsdocMetadataPathFromPackageJson(packageJsonFolder, packageJson);
if (node_core_library_1.FileSystem.exists(tsdocMetadataPath)) {
this._messageRouter.logVerbose("console-found-tsdoc-metadata" /* ConsoleMessageId.FoundTSDocMetadata */, 'Found metadata in ' + tsdocMetadataPath);
// If the file exists at all, assume it was written by API Extractor
aedocSupported = true;
}
packageMetadata = new PackageMetadata(packageJsonFilePath, packageJson, aedocSupported);
this._packageMetadataByPackageJsonPath.set(packageJsonFilePath, packageMetadata);
}
return packageMetadata;
}
/**
* Returns true if the source file is part of a package whose .d.ts files support AEDoc annotations.
*/
isAedocSupportedFor(sourceFilePath) {
const packageMetadata = this.tryFetchPackageMetadata(sourceFilePath);
if (!packageMetadata) {
return false;
}
return packageMetadata.aedocSupported;
}
}
PackageMetadataManager.tsdocMetadataFilename = 'tsdoc-metadata.json';
exports.PackageMetadataManager = PackageMetadataManager;
//# sourceMappingURL=PackageMetadataManager.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,15 @@
import type * as ts from 'typescript';
export interface ISourceFileLocationFormatOptions {
sourceFileLine?: number;
sourceFileColumn?: number;
workingPackageFolderPath?: string;
}
export declare class SourceFileLocationFormatter {
/**
* Returns a string such as this, based on the context information in the provided node:
* "[C:\Folder\File.ts#123]"
*/
static formatDeclaration(node: ts.Node, workingPackageFolderPath?: string): string;
static formatPath(sourceFilePath: string, options?: ISourceFileLocationFormatOptions): string;
}
//# sourceMappingURL=SourceFileLocationFormatter.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"SourceFileLocationFormatter.d.ts","sourceRoot":"","sources":["../../src/analyzer/SourceFileLocationFormatter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAItC,MAAM,WAAW,gCAAgC;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,qBAAa,2BAA2B;IACtC;;;OAGG;WACW,iBAAiB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,wBAAwB,CAAC,EAAE,MAAM,GAAG,MAAM;WAW3E,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gCAAgC,GAAG,MAAM;CA+BrG"}

View File

@ -0,0 +1,71 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SourceFileLocationFormatter = void 0;
const path = __importStar(require("path"));
const node_core_library_1 = require("@rushstack/node-core-library");
class SourceFileLocationFormatter {
/**
* Returns a string such as this, based on the context information in the provided node:
* "[C:\Folder\File.ts#123]"
*/
static formatDeclaration(node, workingPackageFolderPath) {
const sourceFile = node.getSourceFile();
const lineAndCharacter = sourceFile.getLineAndCharacterOfPosition(node.getStart());
return SourceFileLocationFormatter.formatPath(sourceFile.fileName, {
sourceFileLine: lineAndCharacter.line + 1,
sourceFileColumn: lineAndCharacter.character + 1,
workingPackageFolderPath
});
}
static formatPath(sourceFilePath, options) {
if (!options) {
options = {};
}
let result = '';
// Make the path relative to the workingPackageFolderPath
let scrubbedPath = sourceFilePath;
if (options.workingPackageFolderPath) {
// If it's under the working folder, make it a relative path
if (node_core_library_1.Path.isUnderOrEqual(sourceFilePath, options.workingPackageFolderPath)) {
scrubbedPath = path.relative(options.workingPackageFolderPath, sourceFilePath);
}
}
// Convert it to a Unix-style path
scrubbedPath = node_core_library_1.Text.replaceAll(scrubbedPath, '\\', '/');
result += scrubbedPath;
if (options.sourceFileLine) {
result += `:${options.sourceFileLine}`;
if (options.sourceFileColumn) {
result += `:${options.sourceFileColumn}`;
}
}
return result;
}
}
exports.SourceFileLocationFormatter = SourceFileLocationFormatter;
//# sourceMappingURL=SourceFileLocationFormatter.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"SourceFileLocationFormatter.js","sourceRoot":"","sources":["../../src/analyzer/SourceFileLocationFormatter.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;;;;;;;;;;;;;;;;;;;;;;;;AAG3D,2CAA6B;AAC7B,oEAA0D;AAQ1D,MAAa,2BAA2B;IACtC;;;OAGG;IACI,MAAM,CAAC,iBAAiB,CAAC,IAAa,EAAE,wBAAiC;QAC9E,MAAM,UAAU,GAAkB,IAAI,CAAC,aAAa,EAAE,CAAC;QACvD,MAAM,gBAAgB,GAAwB,UAAU,CAAC,6BAA6B,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QAExG,OAAO,2BAA2B,CAAC,UAAU,CAAC,UAAU,CAAC,QAAQ,EAAE;YACjE,cAAc,EAAE,gBAAgB,CAAC,IAAI,GAAG,CAAC;YACzC,gBAAgB,EAAE,gBAAgB,CAAC,SAAS,GAAG,CAAC;YAChD,wBAAwB;SACzB,CAAC,CAAC;IACL,CAAC;IAEM,MAAM,CAAC,UAAU,CAAC,cAAsB,EAAE,OAA0C;QACzF,IAAI,CAAC,OAAO,EAAE;YACZ,OAAO,GAAG,EAAE,CAAC;SACd;QAED,IAAI,MAAM,GAAW,EAAE,CAAC;QAExB,yDAAyD;QACzD,IAAI,YAAY,GAAW,cAAc,CAAC;QAE1C,IAAI,OAAO,CAAC,wBAAwB,EAAE;YACpC,4DAA4D;YAC5D,IAAI,wBAAI,CAAC,cAAc,CAAC,cAAc,EAAE,OAAO,CAAC,wBAAwB,CAAC,EAAE;gBACzE,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,wBAAwB,EAAE,cAAc,CAAC,CAAC;aAChF;SACF;QAED,kCAAkC;QAClC,YAAY,GAAG,wBAAI,CAAC,UAAU,CAAC,YAAY,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QACxD,MAAM,IAAI,YAAY,CAAC;QAEvB,IAAI,OAAO,CAAC,cAAc,EAAE;YAC1B,MAAM,IAAI,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;YAEvC,IAAI,OAAO,CAAC,gBAAgB,EAAE;gBAC5B,MAAM,IAAI,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;aAC1C;SACF;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF;AA/CD,kEA+CC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type * as ts from 'typescript';\nimport * as path from 'path';\nimport { Path, Text } from '@rushstack/node-core-library';\n\nexport interface ISourceFileLocationFormatOptions {\n sourceFileLine?: number;\n sourceFileColumn?: number;\n workingPackageFolderPath?: string;\n}\n\nexport class SourceFileLocationFormatter {\n /**\n * Returns a string such as this, based on the context information in the provided node:\n * \"[C:\\Folder\\File.ts#123]\"\n */\n public static formatDeclaration(node: ts.Node, workingPackageFolderPath?: string): string {\n const sourceFile: ts.SourceFile = node.getSourceFile();\n const lineAndCharacter: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n\n return SourceFileLocationFormatter.formatPath(sourceFile.fileName, {\n sourceFileLine: lineAndCharacter.line + 1,\n sourceFileColumn: lineAndCharacter.character + 1,\n workingPackageFolderPath\n });\n }\n\n public static formatPath(sourceFilePath: string, options?: ISourceFileLocationFormatOptions): string {\n if (!options) {\n options = {};\n }\n\n let result: string = '';\n\n // Make the path relative to the workingPackageFolderPath\n let scrubbedPath: string = sourceFilePath;\n\n if (options.workingPackageFolderPath) {\n // If it's under the working folder, make it a relative path\n if (Path.isUnderOrEqual(sourceFilePath, options.workingPackageFolderPath)) {\n scrubbedPath = path.relative(options.workingPackageFolderPath, sourceFilePath);\n }\n }\n\n // Convert it to a Unix-style path\n scrubbedPath = Text.replaceAll(scrubbedPath, '\\\\', '/');\n result += scrubbedPath;\n\n if (options.sourceFileLine) {\n result += `:${options.sourceFileLine}`;\n\n if (options.sourceFileColumn) {\n result += `:${options.sourceFileColumn}`;\n }\n }\n\n return result;\n }\n}\n"]}

View File

@ -0,0 +1,199 @@
import * as ts from 'typescript';
import { IndentedWriter } from '../generators/IndentedWriter';
/**
* Choices for SpanModification.indentDocComment.
*/
export declare enum IndentDocCommentScope {
/**
* Do not detect and indent comments.
*/
None = 0,
/**
* Look for one doc comment in the {@link Span.prefix} text only.
*/
PrefixOnly = 1,
/**
* Look for one doc comment potentially distributed across the Span and its children.
*/
SpanAndChildren = 2
}
/**
* Specifies various transformations that will be performed by Span.getModifiedText().
*/
export declare class SpanModification {
/**
* If true, all of the child spans will be omitted from the Span.getModifiedText() output.
* @remarks
* Also, the modify() operation will not recurse into these spans.
*/
omitChildren: boolean;
/**
* If true, then the Span.separator will be removed from the Span.getModifiedText() output.
*/
omitSeparatorAfter: boolean;
/**
* If true, then Span.getModifiedText() will sort the immediate children according to their Span.sortKey
* property. The separators will also be fixed up to ensure correct indentation. If the Span.sortKey is undefined
* for some items, those items will not be moved, i.e. their array indexes will be unchanged.
*/
sortChildren: boolean;
/**
* Used if the parent span has Span.sortChildren=true.
*/
sortKey: string | undefined;
/**
* Optionally configures getModifiedText() to search for a "/*" doc comment and indent it.
* At most one comment is detected.
*
* @remarks
* The indentation can be applied to the `Span.modifier.prefix` only, or it can be applied to the
* full subtree of nodes (as needed for `ts.SyntaxKind.JSDocComment` trees). However the enabled
* scopes must not overlap.
*
* This feature is enabled selectively because (1) we do not want to accidentally match `/*` appearing
* in a string literal or other expression that is not a comment, and (2) parsing comments is relatively
* expensive.
*/
indentDocComment: IndentDocCommentScope;
private readonly _span;
private _prefix;
private _suffix;
constructor(span: Span);
/**
* Allows the Span.prefix text to be changed.
*/
get prefix(): string;
set prefix(value: string);
/**
* Allows the Span.suffix text to be changed.
*/
get suffix(): string;
set suffix(value: string);
/**
* Reverts any modifications made to this object.
*/
reset(): void;
/**
* Effectively deletes the Span from the tree, by skipping its children, skipping its separator,
* and setting its prefix/suffix to the empty string.
*/
skipAll(): void;
}
/**
* The Span class provides a simple way to rewrite TypeScript source files
* based on simple syntax transformations, i.e. without having to process deeper aspects
* of the underlying grammar. An example transformation might be deleting JSDoc comments
* from a source file.
*
* @remarks
* TypeScript's abstract syntax tree (AST) is represented using Node objects.
* The Node text ignores its surrounding whitespace, and does not have an ordering guarantee.
* For example, a JSDocComment node can be a child of a FunctionDeclaration node, even though
* the actual comment precedes the function in the input stream.
*
* The Span class is a wrapper for a single Node, that provides access to every character
* in the input stream, such that Span.getText() will exactly reproduce the corresponding
* full Node.getText() output.
*
* A Span is comprised of these parts, which appear in sequential order:
* - A prefix
* - A collection of child spans
* - A suffix
* - A separator (e.g. whitespace between this span and the next item in the tree)
*
* These parts can be modified via Span.modification. The modification is applied by
* calling Span.getModifiedText().
*/
export declare class Span {
readonly node: ts.Node;
readonly startIndex: number;
readonly endIndex: number;
readonly children: Span[];
readonly modification: SpanModification;
private _parent;
private _previousSibling;
private _nextSibling;
private _separatorStartIndex;
private _separatorEndIndex;
constructor(node: ts.Node);
get kind(): ts.SyntaxKind;
/**
* The parent Span, if any.
* NOTE: This will be undefined for a root Span, even though the corresponding Node
* may have a parent in the AST.
*/
get parent(): Span | undefined;
/**
* If the current object is this.parent.children[i], then previousSibling corresponds
* to this.parent.children[i-1] if it exists.
* NOTE: This will be undefined for a root Span, even though the corresponding Node
* may have a previous sibling in the AST.
*/
get previousSibling(): Span | undefined;
/**
* If the current object is this.parent.children[i], then previousSibling corresponds
* to this.parent.children[i+1] if it exists.
* NOTE: This will be undefined for a root Span, even though the corresponding Node
* may have a previous sibling in the AST.
*/
get nextSibling(): Span | undefined;
/**
* The text associated with the underlying Node, up to its first child.
*/
get prefix(): string;
/**
* The text associated with the underlying Node, after its last child.
* If there are no children, this is always an empty string.
*/
get suffix(): string;
/**
* Whitespace that appeared after this node, and before the "next" node in the tree.
* Here we mean "next" according to an inorder traversal, not necessarily a sibling.
*/
get separator(): string;
/**
* Returns the separator of this Span, or else recursively calls getLastInnerSeparator()
* on the last child.
*/
getLastInnerSeparator(): string;
/**
* Returns the first parent node with the specified SyntaxKind, or undefined if there is no match.
*/
findFirstParent(kindToMatch: ts.SyntaxKind): Span | undefined;
/**
* Recursively invokes the callback on this Span and all its children. The callback
* can make changes to Span.modification for each node.
*/
forEach(callback: (span: Span) => void): void;
/**
* Returns the original unmodified text represented by this Span.
*/
getText(): string;
/**
* Returns the text represented by this Span, after applying all requested modifications.
*/
getModifiedText(): string;
writeModifiedText(output: IndentedWriter): void;
/**
* Returns a diagnostic dump of the tree, showing the prefix/suffix/separator for
* each node.
*/
getDump(indent?: string): string;
/**
* Returns a diagnostic dump of the tree, showing the SpanModification settings for each nodde.
*/
getModifiedDump(indent?: string): string;
/**
* Recursive implementation of `getModifiedText()` and `writeModifiedText()`.
*/
private _writeModifiedText;
private _beginIndentDocComment;
private _endIndentDocComment;
/**
* Writes one chunk of `text` to the `options.writer`, applying the `indentDocComment` rewriting.
*/
private _write;
private _getTrimmed;
private _getSubstring;
}
//# sourceMappingURL=Span.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"Span.d.ts","sourceRoot":"","sources":["../../src/analyzer/Span.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAGjC,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AA2B9D;;GAEG;AACH,oBAAY,qBAAqB;IAC/B;;OAEG;IACH,IAAI,IAAI;IAER;;OAEG;IACH,UAAU,IAAI;IAEd;;OAEG;IACH,eAAe,IAAI;CACpB;AAED;;GAEG;AACH,qBAAa,gBAAgB;IAC3B;;;;OAIG;IACI,YAAY,EAAE,OAAO,CAAS;IAErC;;OAEG;IACI,kBAAkB,EAAE,OAAO,CAAS;IAE3C;;;;OAIG;IACI,YAAY,EAAE,OAAO,CAAS;IAErC;;OAEG;IACI,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAEnC;;;;;;;;;;;;OAYG;IACI,gBAAgB,EAAE,qBAAqB,CAA8B;IAE5E,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAO;IAC7B,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,OAAO,CAAqB;gBAEjB,IAAI,EAAE,IAAI;IAK7B;;OAEG;IACH,IAAW,MAAM,IAAI,MAAM,CAE1B;IAED,IAAW,MAAM,CAAC,KAAK,EAAE,MAAM,EAE9B;IAED;;OAEG;IACH,IAAW,MAAM,IAAI,MAAM,CAE1B;IAED,IAAW,MAAM,CAAC,KAAK,EAAE,MAAM,EAE9B;IAED;;OAEG;IACI,KAAK,IAAI,IAAI;IAYpB;;;OAGG;IACI,OAAO,IAAI,IAAI;CAMvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,IAAI;IACf,SAAgB,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC;IAG9B,SAAgB,UAAU,EAAE,MAAM,CAAC;IACnC,SAAgB,QAAQ,EAAE,MAAM,CAAC;IAEjC,SAAgB,QAAQ,EAAE,IAAI,EAAE,CAAC;IAEjC,SAAgB,YAAY,EAAE,gBAAgB,CAAC;IAE/C,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,YAAY,CAAmB;IAEvC,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,kBAAkB,CAAS;gBAEhB,IAAI,EAAE,EAAE,CAAC,IAAI;IA0DhC,IAAW,IAAI,IAAI,EAAE,CAAC,UAAU,CAE/B;IAED;;;;OAIG;IACH,IAAW,MAAM,IAAI,IAAI,GAAG,SAAS,CAEpC;IAED;;;;;OAKG;IACH,IAAW,eAAe,IAAI,IAAI,GAAG,SAAS,CAE7C;IAED;;;;;OAKG;IACH,IAAW,WAAW,IAAI,IAAI,GAAG,SAAS,CAEzC;IAED;;OAEG;IACH,IAAW,MAAM,IAAI,MAAM,CAO1B;IAED;;;OAGG;IACH,IAAW,MAAM,IAAI,MAAM,CAO1B;IAED;;;OAGG;IACH,IAAW,SAAS,IAAI,MAAM,CAE7B;IAED;;;OAGG;IACI,qBAAqB,IAAI,MAAM;IAUtC;;OAEG;IACI,eAAe,CAAC,WAAW,EAAE,EAAE,CAAC,UAAU,GAAG,IAAI,GAAG,SAAS;IAapE;;;OAGG;IACI,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,GAAG,IAAI;IAOpD;;OAEG;IACI,OAAO,IAAI,MAAM;IAcxB;;OAEG;IACI,eAAe,IAAI,MAAM;IAazB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAQtD;;;OAGG;IACI,OAAO,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM;IAqB3C;;OAEG;IACI,eAAe,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM;IAwCnD;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAqH1B,OAAO,CAAC,sBAAsB;IAO9B,OAAO,CAAC,oBAAoB;IAO5B;;OAEG;IACH,OAAO,CAAC,MAAM;IA8Bd,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,aAAa;CAMtB"}

View File

@ -0,0 +1,577 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Span = exports.SpanModification = exports.IndentDocCommentScope = void 0;
const ts = __importStar(require("typescript"));
const node_core_library_1 = require("@rushstack/node-core-library");
const IndentedWriter_1 = require("../generators/IndentedWriter");
var IndentDocCommentState;
(function (IndentDocCommentState) {
/**
* `indentDocComment` was not requested for this subtree.
*/
IndentDocCommentState[IndentDocCommentState["Inactive"] = 0] = "Inactive";
/**
* `indentDocComment` was requested and we are looking for the opening `/` `*`
*/
IndentDocCommentState[IndentDocCommentState["AwaitingOpenDelimiter"] = 1] = "AwaitingOpenDelimiter";
/**
* `indentDocComment` was requested and we are looking for the closing `*` `/`
*/
IndentDocCommentState[IndentDocCommentState["AwaitingCloseDelimiter"] = 2] = "AwaitingCloseDelimiter";
/**
* `indentDocComment` was requested and we have finished indenting the comment.
*/
IndentDocCommentState[IndentDocCommentState["Done"] = 3] = "Done";
})(IndentDocCommentState || (IndentDocCommentState = {}));
/**
* Choices for SpanModification.indentDocComment.
*/
var IndentDocCommentScope;
(function (IndentDocCommentScope) {
/**
* Do not detect and indent comments.
*/
IndentDocCommentScope[IndentDocCommentScope["None"] = 0] = "None";
/**
* Look for one doc comment in the {@link Span.prefix} text only.
*/
IndentDocCommentScope[IndentDocCommentScope["PrefixOnly"] = 1] = "PrefixOnly";
/**
* Look for one doc comment potentially distributed across the Span and its children.
*/
IndentDocCommentScope[IndentDocCommentScope["SpanAndChildren"] = 2] = "SpanAndChildren";
})(IndentDocCommentScope = exports.IndentDocCommentScope || (exports.IndentDocCommentScope = {}));
/**
* Specifies various transformations that will be performed by Span.getModifiedText().
*/
class SpanModification {
constructor(span) {
/**
* If true, all of the child spans will be omitted from the Span.getModifiedText() output.
* @remarks
* Also, the modify() operation will not recurse into these spans.
*/
this.omitChildren = false;
/**
* If true, then the Span.separator will be removed from the Span.getModifiedText() output.
*/
this.omitSeparatorAfter = false;
/**
* If true, then Span.getModifiedText() will sort the immediate children according to their Span.sortKey
* property. The separators will also be fixed up to ensure correct indentation. If the Span.sortKey is undefined
* for some items, those items will not be moved, i.e. their array indexes will be unchanged.
*/
this.sortChildren = false;
/**
* Optionally configures getModifiedText() to search for a "/*" doc comment and indent it.
* At most one comment is detected.
*
* @remarks
* The indentation can be applied to the `Span.modifier.prefix` only, or it can be applied to the
* full subtree of nodes (as needed for `ts.SyntaxKind.JSDocComment` trees). However the enabled
* scopes must not overlap.
*
* This feature is enabled selectively because (1) we do not want to accidentally match `/*` appearing
* in a string literal or other expression that is not a comment, and (2) parsing comments is relatively
* expensive.
*/
this.indentDocComment = IndentDocCommentScope.None;
this._span = span;
this.reset();
}
/**
* Allows the Span.prefix text to be changed.
*/
get prefix() {
return this._prefix !== undefined ? this._prefix : this._span.prefix;
}
set prefix(value) {
this._prefix = value;
}
/**
* Allows the Span.suffix text to be changed.
*/
get suffix() {
return this._suffix !== undefined ? this._suffix : this._span.suffix;
}
set suffix(value) {
this._suffix = value;
}
/**
* Reverts any modifications made to this object.
*/
reset() {
this.omitChildren = false;
this.omitSeparatorAfter = false;
this.sortChildren = false;
this.sortKey = undefined;
this._prefix = undefined;
this._suffix = undefined;
if (this._span.kind === ts.SyntaxKind.JSDocComment) {
this.indentDocComment = IndentDocCommentScope.SpanAndChildren;
}
}
/**
* Effectively deletes the Span from the tree, by skipping its children, skipping its separator,
* and setting its prefix/suffix to the empty string.
*/
skipAll() {
this.prefix = '';
this.suffix = '';
this.omitChildren = true;
this.omitSeparatorAfter = true;
}
}
exports.SpanModification = SpanModification;
/**
* The Span class provides a simple way to rewrite TypeScript source files
* based on simple syntax transformations, i.e. without having to process deeper aspects
* of the underlying grammar. An example transformation might be deleting JSDoc comments
* from a source file.
*
* @remarks
* TypeScript's abstract syntax tree (AST) is represented using Node objects.
* The Node text ignores its surrounding whitespace, and does not have an ordering guarantee.
* For example, a JSDocComment node can be a child of a FunctionDeclaration node, even though
* the actual comment precedes the function in the input stream.
*
* The Span class is a wrapper for a single Node, that provides access to every character
* in the input stream, such that Span.getText() will exactly reproduce the corresponding
* full Node.getText() output.
*
* A Span is comprised of these parts, which appear in sequential order:
* - A prefix
* - A collection of child spans
* - A suffix
* - A separator (e.g. whitespace between this span and the next item in the tree)
*
* These parts can be modified via Span.modification. The modification is applied by
* calling Span.getModifiedText().
*/
class Span {
constructor(node) {
this.node = node;
this.startIndex = node.kind === ts.SyntaxKind.SourceFile ? node.getFullStart() : node.getStart();
this.endIndex = node.end;
this._separatorStartIndex = 0;
this._separatorEndIndex = 0;
this.children = [];
this.modification = new SpanModification(this);
let previousChildSpan = undefined;
for (const childNode of this.node.getChildren() || []) {
const childSpan = new Span(childNode);
childSpan._parent = this;
childSpan._previousSibling = previousChildSpan;
if (previousChildSpan) {
previousChildSpan._nextSibling = childSpan;
}
this.children.push(childSpan);
// Normalize the bounds so that a child is never outside its parent
if (childSpan.startIndex < this.startIndex) {
this.startIndex = childSpan.startIndex;
}
if (childSpan.endIndex > this.endIndex) {
// This has never been observed empirically, but here's how we would handle it
this.endIndex = childSpan.endIndex;
throw new node_core_library_1.InternalError('Unexpected AST case');
}
if (previousChildSpan) {
if (previousChildSpan.endIndex < childSpan.startIndex) {
// There is some leftover text after previous child -- assign it as the separator for
// the preceding span. If the preceding span has no suffix, then assign it to the
// deepest preceding span with no suffix. This heuristic simplifies the most
// common transformations, and otherwise it can be fished out using getLastInnerSeparator().
let separatorRecipient = previousChildSpan;
while (separatorRecipient.children.length > 0) {
const lastChild = separatorRecipient.children[separatorRecipient.children.length - 1];
if (lastChild.endIndex !== separatorRecipient.endIndex) {
// There is a suffix, so we cannot push the separator any further down, or else
// it would get printed before this suffix.
break;
}
separatorRecipient = lastChild;
}
separatorRecipient._separatorStartIndex = previousChildSpan.endIndex;
separatorRecipient._separatorEndIndex = childSpan.startIndex;
}
}
previousChildSpan = childSpan;
}
}
get kind() {
return this.node.kind;
}
/**
* The parent Span, if any.
* NOTE: This will be undefined for a root Span, even though the corresponding Node
* may have a parent in the AST.
*/
get parent() {
return this._parent;
}
/**
* If the current object is this.parent.children[i], then previousSibling corresponds
* to this.parent.children[i-1] if it exists.
* NOTE: This will be undefined for a root Span, even though the corresponding Node
* may have a previous sibling in the AST.
*/
get previousSibling() {
return this._previousSibling;
}
/**
* If the current object is this.parent.children[i], then previousSibling corresponds
* to this.parent.children[i+1] if it exists.
* NOTE: This will be undefined for a root Span, even though the corresponding Node
* may have a previous sibling in the AST.
*/
get nextSibling() {
return this._nextSibling;
}
/**
* The text associated with the underlying Node, up to its first child.
*/
get prefix() {
if (this.children.length) {
// Everything up to the first child
return this._getSubstring(this.startIndex, this.children[0].startIndex);
}
else {
return this._getSubstring(this.startIndex, this.endIndex);
}
}
/**
* The text associated with the underlying Node, after its last child.
* If there are no children, this is always an empty string.
*/
get suffix() {
if (this.children.length) {
// Everything after the last child
return this._getSubstring(this.children[this.children.length - 1].endIndex, this.endIndex);
}
else {
return '';
}
}
/**
* Whitespace that appeared after this node, and before the "next" node in the tree.
* Here we mean "next" according to an inorder traversal, not necessarily a sibling.
*/
get separator() {
return this._getSubstring(this._separatorStartIndex, this._separatorEndIndex);
}
/**
* Returns the separator of this Span, or else recursively calls getLastInnerSeparator()
* on the last child.
*/
getLastInnerSeparator() {
if (this.separator) {
return this.separator;
}
if (this.children.length > 0) {
return this.children[this.children.length - 1].getLastInnerSeparator();
}
return '';
}
/**
* Returns the first parent node with the specified SyntaxKind, or undefined if there is no match.
*/
findFirstParent(kindToMatch) {
let current = this;
while (current) {
if (current.kind === kindToMatch) {
return current;
}
current = current.parent;
}
return undefined;
}
/**
* Recursively invokes the callback on this Span and all its children. The callback
* can make changes to Span.modification for each node.
*/
forEach(callback) {
callback(this);
for (const child of this.children) {
child.forEach(callback);
}
}
/**
* Returns the original unmodified text represented by this Span.
*/
getText() {
let result = '';
result += this.prefix;
for (const child of this.children) {
result += child.getText();
}
result += this.suffix;
result += this.separator;
return result;
}
/**
* Returns the text represented by this Span, after applying all requested modifications.
*/
getModifiedText() {
const writer = new IndentedWriter_1.IndentedWriter();
writer.trimLeadingSpaces = true;
this._writeModifiedText({
writer: writer,
separatorOverride: undefined,
indentDocCommentState: IndentDocCommentState.Inactive
});
return writer.getText();
}
writeModifiedText(output) {
this._writeModifiedText({
writer: output,
separatorOverride: undefined,
indentDocCommentState: IndentDocCommentState.Inactive
});
}
/**
* Returns a diagnostic dump of the tree, showing the prefix/suffix/separator for
* each node.
*/
getDump(indent = '') {
let result = indent + ts.SyntaxKind[this.node.kind] + ': ';
if (this.prefix) {
result += ' pre=[' + this._getTrimmed(this.prefix) + ']';
}
if (this.suffix) {
result += ' suf=[' + this._getTrimmed(this.suffix) + ']';
}
if (this.separator) {
result += ' sep=[' + this._getTrimmed(this.separator) + ']';
}
result += '\n';
for (const child of this.children) {
result += child.getDump(indent + ' ');
}
return result;
}
/**
* Returns a diagnostic dump of the tree, showing the SpanModification settings for each nodde.
*/
getModifiedDump(indent = '') {
let result = indent + ts.SyntaxKind[this.node.kind] + ': ';
if (this.prefix) {
result += ' pre=[' + this._getTrimmed(this.modification.prefix) + ']';
}
if (this.suffix) {
result += ' suf=[' + this._getTrimmed(this.modification.suffix) + ']';
}
if (this.separator) {
result += ' sep=[' + this._getTrimmed(this.separator) + ']';
}
if (this.modification.indentDocComment !== IndentDocCommentScope.None) {
result += ' indentDocComment=' + IndentDocCommentScope[this.modification.indentDocComment];
}
if (this.modification.omitChildren) {
result += ' omitChildren';
}
if (this.modification.omitSeparatorAfter) {
result += ' omitSeparatorAfter';
}
if (this.modification.sortChildren) {
result += ' sortChildren';
}
if (this.modification.sortKey !== undefined) {
result += ` sortKey="${this.modification.sortKey}"`;
}
result += '\n';
if (!this.modification.omitChildren) {
for (const child of this.children) {
result += child.getModifiedDump(indent + ' ');
}
}
else {
result += `${indent} (${this.children.length} children)\n`;
}
return result;
}
/**
* Recursive implementation of `getModifiedText()` and `writeModifiedText()`.
*/
_writeModifiedText(options) {
// Apply indentation based on "{" and "}"
if (this.prefix === '{') {
options.writer.increaseIndent();
}
else if (this.prefix === '}') {
options.writer.decreaseIndent();
}
if (this.modification.indentDocComment !== IndentDocCommentScope.None) {
this._beginIndentDocComment(options);
}
this._write(this.modification.prefix, options);
if (this.modification.indentDocComment === IndentDocCommentScope.PrefixOnly) {
this._endIndentDocComment(options);
}
let sortedSubset;
if (!this.modification.omitChildren) {
if (this.modification.sortChildren) {
// We will only sort the items with a sortKey
const filtered = this.children.filter((x) => x.modification.sortKey !== undefined);
// Is there at least one of them?
if (filtered.length > 1) {
sortedSubset = filtered;
}
}
}
if (sortedSubset) {
// This is the complicated special case that sorts an arbitrary subset of the child nodes,
// preserving the surrounding nodes.
const sortedSubsetCount = sortedSubset.length;
// Remember the separator for the first and last ones
const firstSeparator = sortedSubset[0].getLastInnerSeparator();
const lastSeparator = sortedSubset[sortedSubsetCount - 1].getLastInnerSeparator();
node_core_library_1.Sort.sortBy(sortedSubset, (x) => x.modification.sortKey);
const childOptions = Object.assign({}, options);
let sortedSubsetIndex = 0;
for (let index = 0; index < this.children.length; ++index) {
let current;
// Is this an item that we sorted?
if (this.children[index].modification.sortKey === undefined) {
// No, take the next item from the original array
current = this.children[index];
childOptions.separatorOverride = undefined;
}
else {
// Yes, take the next item from the sortedSubset
current = sortedSubset[sortedSubsetIndex++];
if (sortedSubsetIndex < sortedSubsetCount) {
childOptions.separatorOverride = firstSeparator;
}
else {
childOptions.separatorOverride = lastSeparator;
}
}
current._writeModifiedText(childOptions);
}
}
else {
// This is the normal case that does not need to sort children
const childrenLength = this.children.length;
if (!this.modification.omitChildren) {
if (options.separatorOverride !== undefined) {
// Special case where the separatorOverride is passed down to the "last inner separator" span
for (let i = 0; i < childrenLength; ++i) {
const child = this.children[i];
if (
// Only the last child inherits the separatorOverride, because only it can contain
// the "last inner separator" span
i < childrenLength - 1 ||
// If this.separator is specified, then we will write separatorOverride below, so don't pass it along
this.separator) {
const childOptions = Object.assign({}, options);
childOptions.separatorOverride = undefined;
child._writeModifiedText(childOptions);
}
else {
child._writeModifiedText(options);
}
}
}
else {
// The normal simple case
for (const child of this.children) {
child._writeModifiedText(options);
}
}
}
this._write(this.modification.suffix, options);
if (options.separatorOverride !== undefined) {
if (this.separator || childrenLength === 0) {
this._write(options.separatorOverride, options);
}
}
else {
if (!this.modification.omitSeparatorAfter) {
this._write(this.separator, options);
}
}
}
if (this.modification.indentDocComment === IndentDocCommentScope.SpanAndChildren) {
this._endIndentDocComment(options);
}
}
_beginIndentDocComment(options) {
if (options.indentDocCommentState !== IndentDocCommentState.Inactive) {
throw new node_core_library_1.InternalError('indentDocComment cannot be nested');
}
options.indentDocCommentState = IndentDocCommentState.AwaitingOpenDelimiter;
}
_endIndentDocComment(options) {
if (options.indentDocCommentState === IndentDocCommentState.AwaitingCloseDelimiter) {
throw new node_core_library_1.InternalError('missing "*/" delimiter for comment block');
}
options.indentDocCommentState = IndentDocCommentState.Inactive;
}
/**
* Writes one chunk of `text` to the `options.writer`, applying the `indentDocComment` rewriting.
*/
_write(text, options) {
let parsedText = text;
if (options.indentDocCommentState === IndentDocCommentState.AwaitingOpenDelimiter) {
let index = parsedText.indexOf('/*');
if (index >= 0) {
index += '/*'.length;
options.writer.write(parsedText.substring(0, index));
parsedText = parsedText.substring(index);
options.indentDocCommentState = IndentDocCommentState.AwaitingCloseDelimiter;
options.writer.increaseIndent(' ');
}
}
if (options.indentDocCommentState === IndentDocCommentState.AwaitingCloseDelimiter) {
let index = parsedText.indexOf('*/');
if (index >= 0) {
index += '*/'.length;
options.writer.write(parsedText.substring(0, index));
parsedText = parsedText.substring(index);
options.indentDocCommentState = IndentDocCommentState.Done;
options.writer.decreaseIndent();
}
}
options.writer.write(parsedText);
}
_getTrimmed(text) {
const trimmed = text.replace(/\r?\n/g, '\\n');
if (trimmed.length > 100) {
return trimmed.substr(0, 97) + '...';
}
return trimmed;
}
_getSubstring(startIndex, endIndex) {
if (startIndex === endIndex) {
return '';
}
return this.node.getSourceFile().text.substring(startIndex, endIndex);
}
}
exports.Span = Span;
//# sourceMappingURL=Span.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,32 @@
/**
* Helpers for validating various text string formats.
*/
export declare class SyntaxHelpers {
/**
* Tests whether the input string is safe to use as an ECMAScript identifier without quotes.
*
* @remarks
* For example:
*
* ```ts
* class X {
* public okay: number = 1;
* public "not okay!": number = 2;
* }
* ```
*
* A precise check is extremely complicated and highly dependent on the ECMAScript standard version
* and how faithfully the interpreter implements it. To keep things simple, `isSafeUnquotedMemberIdentifier()`
* conservatively accepts any identifier that would be valid with ECMAScript 5, and returns false otherwise.
*/
static isSafeUnquotedMemberIdentifier(identifier: string): boolean;
/**
* Given an arbitrary input string, return a regular TypeScript identifier name.
*
* @remarks
* Example input: "api-extractor-lib1-test"
* Example output: "apiExtractorLib1Test"
*/
static makeCamelCaseIdentifier(input: string): string;
}
//# sourceMappingURL=SyntaxHelpers.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"SyntaxHelpers.d.ts","sourceRoot":"","sources":["../../src/analyzer/SyntaxHelpers.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,qBAAa,aAAa;IACxB;;;;;;;;;;;;;;;;OAgBG;WACW,8BAA8B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAkBzE;;;;;;OAMG;WACW,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAyB7D"}

View File

@ -0,0 +1,99 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SyntaxHelpers = void 0;
const ts = __importStar(require("typescript"));
/**
* Helpers for validating various text string formats.
*/
class SyntaxHelpers {
/**
* Tests whether the input string is safe to use as an ECMAScript identifier without quotes.
*
* @remarks
* For example:
*
* ```ts
* class X {
* public okay: number = 1;
* public "not okay!": number = 2;
* }
* ```
*
* A precise check is extremely complicated and highly dependent on the ECMAScript standard version
* and how faithfully the interpreter implements it. To keep things simple, `isSafeUnquotedMemberIdentifier()`
* conservatively accepts any identifier that would be valid with ECMAScript 5, and returns false otherwise.
*/
static isSafeUnquotedMemberIdentifier(identifier) {
if (identifier.length === 0) {
return false; // cannot be empty
}
if (!ts.isIdentifierStart(identifier.charCodeAt(0), ts.ScriptTarget.ES5)) {
return false;
}
for (let i = 1; i < identifier.length; i++) {
if (!ts.isIdentifierPart(identifier.charCodeAt(i), ts.ScriptTarget.ES5)) {
return false;
}
}
return true;
}
/**
* Given an arbitrary input string, return a regular TypeScript identifier name.
*
* @remarks
* Example input: "api-extractor-lib1-test"
* Example output: "apiExtractorLib1Test"
*/
static makeCamelCaseIdentifier(input) {
const parts = input.split(/\W+/).filter((x) => x.length > 0);
if (parts.length === 0) {
return '_';
}
for (let i = 0; i < parts.length; ++i) {
let part = parts[i];
if (part.toUpperCase() === part) {
// Preserve existing case unless the part is all upper-case
part = part.toLowerCase();
}
if (i === 0) {
// If the first part starts with a number, prepend "_"
if (/[0-9]/.test(part.charAt(0))) {
part = '_' + part;
}
}
else {
// Capitalize the first letter of each part, except for the first one
part = part.charAt(0).toUpperCase() + part.slice(1);
}
parts[i] = part;
}
return parts.join('');
}
}
exports.SyntaxHelpers = SyntaxHelpers;
//# sourceMappingURL=SyntaxHelpers.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"SyntaxHelpers.js","sourceRoot":"","sources":["../../src/analyzer/SyntaxHelpers.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;;;;;;;;;;;;;;;;;;;;;;;;AAE3D,+CAAiC;AAEjC;;GAEG;AACH,MAAa,aAAa;IACxB;;;;;;;;;;;;;;;;OAgBG;IACI,MAAM,CAAC,8BAA8B,CAAC,UAAkB;QAC7D,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE;YAC3B,OAAO,KAAK,CAAC,CAAC,kBAAkB;SACjC;QAED,IAAI,CAAC,EAAE,CAAC,iBAAiB,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE;YACxE,OAAO,KAAK,CAAC;SACd;QAED,KAAK,IAAI,CAAC,GAAW,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YAClD,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE;gBACvE,OAAO,KAAK,CAAC;aACd;SACF;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;OAMG;IACI,MAAM,CAAC,uBAAuB,CAAC,KAAa;QACjD,MAAM,KAAK,GAAa,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACvE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE;YACtB,OAAO,GAAG,CAAC;SACZ;QAED,KAAK,IAAI,CAAC,GAAW,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE;YAC7C,IAAI,IAAI,GAAW,KAAK,CAAC,CAAC,CAAC,CAAC;YAC5B,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,IAAI,EAAE;gBAC/B,2DAA2D;gBAC3D,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;aAC3B;YACD,IAAI,CAAC,KAAK,CAAC,EAAE;gBACX,sDAAsD;gBACtD,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;oBAChC,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC;iBACnB;aACF;iBAAM;gBACL,qEAAqE;gBACrE,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;aACrD;YACD,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;SACjB;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC;CACF;AApED,sCAoEC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport * as ts from 'typescript';\n\n/**\n * Helpers for validating various text string formats.\n */\nexport class SyntaxHelpers {\n /**\n * Tests whether the input string is safe to use as an ECMAScript identifier without quotes.\n *\n * @remarks\n * For example:\n *\n * ```ts\n * class X {\n * public okay: number = 1;\n * public \"not okay!\": number = 2;\n * }\n * ```\n *\n * A precise check is extremely complicated and highly dependent on the ECMAScript standard version\n * and how faithfully the interpreter implements it. To keep things simple, `isSafeUnquotedMemberIdentifier()`\n * conservatively accepts any identifier that would be valid with ECMAScript 5, and returns false otherwise.\n */\n public static isSafeUnquotedMemberIdentifier(identifier: string): boolean {\n if (identifier.length === 0) {\n return false; // cannot be empty\n }\n\n if (!ts.isIdentifierStart(identifier.charCodeAt(0), ts.ScriptTarget.ES5)) {\n return false;\n }\n\n for (let i: number = 1; i < identifier.length; i++) {\n if (!ts.isIdentifierPart(identifier.charCodeAt(i), ts.ScriptTarget.ES5)) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Given an arbitrary input string, return a regular TypeScript identifier name.\n *\n * @remarks\n * Example input: \"api-extractor-lib1-test\"\n * Example output: \"apiExtractorLib1Test\"\n */\n public static makeCamelCaseIdentifier(input: string): string {\n const parts: string[] = input.split(/\\W+/).filter((x) => x.length > 0);\n if (parts.length === 0) {\n return '_';\n }\n\n for (let i: number = 0; i < parts.length; ++i) {\n let part: string = parts[i];\n if (part.toUpperCase() === part) {\n // Preserve existing case unless the part is all upper-case\n part = part.toLowerCase();\n }\n if (i === 0) {\n // If the first part starts with a number, prepend \"_\"\n if (/[0-9]/.test(part.charAt(0))) {\n part = '_' + part;\n }\n } else {\n // Capitalize the first letter of each part, except for the first one\n part = part.charAt(0).toUpperCase() + part.slice(1);\n }\n parts[i] = part;\n }\n return parts.join('');\n }\n}\n"]}

View File

@ -0,0 +1,85 @@
import * as ts from 'typescript';
export declare class TypeScriptHelpers {
private static readonly _wellKnownSymbolNameRegExp;
private static readonly _uniqueSymbolNameRegExp;
/**
* This traverses any symbol aliases to find the original place where an item was defined.
* For example, suppose a class is defined as "export default class MyClass { }"
* but exported from the package's index.ts like this:
*
* export { default as _MyClass } from './MyClass';
*
* In this example, calling followAliases() on the _MyClass symbol will return the
* original definition of MyClass, traversing any intermediary places where the
* symbol was imported and re-exported.
*/
static followAliases(symbol: ts.Symbol, typeChecker: ts.TypeChecker): ts.Symbol;
/**
* Returns true if TypeScriptHelpers.followAliases() would return something different
* from the input `symbol`.
*/
static isFollowableAlias(symbol: ts.Symbol, typeChecker: ts.TypeChecker): boolean;
/**
* Certain virtual symbols do not have any declarations. For example, `ts.TypeChecker.getExportsOfModule()` can
* sometimes return a "prototype" symbol for an object, even though there is no corresponding declaration in the
* source code. API Extractor generally ignores such symbols.
*/
static tryGetADeclaration(symbol: ts.Symbol): ts.Declaration | undefined;
/**
* Returns true if the specified symbol is an ambient declaration.
*/
static isAmbient(symbol: ts.Symbol, typeChecker: ts.TypeChecker): boolean;
/**
* Same semantics as tryGetSymbolForDeclaration(), but throws an exception if the symbol
* cannot be found.
*/
static getSymbolForDeclaration(declaration: ts.Declaration, checker: ts.TypeChecker): ts.Symbol;
static getModuleSpecifier(nodeWithModuleSpecifier: ts.ImportDeclaration | ts.ExportDeclaration | ts.ImportTypeNode): string | undefined;
/**
* Returns an ancestor of "node", such that the ancestor, any intermediary nodes,
* and the starting node match a list of expected kinds. Undefined is returned
* if there aren't enough ancestors, or if the kinds are incorrect.
*
* For example, suppose child "C" has parents A --> B --> C.
*
* Calling _matchAncestor(C, [ExportSpecifier, NamedExports, ExportDeclaration])
* would return A only if A is of kind ExportSpecifier, B is of kind NamedExports,
* and C is of kind ExportDeclaration.
*
* Calling _matchAncestor(C, [ExportDeclaration]) would return C.
*/
static matchAncestor<T extends ts.Node>(node: ts.Node, kindsToMatch: ts.SyntaxKind[]): T | undefined;
/**
* Does a depth-first search of the children of the specified node. Returns the first child
* with the specified kind, or undefined if there is no match.
*/
static findFirstChildNode<T extends ts.Node>(node: ts.Node, kindToMatch: ts.SyntaxKind): T | undefined;
/**
* Returns the first parent node with the specified SyntaxKind, or undefined if there is no match.
*/
static findFirstParent<T extends ts.Node>(node: ts.Node, kindToMatch: ts.SyntaxKind): T | undefined;
/**
* Returns the highest parent node with the specified SyntaxKind, or undefined if there is no match.
* @remarks
* Whereas findFirstParent() returns the first match, findHighestParent() returns the last match.
*/
static findHighestParent<T extends ts.Node>(node: ts.Node, kindToMatch: ts.SyntaxKind): T | undefined;
/**
* Decodes the names that the compiler generates for a built-in ECMAScript symbol.
*
* @remarks
* TypeScript binds well-known ECMAScript symbols like `[Symbol.iterator]` as `__@iterator`.
* If `name` is of this form, then `tryGetWellKnownSymbolName()` converts it back into e.g. `[Symbol.iterator]`.
* If the string does not start with `__@` then `undefined` is returned.
*/
static tryDecodeWellKnownSymbolName(name: ts.__String): string | undefined;
/**
* Returns whether the provided name was generated for a TypeScript `unique symbol`.
*/
static isUniqueSymbolName(name: ts.__String): boolean;
/**
* Derives the string representation of a TypeScript late-bound symbol.
*/
static tryGetLateBoundName(declarationName: ts.ComputedPropertyName): string | undefined;
}
//# sourceMappingURL=TypeScriptHelpers.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"TypeScriptHelpers.d.ts","sourceRoot":"","sources":["../../src/analyzer/TypeScriptHelpers.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAKjC,qBAAa,iBAAiB;IAG5B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAwB;IAI1E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAyB;IAExE;;;;;;;;;;OAUG;WACW,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,CAAC,WAAW,GAAG,EAAE,CAAC,MAAM;IAgBtF;;;OAGG;WACW,iBAAiB,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,CAAC,WAAW,GAAG,OAAO;IAaxF;;;;OAIG;WACW,kBAAkB,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,GAAG,EAAE,CAAC,WAAW,GAAG,SAAS;IAO/E;;OAEG;WACW,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,CAAC,WAAW,GAAG,OAAO;IA8BhF;;;OAGG;WACW,uBAAuB,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC,WAAW,GAAG,EAAE,CAAC,MAAM;WAexF,kBAAkB,CAC9B,uBAAuB,EAAE,EAAE,CAAC,iBAAiB,GAAG,EAAE,CAAC,iBAAiB,GAAG,EAAE,CAAC,cAAc,GACvF,MAAM,GAAG,SAAS;IA4BrB;;;;;;;;;;;;OAYG;WACW,aAAa,CAAC,CAAC,SAAS,EAAE,CAAC,IAAI,EAC3C,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,YAAY,EAAE,EAAE,CAAC,UAAU,EAAE,GAC5B,CAAC,GAAG,SAAS;IAyBhB;;;OAGG;WACW,kBAAkB,CAAC,CAAC,SAAS,EAAE,CAAC,IAAI,EAChD,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,WAAW,EAAE,EAAE,CAAC,UAAU,GACzB,CAAC,GAAG,SAAS;IAehB;;OAEG;WACW,eAAe,CAAC,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC,UAAU,GAAG,CAAC,GAAG,SAAS;IAa1G;;;;OAIG;WACW,iBAAiB,CAAC,CAAC,SAAS,EAAE,CAAC,IAAI,EAC/C,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,WAAW,EAAE,EAAE,CAAC,UAAU,GACzB,CAAC,GAAG,SAAS;IAehB;;;;;;;OAOG;WACW,4BAA4B,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,GAAG,MAAM,GAAG,SAAS;IASjF;;OAEG;WACW,kBAAkB,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,GAAG,OAAO;IAI5D;;OAEG;WACW,mBAAmB,CAAC,eAAe,EAAE,EAAE,CAAC,oBAAoB,GAAG,MAAM,GAAG,SAAS;CAsBhG"}

View File

@ -0,0 +1,269 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeScriptHelpers = void 0;
/* eslint-disable no-bitwise */
const ts = __importStar(require("typescript"));
const SourceFileLocationFormatter_1 = require("./SourceFileLocationFormatter");
const TypeScriptInternals_1 = require("./TypeScriptInternals");
const node_core_library_1 = require("@rushstack/node-core-library");
class TypeScriptHelpers {
/**
* This traverses any symbol aliases to find the original place where an item was defined.
* For example, suppose a class is defined as "export default class MyClass { }"
* but exported from the package's index.ts like this:
*
* export { default as _MyClass } from './MyClass';
*
* In this example, calling followAliases() on the _MyClass symbol will return the
* original definition of MyClass, traversing any intermediary places where the
* symbol was imported and re-exported.
*/
static followAliases(symbol, typeChecker) {
let current = symbol;
for (;;) {
if (!(current.flags & ts.SymbolFlags.Alias)) {
break;
}
const currentAlias = typeChecker.getAliasedSymbol(current);
if (!currentAlias || currentAlias === current) {
break;
}
current = currentAlias;
}
return current;
}
/**
* Returns true if TypeScriptHelpers.followAliases() would return something different
* from the input `symbol`.
*/
static isFollowableAlias(symbol, typeChecker) {
if (!(symbol.flags & ts.SymbolFlags.Alias)) {
return false;
}
const alias = typeChecker.getAliasedSymbol(symbol);
if (!alias || alias === symbol) {
return false;
}
return true;
}
/**
* Certain virtual symbols do not have any declarations. For example, `ts.TypeChecker.getExportsOfModule()` can
* sometimes return a "prototype" symbol for an object, even though there is no corresponding declaration in the
* source code. API Extractor generally ignores such symbols.
*/
static tryGetADeclaration(symbol) {
if (symbol.declarations && symbol.declarations.length > 0) {
return symbol.declarations[0];
}
return undefined;
}
/**
* Returns true if the specified symbol is an ambient declaration.
*/
static isAmbient(symbol, typeChecker) {
const followedSymbol = TypeScriptHelpers.followAliases(symbol, typeChecker);
if (followedSymbol.declarations && followedSymbol.declarations.length > 0) {
const firstDeclaration = followedSymbol.declarations[0];
// Test 1: Are we inside the sinister "declare global {" construct?
const highestModuleDeclaration = TypeScriptHelpers.findHighestParent(firstDeclaration, ts.SyntaxKind.ModuleDeclaration);
if (highestModuleDeclaration) {
if (highestModuleDeclaration.name.getText().trim() === 'global') {
return true;
}
}
// Test 2: Otherwise, the main heuristic for ambient declarations is by looking at the
// ts.SyntaxKind.SourceFile node to see whether it has a symbol or not (i.e. whether it
// is acting as a module or not).
const sourceFile = firstDeclaration.getSourceFile();
if (typeChecker.getSymbolAtLocation(sourceFile)) {
return false;
}
}
return true;
}
/**
* Same semantics as tryGetSymbolForDeclaration(), but throws an exception if the symbol
* cannot be found.
*/
static getSymbolForDeclaration(declaration, checker) {
const symbol = TypeScriptInternals_1.TypeScriptInternals.tryGetSymbolForDeclaration(declaration, checker);
if (!symbol) {
throw new node_core_library_1.InternalError('Unable to determine semantic information for declaration:\n' +
SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(declaration));
}
return symbol;
}
// Return name of the module, which could be like "./SomeLocalFile' or like 'external-package/entry/point'
static getModuleSpecifier(nodeWithModuleSpecifier) {
if (nodeWithModuleSpecifier.kind === ts.SyntaxKind.ImportType) {
// As specified internally in typescript:/src/compiler/types.ts#ValidImportTypeNode
if (nodeWithModuleSpecifier.argument.kind !== ts.SyntaxKind.LiteralType ||
nodeWithModuleSpecifier.argument.literal.kind !== ts.SyntaxKind.StringLiteral) {
throw new node_core_library_1.InternalError(`Invalid ImportTypeNode: ${nodeWithModuleSpecifier.getText()}\n` +
SourceFileLocationFormatter_1.SourceFileLocationFormatter.formatDeclaration(nodeWithModuleSpecifier));
}
const literalTypeNode = nodeWithModuleSpecifier.argument;
const stringLiteral = literalTypeNode.literal;
return stringLiteral.text.trim();
}
// Node is a declaration
if (nodeWithModuleSpecifier.moduleSpecifier &&
ts.isStringLiteralLike(nodeWithModuleSpecifier.moduleSpecifier)) {
return TypeScriptInternals_1.TypeScriptInternals.getTextOfIdentifierOrLiteral(nodeWithModuleSpecifier.moduleSpecifier);
}
return undefined;
}
/**
* Returns an ancestor of "node", such that the ancestor, any intermediary nodes,
* and the starting node match a list of expected kinds. Undefined is returned
* if there aren't enough ancestors, or if the kinds are incorrect.
*
* For example, suppose child "C" has parents A --> B --> C.
*
* Calling _matchAncestor(C, [ExportSpecifier, NamedExports, ExportDeclaration])
* would return A only if A is of kind ExportSpecifier, B is of kind NamedExports,
* and C is of kind ExportDeclaration.
*
* Calling _matchAncestor(C, [ExportDeclaration]) would return C.
*/
static matchAncestor(node, kindsToMatch) {
// (slice(0) clones an array)
const reversedParentKinds = kindsToMatch.slice(0).reverse();
let current = undefined;
for (const parentKind of reversedParentKinds) {
if (!current) {
// The first time through, start with node
current = node;
}
else {
// Then walk the parents
current = current.parent;
}
// If we ran out of items, or if the kind doesn't match, then fail
if (!current || current.kind !== parentKind) {
return undefined;
}
}
// If we matched everything, then return the node that matched the last parentKinds item
return current;
}
/**
* Does a depth-first search of the children of the specified node. Returns the first child
* with the specified kind, or undefined if there is no match.
*/
static findFirstChildNode(node, kindToMatch) {
for (const child of node.getChildren()) {
if (child.kind === kindToMatch) {
return child;
}
const recursiveMatch = TypeScriptHelpers.findFirstChildNode(child, kindToMatch);
if (recursiveMatch) {
return recursiveMatch;
}
}
return undefined;
}
/**
* Returns the first parent node with the specified SyntaxKind, or undefined if there is no match.
*/
static findFirstParent(node, kindToMatch) {
let current = node.parent;
while (current) {
if (current.kind === kindToMatch) {
return current;
}
current = current.parent;
}
return undefined;
}
/**
* Returns the highest parent node with the specified SyntaxKind, or undefined if there is no match.
* @remarks
* Whereas findFirstParent() returns the first match, findHighestParent() returns the last match.
*/
static findHighestParent(node, kindToMatch) {
let current = node;
let highest = undefined;
for (;;) {
current = TypeScriptHelpers.findFirstParent(current, kindToMatch);
if (!current) {
break;
}
highest = current;
}
return highest;
}
/**
* Decodes the names that the compiler generates for a built-in ECMAScript symbol.
*
* @remarks
* TypeScript binds well-known ECMAScript symbols like `[Symbol.iterator]` as `__@iterator`.
* If `name` is of this form, then `tryGetWellKnownSymbolName()` converts it back into e.g. `[Symbol.iterator]`.
* If the string does not start with `__@` then `undefined` is returned.
*/
static tryDecodeWellKnownSymbolName(name) {
const match = TypeScriptHelpers._wellKnownSymbolNameRegExp.exec(name);
if (match) {
const identifier = match[1];
return `[Symbol.${identifier}]`;
}
return undefined;
}
/**
* Returns whether the provided name was generated for a TypeScript `unique symbol`.
*/
static isUniqueSymbolName(name) {
return TypeScriptHelpers._uniqueSymbolNameRegExp.test(name);
}
/**
* Derives the string representation of a TypeScript late-bound symbol.
*/
static tryGetLateBoundName(declarationName) {
// Create a node printer that ignores comments and indentation that we can use to convert
// declarationName to a string.
const printer = ts.createPrinter({ removeComments: true }, {
onEmitNode(hint, node, emitCallback) {
ts.setEmitFlags(declarationName, ts.EmitFlags.NoIndentation | ts.EmitFlags.SingleLine);
emitCallback(hint, node);
}
});
const sourceFile = declarationName.getSourceFile();
const text = printer.printNode(ts.EmitHint.Unspecified, declarationName, sourceFile);
// clean up any emit flags we've set on any nodes in the tree.
ts.disposeEmitNodes(sourceFile);
return text;
}
}
// Matches TypeScript's encoded names for well-known ECMAScript symbols like
// "__@iterator" or "__@toStringTag".
TypeScriptHelpers._wellKnownSymbolNameRegExp = /^__@(\w+)$/;
// Matches TypeScript's encoded names for late-bound symbols derived from `unique symbol` declarations
// which have the form of "__@<variableName>@<symbolId>", i.e. "__@someSymbol@12345".
TypeScriptHelpers._uniqueSymbolNameRegExp = /^__@.*@\d+$/;
exports.TypeScriptHelpers = TypeScriptHelpers;
//# sourceMappingURL=TypeScriptHelpers.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,58 @@
import * as ts from 'typescript';
/**
* Exposes the TypeScript compiler internals for detecting global variable names.
*/
export interface IGlobalVariableAnalyzer {
hasGlobalName(name: string): boolean;
}
export declare class TypeScriptInternals {
static getImmediateAliasedSymbol(symbol: ts.Symbol, typeChecker: ts.TypeChecker): ts.Symbol;
/**
* Returns the Symbol for the provided Declaration. This is a workaround for a missing
* feature of the TypeScript Compiler API. It is the only apparent way to reach
* certain data structures, and seems to always work, but is not officially documented.
*
* @returns The associated Symbol. If there is no semantic information (e.g. if the
* declaration is an extra semicolon somewhere), then "undefined" is returned.
*/
static tryGetSymbolForDeclaration(declaration: ts.Declaration, checker: ts.TypeChecker): ts.Symbol | undefined;
/**
* Returns whether the provided Symbol is a TypeScript "late-bound" Symbol (i.e. was created by the Checker
* for a computed property based on its type, rather than by the Binder).
*/
static isLateBoundSymbol(symbol: ts.Symbol): boolean;
/**
* Retrieves the comment ranges associated with the specified node.
*/
static getJSDocCommentRanges(node: ts.Node, text: string): ts.CommentRange[] | undefined;
/**
* Retrieves the (unescaped) value of an string literal, numeric literal, or identifier.
*/
static getTextOfIdentifierOrLiteral(node: ts.Identifier | ts.StringLiteralLike | ts.NumericLiteral): string;
/**
* Retrieves the (cached) module resolution information for a module name that was exported from a SourceFile.
* The compiler populates this cache as part of analyzing the source file.
*/
static getResolvedModule(program: ts.Program, sourceFile: ts.SourceFile, moduleNameText: string, mode: ts.ModuleKind.CommonJS | ts.ModuleKind.ESNext | undefined): ts.ResolvedModuleFull | undefined;
/**
* Gets the mode required for module resolution required with the addition of Node16/nodenext
*/
static getModeForUsageLocation(file: {
impliedNodeFormat?: ts.SourceFile['impliedNodeFormat'];
}, usage: ts.StringLiteralLike | undefined): ts.ModuleKind.CommonJS | ts.ModuleKind.ESNext | undefined;
/**
* Returns ts.Symbol.parent if it exists.
*/
static getSymbolParent(symbol: ts.Symbol): ts.Symbol | undefined;
/**
* In an statement like `export default class X { }`, the `Symbol.name` will be `default`
* whereas the `localSymbol` is `X`.
*/
static tryGetLocalSymbol(declaration: ts.Declaration): ts.Symbol | undefined;
static getGlobalVariableAnalyzer(program: ts.Program): IGlobalVariableAnalyzer;
/**
* Returns whether a variable is declared with the const keyword
*/
static isVarConst(node: ts.VariableDeclaration | ts.VariableDeclarationList): boolean;
}
//# sourceMappingURL=TypeScriptInternals.d.ts.map

View File

@ -0,0 +1 @@
{"version":3,"file":"TypeScriptInternals.d.ts","sourceRoot":"","sources":["../../src/analyzer/TypeScriptInternals.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAGjC;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CACtC;AAED,qBAAa,mBAAmB;WAChB,yBAAyB,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,CAAC,WAAW,GAAG,EAAE,CAAC,MAAM;IAMlG;;;;;;;OAOG;WACW,0BAA0B,CACtC,WAAW,EAAE,EAAE,CAAC,WAAW,EAC3B,OAAO,EAAE,EAAE,CAAC,WAAW,GACtB,EAAE,CAAC,MAAM,GAAG,SAAS;IASxB;;;OAGG;WACW,iBAAiB,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,GAAG,OAAO;IAW3D;;OAEG;WACW,qBAAqB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,EAAE,CAAC,YAAY,EAAE,GAAG,SAAS;IAO/F;;OAEG;WACW,4BAA4B,CACxC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,iBAAiB,GAAG,EAAE,CAAC,cAAc,GAC7D,MAAM;IAOT;;;OAGG;WACW,iBAAiB,CAC7B,OAAO,EAAE,EAAE,CAAC,OAAO,EACnB,UAAU,EAAE,EAAE,CAAC,UAAU,EACzB,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,GAAG,SAAS,GAC9D,EAAE,CAAC,kBAAkB,GAAG,SAAS;IAWpC;;OAEG;WACW,uBAAuB,CACnC,IAAI,EAAE;QAAE,iBAAiB,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAA;KAAE,EAChE,KAAK,EAAE,EAAE,CAAC,iBAAiB,GAAG,SAAS,GACtC,EAAE,CAAC,UAAU,CAAC,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,GAAG,SAAS;IAO5D;;OAEG;WACW,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,GAAG,EAAE,CAAC,MAAM,GAAG,SAAS;IAIvE;;;OAGG;WACW,iBAAiB,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,GAAG,EAAE,CAAC,MAAM,GAAG,SAAS;WAIrE,yBAAyB,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,GAAG,uBAAuB;IAmBrF;;OAEG;WACW,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,mBAAmB,GAAG,EAAE,CAAC,uBAAuB,GAAG,OAAO;CAI7F"}

View File

@ -0,0 +1,141 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeScriptInternals = void 0;
/* eslint-disable @typescript-eslint/no-explicit-any */
const ts = __importStar(require("typescript"));
const node_core_library_1 = require("@rushstack/node-core-library");
class TypeScriptInternals {
static getImmediateAliasedSymbol(symbol, typeChecker) {
// Compiler internal:
// https://github.com/microsoft/TypeScript/blob/v3.2.2/src/compiler/checker.ts
return typeChecker.getImmediateAliasedSymbol(symbol); // eslint-disable-line @typescript-eslint/no-explicit-any
}
/**
* Returns the Symbol for the provided Declaration. This is a workaround for a missing
* feature of the TypeScript Compiler API. It is the only apparent way to reach
* certain data structures, and seems to always work, but is not officially documented.
*
* @returns The associated Symbol. If there is no semantic information (e.g. if the
* declaration is an extra semicolon somewhere), then "undefined" is returned.
*/
static tryGetSymbolForDeclaration(declaration, checker) {
let symbol = declaration.symbol;
if (symbol && symbol.escapedName === ts.InternalSymbolName.Computed) {
const name = ts.getNameOfDeclaration(declaration);
symbol = (name && checker.getSymbolAtLocation(name)) || symbol;
}
return symbol;
}
/**
* Returns whether the provided Symbol is a TypeScript "late-bound" Symbol (i.e. was created by the Checker
* for a computed property based on its type, rather than by the Binder).
*/
static isLateBoundSymbol(symbol) {
if (
// eslint-disable-next-line no-bitwise
symbol.flags & ts.SymbolFlags.Transient &&
ts.getCheckFlags(symbol) === ts.CheckFlags.Late) {
return true;
}
return false;
}
/**
* Retrieves the comment ranges associated with the specified node.
*/
static getJSDocCommentRanges(node, text) {
// Compiler internal:
// https://github.com/microsoft/TypeScript/blob/v2.4.2/src/compiler/utilities.ts#L616
return ts.getJSDocCommentRanges.apply(this, arguments);
}
/**
* Retrieves the (unescaped) value of an string literal, numeric literal, or identifier.
*/
static getTextOfIdentifierOrLiteral(node) {
// Compiler internal:
// https://github.com/microsoft/TypeScript/blob/v3.2.2/src/compiler/utilities.ts#L2721
return ts.getTextOfIdentifierOrLiteral(node);
}
/**
* Retrieves the (cached) module resolution information for a module name that was exported from a SourceFile.
* The compiler populates this cache as part of analyzing the source file.
*/
static getResolvedModule(program, sourceFile, moduleNameText, mode) {
// Compiler internal:
// https://github.com/microsoft/TypeScript/blob/v5.3.3/src/compiler/types.ts#L4698
const result = program.getResolvedModule(sourceFile, moduleNameText, mode);
return result === null || result === void 0 ? void 0 : result.resolvedModule;
}
/**
* Gets the mode required for module resolution required with the addition of Node16/nodenext
*/
static getModeForUsageLocation(file, usage) {
// Compiler internal:
// https://github.com/microsoft/TypeScript/blob/v4.7.2/src/compiler/program.ts#L568
var _a, _b;
return (_b = (_a = ts).getModeForUsageLocation) === null || _b === void 0 ? void 0 : _b.call(_a, file, usage);
}
/**
* Returns ts.Symbol.parent if it exists.
*/
static getSymbolParent(symbol) {
return symbol.parent;
}
/**
* In an statement like `export default class X { }`, the `Symbol.name` will be `default`
* whereas the `localSymbol` is `X`.
*/
static tryGetLocalSymbol(declaration) {
return declaration.localSymbol;
}
static getGlobalVariableAnalyzer(program) {
var _a;
const anyProgram = program;
const typeCheckerInstance = (_a = anyProgram.getDiagnosticsProducingTypeChecker) !== null && _a !== void 0 ? _a : anyProgram.getTypeChecker;
if (!typeCheckerInstance) {
throw new node_core_library_1.InternalError('Missing Program.getDiagnosticsProducingTypeChecker or Program.getTypeChecker');
}
const typeChecker = typeCheckerInstance();
if (!typeChecker.getEmitResolver) {
throw new node_core_library_1.InternalError('Missing TypeChecker.getEmitResolver');
}
const resolver = typeChecker.getEmitResolver();
if (!resolver.hasGlobalName) {
throw new node_core_library_1.InternalError('Missing EmitResolver.hasGlobalName');
}
return resolver;
}
/**
* Returns whether a variable is declared with the const keyword
*/
static isVarConst(node) {
// Compiler internal: https://github.com/microsoft/TypeScript/blob/71286e3d49c10e0e99faac360a6bbd40f12db7b6/src/compiler/utilities.ts#L925
return ts.isVarConst(node);
}
}
exports.TypeScriptInternals = TypeScriptInternals;
//# sourceMappingURL=TypeScriptInternals.js.map

File diff suppressed because one or more lines are too long