Skip to content

Refactor node factory API, use node factory in parser #35282

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jun 16, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
Prev Previous commit
Next Next commit
Make most Node properties read-only
  • Loading branch information
rbuckton committed Dec 9, 2019
commit 602efc5b87936969ff56cc4dd54756d61eb1fb0c
6 changes: 5 additions & 1 deletion src/compat/deprecations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1928,7 +1928,11 @@ namespace ts {
});

/**
* Creates a shallow, memberwise clone of a node for mutation.
* Creates a shallow, memberwise clone of a node ~for mutation~ with its `pos`, `end`, and `parent` set.
*
* NOTE: It is unsafe to change any properties of a `Node` that relate to its AST children, as those changes won't be
* captured with respect to transformations.
*
* @deprecated Use `factory.cloneNode` instead and set `pos`, `end`, and `parent` as needed.
*/
export function getMutableClone<T extends Node>(node: T): T {
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ namespace ts {
// All container nodes are kept on a linked list in declaration order. This list is used by
// the getLocalNameOfContainer function in the type checker to validate that the local name
// used for a container is unique.
function bindContainer(node: Node, containerFlags: ContainerFlags) {
function bindContainer(node: Mutable<Node>, containerFlags: ContainerFlags) {
// Before we recurse into a node's children, we first save the existing parent, container
// and block-container. Then after we pop out of processing the children, we restore
// these saved values.
Expand Down Expand Up @@ -1757,7 +1757,7 @@ namespace ts {
return !!body && body.statements.some(s => isExportDeclaration(s) || isExportAssignment(s));
}

function setExportContextFlag(node: ModuleDeclaration | SourceFile) {
function setExportContextFlag(node: Mutable<ModuleDeclaration | SourceFile>) {
// A declaration source file or ambient module declaration that contains no export declarations (but possibly regular
// declarations with export modifiers) is an export context in which declarations are implicitly exported.
if (node.flags & NodeFlags.Ambient && !hasExportDeclarations(node)) {
Expand Down
286 changes: 185 additions & 101 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -719,10 +719,18 @@ namespace ts {
return [...array1, ...array2];
}

function selectIndex(_: unknown, i: number) {
return i;
}

export function indicesOf(array: readonly unknown[]): number[] {
return array.map(selectIndex);
}

function deduplicateRelational<T>(array: readonly T[], equalityComparer: EqualityComparer<T>, comparer: Comparer<T>) {
// Perform a stable sort of the array. This ensures the first entry in a list of
// duplicates remains the first entry in the result.
const indices = array.map((_, i) => i);
const indices = indicesOf(array);
stableSortIndices(array, indices, comparer);

let last = array[indices[0]];
Expand Down Expand Up @@ -1028,7 +1036,7 @@ namespace ts {
* Stable sort of an array. Elements equal to each other maintain their relative position in the array.
*/
export function stableSort<T>(array: readonly T[], comparer: Comparer<T>): SortedReadonlyArray<T> {
const indices = array.map((_, i) => i);
const indices = indicesOf(array);
stableSortIndices(array, indices, comparer);
return indices.map(i => array[i]) as SortedArray<T> as SortedReadonlyArray<T>;
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,7 @@ namespace ts {
return statement;
});
const eofToken = factory.createToken(SyntaxKind.EndOfFileToken);
const sourceFile = factory.createSourceFile(statements ?? [], eofToken);
const sourceFile = factory.createSourceFile(statements ?? [], eofToken, NodeFlags.None);
sourceFile.fileName = getRelativePathFromDirectory(
host.getCurrentDirectory(),
getNormalizedAbsolutePath(fileName, buildInfoDirectory),
Expand Down
118 changes: 78 additions & 40 deletions src/compiler/factory/nodeFactory.ts

Large diffs are not rendered by default.

29 changes: 28 additions & 1 deletion src/compiler/factory/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ namespace ts {
function createJsxFactoryExpressionFromEntityName(factory: NodeFactory, jsxFactory: EntityName, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression {
if (isQualifiedName(jsxFactory)) {
const left = createJsxFactoryExpressionFromEntityName(factory, jsxFactory.left, parent);
const right = factory.createIdentifier(idText(jsxFactory.right));
const right = factory.createIdentifier(idText(jsxFactory.right)) as Mutable<Identifier>;
right.escapedText = jsxFactory.right.escapedText;
return factory.createPropertyAccess(left, right);
}
Expand Down Expand Up @@ -780,4 +780,31 @@ namespace ts {
return <readonly BindingOrAssignmentElement[]>name.properties;
}
}

export function canHaveModifiers(node: Node): node is HasModifiers {
const kind = node.kind;
return kind === SyntaxKind.Parameter
|| kind === SyntaxKind.PropertySignature
|| kind === SyntaxKind.PropertyDeclaration
|| kind === SyntaxKind.MethodSignature
|| kind === SyntaxKind.MethodDeclaration
|| kind === SyntaxKind.Constructor
|| kind === SyntaxKind.GetAccessor
|| kind === SyntaxKind.SetAccessor
|| kind === SyntaxKind.IndexSignature
|| kind === SyntaxKind.FunctionExpression
|| kind === SyntaxKind.ArrowFunction
|| kind === SyntaxKind.ClassExpression
|| kind === SyntaxKind.VariableStatement
|| kind === SyntaxKind.FunctionDeclaration
|| kind === SyntaxKind.ClassDeclaration
|| kind === SyntaxKind.InterfaceDeclaration
|| kind === SyntaxKind.TypeAliasDeclaration
|| kind === SyntaxKind.EnumDeclaration
|| kind === SyntaxKind.ModuleDeclaration
|| kind === SyntaxKind.ImportEqualsDeclaration
|| kind === SyntaxKind.ImportDeclaration
|| kind === SyntaxKind.ExportAssignment
|| kind === SyntaxKind.ExportDeclaration;
}
}
38 changes: 19 additions & 19 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ namespace ts {
const newSourceFile = IncrementalParser.updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks);
// Because new source file node is created, it may not have the flag PossiblyContainDynamicImport. This is the case if there is no new edit to add dynamic import.
// We will manually port the flag to the new source file.
newSourceFile.flags |= (sourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags);
(newSourceFile as Mutable<SourceFile>).flags |= (sourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags);
return newSourceFile;
}

Expand Down Expand Up @@ -824,8 +824,7 @@ namespace ts {
}

// Set source file so that errors will be reported with this file name
const sourceFile = createSourceFile(fileName, ScriptTarget.ES2015, ScriptKind.JSON, /*isDeclaration*/ false, statements, endOfFileToken);
sourceFile.flags |= sourceFlags;
const sourceFile = createSourceFile(fileName, ScriptTarget.ES2015, ScriptKind.JSON, /*isDeclaration*/ false, statements, endOfFileToken, sourceFlags);

if (setParentNodes) {
fixupParentReferences(sourceFile);
Expand Down Expand Up @@ -923,8 +922,7 @@ namespace ts {
Debug.assert(token() === SyntaxKind.EndOfFileToken);
const endOfFileToken = addJSDocComment(parseTokenNode<EndOfFileToken>());

const sourceFile = createSourceFile(fileName, languageVersion, scriptKind, isDeclarationFile, statements, endOfFileToken);
sourceFile.flags |= sourceFlags;
const sourceFile = createSourceFile(fileName, languageVersion, scriptKind, isDeclarationFile, statements, endOfFileToken, sourceFlags);

// A member of ReadonlyArray<T> isn't assignable to a member of T[] (and prevents a direct cast) - but this is where we set up those members so they can be readonly in the future
processCommentPragmas(sourceFile as {} as PragmaContext, sourceText);
Expand Down Expand Up @@ -994,10 +992,10 @@ namespace ts {
}
}

function createSourceFile(fileName: string, languageVersion: ScriptTarget, scriptKind: ScriptKind, isDeclarationFile: boolean, statements: readonly Statement[], endOfFileToken: EndOfFileToken): SourceFile {
function createSourceFile(fileName: string, languageVersion: ScriptTarget, scriptKind: ScriptKind, isDeclarationFile: boolean, statements: readonly Statement[], endOfFileToken: EndOfFileToken, flags: NodeFlags): SourceFile {
// code from createNode is inlined here so createNode won't have to deal with special case of creating source files
// this is quite rare comparing to other nodes and createNode should be as fast as possible
const sourceFile = factory.createSourceFile(statements, endOfFileToken);
const sourceFile = factory.createSourceFile(statements, endOfFileToken, flags);
sourceFile.pos = 0;
sourceFile.end = sourceText.length;
sourceFile.text = sourceText;
Expand Down Expand Up @@ -1411,15 +1409,15 @@ namespace ts {
node.end = end === undefined ? scanner.getStartPos() : end;

if (contextFlags) {
node.flags |= contextFlags;
(node as Mutable<T>).flags |= contextFlags;
}

// Keep track on the node if we encountered an error while parsing it. If we did, then
// we cannot reuse the node incrementally. Once we've marked this node, clear out the
// flag so that we don't mark any subsequent nodes.
if (parseErrorBeforeNextFinishedNode) {
parseErrorBeforeNextFinishedNode = false;
node.flags |= NodeFlags.ThisNodeHasError;
(node as Mutable<T>).flags |= NodeFlags.ThisNodeHasError;
}

return node;
Expand Down Expand Up @@ -3040,7 +3038,7 @@ namespace ts {
const node = factory.createOptionalTypeNode(type.type);
node.pos = type.pos;
node.end = type.end;
node.flags = type.flags;
(node as Mutable<Node>).flags = type.flags;
return node;
}
return type;
Expand Down Expand Up @@ -4783,7 +4781,7 @@ namespace ts {
parseTemplateExpression()
);
if (questionDotToken || tag.flags & NodeFlags.OptionalChain) {
tagExpression.flags |= NodeFlags.OptionalChain;
(tagExpression as Mutable<Node>).flags |= NodeFlags.OptionalChain;
}
factory.trackExtraneousChildNode(tagExpression, tagExpression.questionDotToken = questionDotToken);
return finishNode(tagExpression, pos);
Expand Down Expand Up @@ -5019,7 +5017,7 @@ namespace ts {
// CoverInitializedName[Yield] :
// IdentifierReference[?Yield] Initializer[In, ?Yield]
// this is necessary because ObjectLiteral productions are also used to cover grammar for ObjectAssignmentPattern
let node: ShorthandPropertyAssignment | PropertyAssignment;
let node: Mutable<ShorthandPropertyAssignment | PropertyAssignment>;
const isShorthandPropertyAssignment = tokenIsIdentifier && (token() !== SyntaxKind.ColonToken);
if (isShorthandPropertyAssignment) {
const equalsToken = parseOptionalToken(SyntaxKind.EqualsToken);
Expand Down Expand Up @@ -5323,13 +5321,15 @@ namespace ts {
// ThrowStatement[Yield] :
// throw [no LineTerminator here]Expression[In, ?Yield];

const pos = getNodePos();
parseExpected(SyntaxKind.ThrowKeyword);

// Because of automatic semicolon insertion, we need to report error if this
// throw could be terminated with a semicolon. Note: we can't call 'parseExpression'
// directly as that might consume an expression on the following line.
// We just return 'undefined' in that case. The actual error will be reported in the
// grammar walker.
const pos = getNodePos();
parseExpected(SyntaxKind.ThrowKeyword);
// TODO(rbuckton): Should we use `createMissingNode` here instead?
const expression = scanner.hasPrecedingLineBreak() ? undefined : allowInAnd(parseExpression);
parseSemicolon();
return finishNode(factory.createThrow(expression!), pos);
Expand Down Expand Up @@ -5667,7 +5667,7 @@ namespace ts {
const modifiers = parseModifiers();
if (isAmbient) {
for (const m of modifiers!) {
m.flags |= NodeFlags.Ambient;
(m as Mutable<Node>).flags |= NodeFlags.Ambient;
}
return doInsideOfContext(NodeFlags.Ambient, () => parseDeclarationWorker(pos, hasJSDoc, decorators, modifiers));
}
Expand Down Expand Up @@ -5722,7 +5722,7 @@ namespace ts {
if (decorators || modifiers) {
// We reached this point because we encountered decorators and/or modifiers and assumed a declaration
// would follow. For recovery and error reporting purposes, return an incomplete declaration.
const missing = createMissingNode<Statement>(SyntaxKind.MissingDeclaration, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected);
const missing = createMissingNode<MissingDeclaration>(SyntaxKind.MissingDeclaration, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected);
missing.pos = pos;
missing.decorators = decorators;
missing.modifiers = modifiers;
Expand Down Expand Up @@ -6004,7 +6004,7 @@ namespace ts {
: factory.createSetAccessorDeclaration(decorators, modifiers, name, parameters, body);
// Keep track of `typeParameters` (for both) and `type` (for setters) if they were parsed those indicate grammar errors
if (typeParameters) factory.trackExtraneousChildNodes(node, node.typeParameters = typeParameters);
if (type && kind === SyntaxKind.SetAccessor) factory.trackExtraneousChildNode(node, node.type = type);
if (type && node.kind === SyntaxKind.SetAccessor) factory.trackExtraneousChildNode(node, node.type = type);
return withJSDoc(finishNode(node, pos), hasJSDoc);
}

Expand Down Expand Up @@ -6182,7 +6182,7 @@ namespace ts {
const isAmbient = some(modifiers, isDeclareModifier);
if (isAmbient) {
for (const m of modifiers!) {
m.flags |= NodeFlags.Ambient;
(m as Mutable<Node>).flags |= NodeFlags.Ambient;
}
return doInsideOfContext(NodeFlags.Ambient, () => parsePropertyOrMethodDeclaration(pos, hasJSDoc, decorators, modifiers));
}
Expand Down Expand Up @@ -6690,7 +6690,7 @@ namespace ts {
currentToken = scanner.scan();
const jsDocTypeExpression = parseJSDocTypeExpression();

const sourceFile = createSourceFile("file.js", ScriptTarget.Latest, ScriptKind.JS, /*isDeclarationFile*/ false, [], factory.createToken(SyntaxKind.EndOfFileToken));
const sourceFile = createSourceFile("file.js", ScriptTarget.Latest, ScriptKind.JS, /*isDeclarationFile*/ false, [], factory.createToken(SyntaxKind.EndOfFileToken), NodeFlags.None);
const diagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile);
if (jsDocDiagnostics) {
sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile);
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/transformers/classFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ namespace ts {
factory.createConstructorDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
parameters,
parameters ?? [],
body
),
constructor || node
Expand Down Expand Up @@ -423,7 +423,7 @@ namespace ts {
? factory.updateComputedPropertyName(property.name, factory.getGeneratedNameForNode(property.name))
: property.name;

const initializer = property.initializer || emitAssignment ? visitNode(property.initializer, visitor, isExpression) : factory.createVoidZero();
const initializer = visitNode(property.initializer, visitor, isExpression) ?? factory.createVoidZero();
if (emitAssignment) {
const memberAccess = createMemberAccessForPropertyName(factory, receiver, propertyName, /*location*/ propertyName);
return factory.createAssignment(memberAccess, initializer);
Expand Down
12 changes: 5 additions & 7 deletions src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1050,16 +1050,14 @@ namespace ts {
}

function stripExportModifiers(statement: Statement): Statement {
if (isImportEqualsDeclaration(statement) || hasModifier(statement, ModifierFlags.Default)) {
if (isImportEqualsDeclaration(statement) || hasModifier(statement, ModifierFlags.Default) || !canHaveModifiers(statement)) {
// `export import` statements should remain as-is, as imports are _not_ implicitly exported in an ambient namespace
// Likewise, `export default` classes and the like and just be `default`, so we preserve their `export` modifiers, too
return statement;
}
// TODO(rbuckton): Does this need to be parented?
const clone = setParent(setTextRange(factory.cloneNode(statement), statement), statement.parent);

const modifiers = factory.createModifiersFromModifierFlags(getModifierFlags(statement) & (ModifierFlags.All ^ ModifierFlags.Export));
clone.modifiers = modifiers.length ? factory.createNodeArray(modifiers) : undefined;
return clone;
return factory.updateModifiers(statement, modifiers);
}

function transformTopLevelDeclaration(input: LateVisibilityPaintedStatement) {
Expand Down Expand Up @@ -1126,8 +1124,8 @@ namespace ts {
));
if (clean && resolver.isExpandoFunctionDeclaration(input)) {
const props = resolver.getPropertiesOfContainerFunction(input);
const fakespace = factory.createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, clean.name || factory.createIdentifier("_default"), factory.createModuleBlock([]), NodeFlags.Namespace);
fakespace.flags ^= NodeFlags.Synthesized; // unset synthesized so it is usable as an enclosing declaration
// Use parseNodeFactory so it is usable as an enclosing declaration
const fakespace = parseNodeFactory.createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, clean.name || factory.createIdentifier("_default"), factory.createModuleBlock([]), NodeFlags.Namespace);
fakespace.parent = enclosingDeclaration as SourceFile | NamespaceDeclaration;
fakespace.locals = createSymbolTable(props);
fakespace.symbol = props[0].parent!;
Expand Down
1 change: 0 additions & 1 deletion src/compiler/transformers/destructuring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,6 @@ namespace ts {
}
else if (isStringOrNumericLiteralLike(propertyName)) {
const argumentExpression = factory.cloneNode(propertyName);
argumentExpression.text = argumentExpression.text;
return flattenContext.context.factory.createElementAccess(value, argumentExpression);
}
else {
Expand Down
Loading