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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 1 addition & 40 deletions packages/typescript/src/api/node/node.infrastructure.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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
// ═══════════════════════════════════════════════════════════════════════════
Expand Down
2 changes: 1 addition & 1 deletion packages/typescript/src/ast/ast.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion packages/typescript/src/ast/is.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
41 changes: 41 additions & 0 deletions packages/typescript/src/ast/modifiers.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
250 changes: 250 additions & 0 deletions packages/typescript/src/ast/utils.ts
Original file line number Diff line number Diff line change
@@ -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<number, string> | undefined;
function getSyntaxKindNames(): Map<number, string> {
Expand Down Expand Up @@ -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;
Comment on lines +115 to +116

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're never going to be able to rid ourselves of that property are we

}

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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was excited to use this, but it should be callable for ClassElements too. (From what I can tell all ClassElementBase are also Declaration but the two are not related.

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 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have instructions or any sort of way to remember / validate that we ported these right into the future?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean if we change the implementation in Go?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, right (I chose a random place to comment, sorry)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably add something in the general Copilot instructions, honestly. That will probably help turn a lot of stuff up during automated reviews. The API is so broad that I don't know if there's a good way to get more specific.

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);
Comment on lines +293 to +297

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new behavior is accurate to Go's ast.GetNameOfDeclaration.

}

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<string, unknown> {
return {
statements: sourceFile.statements,
Expand Down
11 changes: 11 additions & 0 deletions packages/typescript/src/enums/jsDeclarationKind.enum.ts
Original file line number Diff line number Diff line change
@@ -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,
}
Loading
Loading