Skip to content

Commit f30371a

Browse files
committed
Fix crash in extract type and generate get/set refactors
1 parent 2c67c8f commit f30371a

10 files changed

+110
-20
lines changed

src/compiler/checker.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46927,7 +46927,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4692746927
case SyntaxKind.PropertyAssignment:
4692846928
case SyntaxKind.ShorthandPropertyAssignment:
4692946929
case SyntaxKind.NamespaceExportDeclaration:
46930-
case SyntaxKind.FunctionType:
4693146930
case SyntaxKind.MissingDeclaration:
4693246931
return find(node.modifiers, isModifier);
4693346932
default:

src/compiler/factory/utilities.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1151,7 +1151,6 @@ export function canHaveIllegalModifiers(node: Node): node is HasIllegalModifiers
11511151
return kind === SyntaxKind.ClassStaticBlockDeclaration
11521152
|| kind === SyntaxKind.PropertyAssignment
11531153
|| kind === SyntaxKind.ShorthandPropertyAssignment
1154-
|| kind === SyntaxKind.FunctionType
11551154
|| kind === SyntaxKind.MissingDeclaration
11561155
|| kind === SyntaxKind.NamespaceExportDeclaration;
11571156
}

src/compiler/parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4359,13 +4359,13 @@ namespace Parser {
43594359
const hasJSDoc = hasPrecedingJSDocComment();
43604360
const modifiers = parseModifiersForConstructorType();
43614361
const isConstructorType = parseOptional(SyntaxKind.NewKeyword);
4362+
Debug.assert(!modifiers || isConstructorType, "Per isStartOfFunctionOrConstructorType, a function type cannot have modifiers.");
43624363
const typeParameters = parseTypeParameters();
43634364
const parameters = parseParameters(SignatureFlags.Type);
43644365
const type = parseReturnType(SyntaxKind.EqualsGreaterThanToken, /*isType*/ false);
43654366
const node = isConstructorType
43664367
? factory.createConstructorTypeNode(modifiers, typeParameters, parameters, type)
43674368
: factory.createFunctionTypeNode(typeParameters, parameters, type);
4368-
if (!isConstructorType) (node as Mutable<FunctionTypeNode>).modifiers = modifiers;
43694369
return withJSDoc(finishNode(node, pos), hasJSDoc);
43704370
}
43714371

src/compiler/types.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,7 +1362,6 @@ export type HasIllegalModifiers =
13621362
| PropertyAssignment
13631363
| ShorthandPropertyAssignment
13641364
| MissingDeclaration
1365-
| FunctionTypeNode
13661365
| NamespaceExportDeclaration
13671366
;
13681367

@@ -1801,7 +1800,7 @@ export interface TypeParameterDeclaration extends NamedDeclaration, JSDocContain
18011800
readonly constraint?: TypeNode;
18021801
readonly default?: TypeNode;
18031802

1804-
// For error recovery purposes.
1803+
// For error recovery purposes (see `isGrammarError` in utilities.ts).
18051804
expression?: Expression;
18061805
}
18071806

@@ -1888,7 +1887,7 @@ export interface PropertySignature extends TypeElement, JSDocContainer {
18881887
readonly questionToken?: QuestionToken; // Present on optional property
18891888
readonly type?: TypeNode; // Optional type annotation
18901889

1891-
// The following properties are used only to report grammar errors
1890+
// The following properties are used only to report grammar errors (see `isGrammarError` in utilities.ts)
18921891
/** @internal */ readonly initializer?: Expression | undefined; // A property signature cannot have an initializer
18931892
}
18941893

@@ -1897,7 +1896,7 @@ export interface PropertyDeclaration extends ClassElement, JSDocContainer {
18971896
readonly parent: ClassLikeDeclaration;
18981897
readonly modifiers?: NodeArray<ModifierLike>;
18991898
readonly name: PropertyName;
1900-
readonly questionToken?: QuestionToken; // Present for use with reporting a grammar error
1899+
readonly questionToken?: QuestionToken; // Present for use with reporting a grammar error for auto-accessors (see `isGrammarError` in utilities.ts)
19011900
readonly exclamationToken?: ExclamationToken;
19021901
readonly type?: TypeNode;
19031902
readonly initializer?: Expression; // Optional initializer
@@ -1960,7 +1959,7 @@ export interface PropertyAssignment extends ObjectLiteralElement, JSDocContainer
19601959
readonly name: PropertyName;
19611960
readonly initializer: Expression;
19621961

1963-
// The following properties are used only to report grammar errors
1962+
// The following properties are used only to report grammar errors (see `isGrammarError` in utilities.ts)
19641963
/** @internal */ readonly modifiers?: NodeArray<ModifierLike> | undefined; // property assignment cannot have decorators or modifiers
19651964
/** @internal */ readonly questionToken?: QuestionToken | undefined; // property assignment cannot have a question token
19661965
/** @internal */ readonly exclamationToken?: ExclamationToken | undefined; // property assignment cannot have an exclamation token
@@ -1971,11 +1970,11 @@ export interface ShorthandPropertyAssignment extends ObjectLiteralElement, JSDoc
19711970
readonly parent: ObjectLiteralExpression;
19721971
readonly name: Identifier;
19731972
// used when ObjectLiteralExpression is used in ObjectAssignmentPattern
1974-
// it is a grammar error to appear in actual object initializer:
1973+
// it is a grammar error to appear in actual object initializer (see `isGrammarError` in utilities.ts):
19751974
readonly equalsToken?: EqualsToken;
19761975
readonly objectAssignmentInitializer?: Expression;
19771976

1978-
// The following properties are used only to report grammar errors
1977+
// The following properties are used only to report grammar errors (see `isGrammarError` in utilities.ts)
19791978
/** @internal */ readonly modifiers?: NodeArray<ModifierLike> | undefined; // shorthand property assignment cannot have decorators or modifiers
19801979
/** @internal */ readonly questionToken?: QuestionToken | undefined; // shorthand property assignment cannot have a question token
19811980
/** @internal */ readonly exclamationToken?: ExclamationToken | undefined; // shorthand property assignment cannot have an exclamation token
@@ -2076,7 +2075,7 @@ export interface MethodDeclaration extends FunctionLikeDeclarationBase, ClassEle
20762075
readonly name: PropertyName;
20772076
readonly body?: FunctionBody | undefined;
20782077

2079-
// The following properties are used only to report grammar errors
2078+
// The following properties are used only to report grammar errors (see `isGrammarError` in utilities.ts)
20802079
/** @internal */ readonly exclamationToken?: ExclamationToken | undefined; // A method cannot have an exclamation token
20812080
}
20822081

@@ -2086,7 +2085,7 @@ export interface ConstructorDeclaration extends FunctionLikeDeclarationBase, Cla
20862085
readonly modifiers?: NodeArray<ModifierLike> | undefined;
20872086
readonly body?: FunctionBody | undefined;
20882087

2089-
// The following properties are used only to report grammar errors
2088+
// The following properties are used only to report grammar errors (see `isGrammarError` in utilities.ts)
20902089
/** @internal */ readonly typeParameters?: NodeArray<TypeParameterDeclaration>; // A constructor cannot have type parameters
20912090
/** @internal */ readonly type?: TypeNode; // A constructor cannot have a return type annotation
20922091
}
@@ -2106,7 +2105,7 @@ export interface GetAccessorDeclaration extends FunctionLikeDeclarationBase, Cla
21062105
readonly name: PropertyName;
21072106
readonly body?: FunctionBody;
21082107

2109-
// The following properties are used only to report grammar errors
2108+
// The following properties are used only to report grammar errors (see `isGrammarError` in utilities.ts)
21102109
/** @internal */ readonly typeParameters?: NodeArray<TypeParameterDeclaration> | undefined; // A get accessor cannot have type parameters
21112110
}
21122111

@@ -2119,7 +2118,7 @@ export interface SetAccessorDeclaration extends FunctionLikeDeclarationBase, Cla
21192118
readonly name: PropertyName;
21202119
readonly body?: FunctionBody;
21212120

2122-
// The following properties are used only to report grammar errors
2121+
// The following properties are used only to report grammar errors (see `isGrammarError` in utilities.ts)
21232122
/** @internal */ readonly typeParameters?: NodeArray<TypeParameterDeclaration> | undefined; // A set accessor cannot have type parameters
21242123
/** @internal */ readonly type?: TypeNode | undefined; // A set accessor cannot have a return type
21252124
}
@@ -2141,7 +2140,7 @@ export interface ClassStaticBlockDeclaration extends ClassElement, JSDocContaine
21412140
/** @internal */ endFlowNode?: FlowNode;
21422141
/** @internal */ returnFlowNode?: FlowNode;
21432142

2144-
// The following properties are used only to report grammar errors
2143+
// The following properties are used only to report grammar errors (see `isGrammarError` in utilities.ts)
21452144
/** @internal */ readonly modifiers?: NodeArray<ModifierLike> | undefined;
21462145
}
21472146

@@ -2190,8 +2189,8 @@ export interface FunctionOrConstructorTypeNodeBase extends TypeNode, SignatureDe
21902189
export interface FunctionTypeNode extends FunctionOrConstructorTypeNodeBase, LocalsContainer {
21912190
readonly kind: SyntaxKind.FunctionType;
21922191

2193-
// The following properties are used only to report grammar errors
2194-
/** @internal */ readonly modifiers?: NodeArray<Modifier> | undefined;
2192+
// A function type cannot have modifiers
2193+
/** @internal */ readonly modifiers?: undefined;
21952194
}
21962195

21972196
export interface ConstructorTypeNode extends FunctionOrConstructorTypeNodeBase, LocalsContainer {
@@ -3723,7 +3722,7 @@ export interface NamespaceExportDeclaration extends DeclarationStatement, JSDocC
37233722
readonly kind: SyntaxKind.NamespaceExportDeclaration;
37243723
readonly name: Identifier;
37253724

3726-
// The following properties are used only to report grammar errors
3725+
// The following properties are used only to report grammar errors (see `isGrammarError` in utilities.ts)
37273726
/** @internal */ readonly modifiers?: NodeArray<ModifierLike> | undefined;
37283727
}
37293728

src/compiler/utilities.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ import {
234234
isArray,
235235
isArrayLiteralExpression,
236236
isArrowFunction,
237+
isAutoAccessorPropertyDeclaration,
237238
isBigIntLiteral,
238239
isBinaryExpression,
239240
isBindingElement,
@@ -265,6 +266,7 @@ import {
265266
isFunctionLike,
266267
isFunctionLikeDeclaration,
267268
isFunctionLikeOrClassStaticBlockDeclaration,
269+
isFunctionTypeNode,
268270
isGetAccessorDeclaration,
269271
isHeritageClause,
270272
isIdentifier,
@@ -300,6 +302,7 @@ import {
300302
isMetaProperty,
301303
isMethodDeclaration,
302304
isMethodOrAccessor,
305+
isModifierLike,
303306
isModuleDeclaration,
304307
isNamedDeclaration,
305308
isNamespaceExport,
@@ -333,6 +336,7 @@ import {
333336
isTypeElement,
334337
isTypeLiteralNode,
335338
isTypeNode,
339+
isTypeParameterDeclaration,
336340
isTypeReferenceNode,
337341
isVariableDeclaration,
338342
isVariableStatement,
@@ -964,6 +968,31 @@ export function nodeIsPresent(node: Node | undefined): boolean {
964968
return !nodeIsMissing(node);
965969
}
966970

971+
/**
972+
* Tests whether `child` is a grammar error on `parent`.
973+
* @internal
974+
*/
975+
export function isGrammarError(parent: Node, child: Node | NodeArray<Node>) {
976+
if (isTypeParameterDeclaration(parent)) return child === parent.expression;
977+
if (isClassStaticBlockDeclaration(parent)) return child === parent.modifiers;
978+
if (isPropertySignature(parent)) return child === parent.initializer;
979+
if (isPropertyDeclaration(parent)) return child === parent.questionToken && isAutoAccessorPropertyDeclaration(parent);
980+
if (isPropertyAssignment(parent)) return child === parent.modifiers || child === parent.questionToken || child === parent.exclamationToken || isGrammarErrorElement(parent.modifiers, child, isModifierLike);
981+
if (isShorthandPropertyAssignment(parent)) return child === parent.equalsToken || child === parent.modifiers || child === parent.questionToken || child === parent.exclamationToken || isGrammarErrorElement(parent.modifiers, child, isModifierLike);
982+
if (isMethodDeclaration(parent)) return child === parent.exclamationToken;
983+
if (isConstructorDeclaration(parent)) return child === parent.typeParameters || child === parent.type || isGrammarErrorElement(parent.typeParameters, child, isTypeParameterDeclaration);
984+
if (isGetAccessorDeclaration(parent)) return child === parent.typeParameters || isGrammarErrorElement(parent.typeParameters, child, isTypeParameterDeclaration);
985+
if (isSetAccessorDeclaration(parent)) return child === parent.typeParameters || child === parent.type || isGrammarErrorElement(parent.typeParameters, child, isTypeParameterDeclaration);
986+
if (isFunctionTypeNode(parent)) return child === parent.modifiers || isGrammarErrorElement(parent.modifiers, child, isModifierLike);
987+
if (isNamespaceExportDeclaration(parent)) return child === parent.modifiers || isGrammarErrorElement(parent.modifiers, child, isModifierLike);
988+
return false;
989+
}
990+
991+
function isGrammarErrorElement<T extends Node>(nodeArray: NodeArray<T> | undefined, child: Node | NodeArray<Node>, isElement: (node: Node) => node is T) {
992+
if (!nodeArray || isArray(child) || !isElement(child)) return false;
993+
return contains(nodeArray, child);
994+
}
995+
967996
function insertStatementsAfterPrologue<T extends Statement>(to: T[], from: readonly T[] | undefined, isPrologueDirective: (node: Node) => boolean): T[] {
968997
if (from === undefined || from.length === 0) return to;
969998
let statementIndex = 0;

src/services/codefixes/generateAccessors.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
isWriteAccess,
3838
ModifierFlags,
3939
ModifierLike,
40+
Mutable,
4041
Node,
4142
nodeOverlapsWithStartEnd,
4243
ObjectLiteralExpression,
@@ -258,7 +259,14 @@ function updatePropertyDeclaration(changeTracker: textChanges.ChangeTracker, fil
258259
}
259260

260261
function updatePropertyAssignmentDeclaration(changeTracker: textChanges.ChangeTracker, file: SourceFile, declaration: PropertyAssignment, fieldName: AcceptedNameType) {
261-
const assignment = factory.updatePropertyAssignment(declaration, fieldName, declaration.initializer);
262+
let assignment = factory.updatePropertyAssignment(declaration, fieldName, declaration.initializer);
263+
// Remove grammar errors from assignment
264+
if (assignment.modifiers || assignment.questionToken || assignment.exclamationToken) {
265+
if (assignment === declaration) assignment = factory.cloneNode(assignment);
266+
(assignment as Mutable<PropertyAssignment>).modifiers = undefined;
267+
(assignment as Mutable<PropertyAssignment>).questionToken = undefined;
268+
(assignment as Mutable<PropertyAssignment>).exclamationToken = undefined;
269+
}
262270
changeTracker.replacePropertyAssignment(file, declaration, assignment);
263271
}
264272

src/services/formatting/formatting.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
InterfaceDeclaration,
3535
isComment,
3636
isDecorator,
37+
isGrammarError,
3738
isJSDoc,
3839
isLineBreak,
3940
isModifier,
@@ -863,7 +864,7 @@ function formatSpanWorker(
863864
// if child node is a token, it does not impact indentation, proceed it using parent indentation scope rules
864865
const tokenInfo = formattingScanner.readTokenInfo(child);
865866
// JSX text shouldn't affect indenting
866-
if (child.kind !== SyntaxKind.JsxText) {
867+
if (child.kind !== SyntaxKind.JsxText && !isGrammarError(parent, child)) {
867868
Debug.assert(tokenInfo.token.end === child.end, "Token end is child end");
868869
consumeTokenAndAdvanceScanner(tokenInfo, node, parentDynamicIndentation, child);
869870
return inheritedIndentation;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// const foo = {
4+
//// /*a*/async a: 1/*b*/
5+
//// }
6+
7+
goTo.select("a", "b");
8+
edit.applyRefactor({
9+
refactorName: "Generate 'get' and 'set' accessors",
10+
actionName: "Generate 'get' and 'set' accessors",
11+
actionDescription: "Generate 'get' and 'set' accessors",
12+
newContent: `const foo = {
13+
/*RENAME*/_a: 1,
14+
get a() {
15+
return this._a;
16+
},
17+
set a(value) {
18+
this._a = value;
19+
},
20+
}`,
21+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @Filename: a.ts
4+
////type Foo = /*a*/{ x: string = a }/*b*/
5+
6+
goTo.select("a", "b");
7+
edit.applyRefactor({
8+
refactorName: "Extract type",
9+
actionName: "Extract to type alias",
10+
actionDescription: "Extract to type alias",
11+
newContent:
12+
`type /*RENAME*/NewType = {
13+
x: string;
14+
};
15+
16+
type Foo = NewType`,
17+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @Filename: a.ts
4+
////type Foo<T extends /*a*/{ x: string = a }/*b*/> = T
5+
6+
goTo.select("a", "b");
7+
edit.applyRefactor({
8+
refactorName: "Extract type",
9+
actionName: "Extract to type alias",
10+
actionDescription: "Extract to type alias",
11+
newContent:
12+
`type /*RENAME*/NewType = {
13+
x: string;
14+
};
15+
16+
type Foo<T extends NewType> = T`,
17+
});

0 commit comments

Comments
 (0)