Skip to content

Fix crash in extract type and generate get/set refactors #52803

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 2 commits into from
Feb 16, 2023
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
1 change: 0 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46927,7 +46927,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.PropertyAssignment:
case SyntaxKind.ShorthandPropertyAssignment:
case SyntaxKind.NamespaceExportDeclaration:
case SyntaxKind.FunctionType:
case SyntaxKind.MissingDeclaration:
return find(node.modifiers, isModifier);
default:
Expand Down
1 change: 0 additions & 1 deletion src/compiler/factory/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1151,7 +1151,6 @@ export function canHaveIllegalModifiers(node: Node): node is HasIllegalModifiers
return kind === SyntaxKind.ClassStaticBlockDeclaration
|| kind === SyntaxKind.PropertyAssignment
|| kind === SyntaxKind.ShorthandPropertyAssignment
|| kind === SyntaxKind.FunctionType
|| kind === SyntaxKind.MissingDeclaration
|| kind === SyntaxKind.NamespaceExportDeclaration;
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4359,13 +4359,13 @@ namespace Parser {
const hasJSDoc = hasPrecedingJSDocComment();
const modifiers = parseModifiersForConstructorType();
const isConstructorType = parseOptional(SyntaxKind.NewKeyword);
Debug.assert(!modifiers || isConstructorType, "Per isStartOfFunctionOrConstructorType, a function type cannot have modifiers.");
const typeParameters = parseTypeParameters();
const parameters = parseParameters(SignatureFlags.Type);
const type = parseReturnType(SyntaxKind.EqualsGreaterThanToken, /*isType*/ false);
const node = isConstructorType
? factory.createConstructorTypeNode(modifiers, typeParameters, parameters, type)
: factory.createFunctionTypeNode(typeParameters, parameters, type);
if (!isConstructorType) (node as Mutable<FunctionTypeNode>).modifiers = modifiers;
return withJSDoc(finishNode(node, pos), hasJSDoc);
}

Expand Down
29 changes: 14 additions & 15 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1362,7 +1362,6 @@ export type HasIllegalModifiers =
| PropertyAssignment
| ShorthandPropertyAssignment
| MissingDeclaration
| FunctionTypeNode
| NamespaceExportDeclaration
;

Expand Down Expand Up @@ -1801,7 +1800,7 @@ export interface TypeParameterDeclaration extends NamedDeclaration, JSDocContain
readonly constraint?: TypeNode;
readonly default?: TypeNode;

// For error recovery purposes.
// For error recovery purposes (see `isGrammarError` in utilities.ts).
expression?: Expression;
}

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

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

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

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

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

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

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

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

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

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

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

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

Expand Down Expand Up @@ -2190,8 +2189,8 @@ export interface FunctionOrConstructorTypeNodeBase extends TypeNode, SignatureDe
export interface FunctionTypeNode extends FunctionOrConstructorTypeNodeBase, LocalsContainer {
readonly kind: SyntaxKind.FunctionType;

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

export interface ConstructorTypeNode extends FunctionOrConstructorTypeNodeBase, LocalsContainer {
Expand Down Expand Up @@ -3723,7 +3722,7 @@ export interface NamespaceExportDeclaration extends DeclarationStatement, JSDocC
readonly kind: SyntaxKind.NamespaceExportDeclaration;
readonly name: Identifier;

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

Expand Down
27 changes: 27 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ import {
isArray,
isArrayLiteralExpression,
isArrowFunction,
isAutoAccessorPropertyDeclaration,
isBigIntLiteral,
isBinaryExpression,
isBindingElement,
Expand Down Expand Up @@ -300,6 +301,7 @@ import {
isMetaProperty,
isMethodDeclaration,
isMethodOrAccessor,
isModifierLike,
isModuleDeclaration,
isNamedDeclaration,
isNamespaceExport,
Expand Down Expand Up @@ -333,6 +335,7 @@ import {
isTypeElement,
isTypeLiteralNode,
isTypeNode,
isTypeParameterDeclaration,
isTypeReferenceNode,
isVariableDeclaration,
isVariableStatement,
Expand Down Expand Up @@ -964,6 +967,30 @@ export function nodeIsPresent(node: Node | undefined): boolean {
return !nodeIsMissing(node);
}

/**
* Tests whether `child` is a grammar error on `parent`.
* @internal
*/
export function isGrammarError(parent: Node, child: Node | NodeArray<Node>) {
if (isTypeParameterDeclaration(parent)) return child === parent.expression;
if (isClassStaticBlockDeclaration(parent)) return child === parent.modifiers;
if (isPropertySignature(parent)) return child === parent.initializer;
if (isPropertyDeclaration(parent)) return child === parent.questionToken && isAutoAccessorPropertyDeclaration(parent);
if (isPropertyAssignment(parent)) return child === parent.modifiers || child === parent.questionToken || child === parent.exclamationToken || isGrammarErrorElement(parent.modifiers, child, isModifierLike);
if (isShorthandPropertyAssignment(parent)) return child === parent.equalsToken || child === parent.modifiers || child === parent.questionToken || child === parent.exclamationToken || isGrammarErrorElement(parent.modifiers, child, isModifierLike);
if (isMethodDeclaration(parent)) return child === parent.exclamationToken;
if (isConstructorDeclaration(parent)) return child === parent.typeParameters || child === parent.type || isGrammarErrorElement(parent.typeParameters, child, isTypeParameterDeclaration);
if (isGetAccessorDeclaration(parent)) return child === parent.typeParameters || isGrammarErrorElement(parent.typeParameters, child, isTypeParameterDeclaration);
if (isSetAccessorDeclaration(parent)) return child === parent.typeParameters || child === parent.type || isGrammarErrorElement(parent.typeParameters, child, isTypeParameterDeclaration);
if (isNamespaceExportDeclaration(parent)) return child === parent.modifiers || isGrammarErrorElement(parent.modifiers, child, isModifierLike);
return false;
}

function isGrammarErrorElement<T extends Node>(nodeArray: NodeArray<T> | undefined, child: Node | NodeArray<Node>, isElement: (node: Node) => node is T) {
if (!nodeArray || isArray(child) || !isElement(child)) return false;
return contains(nodeArray, child);
}

function insertStatementsAfterPrologue<T extends Statement>(to: T[], from: readonly T[] | undefined, isPrologueDirective: (node: Node) => boolean): T[] {
if (from === undefined || from.length === 0) return to;
let statementIndex = 0;
Expand Down
10 changes: 9 additions & 1 deletion src/services/codefixes/generateAccessors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
isWriteAccess,
ModifierFlags,
ModifierLike,
Mutable,
Node,
nodeOverlapsWithStartEnd,
ObjectLiteralExpression,
Expand Down Expand Up @@ -258,7 +259,14 @@ function updatePropertyDeclaration(changeTracker: textChanges.ChangeTracker, fil
}

function updatePropertyAssignmentDeclaration(changeTracker: textChanges.ChangeTracker, file: SourceFile, declaration: PropertyAssignment, fieldName: AcceptedNameType) {
const assignment = factory.updatePropertyAssignment(declaration, fieldName, declaration.initializer);
let assignment = factory.updatePropertyAssignment(declaration, fieldName, declaration.initializer);
// Remove grammar errors from assignment
if (assignment.modifiers || assignment.questionToken || assignment.exclamationToken) {
if (assignment === declaration) assignment = factory.cloneNode(assignment);
(assignment as Mutable<PropertyAssignment>).modifiers = undefined;
(assignment as Mutable<PropertyAssignment>).questionToken = undefined;
(assignment as Mutable<PropertyAssignment>).exclamationToken = undefined;
Comment on lines +266 to +268
Copy link
Member

Choose a reason for hiding this comment

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

Is this because update doesn't necessarily create a new node, but the result should still omit error recovery children?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, update only conditionally replaces the node.

}
changeTracker.replacePropertyAssignment(file, declaration, assignment);
}

Expand Down
3 changes: 2 additions & 1 deletion src/services/formatting/formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
InterfaceDeclaration,
isComment,
isDecorator,
isGrammarError,
isJSDoc,
isLineBreak,
isModifier,
Expand Down Expand Up @@ -863,7 +864,7 @@ function formatSpanWorker(
// if child node is a token, it does not impact indentation, proceed it using parent indentation scope rules
const tokenInfo = formattingScanner.readTokenInfo(child);
// JSX text shouldn't affect indenting
if (child.kind !== SyntaxKind.JsxText) {
if (child.kind !== SyntaxKind.JsxText && !isGrammarError(parent, child)) {
Debug.assert(tokenInfo.token.end === child.end, "Token end is child end");
consumeTokenAndAdvanceScanner(tokenInfo, node, parentDynamicIndentation, child);
return inheritedIndentation;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// <reference path='fourslash.ts' />

//// const foo = {
//// /*a*/async a: 1/*b*/
//// }

goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Generate 'get' and 'set' accessors",
actionName: "Generate 'get' and 'set' accessors",
actionDescription: "Generate 'get' and 'set' accessors",
newContent: `const foo = {
/*RENAME*/_a: 1,
get a() {
return this._a;
},
set a(value) {
this._a = value;
},
}`,
});
17 changes: 17 additions & 0 deletions tests/cases/fourslash/refactorExtractTypeRemoveGrammarError1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/// <reference path='fourslash.ts' />

// @Filename: a.ts
////type Foo = /*a*/{ x: string = a }/*b*/

goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Extract type",
actionName: "Extract to type alias",
actionDescription: "Extract to type alias",
newContent:
`type /*RENAME*/NewType = {
x: string;
};

type Foo = NewType`,
});
17 changes: 17 additions & 0 deletions tests/cases/fourslash/refactorExtractTypeRemoveGrammarError2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/// <reference path='fourslash.ts' />

// @Filename: a.ts
////type Foo<T extends /*a*/{ x: string = a }/*b*/> = T

goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Extract type",
actionName: "Extract to type alias",
actionDescription: "Extract to type alias",
newContent:
`type /*RENAME*/NewType = {
x: string;
};

type Foo<T extends NewType> = T`,
});