Skip to content

Commit 602efc5

Browse files
committed
Make most Node properties read-only
1 parent 13a8962 commit 602efc5

29 files changed

+2541
-2142
lines changed

Diff for: src/compat/deprecations.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1928,7 +1928,11 @@ namespace ts {
19281928
});
19291929

19301930
/**
1931-
* Creates a shallow, memberwise clone of a node for mutation.
1931+
* Creates a shallow, memberwise clone of a node ~for mutation~ with its `pos`, `end`, and `parent` set.
1932+
*
1933+
* NOTE: It is unsafe to change any properties of a `Node` that relate to its AST children, as those changes won't be
1934+
* captured with respect to transformations.
1935+
*
19321936
* @deprecated Use `factory.cloneNode` instead and set `pos`, `end`, and `parent` as needed.
19331937
*/
19341938
export function getMutableClone<T extends Node>(node: T): T {

Diff for: src/compiler/binder.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ namespace ts {
584584
// All container nodes are kept on a linked list in declaration order. This list is used by
585585
// the getLocalNameOfContainer function in the type checker to validate that the local name
586586
// used for a container is unique.
587-
function bindContainer(node: Node, containerFlags: ContainerFlags) {
587+
function bindContainer(node: Mutable<Node>, containerFlags: ContainerFlags) {
588588
// Before we recurse into a node's children, we first save the existing parent, container
589589
// and block-container. Then after we pop out of processing the children, we restore
590590
// these saved values.
@@ -1757,7 +1757,7 @@ namespace ts {
17571757
return !!body && body.statements.some(s => isExportDeclaration(s) || isExportAssignment(s));
17581758
}
17591759

1760-
function setExportContextFlag(node: ModuleDeclaration | SourceFile) {
1760+
function setExportContextFlag(node: Mutable<ModuleDeclaration | SourceFile>) {
17611761
// A declaration source file or ambient module declaration that contains no export declarations (but possibly regular
17621762
// declarations with export modifiers) is an export context in which declarations are implicitly exported.
17631763
if (node.flags & NodeFlags.Ambient && !hasExportDeclarations(node)) {

Diff for: src/compiler/checker.ts

+185-101
Large diffs are not rendered by default.

Diff for: src/compiler/core.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -719,10 +719,18 @@ namespace ts {
719719
return [...array1, ...array2];
720720
}
721721

722+
function selectIndex(_: unknown, i: number) {
723+
return i;
724+
}
725+
726+
export function indicesOf(array: readonly unknown[]): number[] {
727+
return array.map(selectIndex);
728+
}
729+
722730
function deduplicateRelational<T>(array: readonly T[], equalityComparer: EqualityComparer<T>, comparer: Comparer<T>) {
723731
// Perform a stable sort of the array. This ensures the first entry in a list of
724732
// duplicates remains the first entry in the result.
725-
const indices = array.map((_, i) => i);
733+
const indices = indicesOf(array);
726734
stableSortIndices(array, indices, comparer);
727735

728736
let last = array[indices[0]];
@@ -1028,7 +1036,7 @@ namespace ts {
10281036
* Stable sort of an array. Elements equal to each other maintain their relative position in the array.
10291037
*/
10301038
export function stableSort<T>(array: readonly T[], comparer: Comparer<T>): SortedReadonlyArray<T> {
1031-
const indices = array.map((_, i) => i);
1039+
const indices = indicesOf(array);
10321040
stableSortIndices(array, indices, comparer);
10331041
return indices.map(i => array[i]) as SortedArray<T> as SortedReadonlyArray<T>;
10341042
}

Diff for: src/compiler/emitter.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ namespace ts {
685685
return statement;
686686
});
687687
const eofToken = factory.createToken(SyntaxKind.EndOfFileToken);
688-
const sourceFile = factory.createSourceFile(statements ?? [], eofToken);
688+
const sourceFile = factory.createSourceFile(statements ?? [], eofToken, NodeFlags.None);
689689
sourceFile.fileName = getRelativePathFromDirectory(
690690
host.getCurrentDirectory(),
691691
getNormalizedAbsolutePath(fileName, buildInfoDirectory),

Diff for: src/compiler/factory/nodeFactory.ts

+78-40
Large diffs are not rendered by default.

Diff for: src/compiler/factory/utilities.ts

+28-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ namespace ts {
3737
function createJsxFactoryExpressionFromEntityName(factory: NodeFactory, jsxFactory: EntityName, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression {
3838
if (isQualifiedName(jsxFactory)) {
3939
const left = createJsxFactoryExpressionFromEntityName(factory, jsxFactory.left, parent);
40-
const right = factory.createIdentifier(idText(jsxFactory.right));
40+
const right = factory.createIdentifier(idText(jsxFactory.right)) as Mutable<Identifier>;
4141
right.escapedText = jsxFactory.right.escapedText;
4242
return factory.createPropertyAccess(left, right);
4343
}
@@ -780,4 +780,31 @@ namespace ts {
780780
return <readonly BindingOrAssignmentElement[]>name.properties;
781781
}
782782
}
783+
784+
export function canHaveModifiers(node: Node): node is HasModifiers {
785+
const kind = node.kind;
786+
return kind === SyntaxKind.Parameter
787+
|| kind === SyntaxKind.PropertySignature
788+
|| kind === SyntaxKind.PropertyDeclaration
789+
|| kind === SyntaxKind.MethodSignature
790+
|| kind === SyntaxKind.MethodDeclaration
791+
|| kind === SyntaxKind.Constructor
792+
|| kind === SyntaxKind.GetAccessor
793+
|| kind === SyntaxKind.SetAccessor
794+
|| kind === SyntaxKind.IndexSignature
795+
|| kind === SyntaxKind.FunctionExpression
796+
|| kind === SyntaxKind.ArrowFunction
797+
|| kind === SyntaxKind.ClassExpression
798+
|| kind === SyntaxKind.VariableStatement
799+
|| kind === SyntaxKind.FunctionDeclaration
800+
|| kind === SyntaxKind.ClassDeclaration
801+
|| kind === SyntaxKind.InterfaceDeclaration
802+
|| kind === SyntaxKind.TypeAliasDeclaration
803+
|| kind === SyntaxKind.EnumDeclaration
804+
|| kind === SyntaxKind.ModuleDeclaration
805+
|| kind === SyntaxKind.ImportEqualsDeclaration
806+
|| kind === SyntaxKind.ImportDeclaration
807+
|| kind === SyntaxKind.ExportAssignment
808+
|| kind === SyntaxKind.ExportDeclaration;
809+
}
783810
}

Diff for: src/compiler/parser.ts

+19-19
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ namespace ts {
568568
const newSourceFile = IncrementalParser.updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks);
569569
// 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.
570570
// We will manually port the flag to the new source file.
571-
newSourceFile.flags |= (sourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags);
571+
(newSourceFile as Mutable<SourceFile>).flags |= (sourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags);
572572
return newSourceFile;
573573
}
574574

@@ -824,8 +824,7 @@ namespace ts {
824824
}
825825

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

830829
if (setParentNodes) {
831830
fixupParentReferences(sourceFile);
@@ -923,8 +922,7 @@ namespace ts {
923922
Debug.assert(token() === SyntaxKind.EndOfFileToken);
924923
const endOfFileToken = addJSDocComment(parseTokenNode<EndOfFileToken>());
925924

926-
const sourceFile = createSourceFile(fileName, languageVersion, scriptKind, isDeclarationFile, statements, endOfFileToken);
927-
sourceFile.flags |= sourceFlags;
925+
const sourceFile = createSourceFile(fileName, languageVersion, scriptKind, isDeclarationFile, statements, endOfFileToken, sourceFlags);
928926

929927
// 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
930928
processCommentPragmas(sourceFile as {} as PragmaContext, sourceText);
@@ -994,10 +992,10 @@ namespace ts {
994992
}
995993
}
996994

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

14131411
if (contextFlags) {
1414-
node.flags |= contextFlags;
1412+
(node as Mutable<T>).flags |= contextFlags;
14151413
}
14161414

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

14251423
return node;
@@ -3040,7 +3038,7 @@ namespace ts {
30403038
const node = factory.createOptionalTypeNode(type.type);
30413039
node.pos = type.pos;
30423040
node.end = type.end;
3043-
node.flags = type.flags;
3041+
(node as Mutable<Node>).flags = type.flags;
30443042
return node;
30453043
}
30463044
return type;
@@ -4783,7 +4781,7 @@ namespace ts {
47834781
parseTemplateExpression()
47844782
);
47854783
if (questionDotToken || tag.flags & NodeFlags.OptionalChain) {
4786-
tagExpression.flags |= NodeFlags.OptionalChain;
4784+
(tagExpression as Mutable<Node>).flags |= NodeFlags.OptionalChain;
47874785
}
47884786
factory.trackExtraneousChildNode(tagExpression, tagExpression.questionDotToken = questionDotToken);
47894787
return finishNode(tagExpression, pos);
@@ -5019,7 +5017,7 @@ namespace ts {
50195017
// CoverInitializedName[Yield] :
50205018
// IdentifierReference[?Yield] Initializer[In, ?Yield]
50215019
// this is necessary because ObjectLiteral productions are also used to cover grammar for ObjectAssignmentPattern
5022-
let node: ShorthandPropertyAssignment | PropertyAssignment;
5020+
let node: Mutable<ShorthandPropertyAssignment | PropertyAssignment>;
50235021
const isShorthandPropertyAssignment = tokenIsIdentifier && (token() !== SyntaxKind.ColonToken);
50245022
if (isShorthandPropertyAssignment) {
50255023
const equalsToken = parseOptionalToken(SyntaxKind.EqualsToken);
@@ -5323,13 +5321,15 @@ namespace ts {
53235321
// ThrowStatement[Yield] :
53245322
// throw [no LineTerminator here]Expression[In, ?Yield];
53255323

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

@@ -6182,7 +6182,7 @@ namespace ts {
61826182
const isAmbient = some(modifiers, isDeclareModifier);
61836183
if (isAmbient) {
61846184
for (const m of modifiers!) {
6185-
m.flags |= NodeFlags.Ambient;
6185+
(m as Mutable<Node>).flags |= NodeFlags.Ambient;
61866186
}
61876187
return doInsideOfContext(NodeFlags.Ambient, () => parsePropertyOrMethodDeclaration(pos, hasJSDoc, decorators, modifiers));
61886188
}
@@ -6690,7 +6690,7 @@ namespace ts {
66906690
currentToken = scanner.scan();
66916691
const jsDocTypeExpression = parseJSDocTypeExpression();
66926692

6693-
const sourceFile = createSourceFile("file.js", ScriptTarget.Latest, ScriptKind.JS, /*isDeclarationFile*/ false, [], factory.createToken(SyntaxKind.EndOfFileToken));
6693+
const sourceFile = createSourceFile("file.js", ScriptTarget.Latest, ScriptKind.JS, /*isDeclarationFile*/ false, [], factory.createToken(SyntaxKind.EndOfFileToken), NodeFlags.None);
66946694
const diagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile);
66956695
if (jsDocDiagnostics) {
66966696
sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile);

Diff for: src/compiler/transformers/classFields.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ namespace ts {
285285
factory.createConstructorDeclaration(
286286
/*decorators*/ undefined,
287287
/*modifiers*/ undefined,
288-
parameters,
288+
parameters ?? [],
289289
body
290290
),
291291
constructor || node
@@ -423,7 +423,7 @@ namespace ts {
423423
? factory.updateComputedPropertyName(property.name, factory.getGeneratedNameForNode(property.name))
424424
: property.name;
425425

426-
const initializer = property.initializer || emitAssignment ? visitNode(property.initializer, visitor, isExpression) : factory.createVoidZero();
426+
const initializer = visitNode(property.initializer, visitor, isExpression) ?? factory.createVoidZero();
427427
if (emitAssignment) {
428428
const memberAccess = createMemberAccessForPropertyName(factory, receiver, propertyName, /*location*/ propertyName);
429429
return factory.createAssignment(memberAccess, initializer);

Diff for: src/compiler/transformers/declarations.ts

+5-7
Original file line numberDiff line numberDiff line change
@@ -1050,16 +1050,14 @@ namespace ts {
10501050
}
10511051

10521052
function stripExportModifiers(statement: Statement): Statement {
1053-
if (isImportEqualsDeclaration(statement) || hasModifier(statement, ModifierFlags.Default)) {
1053+
if (isImportEqualsDeclaration(statement) || hasModifier(statement, ModifierFlags.Default) || !canHaveModifiers(statement)) {
10541054
// `export import` statements should remain as-is, as imports are _not_ implicitly exported in an ambient namespace
10551055
// Likewise, `export default` classes and the like and just be `default`, so we preserve their `export` modifiers, too
10561056
return statement;
10571057
}
1058-
// TODO(rbuckton): Does this need to be parented?
1059-
const clone = setParent(setTextRange(factory.cloneNode(statement), statement), statement.parent);
1058+
10601059
const modifiers = factory.createModifiersFromModifierFlags(getModifierFlags(statement) & (ModifierFlags.All ^ ModifierFlags.Export));
1061-
clone.modifiers = modifiers.length ? factory.createNodeArray(modifiers) : undefined;
1062-
return clone;
1060+
return factory.updateModifiers(statement, modifiers);
10631061
}
10641062

10651063
function transformTopLevelDeclaration(input: LateVisibilityPaintedStatement) {
@@ -1126,8 +1124,8 @@ namespace ts {
11261124
));
11271125
if (clean && resolver.isExpandoFunctionDeclaration(input)) {
11281126
const props = resolver.getPropertiesOfContainerFunction(input);
1129-
const fakespace = factory.createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, clean.name || factory.createIdentifier("_default"), factory.createModuleBlock([]), NodeFlags.Namespace);
1130-
fakespace.flags ^= NodeFlags.Synthesized; // unset synthesized so it is usable as an enclosing declaration
1127+
// Use parseNodeFactory so it is usable as an enclosing declaration
1128+
const fakespace = parseNodeFactory.createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, clean.name || factory.createIdentifier("_default"), factory.createModuleBlock([]), NodeFlags.Namespace);
11311129
fakespace.parent = enclosingDeclaration as SourceFile | NamespaceDeclaration;
11321130
fakespace.locals = createSymbolTable(props);
11331131
fakespace.symbol = props[0].parent!;

Diff for: src/compiler/transformers/destructuring.ts

-1
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,6 @@ namespace ts {
467467
}
468468
else if (isStringOrNumericLiteralLike(propertyName)) {
469469
const argumentExpression = factory.cloneNode(propertyName);
470-
argumentExpression.text = argumentExpression.text;
471470
return flattenContext.context.factory.createElementAccess(value, argumentExpression);
472471
}
473472
else {

0 commit comments

Comments
 (0)