From e3c7dbc676cf2311d596d6c0ebc775d5c010d405 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 24 Sep 2026 11:01:45 -0700 Subject: [PATCH] Add AST helpers used by api-extractor --- .../src/api/node/node.infrastructure.ts | 41 +-- packages/typescript/src/ast/ast.generated.ts | 2 +- packages/typescript/src/ast/is.generated.ts | 2 +- packages/typescript/src/ast/modifiers.ts | 41 +++ packages/typescript/src/ast/utils.ts | 250 ++++++++++++++++++ .../src/enums/jsDeclarationKind.enum.ts | 11 + .../typescript/src/enums/jsDeclarationKind.ts | 11 + packages/typescript/test/sync/ast.test.ts | 110 ++++++++ tools/scripts/tsc/ast.json | 1 + tools/scripts/tsc/generate-enums.ts | 4 +- tsc/internal/api/enum_values_generated.go | 9 + tsc/internal/ast/ast_generated.go | 2 +- 12 files changed, 440 insertions(+), 44 deletions(-) create mode 100644 packages/typescript/src/ast/modifiers.ts create mode 100644 packages/typescript/src/enums/jsDeclarationKind.enum.ts create mode 100644 packages/typescript/src/enums/jsDeclarationKind.ts diff --git a/packages/typescript/src/api/node/node.infrastructure.ts b/packages/typescript/src/api/node/node.infrastructure.ts index 5ce47a9f3d06f..5235a9d526ede 100644 --- a/packages/typescript/src/api/node/node.infrastructure.ts +++ b/packages/typescript/src/api/node/node.infrastructure.ts @@ -1,9 +1,9 @@ import { type FileReference, - ModifierFlags, type Node, SyntaxKind, } from "../../ast/index.ts"; +export { modifierToFlag } from "../../ast/modifiers.ts"; import type { TimingCollector } from "../timing.ts"; import { HEADER_OFFSET_HASH_HI0, @@ -94,45 +94,6 @@ function hex8(n: number): string { return (n >>> 0).toString(16).padStart(8, "0"); } -export function modifierToFlag(kind: SyntaxKind): ModifierFlags { - switch (kind) { - case SyntaxKind.StaticKeyword: - return ModifierFlags.Static; - case SyntaxKind.PublicKeyword: - return ModifierFlags.Public; - case SyntaxKind.ProtectedKeyword: - return ModifierFlags.Protected; - case SyntaxKind.PrivateKeyword: - return ModifierFlags.Private; - case SyntaxKind.AbstractKeyword: - return ModifierFlags.Abstract; - case SyntaxKind.AccessorKeyword: - return ModifierFlags.Accessor; - case SyntaxKind.ExportKeyword: - return ModifierFlags.Export; - case SyntaxKind.DeclareKeyword: - return ModifierFlags.Ambient; - case SyntaxKind.ConstKeyword: - return ModifierFlags.Const; - case SyntaxKind.DefaultKeyword: - return ModifierFlags.Default; - case SyntaxKind.AsyncKeyword: - return ModifierFlags.Async; - case SyntaxKind.ReadonlyKeyword: - return ModifierFlags.Readonly; - case SyntaxKind.OverrideKeyword: - return ModifierFlags.Override; - case SyntaxKind.InKeyword: - return ModifierFlags.In; - case SyntaxKind.OutKeyword: - return ModifierFlags.Out; - case SyntaxKind.Decorator: - return ModifierFlags.Decorator; - default: - return ModifierFlags.None; - } -} - // ═══════════════════════════════════════════════════════════════════════════ // RemoteNodeBase // ═══════════════════════════════════════════════════════════════════════════ diff --git a/packages/typescript/src/ast/ast.generated.ts b/packages/typescript/src/ast/ast.generated.ts index 8ea660ad52bbf..422a639df2b56 100644 --- a/packages/typescript/src/ast/ast.generated.ts +++ b/packages/typescript/src/ast/ast.generated.ts @@ -1343,7 +1343,7 @@ export type HeritageClauseElement = ExpressionWithTypeArguments | TypeReferenceN export type BlockOrExpression = Block | Expression; export type NodeBody = Block | Expression | ModuleBlock | ModuleDeclaration; export type AccessExpression = PropertyAccessExpression | ElementAccessExpression; -export type DeclarationName = Identifier | PrivateIdentifier | StringLiteral | NumericLiteral | BigIntLiteral | NoSubstitutionTemplateLiteral | ComputedPropertyName | BindingPattern | ElementAccessExpression; +export type DeclarationName = Identifier | PrivateIdentifier | StringLiteral | NumericLiteral | BigIntLiteral | NoSubstitutionTemplateLiteral | ComputedPropertyName | BindingPattern | PropertyAccessExpression | ElementAccessExpression; export type ModuleName = Identifier | StringLiteral; export type ModuleExportName = Identifier | StringLiteral; export type PropertyName = Identifier | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | ComputedPropertyName | PrivateIdentifier | BigIntLiteral; diff --git a/packages/typescript/src/ast/is.generated.ts b/packages/typescript/src/ast/is.generated.ts index 2654438f83ab3..7e529557d817c 100644 --- a/packages/typescript/src/ast/is.generated.ts +++ b/packages/typescript/src/ast/is.generated.ts @@ -2129,7 +2129,7 @@ isAccessExpression.Handle = isAccessExpression as any; export function isDeclarationName(node: Node): node is DeclarationName { const kind = node.kind; - return kind === SyntaxKind.Identifier || kind === SyntaxKind.PrivateIdentifier || kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NumericLiteral || kind === SyntaxKind.BigIntLiteral || kind === SyntaxKind.NoSubstitutionTemplateLiteral || kind === SyntaxKind.ComputedPropertyName || kind === SyntaxKind.ObjectBindingPattern || kind === SyntaxKind.ArrayBindingPattern || kind === SyntaxKind.ElementAccessExpression; + return kind === SyntaxKind.Identifier || kind === SyntaxKind.PrivateIdentifier || kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NumericLiteral || kind === SyntaxKind.BigIntLiteral || kind === SyntaxKind.NoSubstitutionTemplateLiteral || kind === SyntaxKind.ComputedPropertyName || kind === SyntaxKind.ObjectBindingPattern || kind === SyntaxKind.ArrayBindingPattern || kind === SyntaxKind.PropertyAccessExpression || kind === SyntaxKind.ElementAccessExpression; } export declare namespace isDeclarationName { diff --git a/packages/typescript/src/ast/modifiers.ts b/packages/typescript/src/ast/modifiers.ts new file mode 100644 index 0000000000000..cc00e1651b00a --- /dev/null +++ b/packages/typescript/src/ast/modifiers.ts @@ -0,0 +1,41 @@ +import { ModifierFlags } from "#enums/modifierFlags"; +import { SyntaxKind } from "#enums/syntaxKind"; + +export function modifierToFlag(kind: SyntaxKind): ModifierFlags { + switch (kind) { + case SyntaxKind.StaticKeyword: + return ModifierFlags.Static; + case SyntaxKind.PublicKeyword: + return ModifierFlags.Public; + case SyntaxKind.ProtectedKeyword: + return ModifierFlags.Protected; + case SyntaxKind.PrivateKeyword: + return ModifierFlags.Private; + case SyntaxKind.AbstractKeyword: + return ModifierFlags.Abstract; + case SyntaxKind.AccessorKeyword: + return ModifierFlags.Accessor; + case SyntaxKind.ExportKeyword: + return ModifierFlags.Export; + case SyntaxKind.DeclareKeyword: + return ModifierFlags.Ambient; + case SyntaxKind.ConstKeyword: + return ModifierFlags.Const; + case SyntaxKind.DefaultKeyword: + return ModifierFlags.Default; + case SyntaxKind.AsyncKeyword: + return ModifierFlags.Async; + case SyntaxKind.ReadonlyKeyword: + return ModifierFlags.Readonly; + case SyntaxKind.OverrideKeyword: + return ModifierFlags.Override; + case SyntaxKind.InKeyword: + return ModifierFlags.In; + case SyntaxKind.OutKeyword: + return ModifierFlags.Out; + case SyntaxKind.Decorator: + return ModifierFlags.Decorator; + default: + return ModifierFlags.None; + } +} diff --git a/packages/typescript/src/ast/utils.ts b/packages/typescript/src/ast/utils.ts index c510d26d6cea0..aef39a99abd87 100644 --- a/packages/typescript/src/ast/utils.ts +++ b/packages/typescript/src/ast/utils.ts @@ -1,13 +1,38 @@ import { CharacterCodes } from "#enums/characterCodes"; +import { JSDeclarationKind } from "#enums/jsDeclarationKind"; +import { ModifierFlags } from "#enums/modifierFlags"; +import { NodeFlags } from "#enums/nodeFlags"; +import { OuterExpressionKinds } from "#enums/outerExpressionKinds"; import { SyntaxKind } from "#enums/syntaxKind"; import type { __String, + AccessExpression, + BinaryExpression, + BindingElement, + CallExpression, + Declaration, + DeclarationName, + ElementAccessExpression, + ExportAssignment, + Expression, HasExpression, HasInitializer, + ModifierLike, ObjectAssignmentInitializer, + PropertyAccessExpression, + PropertyAssignment, SourceFile, + VariableDeclaration, } from "./ast.ts"; import type { Node } from "./ast.ts"; +import { + isAccessExpression, + isIdentifier, + isNumericLiteral, + isStringLiteralLikeNode, +} from "./is.generated.ts"; +import { skipOuterExpressions } from "./is.ts"; +import { modifierToFlag } from "./modifiers.ts"; let syntaxKindNames: Map | undefined; function getSyntaxKindNames(): Map { @@ -87,6 +112,231 @@ export function hasObjectAssignmentInitializer(node: Node): node is ObjectAssign return "objectAssignmentInitializer" in node; } +export function isExternalModule(file: SourceFile): boolean { + return file.externalModuleIndicator !== undefined; +} + +function getRootDeclaration(node: Node): Node { + while (node.kind === SyntaxKind.BindingElement) { + node = node.parent.parent; + } + return node; +} + +function getModifierFlags(node: Node): ModifierFlags { + const modifiers = "modifiers" in node + ? (node as Node & { readonly modifiers?: readonly ModifierLike[]; }).modifiers + : undefined; + let flags = ModifierFlags.None; + if (modifiers) { + for (const modifier of modifiers) { + flags |= modifierToFlag(modifier.kind); + } + } + return flags; +} + +export function getCombinedModifierFlags(node: Declaration): ModifierFlags { + let current: Node | undefined = getRootDeclaration(node); + let flags = getModifierFlags(current); + if (current.kind === SyntaxKind.VariableDeclaration) { + current = current.parent; + } + if (current?.kind === SyntaxKind.VariableDeclarationList) { + flags |= getModifierFlags(current); + current = current.parent; + } + if (current?.kind === SyntaxKind.VariableStatement) { + flags |= getModifierFlags(current); + } + return flags; +} + +function skipParentheses(node: Expression): Expression { + return skipOuterExpressions(node, OuterExpressionKinds.Parentheses); +} + +function isStringOrNumericLiteralLike(node: Node): node is DeclarationName { + return isStringLiteralLikeNode(node) || isNumericLiteral(node); +} + +function getElementOrPropertyAccessName(node: AccessExpression): DeclarationName | undefined { + if (node.kind === SyntaxKind.PropertyAccessExpression) { + return isIdentifier(node.name) ? node.name : undefined; + } + const argument = skipParentheses(node.argumentExpression); + return isStringOrNumericLiteralLike(argument) ? argument : undefined; +} + +function getNameText(node: DeclarationName | undefined): string | undefined { + return node && "text" in node && typeof node.text === "string" ? node.text : undefined; +} + +function isEntityNameExpression(node: Node, allowJS = false): boolean { + if (isIdentifier(node)) return true; + if (node.kind === SyntaxKind.PropertyAccessExpression) { + const access = node as PropertyAccessExpression; + return isIdentifier(access.name) && isEntityNameExpression(access.expression, allowJS); + } + if (!allowJS) return false; + if (node.kind === SyntaxKind.ThisKeyword) return true; + if (node.kind !== SyntaxKind.ElementAccessExpression) return false; + const access = node as ElementAccessExpression; + return isStringOrNumericLiteralLike(access.argumentExpression) && + isEntityNameExpression(access.expression, true); +} + +function isBindableStaticAccessExpression(node: Node, excludeThisKeyword: boolean): node is AccessExpression { + if (node.kind === SyntaxKind.PropertyAccessExpression) { + const access = node as PropertyAccessExpression; + return !excludeThisKeyword && access.expression.kind === SyntaxKind.ThisKeyword || + isIdentifier(access.name) && isBindableStaticNameExpression(access.expression, true); + } + if (node.kind !== SyntaxKind.ElementAccessExpression) return false; + const access = node as ElementAccessExpression; + return isStringOrNumericLiteralLike(access.argumentExpression) && + (!excludeThisKeyword && access.expression.kind === SyntaxKind.ThisKeyword || + isEntityNameExpression(access.expression) || + isBindableStaticAccessExpression(access.expression, true)); +} + +function isBindableStaticNameExpression(node: Node, excludeThisKeyword: boolean): boolean { + return isEntityNameExpression(node) || isBindableStaticAccessExpression(node, excludeThisKeyword); +} + +function isModuleExportsAccessExpression(node: Node): boolean { + return isAccessExpression(node) && + isIdentifier(node.expression) && + node.expression.text === "module" && + getNameText(getElementOrPropertyAccessName(node)) === "exports"; +} + +function isBindableObjectDefinePropertyCall(node: CallExpression): boolean { + if (node.arguments.length !== 3) return false; + const expression = node.expression; + if (expression.kind !== SyntaxKind.PropertyAccessExpression) return false; + const access = expression as PropertyAccessExpression; + return isIdentifier(access.expression) && + access.expression.text === "Object" && + access.name.text === "defineProperty" && + isStringOrNumericLiteralLike(node.arguments[1]) && + isBindableStaticNameExpression(node.arguments[0], true); +} + +function getAssignmentDeclarationKind(node: BinaryExpression | CallExpression): JSDeclarationKind { + if (node.kind === SyntaxKind.BinaryExpression) { + if (node.operatorToken.kind !== SyntaxKind.EqualsToken || !isAccessExpression(node.left)) { + return JSDeclarationKind.None; + } + const left = node.left; + const isInJSFile = (left.flags & NodeFlags.JavaScriptFile) !== 0; + if (isInJSFile) { + if (isModuleExportsAccessExpression(left) && !(isIdentifier(node.right) && node.right.text === "exports")) { + return JSDeclarationKind.ModuleExports; + } + if ( + (isModuleExportsAccessExpression(left.expression) || + isIdentifier(left.expression) && left.expression.text === "exports") && + getElementOrPropertyAccessName(left) + ) { + return JSDeclarationKind.ExportsProperty; + } + if (left.expression.kind === SyntaxKind.ThisKeyword) { + return JSDeclarationKind.ThisProperty; + } + } + if ( + left.kind === SyntaxKind.PropertyAccessExpression && + isEntityNameExpression(left.expression, isInJSFile) && + isIdentifier(left.name) || + left.kind === SyntaxKind.ElementAccessExpression && + isEntityNameExpression(left.expression, isInJSFile) + ) { + return JSDeclarationKind.Property; + } + return JSDeclarationKind.None; + } + + if ( + (node.flags & NodeFlags.JavaScriptFile) !== 0 && + isBindableObjectDefinePropertyCall(node) + ) { + const entityName = node.arguments[0]; + return isIdentifier(entityName) && entityName.text === "exports" || + isModuleExportsAccessExpression(entityName) + ? JSDeclarationKind.ObjectDefinePropertyExports + : JSDeclarationKind.ObjectDefinePropertyValue; + } + return JSDeclarationKind.None; +} + +function getDeclaredName(declaration: Declaration | Expression): DeclarationName | undefined { + return (declaration as (Declaration | Expression) & { readonly name?: DeclarationName; }).name; +} + +function getNonAssignedNameOfDeclaration(declaration: Declaration | Expression): DeclarationName | undefined { + if (declaration.kind === SyntaxKind.BinaryExpression || declaration.kind === SyntaxKind.CallExpression) { + switch (getAssignmentDeclarationKind(declaration as BinaryExpression | CallExpression)) { + case JSDeclarationKind.Property: + case JSDeclarationKind.ThisProperty: + case JSDeclarationKind.ExportsProperty: { + const left = (declaration as BinaryExpression).left as AccessExpression; + return getElementOrPropertyAccessName(left) ?? left; + } + case JSDeclarationKind.ObjectDefinePropertyValue: + case JSDeclarationKind.ObjectDefinePropertyExports: + return (declaration as CallExpression).arguments[1] as DeclarationName; + default: + return undefined; + } + } + if (declaration.kind === SyntaxKind.ExportAssignment) { + const expression = (declaration as ExportAssignment).expression; + return isIdentifier(expression) ? expression : undefined; + } + return getDeclaredName(declaration); +} + +function getAssignedName(node: Node): DeclarationName | undefined { + const parent = node.parent; + if (!parent) return undefined; + switch (parent.kind) { + case SyntaxKind.PropertyAssignment: + return (parent as PropertyAssignment).name; + case SyntaxKind.BindingElement: + return (parent as BindingElement).name; + case SyntaxKind.BinaryExpression: { + const binary = parent as BinaryExpression; + if (node !== binary.right) return undefined; + const left = binary.left; + if (isIdentifier(left)) return left; + if (left.kind === SyntaxKind.PropertyAccessExpression) return (left as PropertyAccessExpression).name; + if (left.kind === SyntaxKind.ElementAccessExpression) { + const argument = skipParentheses((left as ElementAccessExpression).argumentExpression); + return isStringOrNumericLiteralLike(argument) ? argument : undefined; + } + return undefined; + } + case SyntaxKind.VariableDeclaration: { + const name = (parent as VariableDeclaration).name; + return isIdentifier(name) ? name : undefined; + } + default: + return undefined; + } +} + +export function getNameOfDeclaration(declaration: Declaration | Expression | undefined): DeclarationName | undefined { + if (declaration === undefined) return undefined; + const nonAssignedName = getNonAssignedNameOfDeclaration(declaration); + if (nonAssignedName !== undefined) return nonAssignedName; + return declaration.kind === SyntaxKind.FunctionExpression || + declaration.kind === SyntaxKind.ArrowFunction || + declaration.kind === SyntaxKind.ClassExpression + ? getAssignedName(declaration) + : undefined; +} + export function cloneSourceFileData(sourceFile: SourceFile): Record { return { statements: sourceFile.statements, diff --git a/packages/typescript/src/enums/jsDeclarationKind.enum.ts b/packages/typescript/src/enums/jsDeclarationKind.enum.ts new file mode 100644 index 0000000000000..4e6dcbe9fc539 --- /dev/null +++ b/packages/typescript/src/enums/jsDeclarationKind.enum.ts @@ -0,0 +1,11 @@ +// Code generated by tools/scripts/tsc/generate-enums.ts from tsc/internal/ast/utilities.go. DO NOT EDIT. + +export enum JSDeclarationKind { + None = 0, + ModuleExports = 1, + ExportsProperty = 2, + ThisProperty = 3, + Property = 4, + ObjectDefinePropertyValue = 5, + ObjectDefinePropertyExports = 6, +} diff --git a/packages/typescript/src/enums/jsDeclarationKind.ts b/packages/typescript/src/enums/jsDeclarationKind.ts new file mode 100644 index 0000000000000..d09cd90920d20 --- /dev/null +++ b/packages/typescript/src/enums/jsDeclarationKind.ts @@ -0,0 +1,11 @@ +// Code generated by tools/scripts/tsc/generate-enums.ts from tsc/internal/ast/utilities.go. DO NOT EDIT. +export var JSDeclarationKind: any; +(function (JSDeclarationKind) { + JSDeclarationKind[JSDeclarationKind["None"] = 0] = "None"; + JSDeclarationKind[JSDeclarationKind["ModuleExports"] = 1] = "ModuleExports"; + JSDeclarationKind[JSDeclarationKind["ExportsProperty"] = 2] = "ExportsProperty"; + JSDeclarationKind[JSDeclarationKind["ThisProperty"] = 3] = "ThisProperty"; + JSDeclarationKind[JSDeclarationKind["Property"] = 4] = "Property"; + JSDeclarationKind[JSDeclarationKind["ObjectDefinePropertyValue"] = 5] = "ObjectDefinePropertyValue"; + JSDeclarationKind[JSDeclarationKind["ObjectDefinePropertyExports"] = 6] = "ObjectDefinePropertyExports"; +})(JSDeclarationKind || (JSDeclarationKind = {})); diff --git a/packages/typescript/test/sync/ast.test.ts b/packages/typescript/test/sync/ast.test.ts index fad091bbb7732..bdb20c1d77110 100644 --- a/packages/typescript/test/sync/ast.test.ts +++ b/packages/typescript/test/sync/ast.test.ts @@ -1,21 +1,33 @@ import type { + BinaryExpression, + BindingElement, + CallExpression, + ClassDeclaration, + ExportAssignment, ExpressionStatement, + FunctionDeclaration, Identifier, JSDoc, + MethodDeclaration, Node, NodeArray, + ObjectBindingPattern, SourceFile, StringLiteralLikeNode, VariableStatement, } from "@typescript/typescript/unstable/ast"; import { + getCombinedModifierFlags, + getNameOfDeclaration, getTokenAtPosition, isClassDeclaration, + isExternalModule, isImportDeclaration, isInterfaceDeclaration, isJSDocLink, isNamedImports, isValidTypeOnlyAliasUseSite, + ModifierFlags, NodeFlags, SyntaxKind, TokenFlags, @@ -29,6 +41,7 @@ import { createBinaryExpression, createBlock, createExpressionStatement, + createFunctionDeclaration, createIdentifier, createIfStatement, createMissingDeclaration, @@ -605,6 +618,103 @@ function getRemoteSourceFile(api: API, configPath: string, filePath: string) { return getRemoteSourceFileAndChecker(api, configPath, filePath)[0]; } +describe("declaration utilities", { concurrency }, () => { + test("isExternalModule distinguishes external modules from scripts and CommonJS", () => { + using api = spawnAPI({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { allowJs: true, moduleDetection: "legacy" } }), + "/src/module.ts": "export const x = 1;", + "/src/import.ts": 'import { x } from "./module";', + "/src/script.ts": "const y = 1;", + "/src/commonjs.js": "module.exports = 1;", + }); + for ( + const [fileName, expected] of [ + ["module.ts", true], + ["import.ts", true], + ["script.ts", false], + ["commonjs.js", false], + ] as const + ) { + const sourceFile = getRemoteSourceFile(api, "/tsconfig.json", `/src/${fileName}`); + assert.strictEqual(isExternalModule(sourceFile), expected, fileName); + assert.strictEqual(isExternalModule(cloneNode(sourceFile)), expected, `cloned ${fileName}`); + } + }); + + test("getCombinedModifierFlags includes modifiers on factory nodes", () => { + const declaration = createFunctionDeclaration( + [createToken(SyntaxKind.ExportKeyword), createToken(SyntaxKind.AsyncKeyword)], + undefined, + createIdentifier("f"), + undefined, + [], + undefined, + createBlock([]), + ); + + assert.strictEqual( + getCombinedModifierFlags(declaration), + ModifierFlags.Export | ModifierFlags.Async, + ); + }); + + test("getCombinedModifierFlags includes variable statement flags for binding elements", () => { + using api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/index.ts": "export const { x } = value;", + }); + const sf = getRemoteSourceFile(api, "/tsconfig.json", "/src/index.ts"); + const statement = sf.statements[0] as VariableStatement; + const declaration = statement.declarationList.declarations[0]; + const binding = (declaration.name as ObjectBindingPattern).elements[0] as BindingElement; + + assert.strictEqual( + getCombinedModifierFlags(binding), + ModifierFlags.Export, + ); + }); + + test("getNameOfDeclaration returns declared and assigned names", () => { + using api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/index.ts": ` +function declared() {} +const assigned = class {}; +export default declared; +`, + }); + const sf = getRemoteSourceFile(api, "/tsconfig.json", "/src/index.ts"); + const variable = sf.statements[1] as VariableStatement; + const classExpression = variable.declarationList.declarations[0].initializer!; + + assert.strictEqual(getNameOfDeclaration(sf.statements[0] as FunctionDeclaration)?.getText(), "declared"); + assert.strictEqual(getNameOfDeclaration(classExpression)?.getText(), "assigned"); + assert.strictEqual(getNameOfDeclaration(sf.statements[2] as ExportAssignment)?.getText(), "declared"); + assert.strictEqual(getNameOfDeclaration(undefined), undefined); + }); + + test("getNameOfDeclaration returns JavaScript assignment declaration names", () => { + using api = spawnAPI({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { allowJs: true } }), + "/src/index.js": ` +exports.foo = () => {}; +Object.defineProperty(exports, "bar", { value: 1 }); +class C { #x; method() { this.#x = 1; } } +`, + }); + const sf = getRemoteSourceFile(api, "/tsconfig.json", "/src/index.js"); + const assignment = (sf.statements[0] as ExpressionStatement).expression as BinaryExpression; + const defineProperty = (sf.statements[1] as ExpressionStatement).expression as CallExpression; + const classDeclaration = sf.statements[2] as ClassDeclaration; + const method = classDeclaration.members[1] as MethodDeclaration; + const privateAssignment = (method.body!.statements[0] as ExpressionStatement).expression as BinaryExpression; + + assert.strictEqual(getNameOfDeclaration(assignment)?.getText(), "foo"); + assert.strictEqual(getNameOfDeclaration(defineProperty)?.getText(), '"bar"'); + assert.strictEqual(getNameOfDeclaration(privateAssignment), privateAssignment.left); + }); +}); + describe("RemoteNode + cloneNode", { concurrency }, () => { test("does not read a sibling as an invalid JSDoc link name", () => { const api = spawnAPI({ diff --git a/tools/scripts/tsc/ast.json b/tools/scripts/tsc/ast.json index d15f18aa35b81..dc7615dfe4e02 100644 --- a/tools/scripts/tsc/ast.json +++ b/tools/scripts/tsc/ast.json @@ -5174,6 +5174,7 @@ "NoSubstitutionTemplateLiteral", "ComputedPropertyName", "BindingPattern", + "PropertyAccessExpression", "ElementAccessExpression" ], "ModuleName": [ diff --git a/tools/scripts/tsc/generate-enums.ts b/tools/scripts/tsc/generate-enums.ts index f0c67100f5239..e79cfc8c571a8 100644 --- a/tools/scripts/tsc/generate-enums.ts +++ b/tools/scripts/tsc/generate-enums.ts @@ -20,6 +20,7 @@ interface EnumDef { goPrefix: string; goFile: string; outDir: string; + fileName?: string | undefined; stringEnum?: boolean | undefined; excludeMembers?: readonly string[] | undefined; valueReplacements?: Record | undefined; @@ -40,6 +41,7 @@ const enumDefs = [ { name: "SyntaxKind", goPrefix: "Kind", goFile: "tsc/internal/ast/kind_generated.go", outDir: "packages/typescript/src/enums" }, { name: "NodeFlags", goPrefix: "NodeFlags", goFile: "tsc/internal/ast/nodeflags.go", outDir: "packages/typescript/src/enums" }, { name: "OuterExpressionKinds", goPrefix: "OEK", goFile: "tsc/internal/ast/utilities.go", outDir: "packages/typescript/src/enums" }, + { name: "JSDeclarationKind", goPrefix: "JSDeclarationKind", goFile: "tsc/internal/ast/utilities.go", outDir: "packages/typescript/src/enums", fileName: "jsDeclarationKind" }, { name: "ModifierFlags", goPrefix: "ModifierFlags", goFile: "tsc/internal/ast/modifierflags.go", outDir: "packages/typescript/src/enums" }, { name: "ModuleKind", goPrefix: "ModuleKind", goFile: "tsc/internal/core/compileroptions.go", outDir: "packages/typescript/src/enums" }, { name: "ModuleResolutionKind", goPrefix: "ModuleResolutionKind", goFile: "tsc/internal/core/compileroptions.go", outDir: "packages/typescript/src/enums" }, @@ -451,7 +453,7 @@ export default async function generateEnums(force = false) { ...goInputs(), ]; const enumFiles = enumDefs.map(def => { - const camelName = def.name.charAt(0).toLowerCase() + def.name.slice(1); + const camelName = def.fileName ?? def.name.charAt(0).toLowerCase() + def.name.slice(1); return { def, camelName, diff --git a/tsc/internal/api/enum_values_generated.go b/tsc/internal/api/enum_values_generated.go index d1f15a1830af3..cd38442117003 100644 --- a/tsc/internal/api/enum_values_generated.go +++ b/tsc/internal/api/enum_values_generated.go @@ -771,6 +771,15 @@ func main() { "AllExceptAssertionsOrExpressionsWithTypeArguments": toInt32(ast.OEKAllExceptAssertionsOrExpressionsWithTypeArguments), "ExpressionTypePassthrough": toInt32(ast.OEKExpressionTypePassthrough), }, + "JSDeclarationKind": { + "None": toInt32(ast.JSDeclarationKindNone), + "ModuleExports": toInt32(ast.JSDeclarationKindModuleExports), + "ExportsProperty": toInt32(ast.JSDeclarationKindExportsProperty), + "ThisProperty": toInt32(ast.JSDeclarationKindThisProperty), + "Property": toInt32(ast.JSDeclarationKindProperty), + "ObjectDefinePropertyValue": toInt32(ast.JSDeclarationKindObjectDefinePropertyValue), + "ObjectDefinePropertyExports": toInt32(ast.JSDeclarationKindObjectDefinePropertyExports), + }, "ModifierFlags": { "None": toInt32(ast.ModifierFlagsNone), "Public": toInt32(ast.ModifierFlagsPublic), diff --git a/tsc/internal/ast/ast_generated.go b/tsc/internal/ast/ast_generated.go index ea7b17ee4cb47..474160182267c 100644 --- a/tsc/internal/ast/ast_generated.go +++ b/tsc/internal/ast/ast_generated.go @@ -521,7 +521,7 @@ type ( BlockOrExpression = Node // Block | Expression NodeBody = Node // Block | Expression | ModuleBlock | ModuleDeclaration AccessExpression = Node // PropertyAccessExpression | ElementAccessExpression - DeclarationName = Node // Identifier | PrivateIdentifier | StringLiteral | NumericLiteral | BigIntLiteral | NoSubstitutionTemplateLiteral | ComputedPropertyName | BindingPattern | ElementAccessExpression + DeclarationName = Node // Identifier | PrivateIdentifier | StringLiteral | NumericLiteral | BigIntLiteral | NoSubstitutionTemplateLiteral | ComputedPropertyName | BindingPattern | PropertyAccessExpression | ElementAccessExpression ModuleName = Node // Identifier | StringLiteral ModuleExportName = Node // Identifier | StringLiteral PropertyName = Node // Identifier | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | ComputedPropertyName | PrivateIdentifier | BigIntLiteral