From d7e62bb9f7d08907ef8abfd807b018faf9a2b069 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Fri, 3 Mar 2017 13:50:58 -0800 Subject: [PATCH 01/65] initial revision of infrastructure to produce text changes that uses existing node factory, formatter and printer --- Jakefile.js | 5 +- src/compiler/emitter.ts | 33 +- src/compiler/factory.ts | 4 + src/compiler/scanner.ts | 2 +- src/compiler/types.ts | 12 + src/compiler/utilities.ts | 14 +- src/compiler/visitor.ts | 150 ++--- src/harness/fourslash.ts | 11 +- src/harness/tsconfig.json | 3 +- src/harness/unittests/textChanges.ts | 520 ++++++++++++++++++ src/server/session.ts | 3 +- src/services/codeFixProvider.ts | 1 + .../fixClassSuperMustPrecedeThisAccess.ts | 17 +- .../fixConstructorForDerivedNeedSuperCall.ts | 7 +- .../fixExtendsInterfaceBecomesImplements.ts | 14 +- .../fixForgottenThisPropertyAccess.ts | 8 +- .../codefixes/unusedIdentifierFixes.ts | 133 ++--- src/services/formatting/formatting.ts | 50 +- src/services/formatting/formattingContext.ts | 2 +- src/services/formatting/formattingScanner.ts | 6 +- src/services/formatting/rulesProvider.ts | 4 + src/services/formatting/smartIndenter.ts | 87 +-- src/services/services.ts | 14 +- src/services/textChanges.ts | 475 ++++++++++++++++ src/services/tsconfig.json | 1 + src/services/types.ts | 10 +- src/services/utilities.ts | 14 +- .../reference/textChanges/deleteNode1.js | 13 + .../reference/textChanges/deleteNode2.js | 12 + .../reference/textChanges/deleteNode3.js | 14 + .../reference/textChanges/deleteNode4.js | 13 + .../reference/textChanges/deleteNode5.js | 16 + .../textChanges/deleteNodeInList1.js | 4 + .../textChanges/deleteNodeInList10.js | 10 + .../textChanges/deleteNodeInList11.js | 10 + .../textChanges/deleteNodeInList12.js | 10 + .../textChanges/deleteNodeInList13.js | 15 + .../textChanges/deleteNodeInList14.js | 15 + .../textChanges/deleteNodeInList15.js | 15 + .../textChanges/deleteNodeInList1_1.js | 4 + .../textChanges/deleteNodeInList2.js | 4 + .../textChanges/deleteNodeInList2_1.js | 4 + .../textChanges/deleteNodeInList3.js | 4 + .../textChanges/deleteNodeInList3_1.js | 4 + .../textChanges/deleteNodeInList4.js | 13 + .../textChanges/deleteNodeInList4_1.js | 17 + .../textChanges/deleteNodeInList5.js | 13 + .../textChanges/deleteNodeInList5_1.js | 16 + .../textChanges/deleteNodeInList6.js | 13 + .../textChanges/deleteNodeInList6_1.js | 16 + .../textChanges/deleteNodeInList7.js | 10 + .../textChanges/deleteNodeInList8.js | 10 + .../textChanges/deleteNodeInList9.js | 10 + .../reference/textChanges/deleteNodeRange1.js | 16 + .../reference/textChanges/deleteNodeRange2.js | 15 + .../reference/textChanges/deleteNodeRange3.js | 17 + .../reference/textChanges/deleteNodeRange4.js | 16 + .../reference/textChanges/deleteRange1.js | 15 + .../textChanges/extractMethodLike.js | 49 ++ .../reference/textChanges/insertNodeAfter1.js | 26 + .../reference/textChanges/insertNodeAfter2.js | 26 + .../insertNodeAfter3-block with newline.js | 16 + .../reference/textChanges/insertNodeAfter3.js | 14 + .../reference/textChanges/insertNodeAfter4.js | 16 + .../reference/textChanges/insertNodeAt1.js | 22 + .../reference/textChanges/insertNodeAt2.js | 20 + .../textChanges/insertNodeBefore1.js | 26 + .../textChanges/insertNodeBefore2.js | 26 + .../reference/textChanges/replaceNode1.js | 20 + .../replaceNode1NoLineBreakBefore.js | 13 + .../reference/textChanges/replaceNode2.js | 20 + .../reference/textChanges/replaceNode3.js | 21 + .../reference/textChanges/replaceNode4.js | 19 + .../reference/textChanges/replaceNode5.js | 19 + .../textChanges/replaceNodeRange1.js | 19 + .../textChanges/replaceNodeRange2.js | 19 + .../textChanges/replaceNodeRange3.js | 20 + .../textChanges/replaceNodeRange4.js | 18 + .../reference/textChanges/replaceRange.js | 19 + .../replaceRangeNoLineBreakBefore.js | 6 + .../replaceRangeWithForcedIndentation.js | 19 + .../fourslash/codeFixAddForgottenThis01.ts | 10 +- .../cases/fourslash/codeFixSuperAfterThis.ts | 10 +- tests/cases/fourslash/codeFixSuperCall.ts | 11 +- .../fourslash/unusedFunctionInNamespace1.ts | 1 - .../fourslash/unusedVariableInForLoop7FS.ts | 18 +- 86 files changed, 2208 insertions(+), 279 deletions(-) create mode 100644 src/harness/unittests/textChanges.ts create mode 100644 src/services/textChanges.ts create mode 100644 tests/baselines/reference/textChanges/deleteNode1.js create mode 100644 tests/baselines/reference/textChanges/deleteNode2.js create mode 100644 tests/baselines/reference/textChanges/deleteNode3.js create mode 100644 tests/baselines/reference/textChanges/deleteNode4.js create mode 100644 tests/baselines/reference/textChanges/deleteNode5.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList1.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList10.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList11.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList12.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList13.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList14.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList15.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList1_1.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList2.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList2_1.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList3.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList3_1.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList4.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList4_1.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList5.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList5_1.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList6.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList6_1.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList7.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList8.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeInList9.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeRange1.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeRange2.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeRange3.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeRange4.js create mode 100644 tests/baselines/reference/textChanges/deleteRange1.js create mode 100644 tests/baselines/reference/textChanges/extractMethodLike.js create mode 100644 tests/baselines/reference/textChanges/insertNodeAfter1.js create mode 100644 tests/baselines/reference/textChanges/insertNodeAfter2.js create mode 100644 tests/baselines/reference/textChanges/insertNodeAfter3-block with newline.js create mode 100644 tests/baselines/reference/textChanges/insertNodeAfter3.js create mode 100644 tests/baselines/reference/textChanges/insertNodeAfter4.js create mode 100644 tests/baselines/reference/textChanges/insertNodeAt1.js create mode 100644 tests/baselines/reference/textChanges/insertNodeAt2.js create mode 100644 tests/baselines/reference/textChanges/insertNodeBefore1.js create mode 100644 tests/baselines/reference/textChanges/insertNodeBefore2.js create mode 100644 tests/baselines/reference/textChanges/replaceNode1.js create mode 100644 tests/baselines/reference/textChanges/replaceNode1NoLineBreakBefore.js create mode 100644 tests/baselines/reference/textChanges/replaceNode2.js create mode 100644 tests/baselines/reference/textChanges/replaceNode3.js create mode 100644 tests/baselines/reference/textChanges/replaceNode4.js create mode 100644 tests/baselines/reference/textChanges/replaceNode5.js create mode 100644 tests/baselines/reference/textChanges/replaceNodeRange1.js create mode 100644 tests/baselines/reference/textChanges/replaceNodeRange2.js create mode 100644 tests/baselines/reference/textChanges/replaceNodeRange3.js create mode 100644 tests/baselines/reference/textChanges/replaceNodeRange4.js create mode 100644 tests/baselines/reference/textChanges/replaceRange.js create mode 100644 tests/baselines/reference/textChanges/replaceRangeNoLineBreakBefore.js create mode 100644 tests/baselines/reference/textChanges/replaceRangeWithForcedIndentation.js diff --git a/Jakefile.js b/Jakefile.js index f8be7c2b67..489bf62bd9 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -11,11 +11,7 @@ var ts = require("./lib/typescript"); // Variables var compilerDirectory = "src/compiler/"; -var servicesDirectory = "src/services/"; var serverDirectory = "src/server/"; -var typingsInstallerDirectory = "src/server/typingsInstaller"; -var cancellationTokenDirectory = "src/server/cancellationToken"; -var watchGuardDirectory = "src/server/watchGuard"; var harnessDirectory = "src/harness/"; var libraryDirectory = "src/lib/"; var scriptsDirectory = "scripts/"; @@ -131,6 +127,7 @@ var harnessSources = harnessCoreSources.concat([ "matchFiles.ts", "initializeTSConfig.ts", "printer.ts", + "textChanges.ts", "transform.ts", "customTransforms.ts", ].map(function (f) { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index ee224befd4..c286eb9df1 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -199,6 +199,8 @@ namespace ts { onEmitHelpers, onSetSourceFile, substituteNode, + onBeforeEmitNodeArray, + onAfterEmitNodeArray } = handlers; const newLine = getNewLineCharacter(printerOptions); @@ -631,6 +633,11 @@ namespace ts { if (isExpression(node)) { return pipelineEmitExpression(trySubstituteNode(EmitHint.Expression, node)); } + + if (isToken(node)) { + writeTokenText(kind); + return; + } } function pipelineEmitExpression(node: Node): void { @@ -1553,6 +1560,10 @@ namespace ts { emitSignatureAndBody(node, emitSignatureHead); } + function emitBlockCallback(_hint: EmitHint, body: Node): void { + emitBlockFunctionBody(body); + } + function emitSignatureAndBody(node: FunctionLikeDeclaration, emitSignatureHead: (node: SignatureDeclaration) => void) { const body = node.body; if (body) { @@ -1564,12 +1575,22 @@ namespace ts { if (getEmitFlags(node) & EmitFlags.ReuseTempVariableScope) { emitSignatureHead(node); - emitBlockFunctionBody(body); + if (onEmitNode) { + onEmitNode(EmitHint.Unspecified, body, emitBlockCallback); + } + else { + emitBlockFunctionBody(body); + } } else { pushNameGenerationScope(); emitSignatureHead(node); - emitBlockFunctionBody(body); + if (onEmitNode) { + onEmitNode(EmitHint.Unspecified, body, emitBlockCallback); + } + else { + emitBlockFunctionBody(body); + } popNameGenerationScope(); } @@ -2200,6 +2221,10 @@ namespace ts { write(getOpeningBracket(format)); } + if (onBeforeEmitNodeArray) { + onBeforeEmitNodeArray(children); + } + if (isEmpty) { // Write a line terminator if the parent node was multi-line if (format & ListFormat.MultiLine) { @@ -2315,6 +2340,10 @@ namespace ts { } } + if (onAfterEmitNodeArray) { + onAfterEmitNodeArray(children); + } + if (format & ListFormat.BracketsMask) { write(getClosingBracket(format)); } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index cfafaabebf..530b0a240c 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1099,6 +1099,10 @@ namespace ts { : node; } + export function createKeywordTypeNode(kind: KeywordTypeNode["kind"]): KeywordTypeNode { + return createSynthesizedNode(kind); + } + export function createFunctionDeclaration(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: string | Identifier | undefined, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { const node = createSynthesizedNode(SyntaxKind.FunctionDeclaration); node.decorators = asNodeArray(decorators); diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index fb91b0ff5b..b7a3f35d72 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -333,7 +333,7 @@ namespace ts { } /* @internal */ - export function getLineStarts(sourceFile: SourceFile): number[] { + export function getLineStarts(sourceFile: SourceFileLike): number[] { return sourceFile.lineMap || (sourceFile.lineMap = computeLineStarts(sourceFile.text)); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 53d8411860..90b1d2cec8 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2204,6 +2204,16 @@ name: string; } + /* @internal */ + /** + * Subset of properties from SourceFile that are used in multiple utility functions + */ + export interface SourceFileLike { + readonly text: string; + lineMap: number[]; + } + + // Source files are declarations when they are external modules. export interface SourceFile extends Declaration { kind: SyntaxKind.SourceFile; @@ -4129,6 +4139,8 @@ /*@internal*/ onEmitSourceMapOfPosition?: (pos: number) => void; /*@internal*/ onEmitHelpers?: (node: Node, writeLines: (text: string) => void) => void; /*@internal*/ onSetSourceFile?: (node: SourceFile) => void; + /*@internal*/ onBeforeEmitNodeArray?: (nodes: NodeArray) => void; + /*@internal*/ onAfterEmitNodeArray?: (nodes: NodeArray) => void; } export interface PrinterOptions { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index e107c4b669..f37048a55d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -184,7 +184,7 @@ namespace ts { return false; } - export function getStartPositionOfLine(line: number, sourceFile: SourceFile): number { + export function getStartPositionOfLine(line: number, sourceFile: SourceFileLike): number { Debug.assert(line >= 0); return getLineStarts(sourceFile)[line]; } @@ -204,7 +204,7 @@ namespace ts { return value !== undefined; } - export function getEndLinePosition(line: number, sourceFile: SourceFile): number { + export function getEndLinePosition(line: number, sourceFile: SourceFileLike): number { Debug.assert(line >= 0); const lineStarts = getLineStarts(sourceFile); @@ -255,7 +255,11 @@ namespace ts { return !nodeIsMissing(node); } - export function getTokenPosOfNode(node: Node, sourceFile?: SourceFile, includeJsDoc?: boolean): number { + export function isToken(n: Node): boolean { + return n.kind >= SyntaxKind.FirstToken && n.kind <= SyntaxKind.LastToken; + } + + export function getTokenPosOfNode(node: Node, sourceFile?: SourceFileLike, includeJsDoc?: boolean): number { // With nodes that have no width (i.e. 'Missing' nodes), we actually *don't* // want to skip trivia because this will launch us forward to the next token. if (nodeIsMissing(node)) { @@ -289,7 +293,7 @@ namespace ts { return node.kind >= SyntaxKind.FirstJSDocTagNode && node.kind <= SyntaxKind.LastJSDocTagNode; } - export function getNonDecoratorTokenPosOfNode(node: Node, sourceFile?: SourceFile): number { + export function getNonDecoratorTokenPosOfNode(node: Node, sourceFile?: SourceFileLike): number { if (nodeIsMissing(node) || !node.decorators) { return getTokenPosOfNode(node, sourceFile); } @@ -2476,7 +2480,7 @@ namespace ts { return indentStrings[1].length; } - export function createTextWriter(newLine: String): EmitTextWriter { + export function createTextWriter(newLine: string): EmitTextWriter { let output: string; let indent: number; let lineStart: boolean; diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 2d75d64612..db504847e2 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -154,9 +154,9 @@ namespace ts { * Starts a new lexical environment and visits a parameter list, suspending the lexical * environment upon completion. */ - export function visitParameterList(nodes: NodeArray, visitor: Visitor, context: TransformationContext) { + export function visitParameterList(nodes: NodeArray, visitor: Visitor, context: TransformationContext, nodesVisitor = visitNodes) { context.startLexicalEnvironment(); - const updated = visitNodes(nodes, visitor, isParameterDeclaration); + const updated = nodesVisitor(nodes, visitor, isParameterDeclaration); context.suspendLexicalEnvironment(); return updated; } @@ -204,9 +204,9 @@ namespace ts { * @param visitor The callback used to visit each child. * @param context A lexical environment context for the visitor. */ - export function visitEachChild(node: T | undefined, visitor: Visitor, context: TransformationContext): T | undefined; + export function visitEachChild(node: T | undefined, visitor: Visitor, context: TransformationContext, nodesVisitor?: typeof visitNodes): T | undefined; - export function visitEachChild(node: Node, visitor: Visitor, context: TransformationContext): Node { + export function visitEachChild(node: Node, visitor: Visitor, context: TransformationContext, nodesVisitor = visitNodes): Node { if (node === undefined) { return undefined; } @@ -243,8 +243,8 @@ namespace ts { // Signature elements case SyntaxKind.Parameter: return updateParameter(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), (node).dotDotDotToken, visitNode((node).name, visitor, isBindingName), visitNode((node).type, visitor, isTypeNode), @@ -257,55 +257,55 @@ namespace ts { // Type member case SyntaxKind.PropertyDeclaration: return updateProperty(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), visitNode((node).name, visitor, isPropertyName), visitNode((node).type, visitor, isTypeNode), visitNode((node).initializer, visitor, isExpression)); case SyntaxKind.MethodDeclaration: return updateMethod(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), (node).asteriskToken, visitNode((node).name, visitor, isPropertyName), - visitNodes((node).typeParameters, visitor, isTypeParameter), - visitParameterList((node).parameters, visitor, context), + nodesVisitor((node).typeParameters, visitor, isTypeParameter), + visitParameterList((node).parameters, visitor, context, nodesVisitor), visitNode((node).type, visitor, isTypeNode), visitFunctionBody((node).body, visitor, context)); case SyntaxKind.Constructor: return updateConstructor(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), - visitParameterList((node).parameters, visitor, context), + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), + visitParameterList((node).parameters, visitor, context, nodesVisitor), visitFunctionBody((node).body, visitor, context)); case SyntaxKind.GetAccessor: return updateGetAccessor(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), visitNode((node).name, visitor, isPropertyName), - visitParameterList((node).parameters, visitor, context), + visitParameterList((node).parameters, visitor, context, nodesVisitor), visitNode((node).type, visitor, isTypeNode), visitFunctionBody((node).body, visitor, context)); case SyntaxKind.SetAccessor: return updateSetAccessor(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), visitNode((node).name, visitor, isPropertyName), - visitParameterList((node).parameters, visitor, context), + visitParameterList((node).parameters, visitor, context, nodesVisitor), visitFunctionBody((node).body, visitor, context)); // Binding patterns case SyntaxKind.ObjectBindingPattern: return updateObjectBindingPattern(node, - visitNodes((node).elements, visitor, isBindingElement)); + nodesVisitor((node).elements, visitor, isBindingElement)); case SyntaxKind.ArrayBindingPattern: return updateArrayBindingPattern(node, - visitNodes((node).elements, visitor, isArrayBindingElement)); + nodesVisitor((node).elements, visitor, isArrayBindingElement)); case SyntaxKind.BindingElement: return updateBindingElement(node, @@ -317,11 +317,11 @@ namespace ts { // Expression case SyntaxKind.ArrayLiteralExpression: return updateArrayLiteral(node, - visitNodes((node).elements, visitor, isExpression)); + nodesVisitor((node).elements, visitor, isExpression)); case SyntaxKind.ObjectLiteralExpression: return updateObjectLiteral(node, - visitNodes((node).properties, visitor, isObjectLiteralElementLike)); + nodesVisitor((node).properties, visitor, isObjectLiteralElementLike)); case SyntaxKind.PropertyAccessExpression: return updatePropertyAccess(node, @@ -336,14 +336,14 @@ namespace ts { case SyntaxKind.CallExpression: return updateCall(node, visitNode((node).expression, visitor, isExpression), - visitNodes((node).typeArguments, visitor, isTypeNode), - visitNodes((node).arguments, visitor, isExpression)); + nodesVisitor((node).typeArguments, visitor, isTypeNode), + nodesVisitor((node).arguments, visitor, isExpression)); case SyntaxKind.NewExpression: return updateNew(node, visitNode((node).expression, visitor, isExpression), - visitNodes((node).typeArguments, visitor, isTypeNode), - visitNodes((node).arguments, visitor, isExpression)); + nodesVisitor((node).typeArguments, visitor, isTypeNode), + nodesVisitor((node).arguments, visitor, isExpression)); case SyntaxKind.TaggedTemplateExpression: return updateTaggedTemplate(node, @@ -361,19 +361,19 @@ namespace ts { case SyntaxKind.FunctionExpression: return updateFunctionExpression(node, - visitNodes((node).modifiers, visitor, isModifier), + nodesVisitor((node).modifiers, visitor, isModifier), (node).asteriskToken, visitNode((node).name, visitor, isIdentifier), - visitNodes((node).typeParameters, visitor, isTypeParameter), - visitParameterList((node).parameters, visitor, context), + nodesVisitor((node).typeParameters, visitor, isTypeParameter), + visitParameterList((node).parameters, visitor, context, nodesVisitor), visitNode((node).type, visitor, isTypeNode), visitFunctionBody((node).body, visitor, context)); case SyntaxKind.ArrowFunction: return updateArrowFunction(node, - visitNodes((node).modifiers, visitor, isModifier), - visitNodes((node).typeParameters, visitor, isTypeParameter), - visitParameterList((node).parameters, visitor, context), + nodesVisitor((node).modifiers, visitor, isModifier), + nodesVisitor((node).typeParameters, visitor, isTypeParameter), + visitParameterList((node).parameters, visitor, context, nodesVisitor), visitNode((node).type, visitor, isTypeNode), visitFunctionBody((node).body, visitor, context)); @@ -415,7 +415,7 @@ namespace ts { case SyntaxKind.TemplateExpression: return updateTemplateExpression(node, visitNode((node).head, visitor, isTemplateHead), - visitNodes((node).templateSpans, visitor, isTemplateSpan)); + nodesVisitor((node).templateSpans, visitor, isTemplateSpan)); case SyntaxKind.YieldExpression: return updateYield(node, @@ -428,15 +428,15 @@ namespace ts { case SyntaxKind.ClassExpression: return updateClassExpression(node, - visitNodes((node).modifiers, visitor, isModifier), + nodesVisitor((node).modifiers, visitor, isModifier), visitNode((node).name, visitor, isIdentifier), - visitNodes((node).typeParameters, visitor, isTypeParameter), - visitNodes((node).heritageClauses, visitor, isHeritageClause), - visitNodes((node).members, visitor, isClassElement)); + nodesVisitor((node).typeParameters, visitor, isTypeParameter), + nodesVisitor((node).heritageClauses, visitor, isHeritageClause), + nodesVisitor((node).members, visitor, isClassElement)); case SyntaxKind.ExpressionWithTypeArguments: return updateExpressionWithTypeArguments(node, - visitNodes((node).typeArguments, visitor, isTypeNode), + nodesVisitor((node).typeArguments, visitor, isTypeNode), visitNode((node).expression, visitor, isExpression)); case SyntaxKind.AsExpression: @@ -457,11 +457,11 @@ namespace ts { // Element case SyntaxKind.Block: return updateBlock(node, - visitNodes((node).statements, visitor, isStatement)); + nodesVisitor((node).statements, visitor, isStatement)); case SyntaxKind.VariableStatement: return updateVariableStatement(node, - visitNodes((node).modifiers, visitor, isModifier), + nodesVisitor((node).modifiers, visitor, isModifier), visitNode((node).declarationList, visitor, isVariableDeclarationList)); case SyntaxKind.ExpressionStatement: @@ -549,61 +549,61 @@ namespace ts { case SyntaxKind.VariableDeclarationList: return updateVariableDeclarationList(node, - visitNodes((node).declarations, visitor, isVariableDeclaration)); + nodesVisitor((node).declarations, visitor, isVariableDeclaration)); case SyntaxKind.FunctionDeclaration: return updateFunctionDeclaration(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), (node).asteriskToken, visitNode((node).name, visitor, isIdentifier), - visitNodes((node).typeParameters, visitor, isTypeParameter), - visitParameterList((node).parameters, visitor, context), + nodesVisitor((node).typeParameters, visitor, isTypeParameter), + visitParameterList((node).parameters, visitor, context, nodesVisitor), visitNode((node).type, visitor, isTypeNode), visitFunctionBody((node).body, visitor, context)); case SyntaxKind.ClassDeclaration: return updateClassDeclaration(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), visitNode((node).name, visitor, isIdentifier), - visitNodes((node).typeParameters, visitor, isTypeParameter), - visitNodes((node).heritageClauses, visitor, isHeritageClause), - visitNodes((node).members, visitor, isClassElement)); + nodesVisitor((node).typeParameters, visitor, isTypeParameter), + nodesVisitor((node).heritageClauses, visitor, isHeritageClause), + nodesVisitor((node).members, visitor, isClassElement)); case SyntaxKind.EnumDeclaration: return updateEnumDeclaration(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), visitNode((node).name, visitor, isIdentifier), - visitNodes((node).members, visitor, isEnumMember)); + nodesVisitor((node).members, visitor, isEnumMember)); case SyntaxKind.ModuleDeclaration: return updateModuleDeclaration(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), visitNode((node).name, visitor, isIdentifier), visitNode((node).body, visitor, isModuleBody)); case SyntaxKind.ModuleBlock: return updateModuleBlock(node, - visitNodes((node).statements, visitor, isStatement)); + nodesVisitor((node).statements, visitor, isStatement)); case SyntaxKind.CaseBlock: return updateCaseBlock(node, - visitNodes((node).clauses, visitor, isCaseOrDefaultClause)); + nodesVisitor((node).clauses, visitor, isCaseOrDefaultClause)); case SyntaxKind.ImportEqualsDeclaration: return updateImportEqualsDeclaration(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), visitNode((node).name, visitor, isIdentifier), visitNode((node).moduleReference, visitor, isModuleReference)); case SyntaxKind.ImportDeclaration: return updateImportDeclaration(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), visitNode((node).importClause, visitor, isImportClause), visitNode((node).moduleSpecifier, visitor, isExpression)); @@ -618,7 +618,7 @@ namespace ts { case SyntaxKind.NamedImports: return updateNamedImports(node, - visitNodes((node).elements, visitor, isImportSpecifier)); + nodesVisitor((node).elements, visitor, isImportSpecifier)); case SyntaxKind.ImportSpecifier: return updateImportSpecifier(node, @@ -627,20 +627,20 @@ namespace ts { case SyntaxKind.ExportAssignment: return updateExportAssignment(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), visitNode((node).expression, visitor, isExpression)); case SyntaxKind.ExportDeclaration: return updateExportDeclaration(node, - visitNodes((node).decorators, visitor, isDecorator), - visitNodes((node).modifiers, visitor, isModifier), + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier), visitNode((node).exportClause, visitor, isNamedExports), visitNode((node).moduleSpecifier, visitor, isExpression)); case SyntaxKind.NamedExports: return updateNamedExports(node, - visitNodes((node).elements, visitor, isExportSpecifier)); + nodesVisitor((node).elements, visitor, isExportSpecifier)); case SyntaxKind.ExportSpecifier: return updateExportSpecifier(node, @@ -656,12 +656,12 @@ namespace ts { case SyntaxKind.JsxElement: return updateJsxElement(node, visitNode((node).openingElement, visitor, isJsxOpeningElement), - visitNodes((node).children, visitor, isJsxChild), + nodesVisitor((node).children, visitor, isJsxChild), visitNode((node).closingElement, visitor, isJsxClosingElement)); case SyntaxKind.JsxAttributes: return updateJsxAttributes(node, - visitNodes((node).properties, visitor, isJsxAttributeLike)); + nodesVisitor((node).properties, visitor, isJsxAttributeLike)); case SyntaxKind.JsxSelfClosingElement: return updateJsxSelfClosingElement(node, @@ -694,15 +694,15 @@ namespace ts { case SyntaxKind.CaseClause: return updateCaseClause(node, visitNode((node).expression, visitor, isExpression), - visitNodes((node).statements, visitor, isStatement)); + nodesVisitor((node).statements, visitor, isStatement)); case SyntaxKind.DefaultClause: return updateDefaultClause(node, - visitNodes((node).statements, visitor, isStatement)); + nodesVisitor((node).statements, visitor, isStatement)); case SyntaxKind.HeritageClause: return updateHeritageClause(node, - visitNodes((node).types, visitor, isExpressionWithTypeArguments)); + nodesVisitor((node).types, visitor, isExpressionWithTypeArguments)); case SyntaxKind.CatchClause: return updateCatchClause(node, diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 2532a1875d..9f94904186 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -22,6 +22,10 @@ namespace FourSlash { ts.disableIncrementalParsing = false; + function normalizeNewLines(s: string) { + return s.replace(/\r\n/g, "\n"); + } + // Represents a parsed source file with metadata export interface FourSlashFile { // The contents of the file (with markers, etc stripped out) @@ -1958,8 +1962,7 @@ namespace FourSlash { public verifyCurrentFileContent(text: string) { const actual = this.getFileContent(this.activeFile.fileName); - const replaceNewlines = (str: string) => str.replace(/\r\n/g, "\n"); - if (replaceNewlines(actual) !== replaceNewlines(text)) { + if (normalizeNewLines(actual) !== normalizeNewLines(text)) { throw new Error("verifyCurrentFileContent\n" + "\tExpected: \"" + text + "\"\n" + "\t Actual: \"" + actual + "\""); @@ -2135,7 +2138,7 @@ namespace FourSlash { const actualText = this.rangeText(ranges[0]); const result = includeWhiteSpace - ? actualText === expectedText + ? normalizeNewLines(actualText) === normalizeNewLines(expectedText) : this.removeWhitespace(actualText) === this.removeWhitespace(expectedText); if (!result) { @@ -2185,7 +2188,7 @@ namespace FourSlash { continue; } - const newActions = this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.length, [diagnostic.code]); + const newActions = this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.length, [diagnostic.code], this.formatCodeSettings); if (newActions && newActions.length) { actions = actions ? actions.concat(newActions) : newActions; } diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 32af0eb260..ce98261a0c 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -121,6 +121,7 @@ "./unittests/projectErrors.ts", "./unittests/printer.ts", "./unittests/transform.ts", - "./unittests/customTransforms.ts" + "./unittests/customTransforms.ts", + "./unittests/textChanges.ts" ] } diff --git a/src/harness/unittests/textChanges.ts b/src/harness/unittests/textChanges.ts new file mode 100644 index 0000000000..b8c9a8e873 --- /dev/null +++ b/src/harness/unittests/textChanges.ts @@ -0,0 +1,520 @@ +/// +/// +/// + +namespace ts { + describe("textChanges", () => { + function findChild(name: string, n: Node) { + return find(n); + + function find(node: Node): Node { + if (isDeclaration(node) && node.name && isIdentifier(node.name) && node.name.text === name) { + return node; + } + else { + return forEachChild(node, find); + } + } + } + + const printerOptions = { newLine: NewLineKind.LineFeed }; + const newLineCharacter = getNewLineCharacter(printerOptions); + + function getRuleProvider(action?: (opts: FormatCodeSettings) => void) { + const options = { + indentSize: 4, + tabSize: 4, + newLineCharacter, + convertTabsToSpaces: true, + indentStyle: ts.IndentStyle.Smart, + insertSpaceAfterConstructor: false, + insertSpaceAfterCommaDelimiter: true, + insertSpaceAfterSemicolonInForStatements: true, + insertSpaceBeforeAndAfterBinaryOperators: true, + insertSpaceAfterKeywordsInControlFlowStatements: true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, + insertSpaceBeforeFunctionParenthesis: false, + placeOpenBraceOnNewLineForFunctions: false, + placeOpenBraceOnNewLineForControlBlocks: false, + }; + if (action) { + action(options); + } + const rulesProvider = new formatting.RulesProvider(); + rulesProvider.ensureUpToDate(options); + return rulesProvider; + } + + // validate that positions that were recovered from the printed text actually match positions that will be created if the same text is parsed. + function verifyPositions({ text, node }: textChanges.NonFormattedText): void { + const nodeList = flattenNodes(node); + const sourceFile = createSourceFile("f.ts", text, ScriptTarget.ES2015); + const parsedNodeList = flattenNodes(sourceFile.statements[0]); + Debug.assert(nodeList.length === parsedNodeList.length); + for (let i = 0; i < nodeList.length; i++) { + const left = nodeList[i]; + const right = parsedNodeList[i]; + Debug.assert(left.pos === right.pos); + Debug.assert(left.end === right.end); + } + + function flattenNodes(n: Node) { + const data: (Node | NodeArray)[] = []; + walk(n); + return data; + + function walk(n: Node | Node[]): void { + data.push(n); + return isArray(n) ? forEach(n, walk) : forEachChild(n, walk, walk); + } + } + } + + function runSingleFileTest(caption: string, setupFormatOptions: (opts: FormatCodeSettings) => void, text: string, validateNodes: boolean, testBlock: (sourceFile: SourceFile, changeTracker: textChanges.ChangeTracker) => void) { + it(caption, () => { + Harness.Baseline.runBaseline(`textChanges/${caption}.js`, () => { + const sourceFile = createSourceFile("source.ts", text, ScriptTarget.ES2015, /*setParentNodes*/ true); + const rulesProvider = getRuleProvider(setupFormatOptions); + const changeTracker = new textChanges.ChangeTracker(printerOptions.newLine, rulesProvider, validateNodes ? verifyPositions : undefined); + testBlock(sourceFile, changeTracker); + const changes = changeTracker.getChanges(); + assert.equal(changes.length, 1); + assert.equal(changes[0].fileName, sourceFile.fileName); + const modified = textChanges.applyChanges(sourceFile.text, changes[0].textChanges); + return `===ORIGINAL===${newLineCharacter}${text}${newLineCharacter}===MODIFIED===${newLineCharacter}${modified}`; + }); + }); + } + + function setNewLineForOpenBraceInFunctions(opts: FormatCodeSettings) { + opts.placeOpenBraceOnNewLineForFunctions = true; + } + + { + const text = ` +namespace M +{ + namespace M2 + { + function foo() { + // comment 1 + const x = 1; + + /** + * comment 2 line 1 + * comment 2 line 2 + */ + function f() { + return 100; + } + const y = 2; // comment 3 + return 1; + } + } +}`; + runSingleFileTest("extractMethodLike", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + const statements = ((findChild("foo", sourceFile)).body).statements.slice(1); + const newFunction = createFunctionDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + /*name*/ "bar", + /*typeParameters*/ undefined, + /*parameters*/ emptyArray, + /*type*/ createKeywordTypeNode(SyntaxKind.AnyKeyword), + /*body */ createBlock(statements) + ); + + changeTracker.insertNodeBefore(sourceFile, /*before*/findChild("M2", sourceFile), newFunction, { insertTrailingNewLine: true }); + + // replace statements with return statement + const newStatement = createReturn( + createCall( + /*expression*/ newFunction.name, + /*typeArguments*/ undefined, + /*argumentsArray*/ emptyArray + )); + changeTracker.replaceNodeRange(sourceFile, statements[0], lastOrUndefined(statements), newStatement, { insertTrailingNewLine: true }); + }); + } + { + const text = ` +function foo() { + return 1; +} + +function bar() { + return 2; +} +`; + runSingleFileTest("deleteRange1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteRange(sourceFile, { pos: text.indexOf("function foo"), end: text.indexOf("function bar") }); + }); + } + function findVariableStatementContaining(name: string, sourceFile: SourceFile) { + const varDecl = findChild(name, sourceFile); + assert.equal(varDecl.kind, SyntaxKind.VariableDeclaration); + const varStatement = varDecl.parent.parent; + assert.equal(varStatement.kind, SyntaxKind.VariableStatement); + return varStatement; + } + { + const text = ` +var x = 1; // some comment - 1 +/** + * comment 2 + */ +var y = 2; // comment 3 +var z = 3; // comment 4 +`; + runSingleFileTest("deleteNode1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile)); + }); + runSingleFileTest("deleteNode2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedStartPosition: true }); + }); + runSingleFileTest("deleteNode3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedEndPosition: true }); + }); + runSingleFileTest("deleteNode4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true }); + }); + runSingleFileTest("deleteNode5", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNode(sourceFile, findVariableStatementContaining("x", sourceFile)); + }); + } + { + const text = ` +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 +`; + runSingleFileTest("deleteNodeRange1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile)); + }); + runSingleFileTest("deleteNodeRange2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), + { useNonAdjustedStartPosition: true }); + }); + runSingleFileTest("deleteNodeRange3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), + { useNonAdjustedEndPosition: true }); + }); + runSingleFileTest("deleteNodeRange4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), + { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true }); + }); + } + function createTestVariableDeclaration(name: string) { + return createVariableDeclaration(name, /*type*/ undefined, createObjectLiteral([createPropertyAssignment("p1", createLiteral(1))], /*multiline*/ true)); + } + function createTestClass() { + return createClassDeclaration( + /*decorators*/ undefined, + [ + createToken(SyntaxKind.PublicKeyword) + ], + "class1", + /*typeParameters*/ undefined, + [ + createHeritageClause( + SyntaxKind.ImplementsKeyword, + [ + createExpressionWithTypeArguments(/*typeArguments*/ undefined, createIdentifier("interface1")) + ] + ) + ], + [ + createProperty( + /*decorators*/ undefined, + /*modifiers*/ undefined, + "property1", + /*questionToken*/ undefined, + createKeywordTypeNode(SyntaxKind.BooleanKeyword), + /*initializer*/ undefined + ) + ] + ); + } + { + const text = ` +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7`; + runSingleFileTest("replaceRange", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { insertTrailingNewLine: true }); + }); + runSingleFileTest("replaceRangeWithForcedIndentation", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { insertTrailingNewLine: true, indentation: 8, delta: 0 }); + }); + + runSingleFileTest("replaceRangeNoLineBreakBefore", setNewLineForOpenBraceInFunctions, `const x = 1, y = "2";`, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNode = createTestVariableDeclaration("z1"); + changeTracker.replaceRange(sourceFile, { pos: sourceFile.text.indexOf("y"), end: sourceFile.text.indexOf(";") }, newNode); + }); + } + { + const text = ` +namespace A { + const x = 1, y = "2"; +} +`; + runSingleFileTest("replaceNode1NoLineBreakBefore", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + const newNode = createTestVariableDeclaration("z1"); + changeTracker.replaceNode(sourceFile, findChild("y", sourceFile), newNode); + }); + } + { + const text = ` +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7`; + runSingleFileTest("replaceNode1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { insertTrailingNewLine: true }); + }); + runSingleFileTest("replaceNode2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, insertTrailingNewLine: true, insertLeadingNewLine: true }); + }); + runSingleFileTest("replaceNode3", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, insertTrailingNewLine: true }); + }); + runSingleFileTest("replaceNode4", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true }); + }); + runSingleFileTest("replaceNode5", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("x", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true }); + }); + } + { + const text = ` +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7`; + runSingleFileTest("replaceNodeRange1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { insertTrailingNewLine: true }); + }); + runSingleFileTest("replaceNodeRange2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, insertTrailingNewLine: true, insertLeadingNewLine: true }); + }); + runSingleFileTest("replaceNodeRange3", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, insertTrailingNewLine: true }); + }); + runSingleFileTest("replaceNodeRange4", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true }); + }); + } + { + const text = ` +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7`; + runSingleFileTest("insertNodeAt1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.insertNodeAt(sourceFile, text.indexOf("var y"), createTestClass(), { insertTrailingNewLine: true }); + }); + runSingleFileTest("insertNodeAt2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAt(sourceFile, text.indexOf("; // comment 4"), createTestVariableDeclaration("z1")); + }); + } + { + const text = ` +namespace M { + // comment 1 + var x = 1; // comment 2 + // comment 3 + var y; // comment 4 + var z = 3; // comment 5 + // comment 6 + var a = 4; // comment 7 +}`; + runSingleFileTest("insertNodeBefore1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { insertTrailingNewLine: true }); + }); + runSingleFileTest("insertNodeBefore2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.insertNodeBefore(sourceFile, findChild("M", sourceFile), createTestClass(), { insertTrailingNewLine: true }); + }); + runSingleFileTest("insertNodeAfter1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { insertTrailingNewLine: true }); + }); + runSingleFileTest("insertNodeAfter2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { + changeTracker.insertNodeAfter(sourceFile, findChild("M", sourceFile), createTestClass(), { insertLeadingNewLine: true }); + }); + } + { + function findOpenBraceForConstructor(sourceFile: SourceFile) { + const classDecl = sourceFile.statements[0]; + const constructorDecl = forEach(classDecl.members, m => m.kind === SyntaxKind.Constructor && (m).body && m); + return constructorDecl.body.getFirstToken(); + } + function createTestSuperCall() { + const superCall = createCall( + createSuper(), + /*typeArguments*/ undefined, + /*argumentsArray*/ emptyArray + ); + return createStatement(superCall); + } + const text1 = ` +class A { + constructor() { + } +} +`; + runSingleFileTest("insertNodeAfter3", noop, text1, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { insertTrailingNewLine: true }); + }); + const text2 = ` +class A { + constructor() { + var x = 1; + } +} +`; + runSingleFileTest("insertNodeAfter4", noop, text2, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), createTestSuperCall(), { insertTrailingNewLine: true }); + }); + const text3 = ` +class A { + constructor() { + + } +} +`; + runSingleFileTest("insertNodeAfter3-block with newline", noop, text3, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { insertTrailingNewLine: true }); + }); + } + { + const text = `var a = 1, b = 2, c = 3;`; + runSingleFileTest("deleteNodeInList1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = `var a = 1,b = 2,c = 3;`; + runSingleFileTest("deleteNodeInList1_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList2_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList3_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = ` +namespace M { + var a = 1, + b = 2, + c = 3; +}`; + runSingleFileTest("deleteNodeInList4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList5", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList6", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = ` +namespace M { + var a = 1, // comment 1 + // comment 2 + b = 2, // comment 3 + // comment 4 + c = 3; // comment 5 +}`; + runSingleFileTest("deleteNodeInList4_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList5_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList6_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = ` +function foo(a: number, b: string, c = true) { + return 1; +}`; + runSingleFileTest("deleteNodeInList7", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList8", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList9", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile)); + }); + } + { + const text = ` +function foo(a: number,b: string,c = true) { + return 1; +}`; + runSingleFileTest("deleteNodeInList10", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList11", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList12", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile)); + }); + } +{ + const text = ` +function foo( + a: number, + b: string, + c = true) { + return 1; +}`; + runSingleFileTest("deleteNodeInList13", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile)); + }); + runSingleFileTest("deleteNodeInList14", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile)); + }); + runSingleFileTest("deleteNodeInList15", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile)); + }); + } + }); +} \ No newline at end of file diff --git a/src/server/session.ts b/src/server/session.ts index b5e7e44970..64fce61a09 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1420,8 +1420,9 @@ namespace ts.server { const scriptInfo = project.getScriptInfoForNormalizedPath(file); const startPosition = getStartPosition(); const endPosition = getEndPosition(); + const formatOptions = this.projectService.getFormatCodeOptions(file); - const codeActions = project.getLanguageService().getCodeFixesAtPosition(file, startPosition, endPosition, args.errorCodes); + const codeActions = project.getLanguageService().getCodeFixesAtPosition(file, startPosition, endPosition, args.errorCodes, formatOptions); if (!codeActions) { return undefined; } diff --git a/src/services/codeFixProvider.ts b/src/services/codeFixProvider.ts index 10d0b8eef3..c48c5a2460 100644 --- a/src/services/codeFixProvider.ts +++ b/src/services/codeFixProvider.ts @@ -13,6 +13,7 @@ namespace ts { newLineCharacter: string; host: LanguageServiceHost; cancellationToken: CancellationToken; + rulesProvider: formatting.RulesProvider; } export namespace codefix { diff --git a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts index 4528c48774..207d039df8 100644 --- a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts +++ b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts @@ -26,22 +26,13 @@ namespace ts.codefix { } } } - - const newPosition = getOpenBraceEnd(constructor, sourceFile); - const changes = [{ - fileName: sourceFile.fileName, textChanges: [{ - newText: superCall.getText(sourceFile), - span: { start: newPosition, length: 0 } - }, - { - newText: "", - span: { start: superCall.getStart(sourceFile), length: superCall.getWidth(sourceFile) } - }] - }]; + const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); + changeTracker.insertNodeAfter(sourceFile, getOpenBrace(constructor, sourceFile), superCall, { insertTrailingNewLine: true }); + changeTracker.deleteNode(sourceFile, superCall); return [{ description: getLocaleSpecificMessage(Diagnostics.Make_super_call_the_first_statement_in_the_constructor), - changes + changes: changeTracker.getChanges() }]; function findSuperCall(n: Node): ExpressionStatement { diff --git a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts index 0dede87cf2..27b655f19c 100644 --- a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts +++ b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts @@ -10,10 +10,13 @@ namespace ts.codefix { return undefined; } - const newPosition = getOpenBraceEnd(token.parent, sourceFile); + const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); + const superCall = createStatement(createCall(createSuper(), /*typeArguments*/ undefined, /*argumentsArray*/ emptyArray)); + changeTracker.insertNodeAfter(sourceFile, getOpenBrace(token.parent, sourceFile), superCall, { insertTrailingNewLine: true }); + return [{ description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), - changes: [{ fileName: sourceFile.fileName, textChanges: [{ newText: "super();", span: { start: newPosition, length: 0 } }] }] + changes: changeTracker.getChanges() }]; } }); diff --git a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts index 1e72700ec0..80d4345a94 100644 --- a/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts +++ b/src/services/codefixes/fixExtendsInterfaceBecomesImplements.ts @@ -21,26 +21,20 @@ namespace ts.codefix { return undefined; } - let changeStart = extendsToken.getStart(sourceFile); - let changeEnd = extendsToken.getEnd(); - const textChanges: TextChange[] = [{ newText: " implements", span: { start: changeStart, length: changeEnd - changeStart } }]; + const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); + changeTracker.replaceNode(sourceFile, extendsToken, createToken(SyntaxKind.ImplementsKeyword)); // We replace existing keywords with commas. for (let i = 1; i < heritageClauses.length; i++) { const keywordToken = heritageClauses[i].getFirstToken(); if (keywordToken) { - changeStart = keywordToken.getStart(sourceFile); - changeEnd = keywordToken.getEnd(); - textChanges.push({ newText: ",", span: { start: changeStart, length: changeEnd - changeStart } }); + changeTracker.replaceNode(sourceFile, keywordToken, createToken(SyntaxKind.CommaToken)); } } const result = [{ description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements), - changes: [{ - fileName: sourceFile.fileName, - textChanges: textChanges - }] + changes: changeTracker.getChanges() }]; return result; diff --git a/src/services/codefixes/fixForgottenThisPropertyAccess.ts b/src/services/codefixes/fixForgottenThisPropertyAccess.ts index c9d59dd833..711a3289a2 100644 --- a/src/services/codefixes/fixForgottenThisPropertyAccess.ts +++ b/src/services/codefixes/fixForgottenThisPropertyAccess.ts @@ -5,11 +5,15 @@ namespace ts.codefix { getCodeActions: (context: CodeFixContext) => { const sourceFile = context.sourceFile; const token = getTokenAtPosition(sourceFile, context.span.start); - const start = token.getStart(sourceFile); + if (token.kind !== SyntaxKind.Identifier) { + return undefined; + } + const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); + changeTracker.replaceNode(sourceFile, token, createPropertyAccess(createThis(), token)); return [{ description: getLocaleSpecificMessage(Diagnostics.Add_this_to_unresolved_variable), - changes: [{ fileName: sourceFile.fileName, textChanges: [{ newText: "this.", span: { start, length: 0 } }] }] + changes: changeTracker.getChanges() }]; } }); diff --git a/src/services/codefixes/unusedIdentifierFixes.ts b/src/services/codefixes/unusedIdentifierFixes.ts index a45a3c26db..e2561e208f 100644 --- a/src/services/codefixes/unusedIdentifierFixes.ts +++ b/src/services/codefixes/unusedIdentifierFixes.ts @@ -25,17 +25,17 @@ namespace ts.codefix { const forStatement = token.parent.parent.parent; const forInitializer = forStatement.initializer; if (forInitializer.declarations.length === 1) { - return createCodeFixToRemoveNode(forInitializer); + return deleteNode(forInitializer); } else { - return removeSingleItem(forInitializer.declarations, token); + return deleteNodeInList(token.parent); } case SyntaxKind.ForOfStatement: const forOfStatement = token.parent.parent.parent; if (forOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList) { const forOfInitializer = forOfStatement.initializer; - return createCodeFix("{}", forOfInitializer.declarations[0].getStart(), forOfInitializer.declarations[0].getWidth()); + return replaceNode(forOfInitializer.declarations[0], createObjectLiteral()); } break; @@ -47,51 +47,59 @@ namespace ts.codefix { case SyntaxKind.CatchClause: const catchClause = token.parent.parent; const parameter = catchClause.variableDeclaration.getChildren()[0]; - return createCodeFixToRemoveNode(parameter); + return deleteNode(parameter); default: const variableStatement = token.parent.parent.parent; if (variableStatement.declarationList.declarations.length === 1) { - return createCodeFixToRemoveNode(variableStatement); + return deleteNode(variableStatement); } else { - const declarations = variableStatement.declarationList.declarations; - return removeSingleItem(declarations, token); + return deleteNodeInList(token.parent); } } case SyntaxKind.TypeParameter: const typeParameters = (token.parent.parent).typeParameters; if (typeParameters.length === 1) { - return createCodeFix("", token.parent.pos - 1, token.parent.end - token.parent.pos + 2); + const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1); + if (!previousToken || previousToken.kind !== SyntaxKind.LessThanToken) { + return deleteRange(typeParameters); + } + const nextToken = getTokenAtPosition(sourceFile, typeParameters.end); + if (!nextToken || nextToken.kind !== SyntaxKind.GreaterThanToken) { + return deleteRange(typeParameters); + } + return deleteNodeRange(previousToken, nextToken); } else { - return removeSingleItem(typeParameters, token); + return deleteNodeInList(token.parent); } case ts.SyntaxKind.Parameter: const functionDeclaration = token.parent.parent; if (functionDeclaration.parameters.length === 1) { - return createCodeFixToRemoveNode(token.parent); + return deleteNode(token.parent); } else { - return removeSingleItem(functionDeclaration.parameters, token); + return deleteNodeInList(token.parent); } // handle case where 'import a = A;' case SyntaxKind.ImportEqualsDeclaration: - const importEquals = findImportDeclaration(token); - return createCodeFixToRemoveNode(importEquals); + const importEquals = getAncestor(token, SyntaxKind.ImportEqualsDeclaration); + return deleteNode(importEquals); case SyntaxKind.ImportSpecifier: const namedImports = token.parent.parent; if (namedImports.elements.length === 1) { // Only 1 import and it is unused. So the entire declaration should be removed. - const importSpec = findImportDeclaration(token); - return createCodeFixToRemoveNode(importSpec); + const importSpec = getAncestor(token, SyntaxKind.ImportDeclaration); + return deleteNode(importSpec); } else { - return removeSingleItem(namedImports.elements, token); + // delete import specifier + return deleteNodeInList(token.parent); } // handle case where "import d, * as ns from './file'" @@ -99,98 +107,79 @@ namespace ts.codefix { case SyntaxKind.ImportClause: // this covers both 'import |d|' and 'import |d,| *' const importClause = token.parent; if (!importClause.namedBindings) { // |import d from './file'| or |import * as ns from './file'| - const importDecl = findImportDeclaration(importClause); - return createCodeFixToRemoveNode(importDecl); + const importDecl = getAncestor(importClause, SyntaxKind.ImportDeclaration); + return deleteNode(importDecl); } else { // import |d,| * as ns from './file' - const start = importClause.name.getStart(); - let end = findFirstNonSpaceCharPosStarting(importClause.name.end); - if (sourceFile.text.charCodeAt(end) === CharacterCodes.comma) { - end = findFirstNonSpaceCharPosStarting(end + 1); + const start = importClause.name.getStart(sourceFile); + const nextToken = getTokenAtPosition(sourceFile, importClause.name.end); + if (nextToken && nextToken.kind === SyntaxKind.CommaToken) { + // shift first non-whitespace position after comma to the start position of the node + return deleteRange({ pos: start, end: skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/true) }); + } + else { + return deleteNode(importClause.name); } - - return createCodeFix("", start, end - start); } case SyntaxKind.NamespaceImport: const namespaceImport = token.parent; if (namespaceImport.name == token && !(namespaceImport.parent).name) { - const importDecl = findImportDeclaration(namespaceImport); - return createCodeFixToRemoveNode(importDecl); + const importDecl = getAncestor(namespaceImport, SyntaxKind.ImportDeclaration); + return deleteNode(importDecl); } else { - const start = (namespaceImport.parent).name.end; - return createCodeFix("", start, (namespaceImport.parent).namedBindings.end - start); + const previousToken = getTokenAtPosition(sourceFile, namespaceImport.pos - 1); + if (previousToken && previousToken.kind === SyntaxKind.CommaToken) { + const startPosition = textChanges.getAdjustedStartPosition(sourceFile, previousToken, {}, /*forDeleteOperation*/ true); + return deleteRange({ pos: startPosition, end: namespaceImport.end }); + } + return deleteRange(namespaceImport); } } break; case SyntaxKind.PropertyDeclaration: case SyntaxKind.NamespaceImport: - return createCodeFixToRemoveNode(token.parent); + return deleteNode(token.parent); } if (isDeclarationName(token)) { - return createCodeFixToRemoveNode(token.parent); + return deleteNode(token.parent); } else if (isLiteralComputedPropertyDeclarationName(token)) { - return createCodeFixToRemoveNode(token.parent.parent); + return deleteNode(token.parent.parent); } else { return undefined; } - function findImportDeclaration(token: Node): Node { - let importDecl = token; - while (importDecl.kind != SyntaxKind.ImportDeclaration && importDecl.parent) { - importDecl = importDecl.parent; - } - - return importDecl; + function deleteNode(n: Node) { + return makeChange(textChanges.ChangeTracker.fromCodeFixContext(context).deleteNode(sourceFile, n)); } - function createCodeFixToRemoveNode(node: Node) { - let end = node.getEnd(); - const endCharCode = sourceFile.text.charCodeAt(end); - const afterEndCharCode = sourceFile.text.charCodeAt(end + 1); - if (isLineBreak(endCharCode)) { - end += 1; - } - // in the case of CR LF, you could have two consecutive new line characters for one new line. - // this needs to be differenciated from two LF LF chars that actually mean two new lines. - if (isLineBreak(afterEndCharCode) && endCharCode !== afterEndCharCode) { - end += 1; - } - - const start = node.getStart(); - return createCodeFix("", start, end - start); + function deleteRange(range: TextRange) { + return makeChange(textChanges.ChangeTracker.fromCodeFixContext(context).deleteRange(sourceFile, range)); } - function findFirstNonSpaceCharPosStarting(start: number) { - while (isWhiteSpace(sourceFile.text.charCodeAt(start))) { - start += 1; - } - return start; + function deleteNodeInList(n: Node) { + return makeChange(textChanges.ChangeTracker.fromCodeFixContext(context).deleteNodeInList(sourceFile, n)); } - function createCodeFix(newText: string, start: number, length: number): CodeAction[] { + function deleteNodeRange(start: Node, end: Node) { + return makeChange(textChanges.ChangeTracker.fromCodeFixContext(context).deleteNodeRange(sourceFile, start, end)); + } + + function replaceNode(n: Node, newNode: Node) { + return makeChange(textChanges.ChangeTracker.fromCodeFixContext(context).replaceNode(sourceFile, n, newNode)); + } + + function makeChange(changeTracker: textChanges.ChangeTracker) { return [{ description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Remove_declaration_for_Colon_0), { 0: token.getText() }), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [{ newText, span: { start, length } }] - }] + changes: changeTracker.getChanges() }]; } - - function removeSingleItem(elements: NodeArray, token: T): CodeAction[] { - if (elements[0] === token.parent) { - return createCodeFix("", token.parent.pos, token.parent.end - token.parent.pos + 1); - } - else { - return createCodeFix("", token.parent.pos - 1, token.parent.end - token.parent.pos + 1); - } - } } }); } \ No newline at end of file diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index 201d64e4d7..4fa3b71a3a 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -314,24 +314,55 @@ namespace ts.formatting { return 0; } + /* @internal */ + export function formatNode(node: Node, sourceFileLike: SourceFileLike, languageVariant: LanguageVariant, initialIndentation: number, delta: number, rulesProvider: RulesProvider): TextChange[] { + const range = { pos: 0, end: sourceFileLike.text.length }; + return formatSpanWorker( + range, + node, + initialIndentation, + delta, + getFormattingScanner(sourceFileLike.text, languageVariant, range.pos, range.end), + rulesProvider.getFormatOptions(), + rulesProvider, + FormattingRequestKind.FormatSelection, + _ => false, // assume that node does not have any errors + sourceFileLike); + } + function formatSpan(originalRange: TextRange, sourceFile: SourceFile, options: FormatCodeSettings, rulesProvider: RulesProvider, requestKind: FormattingRequestKind): TextChange[] { + // find the smallest node that fully wraps the range and compute the initial indentation for the node + const enclosingNode = findEnclosingNode(originalRange, sourceFile); + return formatSpanWorker( + originalRange, + enclosingNode, + SmartIndenter.getIndentationForNode(enclosingNode, originalRange, sourceFile, options), + getOwnOrInheritedDelta(enclosingNode, options, sourceFile), + getFormattingScanner(sourceFile.text, sourceFile.languageVariant, getScanStartPosition(enclosingNode, originalRange, sourceFile), originalRange.end), + options, + rulesProvider, + requestKind, + prepareRangeContainsErrorFunction(sourceFile.parseDiagnostics, originalRange), + sourceFile); + } - const rangeContainsError = prepareRangeContainsErrorFunction(sourceFile.parseDiagnostics, originalRange); + function formatSpanWorker(originalRange: TextRange, + enclosingNode: Node, + initialIndentation: number, + delta: number, + formattingScanner: FormattingScanner, + options: FormatCodeSettings, + rulesProvider: RulesProvider, + requestKind: FormattingRequestKind, + rangeContainsError: (r: TextRange) => boolean, + sourceFile: SourceFileLike): TextChange[] { // formatting context is used by rules provider const formattingContext = new FormattingContext(sourceFile, requestKind); - - // find the smallest node that fully wraps the range and compute the initial indentation for the node - const enclosingNode = findEnclosingNode(originalRange, sourceFile); - - const formattingScanner = getFormattingScanner(sourceFile, getScanStartPosition(enclosingNode, originalRange, sourceFile), originalRange.end); - - const initialIndentation = SmartIndenter.getIndentationForNode(enclosingNode, originalRange, sourceFile, options); - let previousRangeHasError: boolean; let previousRange: TextRangeWithKind; let previousParent: Node; @@ -351,7 +382,6 @@ namespace ts.formatting { undecoratedStartLine = sourceFile.getLineAndCharacterOfPosition(getNonDecoratorTokenPosOfNode(enclosingNode, sourceFile)).line; } - const delta = getOwnOrInheritedDelta(enclosingNode, options, sourceFile); processNode(enclosingNode, enclosingNode, startLine, undecoratedStartLine, initialIndentation, delta); } diff --git a/src/services/formatting/formattingContext.ts b/src/services/formatting/formattingContext.ts index bd5ca35247..26b538dfc5 100644 --- a/src/services/formatting/formattingContext.ts +++ b/src/services/formatting/formattingContext.ts @@ -15,7 +15,7 @@ namespace ts.formatting { private contextNodeBlockIsOnOneLine: boolean; private nextNodeBlockIsOnOneLine: boolean; - constructor(public sourceFile: SourceFile, public formattingRequestKind: FormattingRequestKind) { + constructor(public readonly sourceFile: SourceFileLike, public formattingRequestKind: FormattingRequestKind) { } public updateContext(currentRange: TextRangeWithKind, currentTokenParent: Node, nextRange: TextRangeWithKind, nextTokenParent: Node, commonParent: Node) { diff --git a/src/services/formatting/formattingScanner.ts b/src/services/formatting/formattingScanner.ts index 2020352f86..981ae09b2b 100644 --- a/src/services/formatting/formattingScanner.ts +++ b/src/services/formatting/formattingScanner.ts @@ -30,11 +30,11 @@ namespace ts.formatting { RescanJsxText, } - export function getFormattingScanner(sourceFile: SourceFile, startPos: number, endPos: number): FormattingScanner { + export function getFormattingScanner(text: string, languageVariant: LanguageVariant, startPos: number, endPos: number): FormattingScanner { Debug.assert(scanner === undefined, "Scanner should be undefined"); - scanner = sourceFile.languageVariant === LanguageVariant.JSX ? jsxScanner : standardScanner; + scanner = languageVariant === LanguageVariant.JSX ? jsxScanner : standardScanner; - scanner.setText(sourceFile.text); + scanner.setText(text); scanner.setTextPos(startPos); let wasNewLine = true; diff --git a/src/services/formatting/rulesProvider.ts b/src/services/formatting/rulesProvider.ts index 14e08e4857..660d5f34a0 100644 --- a/src/services/formatting/rulesProvider.ts +++ b/src/services/formatting/rulesProvider.ts @@ -24,6 +24,10 @@ namespace ts.formatting { return this.rulesMap; } + public getFormatOptions(): Readonly { + return this.options; + } + public ensureUpToDate(options: ts.FormatCodeSettings) { if (!this.options || !ts.compareDataObjects(this.options, options)) { const activeRules = this.createActiveRules(options); diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts index eb9e1ba04c..b5c901482a 100644 --- a/src/services/formatting/smartIndenter.ts +++ b/src/services/formatting/smartIndenter.ts @@ -8,7 +8,19 @@ namespace ts.formatting { Unknown = -1 } - export function getIndentation(position: number, sourceFile: SourceFile, options: EditorSettings): number { + /** + * Computed indentation for a given position in source file + * @param position - position in file + * @param sourceFile - target source file + * @param options - set of editor options that control indentation + * @param assumeNewLineBeforeCloseBrace - false when getIndentation is called on the text from the real source file. + * true - when we need to assume that position is on the newline. This is usefult for codefixes, i.e. + * function f() { + * |} + * when inserting some text after open brace we would like to get the value of indentation as if newline was already there. + * However by default indentation at position | will be 0 so 'assumeNewLineBeforeCloseBrace' allows to override this behavior, + */ + export function getIndentation(position: number, sourceFile: SourceFile, options: EditorSettings, assumeNewLineBeforeCloseBrace = false): number { if (position > sourceFile.text.length) { return getBaseIndentation(options); // past EOF } @@ -71,13 +83,14 @@ namespace ts.formatting { if (positionBelongsToNode(current, position, sourceFile) && shouldIndentChildNode(current, previous)) { currentStart = getStartLineAndCharacterForNode(current, sourceFile); - if (nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken, current, lineAtPosition, sourceFile)) { - indentationDelta = 0; + const nextTokenKind = nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken, current, lineAtPosition, sourceFile); + if (nextTokenKind !== NextTokenKind.Unknown) { + // handle cases when codefix is about to be inserted before the close brace + indentationDelta = assumeNewLineBeforeCloseBrace && nextTokenKind === NextTokenKind.CloseBrace ? options.indentSize : 0; } else { indentationDelta = lineAtPosition !== currentStart.line ? options.indentSize : 0; } - break; } @@ -218,15 +231,21 @@ namespace ts.formatting { return findColumnForFirstNonWhitespaceCharacterInLine(currentLineAndChar, sourceFile, options); } - function nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken: Node, current: Node, lineAtPosition: number, sourceFile: SourceFile): boolean { + const enum NextTokenKind { + Unknown, + OpenBrace, + CloseBrace + } + + function nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken: Node, current: Node, lineAtPosition: number, sourceFile: SourceFile): NextTokenKind { const nextToken = findNextToken(precedingToken, current); if (!nextToken) { - return false; + return NextTokenKind.Unknown; } if (nextToken.kind === SyntaxKind.OpenBraceToken) { // open braces are always indented at the parent level - return true; + return NextTokenKind.OpenBrace; } else if (nextToken.kind === SyntaxKind.CloseBraceToken) { // close braces are indented at the parent level if they are located on the same line with cursor @@ -239,17 +258,17 @@ namespace ts.formatting { // $} const nextTokenStartLine = getStartLineAndCharacterForNode(nextToken, sourceFile).line; - return lineAtPosition === nextTokenStartLine; + return lineAtPosition === nextTokenStartLine ? NextTokenKind.CloseBrace : NextTokenKind.Unknown; } - return false; + return NextTokenKind.Unknown; } - function getStartLineAndCharacterForNode(n: Node, sourceFile: SourceFile): LineAndCharacter { + function getStartLineAndCharacterForNode(n: Node, sourceFile: SourceFileLike): LineAndCharacter { return sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)); } - export function childStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFile): boolean { + export function childStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFileLike): boolean { if (parent.kind === SyntaxKind.IfStatement && (parent).elseStatement === child) { const elseKeyword = findChildOfKind(parent, SyntaxKind.ElseKeyword, sourceFile); Debug.assert(elseKeyword !== undefined); @@ -261,15 +280,15 @@ namespace ts.formatting { return false; } - function getContainingList(node: Node, sourceFile: SourceFile): NodeArray { + function getListIfStartEndIsInListRange(list: NodeArray, start: number, end: number) { + return list && rangeContainsStartEnd(list, start, end) ? list : undefined; + } + + export function getContainingList(node: Node, sourceFile: SourceFile): NodeArray { if (node.parent) { switch (node.parent.kind) { case SyntaxKind.TypeReference: - if ((node.parent).typeArguments && - rangeContainsStartEnd((node.parent).typeArguments, node.getStart(sourceFile), node.getEnd())) { - return (node.parent).typeArguments; - } - break; + return getListIfStartEndIsInListRange((node.parent).typeArguments, node.getStart(sourceFile), node.getEnd()); case SyntaxKind.ObjectLiteralExpression: return (node.parent).properties; case SyntaxKind.ArrayLiteralExpression: @@ -280,30 +299,26 @@ namespace ts.formatting { case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.CallSignature: + case SyntaxKind.Constructor: + case SyntaxKind.ConstructorType: case SyntaxKind.ConstructSignature: { const start = node.getStart(sourceFile); - if ((node.parent).typeParameters && - rangeContainsStartEnd((node.parent).typeParameters, start, node.getEnd())) { - return (node.parent).typeParameters; - } - if (rangeContainsStartEnd((node.parent).parameters, start, node.getEnd())) { - return (node.parent).parameters; - } - break; + return getListIfStartEndIsInListRange((node.parent).typeParameters, start, node.getEnd()) || + getListIfStartEndIsInListRange((node.parent).parameters, start, node.getEnd()); } + case SyntaxKind.ClassDeclaration: + return getListIfStartEndIsInListRange((node.parent).typeParameters, node.getStart(sourceFile), node.getEnd()); case SyntaxKind.NewExpression: case SyntaxKind.CallExpression: { const start = node.getStart(sourceFile); - if ((node.parent).typeArguments && - rangeContainsStartEnd((node.parent).typeArguments, start, node.getEnd())) { - return (node.parent).typeArguments; - } - if ((node.parent).arguments && - rangeContainsStartEnd((node.parent).arguments, start, node.getEnd())) { - return (node.parent).arguments; - } - break; + return getListIfStartEndIsInListRange((node.parent).typeArguments, start, node.getEnd()) || + getListIfStartEndIsInListRange((node.parent).arguments, start, node.getEnd()); } + case SyntaxKind.VariableDeclarationList: + return getListIfStartEndIsInListRange((node.parent).declarations, node.getStart(sourceFile), node.getEnd()); + case SyntaxKind.NamedImports: + case SyntaxKind.NamedExports: + return getListIfStartEndIsInListRange((node.parent).elements, node.getStart(sourceFile), node.getEnd()); } } return undefined; @@ -400,7 +415,7 @@ namespace ts.formatting { value of 'character' for '$' is 3 value of 'column' for '$' is 6 (assuming that tab size is 4) */ - export function findFirstNonWhitespaceCharacterAndColumn(startPos: number, endPos: number, sourceFile: SourceFile, options: EditorSettings) { + export function findFirstNonWhitespaceCharacterAndColumn(startPos: number, endPos: number, sourceFile: SourceFileLike, options: EditorSettings) { let character = 0; let column = 0; for (let pos = startPos; pos < endPos; pos++) { @@ -421,7 +436,7 @@ namespace ts.formatting { return { column, character }; } - export function findFirstNonWhitespaceColumn(startPos: number, endPos: number, sourceFile: SourceFile, options: EditorSettings): number { + export function findFirstNonWhitespaceColumn(startPos: number, endPos: number, sourceFile: SourceFileLike, options: EditorSettings): number { return findFirstNonWhitespaceCharacterAndColumn(startPos, endPos, sourceFile, options).column; } diff --git a/src/services/services.ts b/src/services/services.ts index f122e39bcc..beb52f7011 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -24,6 +24,7 @@ /// /// /// +/// /// /// @@ -63,7 +64,7 @@ namespace ts { return getSourceFileOfNode(this); } - public getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number { + public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number { return getTokenPosOfNode(this, sourceFile, includeJsDocComment); } @@ -129,7 +130,7 @@ namespace ts { return list; } - private createChildren(sourceFile?: SourceFile) { + private createChildren(sourceFile?: SourceFileLike) { let children: Node[]; if (this.kind >= SyntaxKind.FirstNode) { scanner.setText((sourceFile || this.getSourceFile()).text); @@ -182,7 +183,7 @@ namespace ts { return this._children[index]; } - public getChildren(sourceFile?: SourceFile): Node[] { + public getChildren(sourceFile?: SourceFileLike): Node[] { if (!this._children) this.createChildren(sourceFile); return this._children; } @@ -231,7 +232,7 @@ namespace ts { return getSourceFileOfNode(this); } - public getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number { + public getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number { return getTokenPosOfNode(this, sourceFile, includeJsDocComment); } @@ -1682,7 +1683,7 @@ namespace ts { return []; } - function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): CodeAction[] { + function getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[] { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); const span = { start, length: end - start }; @@ -1700,7 +1701,8 @@ namespace ts { program: program, newLineCharacter: newLineChar, host: host, - cancellationToken: cancellationToken + cancellationToken: cancellationToken, + rulesProvider: getRuleProvider(formatOptions) }; const fixes = codefix.getFixes(context); diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts new file mode 100644 index 0000000000..5c345e6e4e --- /dev/null +++ b/src/services/textChanges.ts @@ -0,0 +1,475 @@ +/* @internal */ +namespace ts.textChanges { + + /** + * Currently for simplicity we store recovered positions on the node itself. + * It can be changed to side-table later if we decide that current design is too invasive. + */ + function getPos(n: TextRange) { + return (n)["__pos"]; + } + + function setPos(n: TextRange, pos: number) { + (n)["__pos"] = pos; + } + + function getEnd(n: TextRange) { + return (n)["__end"]; + } + + function setEnd(n: TextRange, end: number) { + (n)["__end"] = end; + } + + export interface ConfigurableStart { + useNonAdjustedStartPosition?: boolean; + } + export interface ConfigurableEnd { + useNonAdjustedEndPosition?: boolean; + } + + /** + * Usually node.pos points to a position immediately after the previous token. + * If this position is used as a beginning of the span to remove - it might lead to removing the trailing trivia of the previous node, i.e: + * const x; // this is x + * ^ - pos for the next variable declaration will point here + * const y; // this is y + * ^ - end for previous variable declaration + * Usually leading trivia of the variable declaration 'y' should not include trailing trivia (whitespace, comment 'this is x' and newline) from the preceding + * variable declaration and trailing trivia for 'y' should include (whitespace, comment 'this is y', newline). + * By default when removing nodes we adjust start and end positions to respect specification of the trivia above. + * If pos\end should be interpreted literally 'useNonAdjustedStartPosition' or 'useNonAdjustedEndPosition' should be set to true + */ + export type ConfigurableStartEnd = ConfigurableStart & ConfigurableEnd; + + export interface InsertNodeOptions { + /** + * Set this value to true to make sure that node text of newly inserted node ends with new line + */ + insertTrailingNewLine?: boolean; + /** + * Set this value to true to make sure that node text of newly inserted node starts with new line + */ + insertLeadingNewLine?: boolean; + /** + * Text of inserted node will be formatted with this indentation, otherwise indentation will be inferred from the old node + */ + indentation?: number; + /** + * Text of inserted node will be formatted with this delta, otherwise delta will be inferred from the new node kind + */ + delta?: number; + } + + export type ChangeNodeOptions = ConfigurableStartEnd & InsertNodeOptions; + + interface Change { + readonly sourceFile: SourceFile; + readonly range: TextRange; + readonly oldNode?: Node; + readonly node?: Node; + readonly options?: ChangeNodeOptions; + } + + export function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStart, forDeleteOperation: boolean) { + if (options.useNonAdjustedStartPosition) { + return node.getFullStart(); + } + const fullStart = node.getFullStart(); + const start = node.getStart(sourceFile); + if (fullStart === start) { + return start; + } + const fullStartLine = getLineStartPositionForPosition(fullStart, sourceFile); + const startLine = getLineStartPositionForPosition(start, sourceFile); + if (startLine === fullStartLine) { + // full start and start of the node are on the same line + // a, b; + // ^ ^ + // | start + // fullstart + // when b is replaced - we usually want to keep the leading trvia + // when b is deleted - we delete it + return forDeleteOperation ? fullStart : start; + } + // get start position of the line following the line that contains fullstart position + let adjustedStartPosition = getStartPositionOfLine(getLineOfLocalPosition(sourceFile, fullStartLine) + 1, sourceFile); + // skip whitespaces/newlines + adjustedStartPosition = skipTrivia(sourceFile.text, adjustedStartPosition, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + return getStartPositionOfLine(getLineOfLocalPosition(sourceFile, adjustedStartPosition), sourceFile); + } + + export function getAdjustedEndPosition(sourceFile: SourceFile, node: Node, options: ConfigurableEnd) { + if (options.useNonAdjustedEndPosition) { + return node.getEnd(); + } + const end = node.getEnd(); + const newEnd = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true); + // check if last character before newPos is linebreak + // if yes - considered all skipped trivia to be trailing trivia of the node + return newEnd !== end && isLineBreak(sourceFile.text.charCodeAt(newEnd - 1)) + ? newEnd + : end; + } + + function isSeparator(node: Node, separator: Node): boolean { + return node.parent && (separator.kind === SyntaxKind.CommaToken || (separator.kind === SyntaxKind.SemicolonToken && node.parent.kind === SyntaxKind.ObjectLiteralExpression)); + } + + export class ChangeTracker { + private changes: Change[] = []; + private readonly newLineCharacter: string; + + public static fromCodeFixContext(context: CodeFixContext) { + return new ChangeTracker(context.newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed, context.rulesProvider); + } + + constructor( + private readonly newLine: NewLineKind, + private readonly rulesProvider: formatting.RulesProvider, + private readonly validator?: (text: NonFormattedText) => void) { + this.newLineCharacter = getNewLineCharacter({ newLine }); + } + + public deleteNode(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = {}) { + const startPosition = getAdjustedStartPosition(sourceFile, node, options, /*forDeleteOperation*/ true); + const endPosition = getAdjustedEndPosition(sourceFile, node, options); + this.changes.push({ sourceFile, options, range: { pos: startPosition, end: endPosition } }); + return this; + } + + public deleteRange(sourceFile: SourceFile, range: TextRange) { + this.changes.push({ sourceFile, range }); + return this; + } + + public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = {}) { + const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, /*forDeleteOperation*/ true); + const endPosition = getAdjustedEndPosition(sourceFile, endNode, options); + this.changes.push({ sourceFile, options, range: { pos: startPosition, end: endPosition } }); + return this; + } + + public deleteNodeInList(sourceFile: SourceFile, node: Node) { + const containingList = formatting.SmartIndenter.getContainingList(node, sourceFile); + if (!containingList) { + return; + } + const index = containingList.indexOf(node); + if (index < 0) { + return this; + } + if (containingList.length === 1) { + this.deleteNode(sourceFile, node); + return this; + } + if (index !== containingList.length - 1) { + const nextToken = getTokenAtPosition(sourceFile, node.end); + if (nextToken && isSeparator(node, nextToken)) { + // find first non-whitespace position in the leading trivia of the node + const startPosition = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, /*forDeleteOperation*/ true), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + const nextElement = containingList[index + 1]; + /// find first non-whitespace position in the leading trivia of the next node + const endPosition = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, nextElement, {}, /*forDeleteOperation*/ true), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + // shift next node so its first non-whitespace position will be moved to the first non-whitespace position of the deleted node + this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); + } + } + else { + const previousToken = getTokenAtPosition(sourceFile, containingList[index - 1].end); + if (previousToken && isSeparator(node, previousToken)) { + this.deleteNodeRange(sourceFile, previousToken, node); + } + } + return this; + } + + public replaceRange(sourceFile: SourceFile, range: TextRange, newNode: Node, options: InsertNodeOptions = {}) { + this.changes.push({ sourceFile, range, options, node: newNode }); + return this; + } + + public replaceNode(sourceFile: SourceFile, oldNode: Node, newNode: Node, options: ChangeNodeOptions = {}) { + const startPosition = getAdjustedStartPosition(sourceFile, oldNode, options, /*forDeleteOperation*/ false); + const endPosition = getAdjustedEndPosition(sourceFile, oldNode, options); + this.changes.push({ sourceFile, options, oldNode, node: newNode, range: { pos: startPosition, end: endPosition } }); + return this; + } + + public replaceNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, newNode: Node, options: ChangeNodeOptions = {}) { + const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, /*forDeleteOperation*/ false); + const endPosition = getAdjustedEndPosition(sourceFile, endNode, options); + this.changes.push({ sourceFile, options, oldNode: startNode, node: newNode, range: { pos: startPosition, end: endPosition } }); + return this; + } + + public insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options: InsertNodeOptions = {}) { + this.changes.push({ sourceFile, options, node: newNode, range: { pos: pos, end: pos } }); + return this; + } + + public insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, options: InsertNodeOptions & ConfigurableStart = {}) { + const startPosition = getAdjustedStartPosition(sourceFile, before, options, /*forDeleteOperation*/ false); + this.changes.push({ sourceFile, options, oldNode: before, node: newNode, range: { pos: startPosition, end: startPosition } }); + return this; + } + + public insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node, options: InsertNodeOptions & ConfigurableEnd = {}) { + const endPosition = getAdjustedEndPosition(sourceFile, after, options); + this.changes.push({ sourceFile, options, oldNode: after, node: newNode, range: { pos: endPosition, end: endPosition } }); + return this; + } + + public getChanges(): FileTextChanges[] { + const changesPerFile = createFileMap(); + // group changes per file + for (const c of this.changes) { + let changesInFile = changesPerFile.get(c.sourceFile.path); + if (!changesInFile) { + changesPerFile.set(c.sourceFile.path, changesInFile = []); + }; + changesInFile.push(c); + } + // convert changes + const fileChangesList: FileTextChanges[] = []; + changesPerFile.forEachValue(path => { + const changesInFile = changesPerFile.get(path); + const sourceFile = changesInFile[0].sourceFile; + ChangeTracker.normalize(changesInFile); + + const fileTextChanges: FileTextChanges = { fileName: sourceFile.fileName, textChanges: [] }; + for (const c of changesInFile) { + fileTextChanges.textChanges.push({ + span: this.computeSpan(c, sourceFile), + newText: this.computeNewText(c, sourceFile) + }); + } + fileChangesList.push(fileTextChanges); + }); + + return fileChangesList; + } + + private computeSpan(change: Change, _sourceFile: SourceFile): TextSpan { + return createTextSpanFromBounds(change.range.pos, change.range.end); + } + + private computeNewText(change: Change, sourceFile: SourceFile): string { + if (!change.node) { + // deletion case + return ""; + } + const options = change.options || {}; + const nonFormattedText = getNonformattedText(change.node, sourceFile, this.newLine); + if (this.validator) { + this.validator(nonFormattedText); + } + + const formatOptions = this.rulesProvider.getFormatOptions(); + const pos = change.range.pos; + const posStartsLine = getLineStartPositionForPosition(pos, sourceFile) === pos; + + const initialIndentation = + change.options.indentation !== undefined + ? change.options.indentation + : change.oldNode + ? formatting.SmartIndenter.getIndentation(change.range.pos, sourceFile, formatOptions, posStartsLine || change.options.insertLeadingNewLine) + : 0; + const delta = + change.options.delta !== undefined + ? change.options.delta + : formatting.SmartIndenter.shouldIndentChildNode(change.node) + ? formatOptions.indentSize + : 0; + + let text = applyFormatting(nonFormattedText, sourceFile, initialIndentation, delta, this.rulesProvider); + // strip initial indentation (spaces or tabs) if text will be inserted in the middle of the line + text = posStartsLine ? text : text.replace(/^\s+/, ""); + + if (options.insertLeadingNewLine) { + text = this.newLineCharacter + text; + } + if (options.insertTrailingNewLine) { + text = text + this.newLineCharacter; + } + return text; + } + + private static normalize(changes: Change[]) { + // order changes by start position + changes.sort((a, b) => a.range.pos - b.range.pos); + // verify that end position of the change is less than start position of the next change + for (let i = 0; i < changes.length - 2; i++) { + Debug.assert(changes[i].range.end <= changes[i + 1].range.pos); + } + } + } + + export interface NonFormattedText { + readonly text: string; + readonly node: Node; + } + + export function getNonformattedText(node: Node, sourceFile: SourceFile, newLine: NewLineKind): NonFormattedText { + const options = { newLine, target: sourceFile.languageVersion }; + const writer = new Writer(getNewLineCharacter(options)); + const printer = createPrinter(options, writer); + printer.writeNode(EmitHint.Unspecified, node, sourceFile, writer); + return { text: writer.getText(), node: assignPositionsToNode(node) }; + } + + export function applyFormatting(nonFormattedText: NonFormattedText, sourceFile: SourceFile, initialIndentation: number, delta: number, rulesProvider: formatting.RulesProvider) { + const lineMap = computeLineStarts(nonFormattedText.text); + const file: SourceFileLike = { + text: nonFormattedText.text, + lineMap, + getLineAndCharacterOfPosition: pos => computeLineAndCharacterOfPosition(lineMap, pos) + }; + const changes = formatting.formatNode(nonFormattedText.node, file, sourceFile.languageVariant, initialIndentation, delta, rulesProvider); + return applyChanges(nonFormattedText.text, changes); + } + + export function applyChanges(text: string, changes: TextChange[]): string { + for (let i = changes.length - 1; i >= 0; i--) { + const change = changes[i]; + text = `${text.substring(0, change.span.start)}${change.newText}${text.substring(textSpanEnd(change.span))}`; + } + return text; + } + + function isTrivia(s: string) { + return skipTrivia(s, 0) === s.length; + } + + const nullTransformationContext: TransformationContext = { + enableEmitNotification: noop, + enableSubstitution: noop, + endLexicalEnvironment: () => undefined, + getCompilerOptions: notImplemented, + getEmitHost: notImplemented, + getEmitResolver: notImplemented, + hoistFunctionDeclaration: noop, + hoistVariableDeclaration: noop, + isEmitNotificationEnabled: notImplemented, + isSubstitutionEnabled: notImplemented, + onEmitNode: noop, + onSubstituteNode: notImplemented, + readEmitHelpers: notImplemented, + requestEmitHelper: noop, + resumeLexicalEnvironment: noop, + startLexicalEnvironment: noop, + suspendLexicalEnvironment: noop + }; + + function assignPositionsToNode(node: Node): Node { + const visited = visitEachChild(node, assignPositionsToNode, nullTransformationContext, assignPositionsToNodeArray); + // create proxy node for non synthesized nodes + const newNode = nodeIsSynthesized(visited) + ? visited + : (Proxy.prototype = visited, new (Proxy)()); + newNode.pos = getPos(node); + newNode.end = getEnd(node); + return newNode; + + function Proxy() { } + } + + function assignPositionsToNodeArray(nodes: NodeArray, visitor: Visitor, test?: (node: Node) => boolean, start?: number, count?: number) { + const visited = visitNodes(nodes, visitor, test, start, count); + if (!visited) { + return visited; + } + // clone nodearray if necessary + const nodeArray = visited === nodes ? createNodeArray(visited) : visited; + nodeArray.pos = getPos(nodes); + nodeArray.end = getEnd(nodes); + return nodeArray; + } + + class Writer implements EmitTextWriter, PrintHandlers { + private lastNonTriviaPosition = 0; + private readonly writer: EmitTextWriter; + + public readonly onEmitNode: PrintHandlers["onEmitNode"]; + public readonly onBeforeEmitNodeArray: PrintHandlers["onBeforeEmitNodeArray"]; + public readonly onAfterEmitNodeArray: PrintHandlers["onAfterEmitNodeArray"]; + + constructor(newLine: string) { + this.writer = createTextWriter(newLine); + this.onEmitNode = (hint, node, printCallback) => { + setPos(node, this.lastNonTriviaPosition); + printCallback(hint, node); + setEnd(node, this.lastNonTriviaPosition); + }; + this.onBeforeEmitNodeArray = nodes => { + if (nodes) { + setPos(nodes, this.lastNonTriviaPosition); + } + }; + this.onAfterEmitNodeArray = nodes => { + if (nodes) { + setEnd(nodes, this.lastNonTriviaPosition); + } + }; + } + + private setLastNonTriviaPosition(s: string, force: boolean) { + if (force || !isTrivia(s)) { + this.lastNonTriviaPosition = this.writer.getTextPos(); + let i = 0; + while (isWhiteSpace(s.charCodeAt(s.length - i - 1))) { + i++; + } + // trim trailing whitespaces + this.lastNonTriviaPosition -= i; + } + } + + write(s: string): void { + this.writer.write(s); + this.setLastNonTriviaPosition(s, /*force*/ false); + } + writeTextOfNode(text: string, node: Node): void { + this.writer.writeTextOfNode(text, node); + } + writeLine(): void { + this.writer.writeLine(); + } + increaseIndent(): void { + this.writer.increaseIndent(); + } + decreaseIndent(): void { + this.writer.decreaseIndent(); + } + getText(): string { + return this.writer.getText(); + } + rawWrite(s: string): void { + this.writer.rawWrite(s); + this.setLastNonTriviaPosition(s, /*force*/ false); + } + writeLiteral(s: string): void { + this.writer.writeLiteral(s); + this.setLastNonTriviaPosition(s, /*force*/ true); + } + getTextPos(): number { + return this.writer.getTextPos(); + } + getLine(): number { + return this.writer.getLine(); + } + getColumn(): number { + return this.writer.getColumn(); + } + getIndent(): number { + return this.writer.getIndent(); + } + isAtStartOfLine(): boolean { + return this.writer.isAtStartOfLine(); + } + reset(): void { + this.writer.reset(); + this.lastNonTriviaPosition = 0; + } + } +} \ No newline at end of file diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index ae2150d981..f5cd2cd475 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -62,6 +62,7 @@ "shims.ts", "signatureHelp.ts", "symbolDisplay.ts", + "textChanges.ts", "formatting/formatting.ts", "formatting/formattingContext.ts", "formatting/formattingRequestKind.ts", diff --git a/src/services/types.ts b/src/services/types.ts index c7852db6d6..149bb4340d 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -4,7 +4,11 @@ namespace ts { getChildCount(sourceFile?: SourceFile): number; getChildAt(index: number, sourceFile?: SourceFile): Node; getChildren(sourceFile?: SourceFile): Node[]; + /* @internal */ + getChildren(sourceFile?: SourceFileLike): Node[]; getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number; + /* @internal */ + getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number; getFullStart(): number; getEnd(): number; getWidth(sourceFile?: SourceFile): number; @@ -59,6 +63,10 @@ namespace ts { update(newText: string, textChangeRange: TextChangeRange): SourceFile; } + export interface SourceFileLike { + getLineAndCharacterOfPosition(pos: number): LineAndCharacter; + } + /** * Represents an immutable snapshot of a script at a specified time.Once acquired, the * snapshot is observably immutable. i.e. the same calls with the same parameters will return @@ -248,7 +256,7 @@ namespace ts { isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean; - getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): CodeAction[]; + getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[], formatOptions: FormatCodeSettings): CodeAction[]; getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index a4f574b9c2..0ffd5d27bc 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -394,8 +394,8 @@ namespace ts { list: Node; } - export function getLineStartPositionForPosition(position: number, sourceFile: SourceFile): number { - const lineStarts = sourceFile.getLineStarts(); + export function getLineStartPositionForPosition(position: number, sourceFile: SourceFileLike): number { + const lineStarts = getLineStarts(sourceFile); const line = sourceFile.getLineAndCharacterOfPosition(position).line; return lineStarts[line]; } @@ -604,7 +604,7 @@ namespace ts { return !!findChildOfKind(n, kind, sourceFile); } - export function findChildOfKind(n: Node, kind: SyntaxKind, sourceFile?: SourceFile): Node | undefined { + export function findChildOfKind(n: Node, kind: SyntaxKind, sourceFile?: SourceFileLike): Node | undefined { return forEach(n.getChildren(sourceFile), c => c.kind === kind && c); } @@ -1003,10 +1003,6 @@ namespace ts { return undefined; } - export function isToken(n: Node): boolean { - return n.kind >= SyntaxKind.FirstToken && n.kind <= SyntaxKind.LastToken; - } - export function isWord(kind: SyntaxKind): boolean { return kind === SyntaxKind.Identifier || isKeyword(kind); } @@ -1384,8 +1380,8 @@ namespace ts { }; } - export function getOpenBraceEnd(constructor: ConstructorDeclaration, sourceFile: SourceFile) { + export function getOpenBrace(constructor: ConstructorDeclaration, sourceFile: SourceFile) { // First token is the open curly, this is where we want to put the 'super' call. - return constructor.body.getFirstToken(sourceFile).getEnd(); + return constructor.body.getFirstToken(sourceFile); } } diff --git a/tests/baselines/reference/textChanges/deleteNode1.js b/tests/baselines/reference/textChanges/deleteNode1.js new file mode 100644 index 0000000000..c73c0eaf9f --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNode1.js @@ -0,0 +1,13 @@ +===ORIGINAL=== + +var x = 1; // some comment - 1 +/** + * comment 2 + */ +var y = 2; // comment 3 +var z = 3; // comment 4 + +===MODIFIED=== + +var x = 1; // some comment - 1 +var z = 3; // comment 4 diff --git a/tests/baselines/reference/textChanges/deleteNode2.js b/tests/baselines/reference/textChanges/deleteNode2.js new file mode 100644 index 0000000000..828a9b5b8f --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNode2.js @@ -0,0 +1,12 @@ +===ORIGINAL=== + +var x = 1; // some comment - 1 +/** + * comment 2 + */ +var y = 2; // comment 3 +var z = 3; // comment 4 + +===MODIFIED=== + +var x = 1;var z = 3; // comment 4 diff --git a/tests/baselines/reference/textChanges/deleteNode3.js b/tests/baselines/reference/textChanges/deleteNode3.js new file mode 100644 index 0000000000..acb26c7daa --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNode3.js @@ -0,0 +1,14 @@ +===ORIGINAL=== + +var x = 1; // some comment - 1 +/** + * comment 2 + */ +var y = 2; // comment 3 +var z = 3; // comment 4 + +===MODIFIED=== + +var x = 1; // some comment - 1 + // comment 3 +var z = 3; // comment 4 diff --git a/tests/baselines/reference/textChanges/deleteNode4.js b/tests/baselines/reference/textChanges/deleteNode4.js new file mode 100644 index 0000000000..b6edb25bc5 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNode4.js @@ -0,0 +1,13 @@ +===ORIGINAL=== + +var x = 1; // some comment - 1 +/** + * comment 2 + */ +var y = 2; // comment 3 +var z = 3; // comment 4 + +===MODIFIED=== + +var x = 1; // comment 3 +var z = 3; // comment 4 diff --git a/tests/baselines/reference/textChanges/deleteNode5.js b/tests/baselines/reference/textChanges/deleteNode5.js new file mode 100644 index 0000000000..6f896535f0 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNode5.js @@ -0,0 +1,16 @@ +===ORIGINAL=== + +var x = 1; // some comment - 1 +/** + * comment 2 + */ +var y = 2; // comment 3 +var z = 3; // comment 4 + +===MODIFIED=== + +/** + * comment 2 + */ +var y = 2; // comment 3 +var z = 3; // comment 4 diff --git a/tests/baselines/reference/textChanges/deleteNodeInList1.js b/tests/baselines/reference/textChanges/deleteNodeInList1.js new file mode 100644 index 0000000000..b603913566 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList1.js @@ -0,0 +1,4 @@ +===ORIGINAL=== +var a = 1, b = 2, c = 3; +===MODIFIED=== +var b = 2, c = 3; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList10.js b/tests/baselines/reference/textChanges/deleteNodeInList10.js new file mode 100644 index 0000000000..46da605a71 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList10.js @@ -0,0 +1,10 @@ +===ORIGINAL=== + +function foo(a: number,b: string,c = true) { + return 1; +} +===MODIFIED=== + +function foo(b: string,c = true) { + return 1; +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList11.js b/tests/baselines/reference/textChanges/deleteNodeInList11.js new file mode 100644 index 0000000000..f5aa666e52 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList11.js @@ -0,0 +1,10 @@ +===ORIGINAL=== + +function foo(a: number,b: string,c = true) { + return 1; +} +===MODIFIED=== + +function foo(a: number,c = true) { + return 1; +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList12.js b/tests/baselines/reference/textChanges/deleteNodeInList12.js new file mode 100644 index 0000000000..db05ef06db --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList12.js @@ -0,0 +1,10 @@ +===ORIGINAL=== + +function foo(a: number,b: string,c = true) { + return 1; +} +===MODIFIED=== + +function foo(a: number,b: string) { + return 1; +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList13.js b/tests/baselines/reference/textChanges/deleteNodeInList13.js new file mode 100644 index 0000000000..599d3a31d4 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList13.js @@ -0,0 +1,15 @@ +===ORIGINAL=== + +function foo( + a: number, + b: string, + c = true) { + return 1; +} +===MODIFIED=== + +function foo( + b: string, + c = true) { + return 1; +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList14.js b/tests/baselines/reference/textChanges/deleteNodeInList14.js new file mode 100644 index 0000000000..da3596b38b --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList14.js @@ -0,0 +1,15 @@ +===ORIGINAL=== + +function foo( + a: number, + b: string, + c = true) { + return 1; +} +===MODIFIED=== + +function foo( + a: number, + c = true) { + return 1; +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList15.js b/tests/baselines/reference/textChanges/deleteNodeInList15.js new file mode 100644 index 0000000000..02f649dabd --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList15.js @@ -0,0 +1,15 @@ +===ORIGINAL=== + +function foo( + a: number, + b: string, + c = true) { + return 1; +} +===MODIFIED=== + +function foo( + a: number, + b: string) { + return 1; +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList1_1.js b/tests/baselines/reference/textChanges/deleteNodeInList1_1.js new file mode 100644 index 0000000000..87709601c4 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList1_1.js @@ -0,0 +1,4 @@ +===ORIGINAL=== +var a = 1,b = 2,c = 3; +===MODIFIED=== +var b = 2,c = 3; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList2.js b/tests/baselines/reference/textChanges/deleteNodeInList2.js new file mode 100644 index 0000000000..18e4a0cd30 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList2.js @@ -0,0 +1,4 @@ +===ORIGINAL=== +var a = 1, b = 2, c = 3; +===MODIFIED=== +var a = 1, c = 3; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList2_1.js b/tests/baselines/reference/textChanges/deleteNodeInList2_1.js new file mode 100644 index 0000000000..b4098a8930 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList2_1.js @@ -0,0 +1,4 @@ +===ORIGINAL=== +var a = 1,b = 2,c = 3; +===MODIFIED=== +var a = 1,c = 3; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList3.js b/tests/baselines/reference/textChanges/deleteNodeInList3.js new file mode 100644 index 0000000000..1c6ff4aa29 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList3.js @@ -0,0 +1,4 @@ +===ORIGINAL=== +var a = 1, b = 2, c = 3; +===MODIFIED=== +var a = 1, b = 2; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList3_1.js b/tests/baselines/reference/textChanges/deleteNodeInList3_1.js new file mode 100644 index 0000000000..ead2a1a42f --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList3_1.js @@ -0,0 +1,4 @@ +===ORIGINAL=== +var a = 1,b = 2,c = 3; +===MODIFIED=== +var a = 1,b = 2; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList4.js b/tests/baselines/reference/textChanges/deleteNodeInList4.js new file mode 100644 index 0000000000..61bdf9159c --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList4.js @@ -0,0 +1,13 @@ +===ORIGINAL=== + +namespace M { + var a = 1, + b = 2, + c = 3; +} +===MODIFIED=== + +namespace M { + var b = 2, + c = 3; +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList4_1.js b/tests/baselines/reference/textChanges/deleteNodeInList4_1.js new file mode 100644 index 0000000000..19ff5475d1 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList4_1.js @@ -0,0 +1,17 @@ +===ORIGINAL=== + +namespace M { + var a = 1, // comment 1 + // comment 2 + b = 2, // comment 3 + // comment 4 + c = 3; // comment 5 +} +===MODIFIED=== + +namespace M { + var // comment 2 + b = 2, // comment 3 + // comment 4 + c = 3; // comment 5 +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList5.js b/tests/baselines/reference/textChanges/deleteNodeInList5.js new file mode 100644 index 0000000000..0ed65877d2 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList5.js @@ -0,0 +1,13 @@ +===ORIGINAL=== + +namespace M { + var a = 1, + b = 2, + c = 3; +} +===MODIFIED=== + +namespace M { + var a = 1, + c = 3; +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList5_1.js b/tests/baselines/reference/textChanges/deleteNodeInList5_1.js new file mode 100644 index 0000000000..224b92481d --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList5_1.js @@ -0,0 +1,16 @@ +===ORIGINAL=== + +namespace M { + var a = 1, // comment 1 + // comment 2 + b = 2, // comment 3 + // comment 4 + c = 3; // comment 5 +} +===MODIFIED=== + +namespace M { + var a = 1, // comment 1 + // comment 4 + c = 3; // comment 5 +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList6.js b/tests/baselines/reference/textChanges/deleteNodeInList6.js new file mode 100644 index 0000000000..4093329023 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList6.js @@ -0,0 +1,13 @@ +===ORIGINAL=== + +namespace M { + var a = 1, + b = 2, + c = 3; +} +===MODIFIED=== + +namespace M { + var a = 1, + b = 2; +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList6_1.js b/tests/baselines/reference/textChanges/deleteNodeInList6_1.js new file mode 100644 index 0000000000..6acc20aa82 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList6_1.js @@ -0,0 +1,16 @@ +===ORIGINAL=== + +namespace M { + var a = 1, // comment 1 + // comment 2 + b = 2, // comment 3 + // comment 4 + c = 3; // comment 5 +} +===MODIFIED=== + +namespace M { + var a = 1, // comment 1 + // comment 2 + b = 2; // comment 5 +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList7.js b/tests/baselines/reference/textChanges/deleteNodeInList7.js new file mode 100644 index 0000000000..564ff3f86a --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList7.js @@ -0,0 +1,10 @@ +===ORIGINAL=== + +function foo(a: number, b: string, c = true) { + return 1; +} +===MODIFIED=== + +function foo(b: string, c = true) { + return 1; +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList8.js b/tests/baselines/reference/textChanges/deleteNodeInList8.js new file mode 100644 index 0000000000..b3a0a0f4c4 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList8.js @@ -0,0 +1,10 @@ +===ORIGINAL=== + +function foo(a: number, b: string, c = true) { + return 1; +} +===MODIFIED=== + +function foo(a: number, c = true) { + return 1; +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeInList9.js b/tests/baselines/reference/textChanges/deleteNodeInList9.js new file mode 100644 index 0000000000..96878a6086 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeInList9.js @@ -0,0 +1,10 @@ +===ORIGINAL=== + +function foo(a: number, b: string, c = true) { + return 1; +} +===MODIFIED=== + +function foo(a: number, b: string) { + return 1; +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeRange1.js b/tests/baselines/reference/textChanges/deleteNodeRange1.js new file mode 100644 index 0000000000..f9fa45ebb1 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeRange1.js @@ -0,0 +1,16 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 + +===MODIFIED=== + +// comment 1 +var x = 1; // comment 2 +// comment 6 +var a = 4; // comment 7 diff --git a/tests/baselines/reference/textChanges/deleteNodeRange2.js b/tests/baselines/reference/textChanges/deleteNodeRange2.js new file mode 100644 index 0000000000..aacea6ff5e --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeRange2.js @@ -0,0 +1,15 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 + +===MODIFIED=== + +// comment 1 +var x = 1;// comment 6 +var a = 4; // comment 7 diff --git a/tests/baselines/reference/textChanges/deleteNodeRange3.js b/tests/baselines/reference/textChanges/deleteNodeRange3.js new file mode 100644 index 0000000000..bb2bb36ca2 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeRange3.js @@ -0,0 +1,17 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 + +===MODIFIED=== + +// comment 1 +var x = 1; // comment 2 + // comment 5 +// comment 6 +var a = 4; // comment 7 diff --git a/tests/baselines/reference/textChanges/deleteNodeRange4.js b/tests/baselines/reference/textChanges/deleteNodeRange4.js new file mode 100644 index 0000000000..3e9ad960b1 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeRange4.js @@ -0,0 +1,16 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 + +===MODIFIED=== + +// comment 1 +var x = 1; // comment 5 +// comment 6 +var a = 4; // comment 7 diff --git a/tests/baselines/reference/textChanges/deleteRange1.js b/tests/baselines/reference/textChanges/deleteRange1.js new file mode 100644 index 0000000000..362e678452 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteRange1.js @@ -0,0 +1,15 @@ +===ORIGINAL=== + +function foo() { + return 1; +} + +function bar() { + return 2; +} + +===MODIFIED=== + +function bar() { + return 2; +} diff --git a/tests/baselines/reference/textChanges/extractMethodLike.js b/tests/baselines/reference/textChanges/extractMethodLike.js new file mode 100644 index 0000000000..fa1d625dc4 --- /dev/null +++ b/tests/baselines/reference/textChanges/extractMethodLike.js @@ -0,0 +1,49 @@ +===ORIGINAL=== + +namespace M +{ + namespace M2 + { + function foo() { + // comment 1 + const x = 1; + + /** + * comment 2 line 1 + * comment 2 line 2 + */ + function f() { + return 100; + } + const y = 2; // comment 3 + return 1; + } + } +} +===MODIFIED=== + +namespace M +{ + function bar(): any + { + /** + * comment 2 line 1 + * comment 2 line 2 + */ + function f() + { + return 100; + } + const y = 2; // comment 3 + return 1; + } + namespace M2 + { + function foo() { + // comment 1 + const x = 1; + + return bar(); + } + } +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeAfter1.js b/tests/baselines/reference/textChanges/insertNodeAfter1.js new file mode 100644 index 0000000000..e6d7387f6b --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeAfter1.js @@ -0,0 +1,26 @@ +===ORIGINAL=== + +namespace M { + // comment 1 + var x = 1; // comment 2 + // comment 3 + var y; // comment 4 + var z = 3; // comment 5 + // comment 6 + var a = 4; // comment 7 +} +===MODIFIED=== + +namespace M { + // comment 1 + var x = 1; // comment 2 + // comment 3 + var y; // comment 4 + public class class1 implements interface1 + { + property1: boolean; + } + var z = 3; // comment 5 + // comment 6 + var a = 4; // comment 7 +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeAfter2.js b/tests/baselines/reference/textChanges/insertNodeAfter2.js new file mode 100644 index 0000000000..7c027e35b5 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeAfter2.js @@ -0,0 +1,26 @@ +===ORIGINAL=== + +namespace M { + // comment 1 + var x = 1; // comment 2 + // comment 3 + var y; // comment 4 + var z = 3; // comment 5 + // comment 6 + var a = 4; // comment 7 +} +===MODIFIED=== + +namespace M { + // comment 1 + var x = 1; // comment 2 + // comment 3 + var y; // comment 4 + var z = 3; // comment 5 + // comment 6 + var a = 4; // comment 7 +} +public class class1 implements interface1 +{ + property1: boolean; +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeAfter3-block with newline.js b/tests/baselines/reference/textChanges/insertNodeAfter3-block with newline.js new file mode 100644 index 0000000000..41bfb72b57 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeAfter3-block with newline.js @@ -0,0 +1,16 @@ +===ORIGINAL=== + +class A { + constructor() { + + } +} + +===MODIFIED=== + +class A { + constructor() { + super(); + + } +} diff --git a/tests/baselines/reference/textChanges/insertNodeAfter3.js b/tests/baselines/reference/textChanges/insertNodeAfter3.js new file mode 100644 index 0000000000..3765bbaf03 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeAfter3.js @@ -0,0 +1,14 @@ +===ORIGINAL=== + +class A { + constructor() { + } +} + +===MODIFIED=== + +class A { + constructor() { + super(); + } +} diff --git a/tests/baselines/reference/textChanges/insertNodeAfter4.js b/tests/baselines/reference/textChanges/insertNodeAfter4.js new file mode 100644 index 0000000000..3ca51db498 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeAfter4.js @@ -0,0 +1,16 @@ +===ORIGINAL=== + +class A { + constructor() { + var x = 1; + } +} + +===MODIFIED=== + +class A { + constructor() { + var x = 1; + super(); + } +} diff --git a/tests/baselines/reference/textChanges/insertNodeAt1.js b/tests/baselines/reference/textChanges/insertNodeAt1.js new file mode 100644 index 0000000000..9c7ac46f8a --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeAt1.js @@ -0,0 +1,22 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 +===MODIFIED=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +public class class1 implements interface1 +{ + property1: boolean; +} +var y; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeAt2.js b/tests/baselines/reference/textChanges/insertNodeAt2.js new file mode 100644 index 0000000000..81c3e2afc3 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeAt2.js @@ -0,0 +1,20 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 +===MODIFIED=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var yz1 = { + p1: 1 +}; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeBefore1.js b/tests/baselines/reference/textChanges/insertNodeBefore1.js new file mode 100644 index 0000000000..deebe10b69 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeBefore1.js @@ -0,0 +1,26 @@ +===ORIGINAL=== + +namespace M { + // comment 1 + var x = 1; // comment 2 + // comment 3 + var y; // comment 4 + var z = 3; // comment 5 + // comment 6 + var a = 4; // comment 7 +} +===MODIFIED=== + +namespace M { + // comment 1 + var x = 1; // comment 2 + public class class1 implements interface1 + { + property1: boolean; + } + // comment 3 + var y; // comment 4 + var z = 3; // comment 5 + // comment 6 + var a = 4; // comment 7 +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeBefore2.js b/tests/baselines/reference/textChanges/insertNodeBefore2.js new file mode 100644 index 0000000000..1792cd5e69 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeBefore2.js @@ -0,0 +1,26 @@ +===ORIGINAL=== + +namespace M { + // comment 1 + var x = 1; // comment 2 + // comment 3 + var y; // comment 4 + var z = 3; // comment 5 + // comment 6 + var a = 4; // comment 7 +} +===MODIFIED=== + +public class class1 implements interface1 +{ + property1: boolean; +} +namespace M { + // comment 1 + var x = 1; // comment 2 + // comment 3 + var y; // comment 4 + var z = 3; // comment 5 + // comment 6 + var a = 4; // comment 7 +} \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/replaceNode1.js b/tests/baselines/reference/textChanges/replaceNode1.js new file mode 100644 index 0000000000..8511d51038 --- /dev/null +++ b/tests/baselines/reference/textChanges/replaceNode1.js @@ -0,0 +1,20 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 +===MODIFIED=== + +// comment 1 +var x = 1; // comment 2 +public class class1 implements interface1 +{ + property1: boolean; +} +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/replaceNode1NoLineBreakBefore.js b/tests/baselines/reference/textChanges/replaceNode1NoLineBreakBefore.js new file mode 100644 index 0000000000..44183d402a --- /dev/null +++ b/tests/baselines/reference/textChanges/replaceNode1NoLineBreakBefore.js @@ -0,0 +1,13 @@ +===ORIGINAL=== + +namespace A { + const x = 1, y = "2"; +} + +===MODIFIED=== + +namespace A { + const x = 1, z1 = { + p1: 1 + }; +} diff --git a/tests/baselines/reference/textChanges/replaceNode2.js b/tests/baselines/reference/textChanges/replaceNode2.js new file mode 100644 index 0000000000..66a17f7c91 --- /dev/null +++ b/tests/baselines/reference/textChanges/replaceNode2.js @@ -0,0 +1,20 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 +===MODIFIED=== + +// comment 1 +var x = 1; +public class class1 implements interface1 +{ + property1: boolean; +} +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/replaceNode3.js b/tests/baselines/reference/textChanges/replaceNode3.js new file mode 100644 index 0000000000..9f321a9743 --- /dev/null +++ b/tests/baselines/reference/textChanges/replaceNode3.js @@ -0,0 +1,21 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 +===MODIFIED=== + +// comment 1 +var x = 1; // comment 2 +public class class1 implements interface1 +{ + property1: boolean; +} + // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/replaceNode4.js b/tests/baselines/reference/textChanges/replaceNode4.js new file mode 100644 index 0000000000..e5bbf0e306 --- /dev/null +++ b/tests/baselines/reference/textChanges/replaceNode4.js @@ -0,0 +1,19 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 +===MODIFIED=== + +// comment 1 +var x = 1;public class class1 implements interface1 +{ + property1: boolean; +} // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/replaceNode5.js b/tests/baselines/reference/textChanges/replaceNode5.js new file mode 100644 index 0000000000..ad3ec0ab0e --- /dev/null +++ b/tests/baselines/reference/textChanges/replaceNode5.js @@ -0,0 +1,19 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 +===MODIFIED=== +public class class1 implements interface1 +{ + property1: boolean; +} // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/replaceNodeRange1.js b/tests/baselines/reference/textChanges/replaceNodeRange1.js new file mode 100644 index 0000000000..5fc16960a8 --- /dev/null +++ b/tests/baselines/reference/textChanges/replaceNodeRange1.js @@ -0,0 +1,19 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 +===MODIFIED=== + +// comment 1 +var x = 1; // comment 2 +public class class1 implements interface1 +{ + property1: boolean; +} +// comment 6 +var a = 4; // comment 7 \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/replaceNodeRange2.js b/tests/baselines/reference/textChanges/replaceNodeRange2.js new file mode 100644 index 0000000000..e8f9506d60 --- /dev/null +++ b/tests/baselines/reference/textChanges/replaceNodeRange2.js @@ -0,0 +1,19 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 +===MODIFIED=== + +// comment 1 +var x = 1; +public class class1 implements interface1 +{ + property1: boolean; +} +// comment 6 +var a = 4; // comment 7 \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/replaceNodeRange3.js b/tests/baselines/reference/textChanges/replaceNodeRange3.js new file mode 100644 index 0000000000..a20dddc53f --- /dev/null +++ b/tests/baselines/reference/textChanges/replaceNodeRange3.js @@ -0,0 +1,20 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 +===MODIFIED=== + +// comment 1 +var x = 1; // comment 2 +public class class1 implements interface1 +{ + property1: boolean; +} + // comment 5 +// comment 6 +var a = 4; // comment 7 \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/replaceNodeRange4.js b/tests/baselines/reference/textChanges/replaceNodeRange4.js new file mode 100644 index 0000000000..ad50a1f01f --- /dev/null +++ b/tests/baselines/reference/textChanges/replaceNodeRange4.js @@ -0,0 +1,18 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 +===MODIFIED=== + +// comment 1 +var x = 1;public class class1 implements interface1 +{ + property1: boolean; +} // comment 5 +// comment 6 +var a = 4; // comment 7 \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/replaceRange.js b/tests/baselines/reference/textChanges/replaceRange.js new file mode 100644 index 0000000000..8cb8328616 --- /dev/null +++ b/tests/baselines/reference/textChanges/replaceRange.js @@ -0,0 +1,19 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 +===MODIFIED=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +public class class1 implements interface1 +{ + property1: boolean; +} +var a = 4; // comment 7 \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/replaceRangeNoLineBreakBefore.js b/tests/baselines/reference/textChanges/replaceRangeNoLineBreakBefore.js new file mode 100644 index 0000000000..72a45ee56e --- /dev/null +++ b/tests/baselines/reference/textChanges/replaceRangeNoLineBreakBefore.js @@ -0,0 +1,6 @@ +===ORIGINAL=== +const x = 1, y = "2"; +===MODIFIED=== +const x = 1, z1 = { + p1: 1 +}; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/replaceRangeWithForcedIndentation.js b/tests/baselines/reference/textChanges/replaceRangeWithForcedIndentation.js new file mode 100644 index 0000000000..304b81b135 --- /dev/null +++ b/tests/baselines/reference/textChanges/replaceRangeWithForcedIndentation.js @@ -0,0 +1,19 @@ +===ORIGINAL=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 +var y = 2; // comment 4 +var z = 3; // comment 5 +// comment 6 +var a = 4; // comment 7 +===MODIFIED=== + +// comment 1 +var x = 1; // comment 2 +// comment 3 + public class class1 implements interface1 + { + property1: boolean; + } +var a = 4; // comment 7 \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixAddForgottenThis01.ts b/tests/cases/fourslash/codeFixAddForgottenThis01.ts index 90d191b6ba..07f0634821 100644 --- a/tests/cases/fourslash/codeFixAddForgottenThis01.ts +++ b/tests/cases/fourslash/codeFixAddForgottenThis01.ts @@ -2,9 +2,11 @@ ////class C { //// foo: number; -//// constructor() { -//// [|foo = 10|]; -//// } +//// constructor() {[| +//// foo = 10; +//// |]} ////} -verify.rangeAfterCodeFix("this.foo = 10"); \ No newline at end of file +verify.rangeAfterCodeFix(` + this.foo = 10; + `, /*includeWhitespace*/ true); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixSuperAfterThis.ts b/tests/cases/fourslash/codeFixSuperAfterThis.ts index 2bff98ceca..55b44b0788 100644 --- a/tests/cases/fourslash/codeFixSuperAfterThis.ts +++ b/tests/cases/fourslash/codeFixSuperAfterThis.ts @@ -6,8 +6,10 @@ //// private a:number; //// constructor() {[| //// this.a = 12; -//// super();|] -//// } +//// super(); +//// |]} ////} - -verify.rangeAfterCodeFix("super(); this.a = 12;"); \ No newline at end of file +verify.rangeAfterCodeFix(` + super(); + this.a = 12; + `, /*includeWhiteSpace*/ true); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixSuperCall.ts b/tests/cases/fourslash/codeFixSuperCall.ts index c918e74dc3..f7584050a1 100644 --- a/tests/cases/fourslash/codeFixSuperCall.ts +++ b/tests/cases/fourslash/codeFixSuperCall.ts @@ -1,10 +1,11 @@ -/// +/// ////class Base{ ////} ////class C extends Base{ -//// constructor() {[| |] -//// } +//// constructor() {[| +//// |]} ////} - -verify.rangeAfterCodeFix('super();'); +verify.rangeAfterCodeFix(` + super(); + `, /*includeWhitespace*/ true); diff --git a/tests/cases/fourslash/unusedFunctionInNamespace1.ts b/tests/cases/fourslash/unusedFunctionInNamespace1.ts index 288573608f..7a63ff13ca 100644 --- a/tests/cases/fourslash/unusedFunctionInNamespace1.ts +++ b/tests/cases/fourslash/unusedFunctionInNamespace1.ts @@ -8,5 +8,4 @@ //// } |] verify.rangeAfterCodeFix(`namespace greeter { - // some legit comments }`); diff --git a/tests/cases/fourslash/unusedVariableInForLoop7FS.ts b/tests/cases/fourslash/unusedVariableInForLoop7FS.ts index 5aa3251c0b..7f99863ba4 100644 --- a/tests/cases/fourslash/unusedVariableInForLoop7FS.ts +++ b/tests/cases/fourslash/unusedVariableInForLoop7FS.ts @@ -1,12 +1,16 @@ /// // @noUnusedLocals: true -//// function f1 () { -//// for (const elem of ["a", "b", "c"]) { -//// elem; -//// [|var x = 20; -//// }|] -////} +////function f1 () [|{ +//// for (const elem of ["a", "b", "c"]) { +//// elem; +//// var x = 20; +//// } +////}|] //// -verify.rangeAfterCodeFix("}"); +verify.rangeAfterCodeFix(`{ + for (const elem of ["a", "b", "c"]) { + elem; + } +}`, /*includeWhiteSpace*/ true); From fd65966a5028d3403dda934f97fe285cee427198 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Fri, 10 Mar 2017 21:18:18 -0800 Subject: [PATCH 02/65] temp --- src/compiler/checker.ts | 191 ++++++++++++++++++++++++++++++++++++++++ src/compiler/factory.ts | 37 +++++++- src/compiler/types.ts | 21 +++-- 3 files changed, 240 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index aa7908f52c..109e6f33ea 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2388,11 +2388,198 @@ namespace ts { } } + function createTypeNode(type: Type) { + let encounteredError = false; + let checkAlias = true; + + return createTypeNodeWorker(type); + + function createTypeNodeWorker(type: Type): TypeNode { + if(!type) { + return undefined; + } + + if (checkAlias && type.aliasSymbol) { + const name = getNameOfSymbol(type.aliasSymbol); + const typeArguments = mapToTypeNodeArray(type.aliasTypeArguments); + return createTypeReferenceNode(createIdentifier(name), typeArguments); + } + checkAlias = false; + + + if(type.flags & TypeFlags.Any) { + // TODO: add other case where type ends up being `any`. + return createKeywordTypeNode(SyntaxKind.StringKeyword); + } + if(type.flags & TypeFlags.String) { + return createKeywordTypeNode(SyntaxKind.StringKeyword); + } + if(type.flags & TypeFlags.Number) { + return createKeywordTypeNode(SyntaxKind.NumberKeyword); + } + if(type.flags & (TypeFlags.Boolean | TypeFlags.StringOrNumberLiteral)) { + // TODO: check if this actually works with boolean. + return createLiteralTypeNode((type).text); + } + if(type.flags & TypeFlags.Void) { + return createKeywordTypeNode(SyntaxKind.VoidKeyword); + } + if(type.flags & TypeFlags.Undefined) { + return createKeywordTypeNode(SyntaxKind.UndefinedKeyword); + } + if(type.flags & TypeFlags.Null) { + return createKeywordTypeNode(SyntaxKind.NullKeyword); + } + if(type.flags & TypeFlags.Never) { + return createKeywordTypeNode(SyntaxKind.NeverKeyword); + } + if(type.flags & TypeFlags.Enum) { + throw new Error("not implemented"); + } + if(type.flags & TypeFlags.ESSymbol) { + throw new Error("not implemented"); + } + if(type.flags & TypeFlags.TypeParameter) { + const constraint = createTypeNodeWorker(getConstraintFromTypeParameter(type)); + const defaultParameter = createTypeNodeWorker(getDefaultFromTypeParameter(type)); + if(!type.symbol) { + encounteredError = true; + throw new Error("No symbol for type parameter so can't get name"); + } + const name = getNameOfSymbol(type.symbol); + return createTypeParameterNode(name, constraint, defaultParameter); + } + if(type.flags & TypeFlags.Union) { + throw new Error("not implemented"); + } + if(type.flags & TypeFlags.Intersection) { + throw new Error("not implemented"); + } + if(type.flags & TypeFlags.Index) { + throw new Error("not implemented"); + } + if(type.flags & TypeFlags.IndexedAccess) { + throw new Error("not implemented"); + } + + // if(type.flags & TypeFlags.Object) { + // throw new Error("not implemented"); + // } + + // TODO: should these be within the if above (check with asserts) + const objectFlags = getObjectFlags(type); + + if (objectFlags & ObjectFlags.ClassOrInterface) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + // If type is a class or interface type that wasn't hit by the isSymbolAccessible check above, + // type must be an anonymous class or interface. + return false; + } + + if (objectFlags & ObjectFlags.Reference) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + // and vice versa. + // this case includes tuple types + const typeArguments = (type as TypeReference).typeArguments || emptyArray; + return allTypesVisible(typeArguments); + } + + // keyword types + // this type node + // function type node + // constructor type node + // type reference node + // type predicate node - is Foo (for return types) + // type query node -- typeof number + // type literal node (like object literal) + // array type + // tuple type + // union type + // might need parens + // intersection type + // Type operator node (eg (ie?): keyof T) + // IndexedAccess Type Node + // mapped type node + // literal type node + + // if (inTypeAlias && type.aliasSymbol) { + // return isSymbolAccessible(type.aliasSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/false).accessibility === SymbolAccessibility.Accessible + // && (!type.aliasTypeArguments || allTypesVisible(type.aliasTypeArguments)); + // } + // const typeSymbolAccessibility = type.symbol && isSymbolAccessible(type.symbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false).accessibility; + // if (type.flags & TypeFlags.TypeParameter) { + // if (inObjectLiteral && (type as TypeParameter).isThisType) { + // return false; + // } + // const constraint = getConstraintFromTypeParameter((type)); + // return typeSymbolAccessibility === SymbolAccessibility.Accessible + // && (!constraint || isTypeAccessibleWorker(constraint, inObjectLiteral, /*inTypeAlias*/false)); + // } + // if (typeSymbolAccessibility === SymbolAccessibility.Accessible) { + // return true; + // } + // if (type.flags & (TypeFlags.Intrinsic | TypeFlags.Literal)) { + // return true; + // } + // const objectFlags = getObjectFlags(type); + // if (objectFlags & ObjectFlags.ClassOrInterface) { + // // If type is a class or interface type that wasn't hit by the isSymbolAccessible check above, + // // type must be an anonymous class or interface. + // return false; + // } + // if (objectFlags & ObjectFlags.Reference) { + // // and vice versa. + // // this case includes tuple types + // const typeArguments = (type as TypeReference).typeArguments || emptyArray; + // return allTypesVisible(typeArguments); + // } + // if (type.flags & TypeFlags.UnionOrIntersection) { + // return allTypesVisible((type as UnionOrIntersectionType).types); + // } + + if (objectFlags & ObjectFlags.Mapped) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + const typeParameter = getTypeParameterFromMappedType(type); + const constraintType = getConstraintTypeFromMappedType(type); + const templateType = getTemplateTypeFromMappedType(type); + } + + if (objectFlags & ObjectFlags.Anonymous) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + // The type is an object literal type. + if (!type.symbol) { + // Anonymous types without symbols are literals. + return true; + } + // what case is this? + const members = type.symbol.members; + let allVisible = true; + members && members.forEach((member) => { + const memberType = getTypeOfSymbolAtLocation(member, enclosingDeclaration); + allVisible = allVisible && isTypeAccessibleWorker(memberType, /*inObjectLiteral*/ true, /*inTypeAlias*/false); + }); + return allVisible; + } + + Debug.fail("Should be unreachable here"); + + /** Note that mapToTypeNodeArray(undefined) === undefined. */ + function mapToTypeNodeArray(types: Type[]): NodeArray { + return asNodeArray(types && types.map(createTypeNodeWorker)); + } + // function allTypesVisible(types: Type[]): boolean { + // return types.every(type => isTypeAccessibleWorker(type, inObjectLiteral, /*inTypeAlias*/false)); + // } + } + } + function buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]) { const globalFlagsToPass = globalFlags & TypeFormatFlags.WriteOwnNameForAnyLike; let inObjectTypeLiteral = false; return writeType(type, globalFlags); + const typeNode = createTypeNode(type, enclosingDeclaration); + function writeType(type: Type, flags: TypeFormatFlags) { const nextFlags = flags & ~TypeFormatFlags.InTypeAlias; // Write undefined/null type as any @@ -6939,6 +7126,10 @@ namespace ts { } } + // export function synthesizeTypeNode(type: Type, enclosingDeclaration: Node): TypeNode { + // throw new Error("Not implemented" + enclosingDeclaration); + // } + function instantiateList(items: T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): T[] { if (items && items.length) { const result: T[] = []; diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 669c66c433..1e5395fe7b 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -213,8 +213,43 @@ namespace ts { : node; } + // Types + + export function createLiteralTypeNode(value: string | number | boolean) { + const literal = createLiteral(value); + const literalTypeNode = createSynthesizedNode(SyntaxKind.LiteralType) as LiteralTypeNode; + literalTypeNode.literal = literal; + return literalTypeNode; + } + + export function createTypeReferenceNode(typeName: string | EntityName, typeArguments?: NodeArray) { + const typeReference = createSynthesizedNode(SyntaxKind.TypeReference) as TypeReferenceNode; + typeReference.typeName = asName(typeName); + typeReference.typeName.parent + typeReference.typeArguments = typeArguments; + return typeReference; + } + + export function createTypeParameterNode(name: string | Identifier, constraint?: TypeNode, defaultParameter?: TypeNode) { + const typeParameter = createSynthesizedNode(SyntaxKind.TypeParameter) as TypeParameterDeclaration; + typeParameter.name = asName(name); + typeParameter.constraint = constraint; + typeParameter.default = defaultParameter; + + return typeParameter; + } + // Signature elements + export function createSignature(kind: SyntaxKind, parameters: NodeArray, name?: PropertyName, typeParameters?: NodeArray, returnType?: TypeNode): SignatureDeclaration { + const signature = createSynthesizedNode(kind) as SignatureDeclaration; + signature.parameters = parameters; + signature.name = name; + signature.typeParameters = typeParameters; + signature.type = returnType; + return signature; + } + export function createParameter(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression) { const node = createSynthesizedNode(SyntaxKind.Parameter); node.decorators = asNodeArray(decorators); @@ -1801,7 +1836,7 @@ namespace ts { return typeof value === "string" || typeof value === "number" ? createLiteral(value) : value; } - function asNodeArray(array: T[] | undefined): NodeArray | undefined { + export function asNodeArray(array: T[] | undefined): NodeArray | undefined { return array ? createNodeArray(array) : undefined; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1492a106d2..52207920b4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -849,14 +849,19 @@ namespace ts { _typeNodeBrand: any; } + export type KeywordKind = SyntaxKind.AnyKeyword + | SyntaxKind.NumberKeyword + | SyntaxKind.ObjectKeyword + | SyntaxKind.BooleanKeyword + | SyntaxKind.StringKeyword + | SyntaxKind.SymbolKeyword + | SyntaxKind.VoidKeyword + | SyntaxKind.UndefinedKeyword + | SyntaxKind.NullKeyword + | SyntaxKind.NeverKeyword; + export interface KeywordTypeNode extends TypeNode { - kind: SyntaxKind.AnyKeyword - | SyntaxKind.NumberKeyword - | SyntaxKind.ObjectKeyword - | SyntaxKind.BooleanKeyword - | SyntaxKind.StringKeyword - | SyntaxKind.SymbolKeyword - | SyntaxKind.VoidKeyword; + kind: KeywordKind; } export interface ThisTypeNode extends TypeNode { @@ -892,7 +897,7 @@ namespace ts { exprName: EntityName; } - // A TypeLiteral is the declaration node for an anonymous symbol. + /** A TypeLiteral is the declaration node for an anonymous symbol. */ export interface TypeLiteralNode extends TypeNode, Declaration { kind: SyntaxKind.TypeLiteral; members: NodeArray; From c6693983f42d570fd4b30f73c2c4506d11a6b326 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Sat, 11 Mar 2017 09:10:26 -0800 Subject: [PATCH 03/65] Revert "temp" This reverts commit fd65966a5028d3403dda934f97fe285cee427198. Accidentally pushed to the wrong branch. --- src/compiler/checker.ts | 191 ---------------------------------------- src/compiler/factory.ts | 37 +------- src/compiler/types.ts | 21 ++--- 3 files changed, 9 insertions(+), 240 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 109e6f33ea..aa7908f52c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2388,198 +2388,11 @@ namespace ts { } } - function createTypeNode(type: Type) { - let encounteredError = false; - let checkAlias = true; - - return createTypeNodeWorker(type); - - function createTypeNodeWorker(type: Type): TypeNode { - if(!type) { - return undefined; - } - - if (checkAlias && type.aliasSymbol) { - const name = getNameOfSymbol(type.aliasSymbol); - const typeArguments = mapToTypeNodeArray(type.aliasTypeArguments); - return createTypeReferenceNode(createIdentifier(name), typeArguments); - } - checkAlias = false; - - - if(type.flags & TypeFlags.Any) { - // TODO: add other case where type ends up being `any`. - return createKeywordTypeNode(SyntaxKind.StringKeyword); - } - if(type.flags & TypeFlags.String) { - return createKeywordTypeNode(SyntaxKind.StringKeyword); - } - if(type.flags & TypeFlags.Number) { - return createKeywordTypeNode(SyntaxKind.NumberKeyword); - } - if(type.flags & (TypeFlags.Boolean | TypeFlags.StringOrNumberLiteral)) { - // TODO: check if this actually works with boolean. - return createLiteralTypeNode((type).text); - } - if(type.flags & TypeFlags.Void) { - return createKeywordTypeNode(SyntaxKind.VoidKeyword); - } - if(type.flags & TypeFlags.Undefined) { - return createKeywordTypeNode(SyntaxKind.UndefinedKeyword); - } - if(type.flags & TypeFlags.Null) { - return createKeywordTypeNode(SyntaxKind.NullKeyword); - } - if(type.flags & TypeFlags.Never) { - return createKeywordTypeNode(SyntaxKind.NeverKeyword); - } - if(type.flags & TypeFlags.Enum) { - throw new Error("not implemented"); - } - if(type.flags & TypeFlags.ESSymbol) { - throw new Error("not implemented"); - } - if(type.flags & TypeFlags.TypeParameter) { - const constraint = createTypeNodeWorker(getConstraintFromTypeParameter(type)); - const defaultParameter = createTypeNodeWorker(getDefaultFromTypeParameter(type)); - if(!type.symbol) { - encounteredError = true; - throw new Error("No symbol for type parameter so can't get name"); - } - const name = getNameOfSymbol(type.symbol); - return createTypeParameterNode(name, constraint, defaultParameter); - } - if(type.flags & TypeFlags.Union) { - throw new Error("not implemented"); - } - if(type.flags & TypeFlags.Intersection) { - throw new Error("not implemented"); - } - if(type.flags & TypeFlags.Index) { - throw new Error("not implemented"); - } - if(type.flags & TypeFlags.IndexedAccess) { - throw new Error("not implemented"); - } - - // if(type.flags & TypeFlags.Object) { - // throw new Error("not implemented"); - // } - - // TODO: should these be within the if above (check with asserts) - const objectFlags = getObjectFlags(type); - - if (objectFlags & ObjectFlags.ClassOrInterface) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - // If type is a class or interface type that wasn't hit by the isSymbolAccessible check above, - // type must be an anonymous class or interface. - return false; - } - - if (objectFlags & ObjectFlags.Reference) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - // and vice versa. - // this case includes tuple types - const typeArguments = (type as TypeReference).typeArguments || emptyArray; - return allTypesVisible(typeArguments); - } - - // keyword types - // this type node - // function type node - // constructor type node - // type reference node - // type predicate node - is Foo (for return types) - // type query node -- typeof number - // type literal node (like object literal) - // array type - // tuple type - // union type - // might need parens - // intersection type - // Type operator node (eg (ie?): keyof T) - // IndexedAccess Type Node - // mapped type node - // literal type node - - // if (inTypeAlias && type.aliasSymbol) { - // return isSymbolAccessible(type.aliasSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/false).accessibility === SymbolAccessibility.Accessible - // && (!type.aliasTypeArguments || allTypesVisible(type.aliasTypeArguments)); - // } - // const typeSymbolAccessibility = type.symbol && isSymbolAccessible(type.symbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false).accessibility; - // if (type.flags & TypeFlags.TypeParameter) { - // if (inObjectLiteral && (type as TypeParameter).isThisType) { - // return false; - // } - // const constraint = getConstraintFromTypeParameter((type)); - // return typeSymbolAccessibility === SymbolAccessibility.Accessible - // && (!constraint || isTypeAccessibleWorker(constraint, inObjectLiteral, /*inTypeAlias*/false)); - // } - // if (typeSymbolAccessibility === SymbolAccessibility.Accessible) { - // return true; - // } - // if (type.flags & (TypeFlags.Intrinsic | TypeFlags.Literal)) { - // return true; - // } - // const objectFlags = getObjectFlags(type); - // if (objectFlags & ObjectFlags.ClassOrInterface) { - // // If type is a class or interface type that wasn't hit by the isSymbolAccessible check above, - // // type must be an anonymous class or interface. - // return false; - // } - // if (objectFlags & ObjectFlags.Reference) { - // // and vice versa. - // // this case includes tuple types - // const typeArguments = (type as TypeReference).typeArguments || emptyArray; - // return allTypesVisible(typeArguments); - // } - // if (type.flags & TypeFlags.UnionOrIntersection) { - // return allTypesVisible((type as UnionOrIntersectionType).types); - // } - - if (objectFlags & ObjectFlags.Mapped) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - const typeParameter = getTypeParameterFromMappedType(type); - const constraintType = getConstraintTypeFromMappedType(type); - const templateType = getTemplateTypeFromMappedType(type); - } - - if (objectFlags & ObjectFlags.Anonymous) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - // The type is an object literal type. - if (!type.symbol) { - // Anonymous types without symbols are literals. - return true; - } - // what case is this? - const members = type.symbol.members; - let allVisible = true; - members && members.forEach((member) => { - const memberType = getTypeOfSymbolAtLocation(member, enclosingDeclaration); - allVisible = allVisible && isTypeAccessibleWorker(memberType, /*inObjectLiteral*/ true, /*inTypeAlias*/false); - }); - return allVisible; - } - - Debug.fail("Should be unreachable here"); - - /** Note that mapToTypeNodeArray(undefined) === undefined. */ - function mapToTypeNodeArray(types: Type[]): NodeArray { - return asNodeArray(types && types.map(createTypeNodeWorker)); - } - // function allTypesVisible(types: Type[]): boolean { - // return types.every(type => isTypeAccessibleWorker(type, inObjectLiteral, /*inTypeAlias*/false)); - // } - } - } - function buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]) { const globalFlagsToPass = globalFlags & TypeFormatFlags.WriteOwnNameForAnyLike; let inObjectTypeLiteral = false; return writeType(type, globalFlags); - const typeNode = createTypeNode(type, enclosingDeclaration); - function writeType(type: Type, flags: TypeFormatFlags) { const nextFlags = flags & ~TypeFormatFlags.InTypeAlias; // Write undefined/null type as any @@ -7126,10 +6939,6 @@ namespace ts { } } - // export function synthesizeTypeNode(type: Type, enclosingDeclaration: Node): TypeNode { - // throw new Error("Not implemented" + enclosingDeclaration); - // } - function instantiateList(items: T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): T[] { if (items && items.length) { const result: T[] = []; diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 1e5395fe7b..669c66c433 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -213,43 +213,8 @@ namespace ts { : node; } - // Types - - export function createLiteralTypeNode(value: string | number | boolean) { - const literal = createLiteral(value); - const literalTypeNode = createSynthesizedNode(SyntaxKind.LiteralType) as LiteralTypeNode; - literalTypeNode.literal = literal; - return literalTypeNode; - } - - export function createTypeReferenceNode(typeName: string | EntityName, typeArguments?: NodeArray) { - const typeReference = createSynthesizedNode(SyntaxKind.TypeReference) as TypeReferenceNode; - typeReference.typeName = asName(typeName); - typeReference.typeName.parent - typeReference.typeArguments = typeArguments; - return typeReference; - } - - export function createTypeParameterNode(name: string | Identifier, constraint?: TypeNode, defaultParameter?: TypeNode) { - const typeParameter = createSynthesizedNode(SyntaxKind.TypeParameter) as TypeParameterDeclaration; - typeParameter.name = asName(name); - typeParameter.constraint = constraint; - typeParameter.default = defaultParameter; - - return typeParameter; - } - // Signature elements - export function createSignature(kind: SyntaxKind, parameters: NodeArray, name?: PropertyName, typeParameters?: NodeArray, returnType?: TypeNode): SignatureDeclaration { - const signature = createSynthesizedNode(kind) as SignatureDeclaration; - signature.parameters = parameters; - signature.name = name; - signature.typeParameters = typeParameters; - signature.type = returnType; - return signature; - } - export function createParameter(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression) { const node = createSynthesizedNode(SyntaxKind.Parameter); node.decorators = asNodeArray(decorators); @@ -1836,7 +1801,7 @@ namespace ts { return typeof value === "string" || typeof value === "number" ? createLiteral(value) : value; } - export function asNodeArray(array: T[] | undefined): NodeArray | undefined { + function asNodeArray(array: T[] | undefined): NodeArray | undefined { return array ? createNodeArray(array) : undefined; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 52207920b4..1492a106d2 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -849,19 +849,14 @@ namespace ts { _typeNodeBrand: any; } - export type KeywordKind = SyntaxKind.AnyKeyword - | SyntaxKind.NumberKeyword - | SyntaxKind.ObjectKeyword - | SyntaxKind.BooleanKeyword - | SyntaxKind.StringKeyword - | SyntaxKind.SymbolKeyword - | SyntaxKind.VoidKeyword - | SyntaxKind.UndefinedKeyword - | SyntaxKind.NullKeyword - | SyntaxKind.NeverKeyword; - export interface KeywordTypeNode extends TypeNode { - kind: KeywordKind; + kind: SyntaxKind.AnyKeyword + | SyntaxKind.NumberKeyword + | SyntaxKind.ObjectKeyword + | SyntaxKind.BooleanKeyword + | SyntaxKind.StringKeyword + | SyntaxKind.SymbolKeyword + | SyntaxKind.VoidKeyword; } export interface ThisTypeNode extends TypeNode { @@ -897,7 +892,7 @@ namespace ts { exprName: EntityName; } - /** A TypeLiteral is the declaration node for an anonymous symbol. */ + // A TypeLiteral is the declaration node for an anonymous symbol. export interface TypeLiteralNode extends TypeNode, Declaration { kind: SyntaxKind.TypeLiteral; members: NodeArray; From fff531ca25e251942bd7c9af3716d986f9774d67 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Sat, 11 Mar 2017 15:16:28 -0800 Subject: [PATCH 04/65] more temp --- src/compiler/checker.ts | 389 +++++++++--------- src/compiler/factory.ts | 66 ++- src/compiler/types.ts | 4 +- src/services/codefixes/fixAddMissingMember.ts | 49 ++- .../fixClassSuperMustPrecedeThisAccess.ts | 2 +- src/services/utilities.ts | 4 + 6 files changed, 302 insertions(+), 212 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 109e6f33ea..3ed15f8b03 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -106,6 +106,7 @@ namespace ts { getParameterType: getTypeAtPosition, getReturnTypeOfSignature, getNonNullableType, + createTypeNode, getSymbolsInScope: (location, meaning) => { location = getParseTreeNode(location); return location ? getSymbolsInScope(location, meaning) : []; @@ -2188,6 +2189,207 @@ namespace ts { return result; } + function createTypeNode(type: Type) { + let encounteredError = false; + let checkAlias = true; + + return createTypeNodeWorker(type); + + // function createTypeDeclaration(type: Type): Declaration { + // if (!type) { + // return undefined; + // } + // throw new Error("not implemented."); + // } + + function createTypeNodeWorker(type: Type): TypeNode { + if (!type) { + return undefined; + } + + if (checkAlias && type.aliasSymbol) { + const name = getNameOfSymbol(type.aliasSymbol); + const typeArguments = mapToTypeNodeArray(type.aliasTypeArguments); + return createTypeReferenceNode(createIdentifier(name), typeArguments); + } + checkAlias = false; + + if (type.flags & TypeFlags.Any) { + // TODO: add other case where type ends up being `any`. + return createKeywordTypeNode(SyntaxKind.StringKeyword); + } + if (type.flags & TypeFlags.String) { + return createKeywordTypeNode(SyntaxKind.StringKeyword); + } + if (type.flags & TypeFlags.Number) { + return createKeywordTypeNode(SyntaxKind.NumberKeyword); + } + if (type.flags & (TypeFlags.Boolean | TypeFlags.StringOrNumberLiteral)) { + // TODO: check if this actually works with boolean. + return createLiteralTypeNode((type).text); + } + if (type.flags & TypeFlags.Void) { + return createKeywordTypeNode(SyntaxKind.VoidKeyword); + } + if (type.flags & TypeFlags.Undefined) { + return createKeywordTypeNode(SyntaxKind.UndefinedKeyword); + } + if (type.flags & TypeFlags.Null) { + return createKeywordTypeNode(SyntaxKind.NullKeyword); + } + if (type.flags & TypeFlags.Never) { + return createKeywordTypeNode(SyntaxKind.NeverKeyword); + } + if (type.flags & TypeFlags.Enum) { + throw new Error("not implemented"); + } + if (type.flags & TypeFlags.ESSymbol) { + throw new Error("not implemented"); + } + if (type.flags & TypeFlags.TypeParameter) { + throw new Error("Type Parameter declarations only handled in other worker."); + // const constraint = createTypeNodeWorker(getConstraintFromTypeParameter(type)) as TypeNode; + // const defaultParameter = createTypeNodeWorker(getDefaultFromTypeParameter(type)) as TypeNode; + // if (!type.symbol) { + // encounteredError = true; + // throw new Error("No symbol for type parameter so can't get name"); + // } + // const name = getNameOfSymbol(type.symbol); + // return createTypeParameterNode(name, constraint, defaultParameter); + } + if (type.flags & TypeFlags.Union) { + throw new Error("not implemented"); + } + if (type.flags & TypeFlags.Intersection) { + throw new Error("not implemented"); + } + if (type.flags & TypeFlags.Index) { + throw new Error("not implemented"); + } + if (type.flags & TypeFlags.IndexedAccess) { + throw new Error("not implemented"); + } + + // if(type.flags & TypeFlags.Object) { + // throw new Error("not implemented"); + // } + + // TODO: should these be within the if above (check with asserts) + const objectFlags = getObjectFlags(type); + + if (objectFlags & ObjectFlags.ClassOrInterface) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + // If type is a class or interface type that wasn't hit by the isSymbolAccessible check above, + // type must be an anonymous class or interface. + + encounteredError = true; + return undefined; + } + + if (objectFlags & ObjectFlags.Reference) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + // and vice versa. + // this case includes tuple types + // TODO: test empty tuples, see if they are coherent. + const typeArguments = (type as TypeReference).typeArguments || emptyArray; + return createTupleTypeNode(mapToTypeNodeArray(typeArguments)) + } + + // keyword types + // this type node + // function type node + // constructor type node + // type reference node + // type predicate node - is Foo (for return types) + // type query node -- typeof number + // type literal node (like object literal) + // array type + // tuple type + // union type + // might need parens + // intersection type + // Type operator node (eg (ie?): keyof T) + // IndexedAccess Type Node + // mapped type node + // literal type node + + // if (inTypeAlias && type.aliasSymbol) { + // return isSymbolAccessible(type.aliasSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/false).accessibility === SymbolAccessibility.Accessible + // && (!type.aliasTypeArguments || allTypesVisible(type.aliasTypeArguments)); + // } + // const typeSymbolAccessibility = type.symbol && isSymbolAccessible(type.symbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false).accessibility; + // if (type.flags & TypeFlags.TypeParameter) { + // if (inObjectLiteral && (type as TypeParameter).isThisType) { + // return false; + // } + // const constraint = getConstraintFromTypeParameter((type)); + // return typeSymbolAccessibility === SymbolAccessibility.Accessible + // && (!constraint || isTypeAccessibleWorker(constraint, inObjectLiteral, /*inTypeAlias*/false)); + // } + // if (typeSymbolAccessibility === SymbolAccessibility.Accessible) { + // return true; + // } + // if (type.flags & (TypeFlags.Intrinsic | TypeFlags.Literal)) { + // return true; + // } + // const objectFlags = getObjectFlags(type); + // if (objectFlags & ObjectFlags.ClassOrInterface) { + // // If type is a class or interface type that wasn't hit by the isSymbolAccessible check above, + // // type must be an anonymous class or interface. + // return false; + // } + // if (objectFlags & ObjectFlags.Reference) { + // // and vice versa. + // // this case includes tuple types + // const typeArguments = (type as TypeReference).typeArguments || emptyArray; + // return allTypesVisible(typeArguments); + // } + // if (type.flags & TypeFlags.UnionOrIntersection) { + // return allTypesVisible((type as UnionOrIntersectionType).types); + // } + + if (objectFlags & ObjectFlags.Mapped) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + // const typeParameter = getTypeParameterFromMappedType(type); + // const constraintType = getConstraintTypeFromMappedType(type); + // const templateType = getTemplateTypeFromMappedType(type); + throw new Error("Mapped types not implemented"); + } + + if (objectFlags & ObjectFlags.Anonymous) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + // The type is an object literal type. + if (!type.symbol) { + // Anonymous types without symbols are literals. + + + // mapToTypeDeclarationsArray(type) + throw new Error("object literal types not implemented."); + } + // what case is this? + throw new Error("unknown case.") + // const members = type.symbol.members; + // let allVisible = true; + // members && members.forEach((member) => { + // const memberType = getTypeOfSymbolAtLocation(member, enclosingDeclaration); + // allVisible = allVisible && isTypeAccessibleWorker(memberType, /*inObjectLiteral*/ true, /*inTypeAlias*/false); + // }); + // return allVisible; + } + + Debug.fail("Should be unreachable."); + + /** Note that mapToTypeNodeArray(undefined) === undefined. */ + function mapToTypeNodeArray(types: Type[]): NodeArray { + return asNodeArray(types && types.map(createTypeNodeWorker) as TypeNode[]); + } + + // function mapToTypeDeclarationsArray(types: Type[]): NodeArray { + // return asNodeArray(types && types.map(createTypeDeclaration)); + // } + } + } + function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Declaration, flags?: TypeFormatFlags): string { const writer = getSingleLineStringWriter(); getSymbolDisplayBuilder().buildTypePredicateDisplay(typePredicate, writer, enclosingDeclaration, flags); @@ -2388,198 +2590,11 @@ namespace ts { } } - function createTypeNode(type: Type) { - let encounteredError = false; - let checkAlias = true; - - return createTypeNodeWorker(type); - - function createTypeNodeWorker(type: Type): TypeNode { - if(!type) { - return undefined; - } - - if (checkAlias && type.aliasSymbol) { - const name = getNameOfSymbol(type.aliasSymbol); - const typeArguments = mapToTypeNodeArray(type.aliasTypeArguments); - return createTypeReferenceNode(createIdentifier(name), typeArguments); - } - checkAlias = false; - - - if(type.flags & TypeFlags.Any) { - // TODO: add other case where type ends up being `any`. - return createKeywordTypeNode(SyntaxKind.StringKeyword); - } - if(type.flags & TypeFlags.String) { - return createKeywordTypeNode(SyntaxKind.StringKeyword); - } - if(type.flags & TypeFlags.Number) { - return createKeywordTypeNode(SyntaxKind.NumberKeyword); - } - if(type.flags & (TypeFlags.Boolean | TypeFlags.StringOrNumberLiteral)) { - // TODO: check if this actually works with boolean. - return createLiteralTypeNode((type).text); - } - if(type.flags & TypeFlags.Void) { - return createKeywordTypeNode(SyntaxKind.VoidKeyword); - } - if(type.flags & TypeFlags.Undefined) { - return createKeywordTypeNode(SyntaxKind.UndefinedKeyword); - } - if(type.flags & TypeFlags.Null) { - return createKeywordTypeNode(SyntaxKind.NullKeyword); - } - if(type.flags & TypeFlags.Never) { - return createKeywordTypeNode(SyntaxKind.NeverKeyword); - } - if(type.flags & TypeFlags.Enum) { - throw new Error("not implemented"); - } - if(type.flags & TypeFlags.ESSymbol) { - throw new Error("not implemented"); - } - if(type.flags & TypeFlags.TypeParameter) { - const constraint = createTypeNodeWorker(getConstraintFromTypeParameter(type)); - const defaultParameter = createTypeNodeWorker(getDefaultFromTypeParameter(type)); - if(!type.symbol) { - encounteredError = true; - throw new Error("No symbol for type parameter so can't get name"); - } - const name = getNameOfSymbol(type.symbol); - return createTypeParameterNode(name, constraint, defaultParameter); - } - if(type.flags & TypeFlags.Union) { - throw new Error("not implemented"); - } - if(type.flags & TypeFlags.Intersection) { - throw new Error("not implemented"); - } - if(type.flags & TypeFlags.Index) { - throw new Error("not implemented"); - } - if(type.flags & TypeFlags.IndexedAccess) { - throw new Error("not implemented"); - } - - // if(type.flags & TypeFlags.Object) { - // throw new Error("not implemented"); - // } - - // TODO: should these be within the if above (check with asserts) - const objectFlags = getObjectFlags(type); - - if (objectFlags & ObjectFlags.ClassOrInterface) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - // If type is a class or interface type that wasn't hit by the isSymbolAccessible check above, - // type must be an anonymous class or interface. - return false; - } - - if (objectFlags & ObjectFlags.Reference) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - // and vice versa. - // this case includes tuple types - const typeArguments = (type as TypeReference).typeArguments || emptyArray; - return allTypesVisible(typeArguments); - } - - // keyword types - // this type node - // function type node - // constructor type node - // type reference node - // type predicate node - is Foo (for return types) - // type query node -- typeof number - // type literal node (like object literal) - // array type - // tuple type - // union type - // might need parens - // intersection type - // Type operator node (eg (ie?): keyof T) - // IndexedAccess Type Node - // mapped type node - // literal type node - - // if (inTypeAlias && type.aliasSymbol) { - // return isSymbolAccessible(type.aliasSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/false).accessibility === SymbolAccessibility.Accessible - // && (!type.aliasTypeArguments || allTypesVisible(type.aliasTypeArguments)); - // } - // const typeSymbolAccessibility = type.symbol && isSymbolAccessible(type.symbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false).accessibility; - // if (type.flags & TypeFlags.TypeParameter) { - // if (inObjectLiteral && (type as TypeParameter).isThisType) { - // return false; - // } - // const constraint = getConstraintFromTypeParameter((type)); - // return typeSymbolAccessibility === SymbolAccessibility.Accessible - // && (!constraint || isTypeAccessibleWorker(constraint, inObjectLiteral, /*inTypeAlias*/false)); - // } - // if (typeSymbolAccessibility === SymbolAccessibility.Accessible) { - // return true; - // } - // if (type.flags & (TypeFlags.Intrinsic | TypeFlags.Literal)) { - // return true; - // } - // const objectFlags = getObjectFlags(type); - // if (objectFlags & ObjectFlags.ClassOrInterface) { - // // If type is a class or interface type that wasn't hit by the isSymbolAccessible check above, - // // type must be an anonymous class or interface. - // return false; - // } - // if (objectFlags & ObjectFlags.Reference) { - // // and vice versa. - // // this case includes tuple types - // const typeArguments = (type as TypeReference).typeArguments || emptyArray; - // return allTypesVisible(typeArguments); - // } - // if (type.flags & TypeFlags.UnionOrIntersection) { - // return allTypesVisible((type as UnionOrIntersectionType).types); - // } - - if (objectFlags & ObjectFlags.Mapped) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - const typeParameter = getTypeParameterFromMappedType(type); - const constraintType = getConstraintTypeFromMappedType(type); - const templateType = getTemplateTypeFromMappedType(type); - } - - if (objectFlags & ObjectFlags.Anonymous) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - // The type is an object literal type. - if (!type.symbol) { - // Anonymous types without symbols are literals. - return true; - } - // what case is this? - const members = type.symbol.members; - let allVisible = true; - members && members.forEach((member) => { - const memberType = getTypeOfSymbolAtLocation(member, enclosingDeclaration); - allVisible = allVisible && isTypeAccessibleWorker(memberType, /*inObjectLiteral*/ true, /*inTypeAlias*/false); - }); - return allVisible; - } - - Debug.fail("Should be unreachable here"); - - /** Note that mapToTypeNodeArray(undefined) === undefined. */ - function mapToTypeNodeArray(types: Type[]): NodeArray { - return asNodeArray(types && types.map(createTypeNodeWorker)); - } - // function allTypesVisible(types: Type[]): boolean { - // return types.every(type => isTypeAccessibleWorker(type, inObjectLiteral, /*inTypeAlias*/false)); - // } - } - } - function buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]) { const globalFlagsToPass = globalFlags & TypeFormatFlags.WriteOwnNameForAnyLike; let inObjectTypeLiteral = false; return writeType(type, globalFlags); - const typeNode = createTypeNode(type, enclosingDeclaration); - function writeType(type: Type, flags: TypeFormatFlags) { const nextFlags = flags & ~TypeFormatFlags.InTypeAlias; // Write undefined/null type as any diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 1e5395fe7b..3633cc657a 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -213,8 +213,28 @@ namespace ts { : node; } + // Type Elements + + export function createPropertySignature(name: PropertyName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression): PropertySignature { + const propertySignature = createSynthesizedNode(SyntaxKind.PropertySignature) as PropertySignature; + propertySignature.name = name; + propertySignature.questionToken = questionToken; + propertySignature.type = type; + propertySignature.initializer = initializer; + return propertySignature; + } + + export function createConstructSignature() { + throw new Error("not implemented."); + } + // Types + export function createKeywordTypeNode(kind: KeywordTypeNode["kind"]): KeywordTypeNode { + return createSynthesizedNode(kind); + } + + export function createLiteralTypeNode(value: string | number | boolean) { const literal = createLiteral(value); const literalTypeNode = createSynthesizedNode(SyntaxKind.LiteralType) as LiteralTypeNode; @@ -222,7 +242,8 @@ namespace ts { return literalTypeNode; } - export function createTypeReferenceNode(typeName: string | EntityName, typeArguments?: NodeArray) { + // TODO: handle qualified names, ie EntityName's. + export function createTypeReferenceNode(typeName: string | Identifier, typeArguments?: NodeArray) { const typeReference = createSynthesizedNode(SyntaxKind.TypeReference) as TypeReferenceNode; typeReference.typeName = asName(typeName); typeReference.typeName.parent @@ -239,8 +260,34 @@ namespace ts { return typeParameter; } + export function createUnionTypeNode(types: NodeArray) { + const unionTypeNode = createSynthesizedNode(SyntaxKind.UnionType) as UnionTypeNode; + unionTypeNode.types = asNodeArray(types); + return unionTypeNode; + } + + export function createIntersectionTypeNode(types: NodeArray) { + const intersectionTypeNode = createSynthesizedNode(SyntaxKind.IntersectionType) as IntersectionTypeNode; + intersectionTypeNode.types = asNodeArray(types); + return intersectionTypeNode; + } + + export function createTypeLiteralNode(typeElements: TypeElement[]) { + const typeLiteralNode = createSynthesizedNode(SyntaxKind.LiteralType) as TypeLiteralNode; + typeLiteralNode.members = asNodeArray(typeElements); + return typeLiteralNode; + } + + export function createTupleTypeNode(types: NodeArray) { + const tupleTypeNode = createSynthesizedNode(SyntaxKind.TupleType) as TupleTypeNode; + tupleTypeNode.elementTypes = asNodeArray(types); + return tupleTypeNode; + } + + // Signature elements + /** Note, can also be used to construct index signatures. */ export function createSignature(kind: SyntaxKind, parameters: NodeArray, name?: PropertyName, typeParameters?: NodeArray, returnType?: TypeNode): SignatureDeclaration { const signature = createSynthesizedNode(kind) as SignatureDeclaration; signature.parameters = parameters; @@ -250,6 +297,19 @@ namespace ts { return signature; } + // TODO: check usage of name... + export function createIndexSignature(parameter: ParameterDeclaration, type: TypeNode, decorators?: Decorator[], modifiers?: Modifier[]): IndexSignatureDeclaration { + const indexSignature = createSignature( + SyntaxKind.IndexSignature + , asNodeArray([parameter]) + , /*name*/ undefined + , /*typeParameters*/undefined + , type) as IndexSignatureDeclaration; + indexSignature.decorators = asNodeArray(decorators); + indexSignature.modifiers = asNodeArray(modifiers); + return indexSignature; + } + export function createParameter(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression) { const node = createSynthesizedNode(SyntaxKind.Parameter); node.decorators = asNodeArray(decorators); @@ -1134,10 +1194,6 @@ namespace ts { : node; } - export function createKeywordTypeNode(kind: KeywordTypeNode["kind"]): KeywordTypeNode { - return createSynthesizedNode(kind); - } - export function createFunctionDeclaration(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: string | Identifier | undefined, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { const node = createSynthesizedNode(SyntaxKind.FunctionDeclaration); node.decorators = asNodeArray(decorators); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 52207920b4..4fd4dd1780 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -673,7 +673,7 @@ namespace ts { initializer?: Expression; // Optional initializer } - export interface BindingElement extends Declaration { + export interface BindingElement extends Declaration { kind: SyntaxKind.BindingElement; parent?: BindingPattern; propertyName?: PropertyName; // Binding property name (in object binding pattern) @@ -2468,6 +2468,8 @@ namespace ts { */ /* @internal */ getParameterType(signature: Signature, parameterIndex: number): Type; getNonNullableType(type: Type): Type; + /** Note that the resulting type node cannot be checked. */ + createTypeNode(type: Type): TypeNode; getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; getSymbolAtLocation(node: Node): Symbol; diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 6ae2ba3f51..05ff29f069 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -31,37 +31,50 @@ namespace ts.codefix { return undefined; } - let typeString = "any"; + let typeNode: TypeNode = createKeywordTypeNode(SyntaxKind.AnyKeyword); if (token.parent.parent.kind === SyntaxKind.BinaryExpression) { const binaryExpression = token.parent.parent as BinaryExpression; const checker = context.program.getTypeChecker(); const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(binaryExpression.right))); - typeString = checker.typeToString(widenedType); + typeNode = checker.createTypeNode(widenedType) || typeNode; } - const startPos = classDeclaration.members.pos; + const openBrace = getOpenBraceOfClassLike(classDeclaration, sourceFile); - return [{ + const property = createProperty( + /*decorators*/undefined + , /*modifiers*/ undefined + , token.getText(sourceFile) + , /*questionToken*/ undefined + , typeNode + , /*initializer*/ undefined); + // TODO: make index signature. + const propertyChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); + propertyChangeTracker.insertNodeAfter(sourceFile, openBrace, property, { insertTrailingNewLine: true }); + + const stringTypeNode = createKeywordTypeNode(SyntaxKind.StringKeyword); + const indexingParameter = createParameter( + /*decorators*/ undefined + , /*modifiers*/ undefined + , /*dotDotDotToken*/ undefined + , "name" + , /*questionToken*/ undefined + , stringTypeNode); + const indexSignature = createIndexSignature(indexingParameter, typeNode); + + // const startPos = classDeclaration.members.pos; + const indexSignatureChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); + indexSignatureChangeTracker.insertNodeAfter(sourceFile, openBrace, indexSignature, { insertTrailingNewLine: true }); + + return [{ description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_declaration_for_missing_property_0), [token.getText()]), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [{ - span: { start: startPos, length: 0 }, - newText: `${token.getFullText(sourceFile)}: ${typeString};` - }] - }] + changes: propertyChangeTracker.getChanges() }, { description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_missing_property_0), [token.getText()]), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [{ - span: { start: startPos, length: 0 }, - newText: `[name: string]: ${typeString};` - }] - }] + changes: indexSignatureChangeTracker.getChanges() }]; } } \ No newline at end of file diff --git a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts index 207d039df8..63fc846f8c 100644 --- a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts +++ b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts @@ -16,7 +16,7 @@ namespace ts.codefix { return undefined; } - // figure out if the this access is actuall inside the supercall + // figure out if the `this` access is actually inside the supercall // i.e. super(this.a), since in that case we won't suggest a fix if (superCall.expression && superCall.expression.kind == SyntaxKind.CallExpression) { const arguments = (superCall.expression).arguments; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 1b32b3bbad..0fed656af5 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1384,4 +1384,8 @@ namespace ts { // First token is the open curly, this is where we want to put the 'super' call. return constructor.body.getFirstToken(sourceFile); } + + export function getOpenBraceOfClassLike(declaration: ClassLikeDeclaration, sourceFile: SourceFile) { + return getTokenAtPosition(sourceFile, declaration.members.pos); + } } From bf2acf1d2cdf51fb462561f054d6dd0f01bd8aef Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Sat, 11 Mar 2017 18:58:46 -0800 Subject: [PATCH 05/65] basic end-to-end building type nodes --- src/compiler/checker.ts | 23 +++-- src/compiler/factory.ts | 99 +++++++++++++------ src/compiler/types.ts | 24 +++-- src/compiler/visitor.ts | 68 +++++++++++-- src/services/codefixes/fixAddMissingMember.ts | 5 +- src/services/utilities.ts | 2 +- 6 files changed, 159 insertions(+), 62 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3ed15f8b03..f29204f7ca 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2190,7 +2190,7 @@ namespace ts { } function createTypeNode(type: Type) { - let encounteredError = false; + // let encounteredError = false; let checkAlias = true; return createTypeNodeWorker(type); @@ -2224,9 +2224,17 @@ namespace ts { if (type.flags & TypeFlags.Number) { return createKeywordTypeNode(SyntaxKind.NumberKeyword); } - if (type.flags & (TypeFlags.Boolean | TypeFlags.StringOrNumberLiteral)) { + if(type.flags & TypeFlags.Boolean) { + // TODO: this is probably x: boolean. How do we deal with x: true ? + return createKeywordTypeNode(SyntaxKind.BooleanKeyword); + } + if (type.flags & (TypeFlags.StringLiteral)) { // TODO: check if this actually works with boolean. - return createLiteralTypeNode((type).text); + return createLiteralTypeNode((createLiteral((type).text))); + } + if (type.flags & (TypeFlags.NumberLiteral)) { + // TODO: check if this actually works with boolean. + return createLiteralTypeNode((createNumericLiteral((type).text))); } if (type.flags & TypeFlags.Void) { return createKeywordTypeNode(SyntaxKind.VoidKeyword); @@ -2279,11 +2287,10 @@ namespace ts { if (objectFlags & ObjectFlags.ClassOrInterface) { Debug.assert(!!(type.flags & TypeFlags.Object)); - // If type is a class or interface type that wasn't hit by the isSymbolAccessible check above, - // type must be an anonymous class or interface. - - encounteredError = true; - return undefined; + const name = getNameOfSymbol(type.symbol); + // TODO: handle type arguments. + // TODO: handle anonymous classes. + return createTypeReferenceNode(name); } if (objectFlags & ObjectFlags.Reference) { diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 3633cc657a..19e8ef25da 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -234,23 +234,75 @@ namespace ts { return createSynthesizedNode(kind); } - - export function createLiteralTypeNode(value: string | number | boolean) { - const literal = createLiteral(value); + export function createLiteralTypeNode(literal: Expression) { const literalTypeNode = createSynthesizedNode(SyntaxKind.LiteralType) as LiteralTypeNode; literalTypeNode.literal = literal; return literalTypeNode; } + export function updateLiteralTypeNode(node: LiteralTypeNode, literal: Expression) { + return node.literal !== literal + ? updateNode(createLiteralTypeNode(literal), node) + : node; + } + // TODO: handle qualified names, ie EntityName's. export function createTypeReferenceNode(typeName: string | Identifier, typeArguments?: NodeArray) { const typeReference = createSynthesizedNode(SyntaxKind.TypeReference) as TypeReferenceNode; typeReference.typeName = asName(typeName); - typeReference.typeName.parent typeReference.typeArguments = typeArguments; return typeReference; } + export function updateTypeReferenceNode(node: TypeReferenceNode, typeName: Identifier, typeArguments?: NodeArray) { + return node.typeName !== typeName + || node.typeArguments !== typeArguments + ? updateNode(createTypeReferenceNode(typeName, typeArguments), node) + : node; + } + + export function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType, types: TypeNode[]): UnionTypeNode; + export function createUnionOrIntersectionTypeNode(kind: SyntaxKind.IntersectionType, types: TypeNode[]): IntersectionTypeNode; + export function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: TypeNode[]): UnionOrIntersectionTypeNode; + export function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: TypeNode[]): UnionOrIntersectionTypeNode { + const unionTypeNode = createSynthesizedNode(kind) as UnionTypeNode | IntersectionTypeNode; + unionTypeNode.types = asNodeArray(types); + return unionTypeNode; + } + + export function updateUnionOrIntersectionTypeNode(node: UnionOrIntersectionTypeNode, kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: NodeArray) { + return node.types !== types + || node.kind !== kind + ? updateNode(createUnionOrIntersectionTypeNode(kind, types), node) + : node; + } + + export function createTypeLiteralNode(members: TypeElement[]) { + const typeLiteralNode = createSynthesizedNode(SyntaxKind.LiteralType) as TypeLiteralNode; + typeLiteralNode.members = asNodeArray(members); + return typeLiteralNode; + } + + export function updateTypeLiteralNode(node: TypeLiteralNode, members: NodeArray) { + return node.members !== members + ? updateNode(createTypeLiteralNode(members), node) + : node; + } + + export function createTupleTypeNode(elementTypes: TypeNode[]) { + const tupleTypeNode = createSynthesizedNode(SyntaxKind.TupleType) as TupleTypeNode; + tupleTypeNode.elementTypes = asNodeArray(elementTypes); + return tupleTypeNode; + } + + export function updateTypleTypeNode(node: TupleTypeNode, elementTypes: TypeNode[]) { + return node.elementTypes !== elementTypes + ? updateNode(createTupleTypeNode(elementTypes), node) + : node; + } + + // Type Declarations + export function createTypeParameterNode(name: string | Identifier, constraint?: TypeNode, defaultParameter?: TypeNode) { const typeParameter = createSynthesizedNode(SyntaxKind.TypeParameter) as TypeParameterDeclaration; typeParameter.name = asName(name); @@ -260,31 +312,6 @@ namespace ts { return typeParameter; } - export function createUnionTypeNode(types: NodeArray) { - const unionTypeNode = createSynthesizedNode(SyntaxKind.UnionType) as UnionTypeNode; - unionTypeNode.types = asNodeArray(types); - return unionTypeNode; - } - - export function createIntersectionTypeNode(types: NodeArray) { - const intersectionTypeNode = createSynthesizedNode(SyntaxKind.IntersectionType) as IntersectionTypeNode; - intersectionTypeNode.types = asNodeArray(types); - return intersectionTypeNode; - } - - export function createTypeLiteralNode(typeElements: TypeElement[]) { - const typeLiteralNode = createSynthesizedNode(SyntaxKind.LiteralType) as TypeLiteralNode; - typeLiteralNode.members = asNodeArray(typeElements); - return typeLiteralNode; - } - - export function createTupleTypeNode(types: NodeArray) { - const tupleTypeNode = createSynthesizedNode(SyntaxKind.TupleType) as TupleTypeNode; - tupleTypeNode.elementTypes = asNodeArray(types); - return tupleTypeNode; - } - - // Signature elements /** Note, can also be used to construct index signatures. */ @@ -298,10 +325,11 @@ namespace ts { } // TODO: check usage of name... - export function createIndexSignature(parameter: ParameterDeclaration, type: TypeNode, decorators?: Decorator[], modifiers?: Modifier[]): IndexSignatureDeclaration { + // TODO: create entry in visitor.ts + export function createIndexSignatureDeclaration(parameters: ParameterDeclaration[], type: TypeNode, decorators?: Decorator[], modifiers?: Modifier[]): IndexSignatureDeclaration { const indexSignature = createSignature( SyntaxKind.IndexSignature - , asNodeArray([parameter]) + , asNodeArray(parameters) , /*name*/ undefined , /*typeParameters*/undefined , type) as IndexSignatureDeclaration; @@ -310,6 +338,15 @@ namespace ts { return indexSignature; } + export function updateIndexSignatureDeclaration(node: IndexSignatureDeclaration, parameters: ParameterDeclaration[], type: TypeNode, decorators?: Decorator[], modifiers?: Modifier[]) { + return node.parameters !== parameters + || node.type !== type + || node.decorators !== decorators + || node.modifiers !== modifiers + ? updateNode(createIndexSignatureDeclaration(parameters, type, decorators, modifiers), node) + : node; + } + export function createParameter(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression) { const node = createSynthesizedNode(SyntaxKind.Parameter); node.decorators = asNodeArray(decorators); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4fd4dd1780..463638484c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -673,7 +673,7 @@ namespace ts { initializer?: Expression; // Optional initializer } - export interface BindingElement extends Declaration { + export interface BindingElement extends Declaration { kind: SyntaxKind.BindingElement; parent?: BindingPattern; propertyName?: PropertyName; // Binding property name (in object binding pattern) @@ -849,19 +849,17 @@ namespace ts { _typeNodeBrand: any; } - export type KeywordKind = SyntaxKind.AnyKeyword - | SyntaxKind.NumberKeyword - | SyntaxKind.ObjectKeyword - | SyntaxKind.BooleanKeyword - | SyntaxKind.StringKeyword - | SyntaxKind.SymbolKeyword - | SyntaxKind.VoidKeyword - | SyntaxKind.UndefinedKeyword - | SyntaxKind.NullKeyword - | SyntaxKind.NeverKeyword; - export interface KeywordTypeNode extends TypeNode { - kind: KeywordKind; + kind: SyntaxKind.AnyKeyword + | SyntaxKind.NumberKeyword + | SyntaxKind.ObjectKeyword + | SyntaxKind.BooleanKeyword + | SyntaxKind.StringKeyword + | SyntaxKind.SymbolKeyword + | SyntaxKind.VoidKeyword + | SyntaxKind.UndefinedKeyword + | SyntaxKind.NullKeyword + | SyntaxKind.NeverKeyword; } export interface ThisTypeNode extends TypeNode { diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 133c62e192..35119dc7fc 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -212,16 +212,12 @@ namespace ts { } const kind = node.kind; + // No need to visit nodes with no children. if ((kind > SyntaxKind.FirstToken && kind <= SyntaxKind.LastToken)) { return node; } - // We do not yet support types. - if ((kind >= SyntaxKind.TypePredicate && kind <= SyntaxKind.LiteralType)) { - return node; - } - switch (node.kind) { case SyntaxKind.SemicolonClassElement: case SyntaxKind.EmptyStatement: @@ -241,6 +237,13 @@ namespace ts { visitNode((node).expression, visitor, isExpression)); // Signature elements + case SyntaxKind.IndexSignature: + return updateIndexSignatureDeclaration(node + , nodesVisitor((node).parameters, visitor) + , visitNode((node).type, visitor) + , nodesVisitor((node).decorators, visitor, isDecorator) + , nodesVisitor((node).modifiers, visitor, isModifier)); + case SyntaxKind.Parameter: return updateParameter(node, nodesVisitor((node).decorators, visitor, isDecorator), @@ -254,7 +257,60 @@ namespace ts { return updateDecorator(node, visitNode((node).expression, visitor, isExpression)); - // Type member + // Keyword Types + + case SyntaxKind.AnyKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.ObjectKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.SymbolKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.UndefinedKeyword: + case SyntaxKind.NullKeyword: + case SyntaxKind.NeverKeyword: + case SyntaxKind.NeverKeyword: + return node; + + // Types + + case SyntaxKind.TypePredicate: + throw new Error("reached unsupported type."); + case SyntaxKind.TypeReference: + return updateTypeReferenceNode(node + , visitNode((node).typeName as Identifier, visitor) + , nodesVisitor((node).typeArguments, visitor) + ); + case SyntaxKind.FunctionType: + throw new Error("reached unsupported type."); + case SyntaxKind.ConstructorType: + throw new Error("reached unsupported type."); + case SyntaxKind.TypeQuery: + throw new Error("reached unsupported type."); + case SyntaxKind.TypeLiteral: + throw new Error("reached unsupported type."); + case SyntaxKind.ArrayType: + throw new Error("reached unsupported type."); + case SyntaxKind.TupleType: + throw new Error("reached unsupported type."); + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + throw new Error("reached unsupported type."); + case SyntaxKind.ParenthesizedType: + throw new Error("reached unsupported type."); + case SyntaxKind.ThisType: + throw new Error("reached unsupported type."); + case SyntaxKind.TypeOperator: + throw new Error("reached unsupported type."); + case SyntaxKind.IndexedAccessType: + throw new Error("reached unsupported type."); + case SyntaxKind.MappedType: + throw new Error("reached unsupported type."); + case SyntaxKind.LiteralType: + throw new Error("reached unsupported type."); + + // Type members + case SyntaxKind.PropertyDeclaration: return updateProperty(node, nodesVisitor((node).decorators, visitor, isDecorator), diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 05ff29f069..79b95e4799 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -62,13 +62,12 @@ namespace ts.codefix { , "name" , /*questionToken*/ undefined , stringTypeNode); - const indexSignature = createIndexSignature(indexingParameter, typeNode); + const indexSignature = createIndexSignatureDeclaration([indexingParameter], typeNode); - // const startPos = classDeclaration.members.pos; const indexSignatureChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); indexSignatureChangeTracker.insertNodeAfter(sourceFile, openBrace, indexSignature, { insertTrailingNewLine: true }); - return [{ + return [{ description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_declaration_for_missing_property_0), [token.getText()]), changes: propertyChangeTracker.getChanges() }, diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 0fed656af5..92b6365657 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1386,6 +1386,6 @@ namespace ts { } export function getOpenBraceOfClassLike(declaration: ClassLikeDeclaration, sourceFile: SourceFile) { - return getTokenAtPosition(sourceFile, declaration.members.pos); + return getTokenAtPosition(sourceFile, declaration.members.pos - 1); } } From d6863bea256cc8a51caf0d978a09fa73ea427cb2 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Sun, 12 Mar 2017 12:49:29 -0700 Subject: [PATCH 06/65] missing member fixes use createTypeNode --- ...sDoesntImplementInheritedAbstractMember.ts | 19 +--- .../fixClassIncorrectlyImplementsInterface.ts | 57 +++++----- src/services/codefixes/helpers.ts | 105 +++++++++++++----- 3 files changed, 112 insertions(+), 69 deletions(-) diff --git a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts index 16edce0a51..513853e461 100644 --- a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts +++ b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts @@ -19,10 +19,9 @@ namespace ts.codefix { const checker = context.program.getTypeChecker(); if (isClassLike(token.parent)) { - const classDecl = token.parent as ClassLikeDeclaration; - const startPos = classDecl.members.pos; + const classDeclaration = token.parent as ClassLikeDeclaration; - const extendsNode = getClassExtendsHeritageClauseElement(classDecl); + const extendsNode = getClassExtendsHeritageClauseElement(classDeclaration); const instantiatedExtendsType = checker.getTypeAtLocation(extendsNode); // Note that this is ultimately derived from a map indexed by symbol names, @@ -30,18 +29,12 @@ namespace ts.codefix { const extendsSymbols = checker.getPropertiesOfType(instantiatedExtendsType); const abstractAndNonPrivateExtendsSymbols = extendsSymbols.filter(symbolPointsToNonPrivateAndAbstractMember); - const insertion = getMissingMembersInsertion(classDecl, abstractAndNonPrivateExtendsSymbols, checker, context.newLineCharacter); - - if (insertion.length) { + const newNodes = createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, checker); + const changes = newNodesToChanges(newNodes, getOpenBraceOfClassLike(classDeclaration, sourceFile), context); + if(changes && changes.length > 0) { return [{ description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), - changes: [{ - fileName: sourceFile.fileName, - textChanges: [{ - span: { start: startPos, length: 0 }, - newText: insertion - }] - }] + changes }]; } } diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index 67b2242c8d..a5bde70c01 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -16,7 +16,7 @@ namespace ts.codefix { return undefined; } - const startPos: number = classDecl.members.pos; + const openBrace = getOpenBraceOfClassLike(classDecl, sourceFile); const classType = checker.getTypeAtLocation(classDecl) as InterfaceType; const implementedTypeNodes = getClassImplementsHeritageClauseElements(classDecl); @@ -31,43 +31,46 @@ namespace ts.codefix { const implementedTypeSymbols = checker.getPropertiesOfType(implementedType); const nonPrivateMembers = implementedTypeSymbols.filter(symbol => !(getModifierFlags(symbol.valueDeclaration) & ModifierFlags.Private)); - let insertion = getMissingIndexSignatureInsertion(implementedType, IndexKind.Number, classDecl, hasNumericIndexSignature); - insertion += getMissingIndexSignatureInsertion(implementedType, IndexKind.String, classDecl, hasStringIndexSignature); - insertion += getMissingMembersInsertion(classDecl, nonPrivateMembers, checker, context.newLineCharacter); - + let newNodes: Node[] = []; + createAndAddMissingIndexSignatureDeclaration(implementedType, IndexKind.Number, hasNumericIndexSignature, newNodes); + createAndAddMissingIndexSignatureDeclaration(implementedType, IndexKind.String, hasStringIndexSignature, newNodes); + newNodes = newNodes.concat(createMissingMemberNodes(classDecl, nonPrivateMembers, checker)); const message = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]); - if (insertion) { - pushAction(result, insertion, message); + if (newNodes.length > 0) { + pushAction(result, newNodes, message); } } return result; - function getMissingIndexSignatureInsertion(type: InterfaceType, kind: IndexKind, enclosingDeclaration: ClassLikeDeclaration, hasIndexSigOfKind: boolean) { - if (!hasIndexSigOfKind) { - const IndexInfoOfKind = checker.getIndexInfoOfType(type, kind); - if (IndexInfoOfKind) { - const writer = getSingleLineStringWriter(); - checker.getSymbolDisplayBuilder().buildIndexSignatureDisplay(IndexInfoOfKind, writer, kind, enclosingDeclaration); - const result = writer.string(); - releaseStringWriter(writer); - - return result; - } + function createAndAddMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind, hasIndexSigOfKind: boolean, newNodes: Node[]): void { + if (hasIndexSigOfKind) { + return undefined; } - return ""; + + const indexInfoOfKind = checker.getIndexInfoOfType(type, kind); + + if (!indexInfoOfKind) { + return undefined; + } + const typeNode = checker.createTypeNode(indexInfoOfKind.type); + let name: string; + const newIndexSignatureDeclaration = createIndexSignatureDeclaration( + [createParameter( + /*decorators*/undefined + , /*modifiers*/ undefined + , /*dotDotDotToken*/ undefined + , name + , /*questionToken*/ undefined + , kind === IndexKind.String ? createKeywordTypeNode(SyntaxKind.StringKeyword) : createKeywordTypeNode(SyntaxKind.NumberKeyword))] + , typeNode); + newNodes.push(newIndexSignatureDeclaration); } - function pushAction(result: CodeAction[], insertion: string, description: string): void { + function pushAction(result: CodeAction[], newNodes: Node[], description: string): void { const newAction: CodeAction = { description: description, - changes: [{ - fileName: sourceFile.fileName, - textChanges: [{ - span: { start: startPos, length: 0 }, - newText: insertion - }] - }] + changes: newNodesToChanges(newNodes, openBrace, context) }; result.push(newAction); } diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index d20fc0129c..0af13bf41e 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -1,31 +1,53 @@ /* @internal */ namespace ts.codefix { + export function newNodesToChanges(newNodes: Node[], insertAfter: Node, context: CodeFixContext) { + const sourceFile = context.sourceFile; + if (!(newNodes)) { + // TODO: make the appropriate value flow through gracefully. + throw new Error("newNodesToChanges expects an array"); + } + + const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); + + for (let i = newNodes.length - 1; i >= 0; i--) { + changeTracker.insertNodeAfter(sourceFile, insertAfter, newNodes[i], { insertTrailingNewLine: true }); + } + return changeTracker.getChanges(); + } + /** * Finds members of the resolved type that are missing in the class pointed to by class decl * and generates source code for the missing members. * @param possiblyMissingSymbols The collection of symbols to filter and then get insertions for. * @returns Empty string iff there are no member insertions. */ - export function getMissingMembersInsertion(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: Symbol[], checker: TypeChecker, newlineChar: string): string { + export function createMissingMemberNodes(classDeclaration: ClassLikeDeclaration, possiblyMissingSymbols: Symbol[], checker: TypeChecker): Node[] { const classMembers = classDeclaration.symbol.members; const missingMembers = possiblyMissingSymbols.filter(symbol => !classMembers.has(symbol.getName())); - let insertion = ""; - + let newNodes: Node[] = []; for (const symbol of missingMembers) { - insertion = insertion.concat(getInsertionForMemberSymbol(symbol, classDeclaration, checker, newlineChar)); + const newNode = getNewNodeForMemberSymbol(symbol, classDeclaration, checker); + if (newNode) { + if (Array.isArray(newNode)) { + newNodes = newNodes.concat(newNode); + } + else { + newNodes.push(newNode); + } + } } - return insertion; + return newNodes; } /** * @returns Empty string iff there we can't figure out a representation for `symbol` in `enclosingDeclaration`. */ - function getInsertionForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker, newlineChar: string): string { + function getNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker): Node[] | Node | undefined { const declarations = symbol.getDeclarations(); if (!(declarations && declarations.length)) { - return ""; + return undefined; } const declaration = declarations[0] as Declaration; @@ -39,9 +61,16 @@ namespace ts.codefix { case SyntaxKind.SetAccessor: case SyntaxKind.PropertySignature: case SyntaxKind.PropertyDeclaration: - const typeString = checker.typeToString(type, enclosingDeclaration, TypeFormatFlags.None); - return `${visibility}${name}: ${typeString};${newlineChar}`; - + const typeNode = checker.createTypeNode(type); + // TODO: add modifiers. + const property = createProperty( + /*decorators*/undefined + , /*modifiers*/ undefined + , name + , /*questionToken*/ undefined + , typeNode + , /*initializer*/ undefined); + return property; case SyntaxKind.MethodSignature: case SyntaxKind.MethodDeclaration: // The signature for the implementation appears as an entry in `signatures` iff @@ -53,18 +82,22 @@ namespace ts.codefix { // correspondence of declarations and signatures. const signatures = checker.getSignaturesOfType(type, SignatureKind.Call); if (!(signatures && signatures.length > 0)) { - return ""; + return undefined; } if (declarations.length === 1) { Debug.assert(signatures.length === 1); - const sigString = checker.signatureToString(signatures[0], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); - return getStubbedMethod(visibility, name, sigString, newlineChar); + // TODO: extract signature declaration from a signature. + // const sigString = checker.signatureToString(signatures[0], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); + // TODO: get parameters working. + // TODO: add support for type parameters. + return createStubbedMethod([visibility], name, /*typeParameters*/undefined, []); } - let result = ""; + let signatureDeclarations = []; for (let i = 0; i < signatures.length; i++) { - const sigString = checker.signatureToString(signatures[i], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); - result += `${visibility}${name}${sigString};${newlineChar}`; + // const sigString = checker.signatureToString(signatures[i], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); + // TODO: make signatures instead of methods + signatureDeclarations.push(createStubbedMethod([visibility], name, /*typeParameters*/undefined, [])); } // If there is a declaration with a body, it is the last declaration, @@ -77,12 +110,12 @@ namespace ts.codefix { Debug.assert(declarations.length === signatures.length); bodySig = createBodySignatureWithAnyTypes(signatures, enclosingDeclaration, checker); } - const sigString = checker.signatureToString(bodySig, enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); - result += getStubbedMethod(visibility, name, sigString, newlineChar); - - return result; + // const sigString = checker.signatureToString(bodySig, enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); + signatureDeclarations.push(createStubbedMethod([visibility], name, /*typeParameters*/undefined, [])); + + return signatureDeclarations; default: - return ""; + return undefined; } } @@ -138,22 +171,36 @@ namespace ts.codefix { } } - export function getStubbedMethod(visibility: string, name: string, sigString = "()", newlineChar: string): string { - return `${visibility}${name}${sigString}${getMethodBodyStub(newlineChar)}`; + export function createStubbedMethod(modifiers: Modifier[], name: string, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], returnType?: TypeNode) { + return createMethod( + /*decorators*/undefined + , /*modifiers*/modifiers + , /*asteriskToken*/undefined + , name + , typeParameters + , parameters + , returnType + , createStubbedMethodBody()); } - function getMethodBodyStub(newlineChar: string) { - return ` {${newlineChar}throw new Error('Method not implemented.');${newlineChar}}${newlineChar}`; + function createStubbedMethodBody() { + return createBlock( + [createThrow( + createNew( + createIdentifier('Error') + , /*typeArguments*/undefined + , [createLiteral('Method not implemented.')]))] + , /*multiline*/true); } - function getVisibilityPrefixWithSpace(flags: ModifierFlags): string { + function getVisibilityPrefixWithSpace(flags: ModifierFlags) { if (flags & ModifierFlags.Public) { - return "public "; + return createToken(SyntaxKind.PublicKeyword); } else if (flags & ModifierFlags.Protected) { - return "protected "; + return createToken(SyntaxKind.ProtectedKeyword); } - return ""; + return undefined; } const SymbolConstructor = objectAllocator.getSymbolConstructor(); From 03511c0b092a33e3e1fad2f61406760f2142c769 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Sun, 12 Mar 2017 16:36:03 -0700 Subject: [PATCH 07/65] snythesize methods in missing member fix --- src/compiler/factory.ts | 5 +- src/services/codefixes/helpers.ts | 123 ++++++++++++++++++------------ 2 files changed, 80 insertions(+), 48 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 19e8ef25da..f59eea8461 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -45,7 +45,10 @@ namespace ts { * Creates a shallow, memberwise clone of a node with no source map location. */ /* @internal */ - export function getSynthesizedClone(node: T): T { + export function getSynthesizedClone(node: T | undefined): T { + if(node === undefined) { + return undefined; + } // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of // the original node. We also need to exclude specific properties and only include own- // properties (to skip members already defined on the shared prototype). diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 0af13bf41e..27195ac0be 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -28,7 +28,7 @@ namespace ts.codefix { let newNodes: Node[] = []; for (const symbol of missingMembers) { - const newNode = getNewNodeForMemberSymbol(symbol, classDeclaration, checker); + const newNode = createNewNodeForMemberSymbol(symbol, classDeclaration, checker); if (newNode) { if (Array.isArray(newNode)) { newNodes = newNodes.concat(newNode); @@ -44,7 +44,7 @@ namespace ts.codefix { /** * @returns Empty string iff there we can't figure out a representation for `symbol` in `enclosingDeclaration`. */ - function getNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker): Node[] | Node | undefined { + function createNewNodeForMemberSymbol(symbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker): Node[] | Node | undefined { const declarations = symbol.getDeclarations(); if (!(declarations && declarations.length)) { return undefined; @@ -52,8 +52,7 @@ namespace ts.codefix { const declaration = declarations[0] as Declaration; const name = declaration.name ? declaration.name.getText() : undefined; - const visibility = getVisibilityPrefixWithSpace(getModifierFlags(declaration)); - + const modifiers = [createVisibilityModifier(getModifierFlags(declaration))]; const type = checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration); switch (declaration.kind) { @@ -86,43 +85,55 @@ namespace ts.codefix { } if (declarations.length === 1) { Debug.assert(signatures.length === 1); - // TODO: extract signature declaration from a signature. - // const sigString = checker.signatureToString(signatures[0], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); + // TODO: suppress any return type // TODO: get parameters working. // TODO: add support for type parameters. - return createStubbedMethod([visibility], name, /*typeParameters*/undefined, []); + const signature = signatures[0]; + const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); + const returnType = checker.createTypeNode(signature.resolvedReturnType); + return createStubbedMethod(modifiers, name, /*typeParameters*/undefined, newParameterNodes, returnType); } let signatureDeclarations = []; for (let i = 0; i < signatures.length; i++) { // const sigString = checker.signatureToString(signatures[i], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); // TODO: make signatures instead of methods - signatureDeclarations.push(createStubbedMethod([visibility], name, /*typeParameters*/undefined, [])); + const signature = signatures[i]; + const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); + const returnType = checker.createTypeNode(signature.resolvedReturnType); + signatureDeclarations.push(createMethod( + /*decorators*/ undefined + , modifiers + , /*asteriskToken*/ undefined + , name + , /*typeParameters*/undefined + , newParameterNodes + , returnType + , /*body*/undefined)); } - // If there is a declaration with a body, it is the last declaration, - // and it isn't caught by `getSignaturesOfType`. - let bodySig: Signature | undefined = undefined; if (declarations.length > signatures.length) { - bodySig = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration); + let signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration); + const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); + const returnType = checker.createTypeNode(signature.resolvedReturnType); + signatureDeclarations.push(createStubbedMethod(modifiers, name, /*typeParameters*/undefined, newParameterNodes, returnType)); } else { Debug.assert(declarations.length === signatures.length); - bodySig = createBodySignatureWithAnyTypes(signatures, enclosingDeclaration, checker); + const methodImplementingSignatures = createMethodImplementingSignatures(signatures, enclosingDeclaration, name, modifiers); + signatureDeclarations.push(methodImplementingSignatures); } - // const sigString = checker.signatureToString(bodySig, enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); - signatureDeclarations.push(createStubbedMethod([visibility], name, /*typeParameters*/undefined, [])); - return signatureDeclarations; default: return undefined; } } - function createBodySignatureWithAnyTypes(signatures: Signature[], enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker): Signature { - const newSignatureDeclaration = createNode(SyntaxKind.CallSignature) as SignatureDeclaration; - newSignatureDeclaration.parent = enclosingDeclaration; - newSignatureDeclaration.name = signatures[0].getDeclaration().name; + // TODO: infer types of arguments? + function createMethodImplementingSignatures(signatures: Signature[], enclosingDeclaration: ClassLikeDeclaration, name: string, modifiers: Modifier[] | undefined): MethodDeclaration { + const newMethodDeclaration = createNode(SyntaxKind.CallSignature) as SignatureDeclaration; + newMethodDeclaration.parent = enclosingDeclaration; + newMethodDeclaration.name = signatures[0].getDeclaration().name; let maxNonRestArgs = -1; let maxArgsIndex = 0; @@ -133,42 +144,47 @@ namespace ts.codefix { minArgumentCount = Math.min(sig.minArgumentCount, minArgumentCount); hasRestParameter = hasRestParameter || sig.hasRestParameter; const nonRestLength = sig.parameters.length - (sig.hasRestParameter ? 1 : 0); - if (nonRestLength > maxNonRestArgs) { + if (nonRestLength >= maxNonRestArgs) { maxNonRestArgs = nonRestLength; maxArgsIndex = i; } } const maxArgsParameterSymbolNames = signatures[maxArgsIndex].getParameters().map(symbol => symbol.getName()); - const optionalToken = createToken(SyntaxKind.QuestionToken); - - newSignatureDeclaration.parameters = createNodeArray(); + const parameters = createNodeArray(); for (let i = 0; i < maxNonRestArgs; i++) { - const newParameter = createParameterDeclarationWithoutType(i, minArgumentCount, newSignatureDeclaration); - newSignatureDeclaration.parameters.push(newParameter); + const newParameter = createParameter( + /*decorators*/ undefined + , /*modifiers*/ undefined + , /*dotDotDotToken*/ undefined + , maxArgsParameterSymbolNames[i] + , /*questionToken*/ i >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined + , /*type*/ undefined + , /*initializer*/ undefined); + parameters.push(newParameter); } if (hasRestParameter) { - const restParameter = createParameterDeclarationWithoutType(maxNonRestArgs, minArgumentCount, newSignatureDeclaration); - restParameter.dotDotDotToken = createToken(SyntaxKind.DotDotDotToken); - newSignatureDeclaration.parameters.push(restParameter); + const restParameter = createParameter( + /*decorators*/ undefined + , /*modifiers*/ undefined + , createToken(SyntaxKind.DotDotDotToken) + , maxArgsParameterSymbolNames[maxNonRestArgs] || "rest" + , /*questionToken*/ maxNonRestArgs >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined + , /*type*/ undefined + , /*initializer*/ undefined); + parameters.push(restParameter); } - return checker.getSignatureFromDeclaration(newSignatureDeclaration); - - function createParameterDeclarationWithoutType(index: number, minArgCount: number, enclosingSignatureDeclaration: SignatureDeclaration): ParameterDeclaration { - const newParameter = createNode(SyntaxKind.Parameter) as ParameterDeclaration; - - newParameter.symbol = new SymbolConstructor(SymbolFlags.FunctionScopedVariable, maxArgsParameterSymbolNames[index] || "rest"); - newParameter.symbol.valueDeclaration = newParameter; - newParameter.symbol.declarations = [newParameter]; - newParameter.parent = enclosingSignatureDeclaration; - if (index >= minArgCount) { - newParameter.questionToken = optionalToken; - } - - return newParameter; - } + return createMethod( + /*decorators*/ undefined + , modifiers + , /*asteriskToken*/ undefined + , name + , /*typeParameters*/undefined + , parameters + , /*type*/ undefined + , /*body*/undefined); } export function createStubbedMethod(modifiers: Modifier[], name: string, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], returnType?: TypeNode) { @@ -187,13 +203,13 @@ namespace ts.codefix { return createBlock( [createThrow( createNew( - createIdentifier('Error') + createIdentifier('Error') , /*typeArguments*/undefined , [createLiteral('Method not implemented.')]))] , /*multiline*/true); } - function getVisibilityPrefixWithSpace(flags: ModifierFlags) { + function createVisibilityModifier(flags: ModifierFlags) { if (flags & ModifierFlags.Public) { return createToken(SyntaxKind.PublicKeyword); } @@ -203,5 +219,18 @@ namespace ts.codefix { return undefined; } - const SymbolConstructor = objectAllocator.getSymbolConstructor(); + function createParameterDeclarationFromSymbol(parameterSymbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker) { + const parameterDeclaration = parameterSymbol.getDeclarations()[0] as ParameterDeclaration; + const parameterType = checker.getTypeOfSymbolAtLocation(parameterSymbol, enclosingDeclaration); + const parameterTypeNode = checker.createTypeNode(parameterType); + // TODO: deep cloning of decorators/any node. + const parameterNode = createParameter( + parameterDeclaration.decorators && parameterDeclaration.decorators.map(getSynthesizedClone) + , parameterDeclaration.modifiers && parameterDeclaration.modifiers.map(getSynthesizedClone) + , parameterDeclaration.dotDotDotToken && createToken(SyntaxKind.DotDotDotToken) + , parameterDeclaration.name + , parameterDeclaration.questionToken && createToken(SyntaxKind.QuestionToken) + , parameterTypeNode); + return parameterNode; + } } \ No newline at end of file From f5f01a162f46f69d8dbb6a0e99db00f49b250503 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Sun, 12 Mar 2017 17:14:20 -0700 Subject: [PATCH 08/65] Simplify Ranges --- ...ixClassExtendAbstractMethodTypeParamsInstantiateNumber.ts | 3 +-- ...codeFixClassExtendAbstractMethodTypeParamsInstantiateU.ts | 3 +-- .../fourslash/codeFixClassExtendAbstractPrivateProperty.ts | 3 +-- tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts | 3 +-- .../fourslash/codeFixClassExtendAbstractProtectedProperty.ts | 3 +-- .../fourslash/codeFixClassExtendAbstractPublicProperty.ts | 4 ++-- .../fourslash/codeFixClassImplementClassMethodViaHeritage.ts | 4 ++-- .../cases/fourslash/codeFixClassImplementDeepInheritance.ts | 3 +-- tests/cases/fourslash/codeFixClassImplementInterface36.ts | 4 +--- tests/cases/fourslash/codeFixClassImplementInterface39.ts | 3 +-- .../codeFixClassImplementInterfaceDuplicateMember1.ts | 3 +-- .../codeFixClassImplementInterfaceIndexSignaturesNumber.ts | 3 +-- ...deFixClassImplementInterfaceMethodThisAndSelfReference.ts | 3 +-- .../codeFixClassImplementInterfaceMultipleImplements1.ts | 5 ++--- .../codeFixClassImplementInterfaceMultipleImplements2.ts | 3 +-- ...ClassImplementInterfaceMultipleImplementsIntersection1.ts | 3 +-- ...ixClassImplementInterfaceMultipleMembersAndPunctuation.ts | 3 +-- .../codeFixClassImplementInterfaceNamespaceConflict.ts | 3 +-- ...mplementInterfacePropertyFromParentConstructorFunction.ts | 2 +- .../codeFixClassImplementInterfaceSomePropertiesPresent.ts | 2 +- ...deFixClassImplementInterfaceTypeParamInstantiateDeeply.ts | 2 +- ...deFixClassImplementInterfaceTypeParamInstantiateNumber.ts | 2 +- .../codeFixClassImplementInterfaceTypeParamInstantiateT.ts | 2 +- .../codeFixClassImplementInterfaceTypeParamInstantiateU.ts | 2 +- 24 files changed, 27 insertions(+), 44 deletions(-) diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateNumber.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateNumber.ts index bdf198b89e..a69852f5e9 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateNumber.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateNumber.ts @@ -4,8 +4,7 @@ //// abstract f(x: T): T; //// } //// -//// class C extends A {[| -//// |]} +//// class C extends A {[| |]} verify.rangeAfterCodeFix(`f(x: number): number{ throw new Error('Method not implemented.'); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateU.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateU.ts index 647a533c1c..98d118214d 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateU.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateU.ts @@ -4,8 +4,7 @@ //// abstract f(x: T): T; //// } //// -//// class C extends A {[| -//// |]} +//// class C extends A {[| |]} verify.rangeAfterCodeFix(`f(x: U): U{ throw new Error('Method not implemented.'); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractPrivateProperty.ts b/tests/cases/fourslash/codeFixClassExtendAbstractPrivateProperty.ts index d395272b36..b8f6b3d81e 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractPrivateProperty.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractPrivateProperty.ts @@ -4,8 +4,7 @@ //// private abstract x: number; //// } //// -//// class C extends A {[| -//// |]} +//// class C extends A {[| |]} // We don't know how to fix this problem. We can: // 1) Make x protected, and then insert. diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts b/tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts index b7300acf5a..1973f452f6 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts @@ -7,8 +7,7 @@ //// abstract foo(): number; //// } //// -//// class C extends A {[| -//// |]} +//// class C extends A {[| |]} verify.rangeAfterCodeFix(` x: number; diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractProtectedProperty.ts b/tests/cases/fourslash/codeFixClassExtendAbstractProtectedProperty.ts index 6d0571fce6..d2532ef16d 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractProtectedProperty.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractProtectedProperty.ts @@ -4,8 +4,7 @@ //// protected abstract x: number; //// } //// -//// class C extends A {[| -//// |]} +//// class C extends A {[| |]} verify.rangeAfterCodeFix(` protected x: number; diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractPublicProperty.ts b/tests/cases/fourslash/codeFixClassExtendAbstractPublicProperty.ts index 917f75051f..495d661ec7 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractPublicProperty.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractPublicProperty.ts @@ -4,8 +4,8 @@ //// public abstract x: number; //// } //// -//// class C extends A {[| -//// |]} +//// class C extends A {[| |]} + verify.rangeAfterCodeFix(` public x: number; diff --git a/tests/cases/fourslash/codeFixClassImplementClassMethodViaHeritage.ts b/tests/cases/fourslash/codeFixClassImplementClassMethodViaHeritage.ts index 4365a16053..b70c2898f1 100644 --- a/tests/cases/fourslash/codeFixClassImplementClassMethodViaHeritage.ts +++ b/tests/cases/fourslash/codeFixClassImplementClassMethodViaHeritage.ts @@ -8,8 +8,8 @@ //// //// } //// -//// class C3 implements C2 {[| -//// |]f2(){} +//// class C3 implements C2 {[| |] +//// f2(){} //// } verify.rangeAfterCodeFix(`f1(): void{ diff --git a/tests/cases/fourslash/codeFixClassImplementDeepInheritance.ts b/tests/cases/fourslash/codeFixClassImplementDeepInheritance.ts index e8b199b21a..f11dc29395 100644 --- a/tests/cases/fourslash/codeFixClassImplementDeepInheritance.ts +++ b/tests/cases/fourslash/codeFixClassImplementDeepInheritance.ts @@ -24,8 +24,7 @@ //// } //// //// interface I6 extends C4 {} -//// class C5 implements I6 {[| -//// |]} +//// class C5 implements I6 {[| |]} /** diff --git a/tests/cases/fourslash/codeFixClassImplementInterface36.ts b/tests/cases/fourslash/codeFixClassImplementInterface36.ts index f9fc48991f..d96ce52390 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterface36.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterface36.ts @@ -10,9 +10,7 @@ //// //// interface I1 extends C1, C2 {} //// -//// class C3 implements I1 {[| -//// -//// |]} +//// class C3 implements I1 {[| |]} verify.rangeAfterCodeFix(`f1(){ throw new Error('Method not implemented.'); diff --git a/tests/cases/fourslash/codeFixClassImplementInterface39.ts b/tests/cases/fourslash/codeFixClassImplementInterface39.ts index f81dea4978..1df34c2e1a 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterface39.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterface39.ts @@ -9,8 +9,7 @@ //// f1(); //// } //// -//// class C1 implements N1.I1 {[| -//// |]} +//// class C1 implements N1.I1 {[| |]} verify.rangeAfterCodeFix(`f1(): string{ throw new Error('Method not implemented.'); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceDuplicateMember1.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceDuplicateMember1.ts index a2154de567..bea14ae404 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceDuplicateMember1.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceDuplicateMember1.ts @@ -7,7 +7,6 @@ //// x: number; //// } //// -//// class C implements I1,I2 {[| -//// |]} +//// class C implements I1,I2 {[| |]} verify.codeFixAvailable(); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesNumber.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesNumber.ts index 0f14344253..41bb7257e4 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesNumber.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesNumber.ts @@ -4,8 +4,7 @@ //// [x: number]: I; //// } //// -//// class C implements I {[| -//// |]} +//// class C implements I {[| |]} verify.rangeAfterCodeFix(` [x: number]: I; diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMethodThisAndSelfReference.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMethodThisAndSelfReference.ts index 95cc3476bf..48e33e6ef0 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMethodThisAndSelfReference.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMethodThisAndSelfReference.ts @@ -4,8 +4,7 @@ //// f(x: number, y: this): I //// } //// -//// class C implements I {[| -//// |]} +//// class C implements I {[| |]} verify.rangeAfterCodeFix(` f(x: number,y: this): I { diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplements1.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplements1.ts index c8d61227c4..5043a4d9c2 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplements1.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplements1.ts @@ -7,13 +7,12 @@ //// y: number; //// } //// -//// class C implements I1,I2 {[| +//// class C implements I1,I2 {[| |] //// y: number; -//// |]} +//// } verify.rangeAfterCodeFix(` x: number; -y: number; `); verify.not.codeFixAvailable(); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplements2.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplements2.ts index d29b23998b..ead2375168 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplements2.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplements2.ts @@ -7,13 +7,12 @@ //// y: number; //// } //// -//// class C implements I1,I2 {[| +//// class C implements I1,I2 {[| |] //// x: number; //// |]} verify.rangeAfterCodeFix(` y: number; -x: number; `); verify.not.codeFixAvailable(); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplementsIntersection1.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplementsIntersection1.ts index 321d41e463..cac1038eb1 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplementsIntersection1.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplementsIntersection1.ts @@ -7,7 +7,6 @@ //// x: string; //// } //// -//// class C implements I1,I2 {[| -//// |]} +//// class C implements I1,I2 {[| |]} verify.codeFixAvailable(); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleMembersAndPunctuation.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleMembersAndPunctuation.ts index beab89c06a..afc1dae551 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleMembersAndPunctuation.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleMembersAndPunctuation.ts @@ -9,8 +9,7 @@ //// h(); //// } //// -//// class C1 implements I1 {[| -//// |]} +//// class C1 implements I1 {[| |]} verify.rangeAfterCodeFix(` x: number; diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts index 4fea72aa48..4ca42640c2 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts @@ -9,8 +9,7 @@ //// f1(); //// } //// -//// class C1 implements N1.I1 {[| -//// |]} +//// class C1 implements N1.I1 {[| |]} verify.rangeAfterCodeFix(` x: number; diff --git a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyFromParentConstructorFunction.ts b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyFromParentConstructorFunction.ts index 2a4ca4df92..acda59513a 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyFromParentConstructorFunction.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyFromParentConstructorFunction.ts @@ -4,7 +4,7 @@ //// constructor(public x: number) { } //// } //// -//// class B implements A {[| |]} +//// class B implements A {[| |]} verify.not.codeFixAvailable(); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceSomePropertiesPresent.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceSomePropertiesPresent.ts index 6e0658019f..03807ab450 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceSomePropertiesPresent.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceSomePropertiesPresent.ts @@ -6,7 +6,7 @@ //// z: number & { __iBrand: any }; //// } //// -//// class C implements I {[| |] +//// class C implements I {[| |] //// constructor(public x: number) { } //// y: number; //// } diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateDeeply.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateDeeply.ts index f76bd5dae9..acccafe0fc 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateDeeply.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateDeeply.ts @@ -4,7 +4,7 @@ //// x: { y: T, z: T[] }; //// } //// -//// class C implements I {[| |]} +//// class C implements I {[| |]} verify.rangeAfterCodeFix(` x: { y: number; z: number[]; }; diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateNumber.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateNumber.ts index af0e7a162e..a11101c7f4 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateNumber.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateNumber.ts @@ -4,7 +4,7 @@ //// x: T; //// } //// -//// class C implements I {[| |]} +//// class C implements I {[| |]} verify.rangeAfterCodeFix(` x: number; diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateT.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateT.ts index 45fa3fd40c..298ee0704f 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateT.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateT.ts @@ -4,7 +4,7 @@ //// x: T; //// } //// -//// class C implements I {[| |]} +//// class C implements I {[| |]} verify.rangeAfterCodeFix(` x: T; diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateU.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateU.ts index 16c35f4177..3f1da116f4 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateU.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamInstantiateU.ts @@ -4,7 +4,7 @@ //// x: T; //// } //// -//// class C implements I {[| |]} +//// class C implements I {[| |]} verify.rangeAfterCodeFix(` x: U; From 965d43c6a553aaeb7c849baa9410566974dfd178 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Sun, 12 Mar 2017 18:35:48 -0700 Subject: [PATCH 09/65] Print new nodes in correct order --- src/services/codefixes/helpers.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 27195ac0be..bbbe14ab33 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -10,8 +10,8 @@ namespace ts.codefix { const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); - for (let i = newNodes.length - 1; i >= 0; i--) { - changeTracker.insertNodeAfter(sourceFile, insertAfter, newNodes[i], { insertTrailingNewLine: true }); + for (const newNode of newNodes) { + changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode, { insertTrailingNewLine: true }); } return changeTracker.getChanges(); } @@ -52,7 +52,8 @@ namespace ts.codefix { const declaration = declarations[0] as Declaration; const name = declaration.name ? declaration.name.getText() : undefined; - const modifiers = [createVisibilityModifier(getModifierFlags(declaration))]; + const visibilityModifier = createVisibilityModifier(getModifierFlags(declaration)); + const modifiers = visibilityModifier ? [visibilityModifier] : undefined; const type = checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration); switch (declaration.kind) { @@ -64,7 +65,7 @@ namespace ts.codefix { // TODO: add modifiers. const property = createProperty( /*decorators*/undefined - , /*modifiers*/ undefined + , modifiers , name , /*questionToken*/ undefined , typeNode From 93cd1dc1eab6dc0f18ae9a6d7408015bbe8d5071 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Sun, 12 Mar 2017 18:47:17 -0700 Subject: [PATCH 10/65] get name for index info --- src/services/codefixes/fixAddMissingMember.ts | 2 +- .../codefixes/fixClassIncorrectlyImplementsInterface.ts | 3 +-- src/services/codefixes/helpers.ts | 4 ++++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 79b95e4799..fe12249f17 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -59,7 +59,7 @@ namespace ts.codefix { /*decorators*/ undefined , /*modifiers*/ undefined , /*dotDotDotToken*/ undefined - , "name" + , "x" , /*questionToken*/ undefined , stringTypeNode); const indexSignature = createIndexSignatureDeclaration([indexingParameter], typeNode); diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index a5bde70c01..f653d21e02 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -54,13 +54,12 @@ namespace ts.codefix { return undefined; } const typeNode = checker.createTypeNode(indexInfoOfKind.type); - let name: string; const newIndexSignatureDeclaration = createIndexSignatureDeclaration( [createParameter( /*decorators*/undefined , /*modifiers*/ undefined , /*dotDotDotToken*/ undefined - , name + , getNameFromIndexInfo(indexInfoOfKind) , /*questionToken*/ undefined , kind === IndexKind.String ? createKeywordTypeNode(SyntaxKind.StringKeyword) : createKeywordTypeNode(SyntaxKind.NumberKeyword))] , typeNode); diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index bbbe14ab33..67f53fb8e5 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -234,4 +234,8 @@ namespace ts.codefix { , parameterTypeNode); return parameterNode; } + + export function getNameFromIndexInfo(info: IndexInfo) { + return info.declaration ? declarationNameToString(info.declaration.parameters[0].name) : "x" + } } \ No newline at end of file From 060317759e3b84f3c10ddc26224fba75a121a640 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Sun, 12 Mar 2017 18:48:51 -0700 Subject: [PATCH 11/65] Add type parameters --- src/compiler/factory.ts | 21 ++++++++++++++------- src/compiler/types.ts | 2 +- src/compiler/visitor.ts | 15 +++++++++++++++ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index f59eea8461..901f3a4bd1 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -306,7 +306,7 @@ namespace ts { // Type Declarations - export function createTypeParameterNode(name: string | Identifier, constraint?: TypeNode, defaultParameter?: TypeNode) { + export function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultParameter?: TypeNode) { const typeParameter = createSynthesizedNode(SyntaxKind.TypeParameter) as TypeParameterDeclaration; typeParameter.name = asName(name); typeParameter.constraint = constraint; @@ -315,6 +315,14 @@ namespace ts { return typeParameter; } + export function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint?: TypeNode, defaultParameter?: TypeNode) { + return node.name !== name + || node.constraint !== constraint + || node.default !== defaultParameter + ? updateNode(createTypeParameterDeclaration(name, constraint, defaultParameter), node) + : node; + } + // Signature elements /** Note, can also be used to construct index signatures. */ @@ -330,12 +338,11 @@ namespace ts { // TODO: check usage of name... // TODO: create entry in visitor.ts export function createIndexSignatureDeclaration(parameters: ParameterDeclaration[], type: TypeNode, decorators?: Decorator[], modifiers?: Modifier[]): IndexSignatureDeclaration { - const indexSignature = createSignature( - SyntaxKind.IndexSignature - , asNodeArray(parameters) - , /*name*/ undefined - , /*typeParameters*/undefined - , type) as IndexSignatureDeclaration; + const indexSignature = createSynthesizedNode(SyntaxKind.IndexSignature) as IndexSignatureDeclaration; + // indexSignature.name = asName(name); + // type parameters + indexSignature.parameters = asNodeArray(parameters); + indexSignature.type = type; indexSignature.decorators = asNodeArray(decorators); indexSignature.modifiers = asNodeArray(modifiers); return indexSignature; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 463638484c..b9f5da0f6e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -818,7 +818,7 @@ namespace ts { body?: FunctionBody; } - // For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. + /** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements.*/ export interface SemicolonClassElement extends ClassElement { kind: SyntaxKind.SemicolonClassElement; } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 35119dc7fc..91110accbb 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -309,8 +309,23 @@ namespace ts { case SyntaxKind.LiteralType: throw new Error("reached unsupported type."); + // Type Declarations + + case SyntaxKind.TypeParameter: + return updateTypeParameterDeclaration(node + , visitNode((node).name, visitor, isIdentifier) + , visitNode((node).constraint, visitor, isTypeNode) + , visitNode((node).default, visitor, isTypeNode)); + // Type members + case SyntaxKind.IndexSignature: + updateIndexSignatureDeclaration(node + , nodesVisitor((node).parameters, visitor, isParameter) + , visitNode((node).type, visitor, isTypeNode) + , nodesVisitor((node).decorators, visitor, isDecorator) + , nodesVisitor((node).modifiers, visitor, isModifier)); + case SyntaxKind.PropertyDeclaration: return updateProperty(node, nodesVisitor((node).decorators, visitor, isDecorator), From 6c72283378a3b3f02dfa380aa41a269fb5078dc6 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Sun, 12 Mar 2017 18:52:38 -0700 Subject: [PATCH 12/65] Use consistent quotes --- ...sExprClassImplementClassFunctionVoidInferred.ts | 2 +- .../fourslash/codeFixClassExtendAbstractMethod.ts | 2 +- .../codeFixClassExtendAbstractMethodThis.ts | 2 +- ...endAbstractMethodTypeParamsInstantiateNumber.ts | 2 +- ...ssExtendAbstractMethodTypeParamsInstantiateU.ts | 2 +- .../codeFixClassExtendAbstractProperty.ts | 2 +- ...deFixClassImplementClassFunctionVoidInferred.ts | 2 +- .../codeFixClassImplementClassMethodViaHeritage.ts | 2 +- ...odeFixClassImplementClassMultipleSignatures1.ts | 2 +- ...odeFixClassImplementClassMultipleSignatures2.ts | 2 +- .../fourslash/codeFixClassImplementInterface36.ts | 2 +- .../fourslash/codeFixClassImplementInterface39.ts | 2 +- ...ssImplementInterfaceComputedPropertyLiterals.ts | 4 ++-- ...nterfaceComputedPropertyNameWellKnownSymbols.ts | 14 +++++++------- ...ImplementInterfaceMethodThisAndSelfReference.ts | 2 +- ...lementInterfaceMultipleMembersAndPunctuation.ts | 6 +++--- ...FixClassImplementInterfaceMultipleSignatures.ts | 2 +- ...assImplementInterfaceMultipleSignaturesRest1.ts | 2 +- ...assImplementInterfaceMultipleSignaturesRest2.ts | 2 +- ...odeFixClassImplementInterfaceTypeParamMethod.ts | 2 +- 20 files changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/cases/fourslash/codeFixClassExprClassImplementClassFunctionVoidInferred.ts b/tests/cases/fourslash/codeFixClassExprClassImplementClassFunctionVoidInferred.ts index bdf8ea3bd2..2568111fb9 100644 --- a/tests/cases/fourslash/codeFixClassExprClassImplementClassFunctionVoidInferred.ts +++ b/tests/cases/fourslash/codeFixClassExprClassImplementClassFunctionVoidInferred.ts @@ -8,6 +8,6 @@ verify.rangeAfterCodeFix(` f(): void{ - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethod.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethod.ts index 344a7ee3f2..b6b408cf5f 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractMethod.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethod.ts @@ -15,6 +15,6 @@ verify.rangeAfterCodeFix(` f(a: string, b: number): Function; f(a: string): Function; f(a: any, b?: any) { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethodThis.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethodThis.ts index 55b3ad4b77..a462ac9812 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractMethodThis.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethodThis.ts @@ -8,6 +8,6 @@ verify.rangeAfterCodeFix(` f(): this { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateNumber.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateNumber.ts index a69852f5e9..c1a55e8803 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateNumber.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateNumber.ts @@ -7,6 +7,6 @@ //// class C extends A {[| |]} verify.rangeAfterCodeFix(`f(x: number): number{ - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateU.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateU.ts index 98d118214d..c2ec5ff803 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateU.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethodTypeParamsInstantiateU.ts @@ -7,6 +7,6 @@ //// class C extends A {[| |]} verify.rangeAfterCodeFix(`f(x: U): U{ - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts b/tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts index 1973f452f6..83f770b555 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts @@ -14,6 +14,6 @@ verify.rangeAfterCodeFix(` y: this; z: A; foo(): number { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); diff --git a/tests/cases/fourslash/codeFixClassImplementClassFunctionVoidInferred.ts b/tests/cases/fourslash/codeFixClassImplementClassFunctionVoidInferred.ts index d9b9f57d4d..b8fdace9f7 100644 --- a/tests/cases/fourslash/codeFixClassImplementClassFunctionVoidInferred.ts +++ b/tests/cases/fourslash/codeFixClassImplementClassFunctionVoidInferred.ts @@ -8,6 +8,6 @@ verify.rangeAfterCodeFix(` f(): void{ - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); diff --git a/tests/cases/fourslash/codeFixClassImplementClassMethodViaHeritage.ts b/tests/cases/fourslash/codeFixClassImplementClassMethodViaHeritage.ts index b70c2898f1..ede19ef570 100644 --- a/tests/cases/fourslash/codeFixClassImplementClassMethodViaHeritage.ts +++ b/tests/cases/fourslash/codeFixClassImplementClassMethodViaHeritage.ts @@ -13,6 +13,6 @@ //// } verify.rangeAfterCodeFix(`f1(): void{ - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); diff --git a/tests/cases/fourslash/codeFixClassImplementClassMultipleSignatures1.ts b/tests/cases/fourslash/codeFixClassImplementClassMultipleSignatures1.ts index 82ecde6c41..2e8dc316b3 100644 --- a/tests/cases/fourslash/codeFixClassImplementClassMultipleSignatures1.ts +++ b/tests/cases/fourslash/codeFixClassImplementClassMultipleSignatures1.ts @@ -9,6 +9,6 @@ verify.rangeAfterCodeFix(` method(a: number, b: string): boolean; method(a: string | number, b?: string | number): boolean | Function { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); diff --git a/tests/cases/fourslash/codeFixClassImplementClassMultipleSignatures2.ts b/tests/cases/fourslash/codeFixClassImplementClassMultipleSignatures2.ts index f187e0ca0f..463b10c450 100644 --- a/tests/cases/fourslash/codeFixClassImplementClassMultipleSignatures2.ts +++ b/tests/cases/fourslash/codeFixClassImplementClassMultipleSignatures2.ts @@ -13,6 +13,6 @@ verify.rangeAfterCodeFix(` method(a: string, b: number): Function; method(a: string): Function; method(a: string | number, b?: string | number): boolean | Function { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); diff --git a/tests/cases/fourslash/codeFixClassImplementInterface36.ts b/tests/cases/fourslash/codeFixClassImplementInterface36.ts index d96ce52390..5b0459ab66 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterface36.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterface36.ts @@ -13,6 +13,6 @@ //// class C3 implements I1 {[| |]} verify.rangeAfterCodeFix(`f1(){ - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassImplementInterface39.ts b/tests/cases/fourslash/codeFixClassImplementInterface39.ts index 1df34c2e1a..e4e1cf3608 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterface39.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterface39.ts @@ -12,6 +12,6 @@ //// class C1 implements N1.I1 {[| |]} verify.rangeAfterCodeFix(`f1(): string{ - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyLiterals.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyLiterals.ts index 341b4e5981..4a6bb20f57 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyLiterals.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyLiterals.ts @@ -11,11 +11,11 @@ verify.rangeAfterCodeFix(` ["foo"](o: any): boolean { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } ["x"]: boolean; [1](): string { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } [2]: boolean; `); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyNameWellKnownSymbols.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyNameWellKnownSymbols.ts index 7c1156acd7..61b4267e7f 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyNameWellKnownSymbols.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyNameWellKnownSymbols.ts @@ -22,30 +22,30 @@ verify.rangeAfterCodeFix(` [Symbol.hasInstance](o: any): boolean { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } [Symbol.isConcatSpreadable]: boolean; [Symbol.iterator]() { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } [Symbol.match]: boolean; [Symbol.replace](...args: {}) { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } [Symbol.search](str: string): number { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } [Symbol.species](): number { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } [Symbol.split](str: string, limit?: number): {} { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } [Symbol.toPrimitive](hint: "number"): number; [Symbol.toPrimitive](hint: "default"): number; [Symbol.toPrimitive](hint: "string"): string; [Symbol.toPrimitive](hint: any) { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } [Symbol.toStringTag]: string; [Symbol.unscopables]: any; diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMethodThisAndSelfReference.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMethodThisAndSelfReference.ts index 48e33e6ef0..b8225f31e0 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMethodThisAndSelfReference.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMethodThisAndSelfReference.ts @@ -8,6 +8,6 @@ verify.rangeAfterCodeFix(` f(x: number,y: this): I { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleMembersAndPunctuation.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleMembersAndPunctuation.ts index afc1dae551..66cdb1f545 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleMembersAndPunctuation.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleMembersAndPunctuation.ts @@ -16,12 +16,12 @@ x: number; y: number; z: number; f() { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } g() { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } h() { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignatures.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignatures.ts index bda58b3767..583dda1305 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignatures.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignatures.ts @@ -13,6 +13,6 @@ verify.rangeAfterCodeFix(` method(a: string, b: number): Function; method(a: string): Function; method(a: any, b?: any) { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignaturesRest1.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignaturesRest1.ts index 6e0a272a42..132c63ae0f 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignaturesRest1.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignaturesRest1.ts @@ -13,6 +13,6 @@ verify.rangeAfterCodeFix(` method(a: string, ...b: number[]): Function; method(a: string): Function; method(a: any, ...b?: any[]) { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignaturesRest2.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignaturesRest2.ts index 7ba7ae2c98..b4e9a8ff91 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignaturesRest2.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleSignaturesRest2.ts @@ -13,6 +13,6 @@ verify.rangeAfterCodeFix(` method(a: string, b: number): Function; method(a: string): Function; method(a: any, b?: any, ...rest?: any[]) { - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamMethod.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamMethod.ts index 4d6afaefed..9bcd4b8845 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamMethod.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceTypeParamMethod.ts @@ -7,6 +7,6 @@ //// class C implements I {[| |]} verify.rangeAfterCodeFix(`f(x: T){ - throw new Error('Method not implemented.'); + throw new Error("Method not implemented."); } `); \ No newline at end of file From 6fe59f34509e9fd99f0ae45964edac8a73e13aa0 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Sun, 12 Mar 2017 19:18:51 -0700 Subject: [PATCH 13/65] Unions and intersections --- src/compiler/checker.ts | 49 ++++++++++++++++++----------------------- src/compiler/factory.ts | 5 ++--- src/compiler/visitor.ts | 29 ++++++++++++------------ 3 files changed, 39 insertions(+), 44 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f29204f7ca..d3e1eeb08e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2190,20 +2190,34 @@ namespace ts { } function createTypeNode(type: Type) { - // let encounteredError = false; + let undefinedArgumentIsError = true; + let encounteredError = false; let checkAlias = true; return createTypeNodeWorker(type); // function createTypeDeclaration(type: Type): Declaration { // if (!type) { + // if (undefinedArgumentIsError) { encounteredError = true; } // return undefined; // } - // throw new Error("not implemented."); + // if (type.flags & TypeFlags.TypeParameter) { + // const constraint = createTypeNodeWorker(getConstraintFromTypeParameter(type)) as TypeNode; + // const defaultParameter = createTypeNodeWorker(getDefaultFromTypeParameter(type)) as TypeNode; + // if (!type.symbol) { + // encounteredError = true; + // throw new Error("No symbol for type parameter so can't get name"); + // } + // const name = getNameOfSymbol(type.symbol); + // return createTypeParameterDeclaration(name, constraint, defaultParameter); + // } + + // throw new Error("type declarations not implemented."); // } function createTypeNodeWorker(type: Type): TypeNode { if (!type) { + if (undefinedArgumentIsError) { encounteredError = true; } return undefined; } @@ -2229,11 +2243,9 @@ namespace ts { return createKeywordTypeNode(SyntaxKind.BooleanKeyword); } if (type.flags & (TypeFlags.StringLiteral)) { - // TODO: check if this actually works with boolean. return createLiteralTypeNode((createLiteral((type).text))); } if (type.flags & (TypeFlags.NumberLiteral)) { - // TODO: check if this actually works with boolean. return createLiteralTypeNode((createNumericLiteral((type).text))); } if (type.flags & TypeFlags.Void) { @@ -2249,40 +2261,27 @@ namespace ts { return createKeywordTypeNode(SyntaxKind.NeverKeyword); } if (type.flags & TypeFlags.Enum) { - throw new Error("not implemented"); + throw new Error("enum not implemented"); } if (type.flags & TypeFlags.ESSymbol) { - throw new Error("not implemented"); + throw new Error("ESSymbol not implemented"); } if (type.flags & TypeFlags.TypeParameter) { throw new Error("Type Parameter declarations only handled in other worker."); - // const constraint = createTypeNodeWorker(getConstraintFromTypeParameter(type)) as TypeNode; - // const defaultParameter = createTypeNodeWorker(getDefaultFromTypeParameter(type)) as TypeNode; - // if (!type.symbol) { - // encounteredError = true; - // throw new Error("No symbol for type parameter so can't get name"); - // } - // const name = getNameOfSymbol(type.symbol); - // return createTypeParameterNode(name, constraint, defaultParameter); } if (type.flags & TypeFlags.Union) { - throw new Error("not implemented"); + return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, mapToTypeNodeArray((type as UnionType).types)); } if (type.flags & TypeFlags.Intersection) { - throw new Error("not implemented"); + return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, mapToTypeNodeArray((type as UnionType).types)); } if (type.flags & TypeFlags.Index) { - throw new Error("not implemented"); + throw new Error("index not implemented"); } if (type.flags & TypeFlags.IndexedAccess) { - throw new Error("not implemented"); + throw new Error("indexed access not implemented"); } - // if(type.flags & TypeFlags.Object) { - // throw new Error("not implemented"); - // } - - // TODO: should these be within the if above (check with asserts) const objectFlags = getObjectFlags(type); if (objectFlags & ObjectFlags.ClassOrInterface) { @@ -2390,10 +2389,6 @@ namespace ts { function mapToTypeNodeArray(types: Type[]): NodeArray { return asNodeArray(types && types.map(createTypeNodeWorker) as TypeNode[]); } - - // function mapToTypeDeclarationsArray(types: Type[]): NodeArray { - // return asNodeArray(types && types.map(createTypeDeclaration)); - // } } } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 901f3a4bd1..6e10d8bb22 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -273,10 +273,9 @@ namespace ts { return unionTypeNode; } - export function updateUnionOrIntersectionTypeNode(node: UnionOrIntersectionTypeNode, kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: NodeArray) { + export function updateUnionOrIntersectionTypeNode(node: UnionOrIntersectionTypeNode, types: NodeArray) { return node.types !== types - || node.kind !== kind - ? updateNode(createUnionOrIntersectionTypeNode(kind, types), node) + ? updateNode(createUnionOrIntersectionTypeNode(node.kind, types), node) : node; } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 91110accbb..bd2d26b775 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -275,39 +275,40 @@ namespace ts { // Types case SyntaxKind.TypePredicate: - throw new Error("reached unsupported type."); + throw new Error("reached unsupported type in visitor."); case SyntaxKind.TypeReference: return updateTypeReferenceNode(node , visitNode((node).typeName as Identifier, visitor) , nodesVisitor((node).typeArguments, visitor) ); case SyntaxKind.FunctionType: - throw new Error("reached unsupported type."); + throw new Error("reached unsupported type in visitor."); case SyntaxKind.ConstructorType: - throw new Error("reached unsupported type."); + throw new Error("reached unsupported type in visitor."); case SyntaxKind.TypeQuery: - throw new Error("reached unsupported type."); + throw new Error("reached unsupported type in visitor."); case SyntaxKind.TypeLiteral: - throw new Error("reached unsupported type."); + throw new Error("reached unsupported type in visitor."); case SyntaxKind.ArrayType: - throw new Error("reached unsupported type."); + throw new Error("reached unsupported type in visitor."); case SyntaxKind.TupleType: - throw new Error("reached unsupported type."); + throw new Error("reached unsupported type in visitor."); case SyntaxKind.UnionType: case SyntaxKind.IntersectionType: - throw new Error("reached unsupported type."); + return updateUnionOrIntersectionTypeNode(node + , nodesVisitor((node).types, visitor, isTypeNode)); case SyntaxKind.ParenthesizedType: - throw new Error("reached unsupported type."); + throw new Error("reached unsupported type in visitor."); case SyntaxKind.ThisType: - throw new Error("reached unsupported type."); + throw new Error("reached unsupported type in visitor."); case SyntaxKind.TypeOperator: - throw new Error("reached unsupported type."); + throw new Error("reached unsupported type in visitor."); case SyntaxKind.IndexedAccessType: - throw new Error("reached unsupported type."); + throw new Error("reached unsupported type in visitor."); case SyntaxKind.MappedType: - throw new Error("reached unsupported type."); + throw new Error("reached unsupported type in visitor."); case SyntaxKind.LiteralType: - throw new Error("reached unsupported type."); + throw new Error("reached unsupported type in visitor."); // Type Declarations From f0b4efe94f7e1817b3075cd2a39d3ccd3544b4c6 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Mon, 13 Mar 2017 09:18:40 -0700 Subject: [PATCH 14/65] type literals, properties only --- src/compiler/checker.ts | 100 ++++++++++-------- src/compiler/factory.ts | 12 ++- src/compiler/types.ts | 2 +- src/compiler/visitor.ts | 25 ++++- src/services/codefixes/helpers.ts | 1 + src/services/textChanges.ts | 20 ---- ...lassExtendAbstractSomePropertiesPresent.ts | 4 +- ...FixClassImplementClassMethodViaHeritage.ts | 4 +- ...lementInterfaceComputedPropertyLiterals.ts | 2 +- 9 files changed, 97 insertions(+), 73 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d3e1eeb08e..65907536f8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2196,25 +2196,6 @@ namespace ts { return createTypeNodeWorker(type); - // function createTypeDeclaration(type: Type): Declaration { - // if (!type) { - // if (undefinedArgumentIsError) { encounteredError = true; } - // return undefined; - // } - // if (type.flags & TypeFlags.TypeParameter) { - // const constraint = createTypeNodeWorker(getConstraintFromTypeParameter(type)) as TypeNode; - // const defaultParameter = createTypeNodeWorker(getDefaultFromTypeParameter(type)) as TypeNode; - // if (!type.symbol) { - // encounteredError = true; - // throw new Error("No symbol for type parameter so can't get name"); - // } - // const name = getNameOfSymbol(type.symbol); - // return createTypeParameterDeclaration(name, constraint, defaultParameter); - // } - - // throw new Error("type declarations not implemented."); - // } - function createTypeNodeWorker(type: Type): TypeNode { if (!type) { if (undefinedArgumentIsError) { encounteredError = true; } @@ -2267,6 +2248,9 @@ namespace ts { throw new Error("ESSymbol not implemented"); } if (type.flags & TypeFlags.TypeParameter) { + if ((type).isThisType) { + return createThis(); + } throw new Error("Type Parameter declarations only handled in other worker."); } if (type.flags & TypeFlags.Union) { @@ -2332,27 +2316,12 @@ namespace ts { // return typeSymbolAccessibility === SymbolAccessibility.Accessible // && (!constraint || isTypeAccessibleWorker(constraint, inObjectLiteral, /*inTypeAlias*/false)); // } - // if (typeSymbolAccessibility === SymbolAccessibility.Accessible) { - // return true; - // } - // if (type.flags & (TypeFlags.Intrinsic | TypeFlags.Literal)) { - // return true; - // } // const objectFlags = getObjectFlags(type); // if (objectFlags & ObjectFlags.ClassOrInterface) { // // If type is a class or interface type that wasn't hit by the isSymbolAccessible check above, // // type must be an anonymous class or interface. // return false; // } - // if (objectFlags & ObjectFlags.Reference) { - // // and vice versa. - // // this case includes tuple types - // const typeArguments = (type as TypeReference).typeArguments || emptyArray; - // return allTypesVisible(typeArguments); - // } - // if (type.flags & TypeFlags.UnionOrIntersection) { - // return allTypesVisible((type as UnionOrIntersectionType).types); - // } if (objectFlags & ObjectFlags.Mapped) { Debug.assert(!!(type.flags & TypeFlags.Object)); @@ -2370,25 +2339,68 @@ namespace ts { // mapToTypeDeclarationsArray(type) - throw new Error("object literal types not implemented."); + throw new Error("unknown case."); } - // what case is this? - throw new Error("unknown case.") - // const members = type.symbol.members; - // let allVisible = true; - // members && members.forEach((member) => { - // const memberType = getTypeOfSymbolAtLocation(member, enclosingDeclaration); - // allVisible = allVisible && isTypeAccessibleWorker(memberType, /*inObjectLiteral*/ true, /*inTypeAlias*/false); - // }); - // return allVisible; + + const members = type.symbol.members; + const newMembers: TypeElement[] = []; + memberLoop: for(const key in members){ + const oldMember = members.get(key); + const name = getNameOfSymbol(oldMember); + const oldDeclaration = oldMember.declarations && oldMember.declarations[0] as TypeElement; + if(!oldDeclaration) { + continue memberLoop; + } + + const kind = oldDeclaration.kind; + + switch (kind) { + case SyntaxKind.PropertySignature: + const optional = !!oldDeclaration.questionToken; + newMembers.push(createPropertySignature( + createIdentifier(name) + , optional ? createToken(SyntaxKind.QuestionToken) : undefined + , createTypeNode(getTypeOfSymbol(oldMember)))); + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + default: + throw new Error("type literal constituent not implemented."); + } + } + return createTypeLiteralNode(newMembers); } Debug.fail("Should be unreachable."); + // function createTypeParameterDeclarationFromType(type: Type): TypeParameterDeclaration { + // if (!type) { + // if (undefinedArgumentIsError) { encounteredError = true; } + // return undefined; + // } + // if (type.flags & TypeFlags.TypeParameter) { + // const constraint = createTypeNodeWorker(getConstraintFromTypeParameter(type)) as TypeNode; + // const defaultParameter = createTypeNodeWorker(getDefaultFromTypeParameter(type)) as TypeNode; + // if (!type.symbol) { + // encounteredError = true; + // throw new Error("No symbol for type parameter so can't get name"); + // } + // const name = getNameOfSymbol(type.symbol); + // return createTypeParameterDeclaration(name, constraint, defaultParameter); + // } + // throw new Error("type declarations not implemented."); + // } + /** Note that mapToTypeNodeArray(undefined) === undefined. */ function mapToTypeNodeArray(types: Type[]): NodeArray { return asNodeArray(types && types.map(createTypeNodeWorker) as TypeNode[]); } + + // /** Note that mapToTypeNodeArray(undefined) === undefined. */ + // function mapToTypeParameterArray(types: Type[]): NodeArray { + // return asNodeArray(types && types.map(createTypeParameterDeclarationFromType) as TypeNode[]); + // } } } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 6e10d8bb22..88bf5f458f 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -67,6 +67,14 @@ namespace ts { return clone; } + /* @internal */ + export function getSynthesizedDeepClone(node: T | undefined): T { + if (node === undefined) { + return undefined; + } + return getSynthesizedClone(visitEachChild(node, getSynthesizedClone, nullTransformationContext)); + } + // Literals export function createLiteral(value: string): StringLiteral; @@ -173,11 +181,11 @@ namespace ts { } export function createThis() { - return createSynthesizedNode(SyntaxKind.ThisKeyword); + return createSynthesizedNode(SyntaxKind.ThisKeyword); } export function createNull() { - return createSynthesizedNode(SyntaxKind.NullKeyword); + return createSynthesizedNode(SyntaxKind.NullKeyword); } export function createTrue() { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b9f5da0f6e..bf8916026f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4065,7 +4065,7 @@ namespace ts { export type Transformer = (node: T) => T; /** - * A function that accepts and possible transforms a node. + * A function that accepts and possibly transforms a node. */ export type Visitor = (node: Node) => VisitResult; diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index bd2d26b775..d2991b7cf0 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -3,6 +3,28 @@ /// namespace ts { + + + export const nullTransformationContext: TransformationContext = { + enableEmitNotification: noop, + enableSubstitution: noop, + endLexicalEnvironment: () => undefined, + getCompilerOptions: notImplemented, + getEmitHost: notImplemented, + getEmitResolver: notImplemented, + hoistFunctionDeclaration: noop, + hoistVariableDeclaration: noop, + isEmitNotificationEnabled: notImplemented, + isSubstitutionEnabled: notImplemented, + onEmitNode: noop, + onSubstituteNode: notImplemented, + readEmitHelpers: notImplemented, + requestEmitHelper: noop, + resumeLexicalEnvironment: noop, + startLexicalEnvironment: noop, + suspendLexicalEnvironment: noop + }; + /** * Visits a Node using the supplied visitor, possibly returning a new Node in its place. * @@ -813,7 +835,8 @@ namespace ts { visitNode((node).expression, visitor, isExpression)); default: - return node; + throw new Error("not handled"); + // return node; } } diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 67f53fb8e5..fb76e83faa 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -51,6 +51,7 @@ namespace ts.codefix { } const declaration = declarations[0] as Declaration; + // TODO: get name as identifier or computer property name, etc. const name = declaration.name ? declaration.name.getText() : undefined; const visibilityModifier = createVisibilityModifier(getModifierFlags(declaration)); const modifiers = visibilityModifier ? [visibilityModifier] : undefined; diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 5c345e6e4e..cb8c614cb2 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -341,26 +341,6 @@ namespace ts.textChanges { return skipTrivia(s, 0) === s.length; } - const nullTransformationContext: TransformationContext = { - enableEmitNotification: noop, - enableSubstitution: noop, - endLexicalEnvironment: () => undefined, - getCompilerOptions: notImplemented, - getEmitHost: notImplemented, - getEmitResolver: notImplemented, - hoistFunctionDeclaration: noop, - hoistVariableDeclaration: noop, - isEmitNotificationEnabled: notImplemented, - isSubstitutionEnabled: notImplemented, - onEmitNode: noop, - onSubstituteNode: notImplemented, - readEmitHelpers: notImplemented, - requestEmitHelper: noop, - resumeLexicalEnvironment: noop, - startLexicalEnvironment: noop, - suspendLexicalEnvironment: noop - }; - function assignPositionsToNode(node: Node): Node { const visited = visitEachChild(node, assignPositionsToNode, nullTransformationContext, assignPositionsToNodeArray); // create proxy node for non synthesized nodes diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractSomePropertiesPresent.ts b/tests/cases/fourslash/codeFixClassExtendAbstractSomePropertiesPresent.ts index b6dd679bb7..fb66a04cd5 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractSomePropertiesPresent.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractSomePropertiesPresent.ts @@ -6,8 +6,8 @@ //// abstract z: number; //// } //// -//// class C extends A {[| |] -//// constructor(public x: number) { super(); } +//// class C extends A {[| +//// |]constructor(public x: number) { super(); } //// y: number; //// } diff --git a/tests/cases/fourslash/codeFixClassImplementClassMethodViaHeritage.ts b/tests/cases/fourslash/codeFixClassImplementClassMethodViaHeritage.ts index ede19ef570..57372dd7e8 100644 --- a/tests/cases/fourslash/codeFixClassImplementClassMethodViaHeritage.ts +++ b/tests/cases/fourslash/codeFixClassImplementClassMethodViaHeritage.ts @@ -8,8 +8,8 @@ //// //// } //// -//// class C3 implements C2 {[| |] -//// f2(){} +//// class C3 implements C2 {[| +//// |]f2(){} //// } verify.rangeAfterCodeFix(`f1(): void{ diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyLiterals.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyLiterals.ts index 4a6bb20f57..7d77c5b9af 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyLiterals.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyLiterals.ts @@ -7,7 +7,7 @@ //// [2]: boolean; //// } //// -//// class C implements I {[| |]} +//// class C implements I {[| |]} verify.rangeAfterCodeFix(` ["foo"](o: any): boolean { From c46a2e7e1b6f05ce79fb2fa551edaec1b481a39c Mon Sep 17 00:00:00 2001 From: Zhengbo Li Date: Mon, 13 Mar 2017 12:45:28 -0700 Subject: [PATCH 15/65] always create new node array when assigning positions --- src/services/textChanges.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 5c345e6e4e..5b9f0f5ef4 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -380,7 +380,7 @@ namespace ts.textChanges { return visited; } // clone nodearray if necessary - const nodeArray = visited === nodes ? createNodeArray(visited) : visited; + const nodeArray = visited === nodes ? createNodeArray(visited.slice(0)) : visited; nodeArray.pos = getPos(nodes); nodeArray.end = getEnd(nodes); return nodeArray; From 1b14ed4d0ecf4b5c05a6d4f3cbb1d6f9f094dcbb Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Mon, 13 Mar 2017 16:51:43 -0700 Subject: [PATCH 16/65] update visitor/factory --- src/compiler/factory.ts | 52 ++++++++++++++++++++++++++++------------- src/compiler/visitor.ts | 19 ++++++++++----- 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 88bf5f458f..14af23e95b 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -189,11 +189,11 @@ namespace ts { } export function createTrue() { - return createSynthesizedNode(SyntaxKind.TrueKeyword); + return createSynthesizedNode(SyntaxKind.TrueKeyword); } export function createFalse() { - return createSynthesizedNode(SyntaxKind.FalseKeyword); + return createSynthesizedNode(SyntaxKind.FalseKeyword); } // Names @@ -226,15 +226,6 @@ namespace ts { // Type Elements - export function createPropertySignature(name: PropertyName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression): PropertySignature { - const propertySignature = createSynthesizedNode(SyntaxKind.PropertySignature) as PropertySignature; - propertySignature.name = name; - propertySignature.questionToken = questionToken; - propertySignature.type = type; - propertySignature.initializer = initializer; - return propertySignature; - } - export function createConstructSignature() { throw new Error("not implemented."); } @@ -258,14 +249,26 @@ namespace ts { } // TODO: handle qualified names, ie EntityName's. - export function createTypeReferenceNode(typeName: string | Identifier, typeArguments?: NodeArray) { + export function createTypeReferenceNode(typeName: string | EntityName, typeArguments?: NodeArray) { const typeReference = createSynthesizedNode(SyntaxKind.TypeReference) as TypeReferenceNode; - typeReference.typeName = asName(typeName); + + typeReference.typeName = isQualifiedName(typeName) ? typeName : asName(typeName); typeReference.typeArguments = typeArguments; return typeReference; } - export function updateTypeReferenceNode(node: TypeReferenceNode, typeName: Identifier, typeArguments?: NodeArray) { + export function createArrayTypeNode(elementType: TypeNode): ArrayTypeNode { + const arrayTypeNode = createSynthesizedNode(SyntaxKind.ArrayType) as ArrayTypeNode; + arrayTypeNode.elementType = elementType; + return arrayTypeNode; + } + + export function updateArrayTypeNode(node: ArrayTypeNode, elementType: TypeNode): ArrayTypeNode { + return node.elementType !== elementType + ? updateNode(createArrayTypeNode(elementType), node) + : node; + } + export function updateTypeReferenceNode(node: TypeReferenceNode, typeName: EntityName, typeArguments?: NodeArray) { return node.typeName !== typeName || node.typeArguments !== typeArguments ? updateNode(createTypeReferenceNode(typeName, typeArguments), node) @@ -288,7 +291,7 @@ namespace ts { } export function createTypeLiteralNode(members: TypeElement[]) { - const typeLiteralNode = createSynthesizedNode(SyntaxKind.LiteralType) as TypeLiteralNode; + const typeLiteralNode = createSynthesizedNode(SyntaxKind.TypeLiteral) as TypeLiteralNode; typeLiteralNode.members = asNodeArray(members); return typeLiteralNode; } @@ -332,7 +335,24 @@ namespace ts { // Signature elements - /** Note, can also be used to construct index signatures. */ + export function createPropertySignature(name: PropertyName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression): PropertySignature { + const propertySignature = createSynthesizedNode(SyntaxKind.PropertySignature) as PropertySignature; + propertySignature.name = name; + propertySignature.questionToken = questionToken; + propertySignature.type = type; + propertySignature.initializer = initializer; + return propertySignature; + } + + export function updatePropertySignature(node: PropertySignature, name: PropertyName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression) { + return node.name !== name + || node.questionToken !== questionToken + || node.type !== type + || node.initializer !== initializer + ? updateNode(createPropertySignature(name, questionToken, type, initializer), node) + : node; + } + export function createSignature(kind: SyntaxKind, parameters: NodeArray, name?: PropertyName, typeParameters?: NodeArray, returnType?: TypeNode): SignatureDeclaration { const signature = createSynthesizedNode(kind) as SignatureDeclaration; signature.parameters = parameters; diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index d2991b7cf0..547178e3b7 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -279,7 +279,7 @@ namespace ts { return updateDecorator(node, visitNode((node).expression, visitor, isExpression)); - // Keyword Types + // Keyword Types and This case SyntaxKind.AnyKeyword: case SyntaxKind.NumberKeyword: @@ -292,6 +292,7 @@ namespace ts { case SyntaxKind.NullKeyword: case SyntaxKind.NeverKeyword: case SyntaxKind.NeverKeyword: + case SyntaxKind.ThisKeyword: return node; // Types @@ -300,8 +301,8 @@ namespace ts { throw new Error("reached unsupported type in visitor."); case SyntaxKind.TypeReference: return updateTypeReferenceNode(node - , visitNode((node).typeName as Identifier, visitor) - , nodesVisitor((node).typeArguments, visitor) + , visitNode((node).typeName, visitor, isEntityName) + , nodesVisitor((node).typeArguments, visitor, isTypeNode) ); case SyntaxKind.FunctionType: throw new Error("reached unsupported type in visitor."); @@ -310,9 +311,9 @@ namespace ts { case SyntaxKind.TypeQuery: throw new Error("reached unsupported type in visitor."); case SyntaxKind.TypeLiteral: - throw new Error("reached unsupported type in visitor."); + return updateTypeLiteralNode((node), nodesVisitor((node).members, visitor)); case SyntaxKind.ArrayType: - throw new Error("reached unsupported type in visitor."); + return updateArrayTypeNode(node, visitNode((node).elementType, visitor, isTypeNode)); case SyntaxKind.TupleType: throw new Error("reached unsupported type in visitor."); case SyntaxKind.UnionType: @@ -342,8 +343,14 @@ namespace ts { // Type members + case SyntaxKind.PropertySignature: + return updatePropertySignature((node) + , visitNode((node).name, visitor, isPropertyName) + , visitNode((node).questionToken, visitor, isToken) + , visitNode((node).type, visitor, isTypeNode)); + case SyntaxKind.IndexSignature: - updateIndexSignatureDeclaration(node + return updateIndexSignatureDeclaration(node , nodesVisitor((node).parameters, visitor, isParameter) , visitNode((node).type, visitor, isTypeNode) , nodesVisitor((node).decorators, visitor, isDecorator) From 20f512cfd218ff45ea817cc7971780c94b92c119 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Mon, 13 Mar 2017 16:54:29 -0700 Subject: [PATCH 17/65] type parameters and basic object literals --- src/compiler/checker.ts | 242 +++++++++++++----- src/compiler/types.ts | 3 + src/services/codefixes/helpers.ts | 12 +- ...ImplementInterfaceSomePropertiesPresent.ts | 4 +- 4 files changed, 190 insertions(+), 71 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 509efe941d..ee999b3fe1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -107,6 +107,7 @@ namespace ts { getReturnTypeOfSignature, getNonNullableType, createTypeNode, + createTypeParameterDeclarationFromType, getSymbolsInScope: (location, meaning) => { location = getParseTreeNode(location); return location ? getSymbolsInScope(location, meaning) : []; @@ -2189,9 +2190,25 @@ namespace ts { return result; } - function createTypeNode(type: Type) { + function createTypeParameterDeclarationFromType(type: Type): TypeParameterDeclaration { + if (!(type && type.symbol && type.flags & TypeFlags.TypeParameter)) { + return undefined; + } + + const constraint = createTypeNode(getConstraintFromTypeParameter(type)) as TypeNode; + const defaultParameter = createTypeNode(getDefaultFromTypeParameter(type)) as TypeNode; + if (!type.symbol) { + return undefined; + } + + const name = symbolToString(type.symbol); + return createTypeParameterDeclaration(name, constraint, defaultParameter); + } + + function createTypeNode(type: Type): TypeNode { let undefinedArgumentIsError = true; let encounteredError = false; + let inObjectTypeLiteral = false; let checkAlias = true; return createTypeNodeWorker(type); @@ -2202,6 +2219,8 @@ namespace ts { return undefined; } + const typeString = typeToString(type); typeString; // TODO: remove. + if (checkAlias && type.aliasSymbol) { const name = getNameOfSymbol(type.aliasSymbol); const typeArguments = mapToTypeNodeArray(type.aliasTypeArguments); @@ -2211,7 +2230,7 @@ namespace ts { if (type.flags & TypeFlags.Any) { // TODO: add other case where type ends up being `any`. - return createKeywordTypeNode(SyntaxKind.StringKeyword); + return createKeywordTypeNode(SyntaxKind.AnyKeyword); } if (type.flags & TypeFlags.String) { return createKeywordTypeNode(SyntaxKind.StringKeyword); @@ -2220,15 +2239,23 @@ namespace ts { return createKeywordTypeNode(SyntaxKind.NumberKeyword); } if(type.flags & TypeFlags.Boolean) { - // TODO: this is probably x: boolean. How do we deal with x: true ? return createKeywordTypeNode(SyntaxKind.BooleanKeyword); } + if (type.flags & TypeFlags.Enum) { + throw new Error("enum not implemented"); + } if (type.flags & (TypeFlags.StringLiteral)) { return createLiteralTypeNode((createLiteral((type).text))); } if (type.flags & (TypeFlags.NumberLiteral)) { return createLiteralTypeNode((createNumericLiteral((type).text))); } + if(type.flags & TypeFlags.BooleanLiteral) { + return (type).intrinsicName === "true" ? createTrue() : createFalse(); + } + if (type.flags & TypeFlags.EnumLiteral) { + throw new Error("enum literal not implemented"); + } if (type.flags & TypeFlags.Void) { return createKeywordTypeNode(SyntaxKind.VoidKeyword); } @@ -2241,18 +2268,29 @@ namespace ts { if (type.flags & TypeFlags.Never) { return createKeywordTypeNode(SyntaxKind.NeverKeyword); } - if (type.flags & TypeFlags.Enum) { - throw new Error("enum not implemented"); - } if (type.flags & TypeFlags.ESSymbol) { throw new Error("ESSymbol not implemented"); } if (type.flags & TypeFlags.TypeParameter) { if ((type).isThisType) { + if (inObjectTypeLiteral) { + encounteredError = true; + } return createThis(); } throw new Error("Type Parameter declarations only handled in other worker."); } + + const objectFlags = getObjectFlags(type); + + if (objectFlags & ObjectFlags.Reference) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + // and vice versa. + // this case includes tuple types + // TODO: test empty tuples, see if they are coherent. + return createTypeReferenceNodeFromType(type); + } + if (type.flags & TypeFlags.Union) { return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, mapToTypeNodeArray((type as UnionType).types)); } @@ -2266,8 +2304,6 @@ namespace ts { throw new Error("indexed access not implemented"); } - const objectFlags = getObjectFlags(type); - if (objectFlags & ObjectFlags.ClassOrInterface) { Debug.assert(!!(type.flags & TypeFlags.Object)); const name = getNameOfSymbol(type.symbol); @@ -2276,15 +2312,6 @@ namespace ts { return createTypeReferenceNode(name); } - if (objectFlags & ObjectFlags.Reference) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - // and vice versa. - // this case includes tuple types - // TODO: test empty tuples, see if they are coherent. - const typeArguments = (type as TypeReference).typeArguments || emptyArray; - return createTupleTypeNode(mapToTypeNodeArray(typeArguments)) - } - // keyword types // this type node // function type node @@ -2336,67 +2363,152 @@ namespace ts { // The type is an object literal type. if (!type.symbol) { // Anonymous types without symbols are literals. - - // mapToTypeDeclarationsArray(type) throw new Error("unknown case."); } - const members = type.symbol.members; - const newMembers: TypeElement[] = []; - memberLoop: for(const key in members){ - const oldMember = members.get(key); - const name = getNameOfSymbol(oldMember); - const oldDeclaration = oldMember.declarations && oldMember.declarations[0] as TypeElement; - if(!oldDeclaration) { - continue memberLoop; - } - - const kind = oldDeclaration.kind; - - switch (kind) { - case SyntaxKind.PropertySignature: - const optional = !!oldDeclaration.questionToken; - newMembers.push(createPropertySignature( - createIdentifier(name) - , optional ? createToken(SyntaxKind.QuestionToken) : undefined - , createTypeNode(getTypeOfSymbol(oldMember)))); - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - default: - throw new Error("type literal constituent not implemented."); - } - } - return createTypeLiteralNode(newMembers); + return createTypeLiteralNodeFromType(type); } Debug.fail("Should be unreachable."); - // function createTypeParameterDeclarationFromType(type: Type): TypeParameterDeclaration { - // if (!type) { - // if (undefinedArgumentIsError) { encounteredError = true; } - // return undefined; - // } - // if (type.flags & TypeFlags.TypeParameter) { - // const constraint = createTypeNodeWorker(getConstraintFromTypeParameter(type)) as TypeNode; - // const defaultParameter = createTypeNodeWorker(getDefaultFromTypeParameter(type)) as TypeNode; - // if (!type.symbol) { - // encounteredError = true; - // throw new Error("No symbol for type parameter so can't get name"); - // } - // const name = getNameOfSymbol(type.symbol); - // return createTypeParameterDeclaration(name, constraint, defaultParameter); - // } - // throw new Error("type declarations not implemented."); - // } - /** Note that mapToTypeNodeArray(undefined) === undefined. */ function mapToTypeNodeArray(types: Type[]): NodeArray { return asNodeArray(types && types.map(createTypeNodeWorker) as TypeNode[]); } + function createTypeReferenceNodeFromType(type: TypeReference) { + const typeArguments: Type[] = type.typeArguments || emptyArray; + if (type.target === globalArrayType) { + const elementType = createTypeNodeWorker(typeArguments[0]); + return createArrayTypeNode(elementType); + } + else if (type.target.objectFlags & ObjectFlags.Tuple) { + return createTupleTypeNode(mapToTypeNodeArray(typeArguments.slice(0, getTypeReferenceArity(type)))); + } + else { + // TODO: handle type parameters in qualified names... + const outerTypeParameters = type.target.outerTypeParameters; + let i = 0; + let qualifiedName: QualifiedName | undefined = undefined; + if (outerTypeParameters) { + const length = outerTypeParameters.length; + while (i < length) { + // Find group of type arguments for type parameters with the same declaring container. + const start = i; + const parent = getParentSymbolOfTypeParameter(outerTypeParameters[i]); + do { + i++; + } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); + // When type parameters are their own type arguments for the whole group (i.e. we have + // the default outer type arguments), we don't show the group. + // TODO: figure out how to handle type arguments + if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { + const name = symbolToString(parent); + const qualifiedNamePart = createIdentifier(name); // createTypeReferenceNode(name, mapToTypeNodeArray(typeArguments.slice(start, i - start))); + if (!qualifiedName) { + qualifiedName = createQualifiedName(qualifiedNamePart, /*right*/undefined); + } + else { + Debug.assert(!qualifiedName.right); + qualifiedName.right = qualifiedNamePart; + qualifiedName = createQualifiedName(qualifiedName, /*right*/undefined); + } + } + } + } + let entityName: EntityName = undefined; + const nameIdentifier = createIdentifier(symbolToString(type.symbol)); + if (qualifiedName) { + // TODO: handle checking of type arguments for qualified names? + Debug.assert(!qualifiedName.right); + qualifiedName.right = nameIdentifier; + entityName = qualifiedName; + } + else { + entityName = nameIdentifier; + } + const typeParameterCount = (type.target.typeParameters || emptyArray).length; + const typeArgumentNodes = mapToTypeNodeArray(typeArguments.slice(i, typeParameterCount - i)); + return createTypeReferenceNode(entityName, typeArgumentNodes); + } + } + + function createTypeLiteralNodeFromType(type: ObjectType) { + // TODO: do we need to do something for mapped types here??? + const resolvedType = resolveStructuredTypeMembers(type); + const newMembers = createTypeNodesFromResolvedType(resolvedType); + return createTypeLiteralNode(newMembers); + } + + function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] { + const typeElements: TypeElement[] = []; + for(const signature of resolvedType.callSignatures) { + signature; + throw new Error("call signatures not implemented"); + } + for (const signature of resolvedType.constructSignatures) { + signature; + throw new Error("Construct signatures not implemented"); + } + if (resolvedType.stringIndexInfo) { + typeElements.push(createIndexSignatureFromIndexInfo(resolvedType.stringIndexInfo, IndexKind.String)); + } + if (resolvedType.numberIndexInfo) { + typeElements.push(createIndexSignatureFromIndexInfo(resolvedType.stringIndexInfo, IndexKind.Number)); + } + + const members = resolvedType.members; + + members.forEach(memberSymbol => { + const oldDeclaration = memberSymbol.declarations && memberSymbol.declarations[0] as TypeElement; + if (!oldDeclaration) { + return; + } + + const kind = oldDeclaration.kind; + const memberName = symbolToString(memberSymbol); + + switch (kind) { + case SyntaxKind.PropertySignature: + const optional = !!oldDeclaration.questionToken; + const typeOfOldMember = getTypeOfSymbol(memberSymbol); + typeElements.push(createPropertySignature( + createIdentifier(memberName) + , optional ? createToken(SyntaxKind.QuestionToken) : undefined + , createTypeNode(typeOfOldMember))); + break; + case SyntaxKind.MethodSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + throw new Error("type literal constituent not implemented."); + default: + throw new Error("Unknown case."); + } + }); + return typeElements.length ? typeElements : undefined; + } + + function createIndexSignatureFromIndexInfo(indexInfo: IndexInfo, kind: IndexKind) { + const stringTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword); + + const name = (indexInfo.declaration && indexInfo.declaration.name && getTextOfPropertyName(indexInfo.declaration.name)) || "x"; + const indexingParameter = createParameter( + /*decorators*/ undefined + , /*modifiers*/ undefined + , /*dotDotDotToken*/ undefined + , name + , /*questionToken*/ undefined + , stringTypeNode); + const typeNode = createTypeNode(indexInfo.type); + return createIndexSignatureDeclaration( + [indexingParameter] + , typeNode + , /*decoarators*/ undefined + , indexInfo.isReadonly ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined); + } + // /** Note that mapToTypeNodeArray(undefined) === undefined. */ // function mapToTypeParameterArray(types: Type[]): NodeArray { // return asNodeArray(types && types.map(createTypeParameterDeclarationFromType) as TypeNode[]); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index fd85ad9cab..e3c5525e06 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2466,8 +2466,11 @@ namespace ts { */ /* @internal */ getParameterType(signature: Signature, parameterIndex: number): Type; getNonNullableType(type: Type): Type; + /** Note that the resulting type node cannot be checked. */ createTypeNode(type: Type): TypeNode; + /** Note that the resulting type node cannot be checked. */ + createTypeParameterDeclarationFromType(type: Type): TypeParameterDeclaration; getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; getSymbolAtLocation(node: Node): Symbol; diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index fb76e83faa..8c4eb40a3e 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -91,9 +91,11 @@ namespace ts.codefix { // TODO: get parameters working. // TODO: add support for type parameters. const signature = signatures[0]; + const newTypeParameters = signature.typeParameters && signature.typeParameters.map(checker.createTypeParameterDeclarationFromType); const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); - const returnType = checker.createTypeNode(signature.resolvedReturnType); - return createStubbedMethod(modifiers, name, /*typeParameters*/undefined, newParameterNodes, returnType); + + const returnType = checker.createTypeNode(checker.getReturnTypeOfSignature(signature)); + return createStubbedMethod(modifiers, name, newTypeParameters, newParameterNodes, returnType); } let signatureDeclarations = []; @@ -101,6 +103,7 @@ namespace ts.codefix { // const sigString = checker.signatureToString(signatures[i], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); // TODO: make signatures instead of methods const signature = signatures[i]; + const newTypeParameters = signature.typeParameters && signature.typeParameters.map(checker.createTypeParameterDeclarationFromType); const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); const returnType = checker.createTypeNode(signature.resolvedReturnType); signatureDeclarations.push(createMethod( @@ -108,7 +111,7 @@ namespace ts.codefix { , modifiers , /*asteriskToken*/ undefined , name - , /*typeParameters*/undefined + , newTypeParameters , newParameterNodes , returnType , /*body*/undefined)); @@ -116,9 +119,10 @@ namespace ts.codefix { if (declarations.length > signatures.length) { let signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration); + const newTypeParameters = signature.typeParameters && signature.typeParameters.map(checker.createTypeParameterDeclarationFromType); const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); const returnType = checker.createTypeNode(signature.resolvedReturnType); - signatureDeclarations.push(createStubbedMethod(modifiers, name, /*typeParameters*/undefined, newParameterNodes, returnType)); + signatureDeclarations.push(createStubbedMethod(modifiers, name, newTypeParameters, newParameterNodes, returnType)); } else { Debug.assert(declarations.length === signatures.length); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceSomePropertiesPresent.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceSomePropertiesPresent.ts index 03807ab450..e667783b99 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceSomePropertiesPresent.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceSomePropertiesPresent.ts @@ -6,8 +6,8 @@ //// z: number & { __iBrand: any }; //// } //// -//// class C implements I {[| |] -//// constructor(public x: number) { } +//// class C implements I {[| +//// |]constructor(public x: number) { } //// y: number; //// } From d298f960ab6dff2acb408e856c1e79ae9302986a Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Mon, 13 Mar 2017 18:09:53 -0700 Subject: [PATCH 18/65] Refactor --- src/compiler/checker.ts | 112 +++++++----------- src/compiler/factory.ts | 45 +++---- src/compiler/visitor.ts | 7 +- src/services/codefixes/fixAddMissingMember.ts | 6 +- .../fixClassIncorrectlyImplementsInterface.ts | 4 +- .../codeFixClassExtendAbstractMethod.ts | 4 + .../codeFixClassExtendAbstractProperty.ts | 4 - 7 files changed, 78 insertions(+), 104 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ee999b3fe1..ea7c01b973 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2211,7 +2211,10 @@ namespace ts { let inObjectTypeLiteral = false; let checkAlias = true; - return createTypeNodeWorker(type); + let result = createTypeNodeWorker(type); + (result).__type_source = type; + (result).__type_source_str = typeToString(type); + return result; function createTypeNodeWorker(type: Type): TypeNode { if (!type) { @@ -2221,12 +2224,6 @@ namespace ts { const typeString = typeToString(type); typeString; // TODO: remove. - if (checkAlias && type.aliasSymbol) { - const name = getNameOfSymbol(type.aliasSymbol); - const typeArguments = mapToTypeNodeArray(type.aliasTypeArguments); - return createTypeReferenceNode(createIdentifier(name), typeArguments); - } - checkAlias = false; if (type.flags & TypeFlags.Any) { // TODO: add other case where type ends up being `any`. @@ -2271,14 +2268,14 @@ namespace ts { if (type.flags & TypeFlags.ESSymbol) { throw new Error("ESSymbol not implemented"); } - if (type.flags & TypeFlags.TypeParameter) { - if ((type).isThisType) { - if (inObjectTypeLiteral) { - encounteredError = true; - } - return createThis(); + if (type.flags & TypeFlags.NonPrimitive) { + throw new Error("Non primitive not implemented"); + } + if (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType) { + if (inObjectTypeLiteral) { + encounteredError = true; } - throw new Error("Type Parameter declarations only handled in other worker."); + return createThis(); } const objectFlags = getObjectFlags(type); @@ -2291,64 +2288,35 @@ namespace ts { return createTypeReferenceNodeFromType(type); } + if (type.flags & TypeFlags.EnumLiteral) { + throw new Error("Enum literal not implemented"); + } + if (objectFlags & ObjectFlags.ClassOrInterface) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + const name = getNameOfSymbol(type.symbol); + // TODO: handle type arguments. + // TODO: handle anonymous classes. + return createTypeReferenceNode(name, /*typeParameters*/undefined); + } + if (type.flags & TypeFlags.TypeParameter) { + throw new Error("Type Parameter declarations only handled in other worker."); + } + + // accessible type aliasSymbol + // TODO: move back up later on? + if (checkAlias && type.aliasSymbol) { + const name = getNameOfSymbol(type.aliasSymbol); + const typeArgumentNodes = mapToTypeNodeArray(type.aliasTypeArguments); + return createTypeReferenceNode(createIdentifier(name), typeArgumentNodes); + } + checkAlias = false; + if (type.flags & TypeFlags.Union) { return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, mapToTypeNodeArray((type as UnionType).types)); } if (type.flags & TypeFlags.Intersection) { return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, mapToTypeNodeArray((type as UnionType).types)); } - if (type.flags & TypeFlags.Index) { - throw new Error("index not implemented"); - } - if (type.flags & TypeFlags.IndexedAccess) { - throw new Error("indexed access not implemented"); - } - - if (objectFlags & ObjectFlags.ClassOrInterface) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - const name = getNameOfSymbol(type.symbol); - // TODO: handle type arguments. - // TODO: handle anonymous classes. - return createTypeReferenceNode(name); - } - - // keyword types - // this type node - // function type node - // constructor type node - // type reference node - // type predicate node - is Foo (for return types) - // type query node -- typeof number - // type literal node (like object literal) - // array type - // tuple type - // union type - // might need parens - // intersection type - // Type operator node (eg (ie?): keyof T) - // IndexedAccess Type Node - // mapped type node - // literal type node - - // if (inTypeAlias && type.aliasSymbol) { - // return isSymbolAccessible(type.aliasSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/false).accessibility === SymbolAccessibility.Accessible - // && (!type.aliasTypeArguments || allTypesVisible(type.aliasTypeArguments)); - // } - // const typeSymbolAccessibility = type.symbol && isSymbolAccessible(type.symbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false).accessibility; - // if (type.flags & TypeFlags.TypeParameter) { - // if (inObjectLiteral && (type as TypeParameter).isThisType) { - // return false; - // } - // const constraint = getConstraintFromTypeParameter((type)); - // return typeSymbolAccessibility === SymbolAccessibility.Accessible - // && (!constraint || isTypeAccessibleWorker(constraint, inObjectLiteral, /*inTypeAlias*/false)); - // } - // const objectFlags = getObjectFlags(type); - // if (objectFlags & ObjectFlags.ClassOrInterface) { - // // If type is a class or interface type that wasn't hit by the isSymbolAccessible check above, - // // type must be an anonymous class or interface. - // return false; - // } if (objectFlags & ObjectFlags.Mapped) { Debug.assert(!!(type.flags & TypeFlags.Object)); @@ -2370,6 +2338,15 @@ namespace ts { return createTypeLiteralNodeFromType(type); } + // TODO: string or number literal here or above? + + if (type.flags & TypeFlags.Index) { + throw new Error("index not implemented"); + } + if (type.flags & TypeFlags.IndexedAccess) { + throw new Error("indexed access not implemented"); + } + Debug.fail("Should be unreachable."); /** Note that mapToTypeNodeArray(undefined) === undefined. */ @@ -2476,7 +2453,8 @@ namespace ts { typeElements.push(createPropertySignature( createIdentifier(memberName) , optional ? createToken(SyntaxKind.QuestionToken) : undefined - , createTypeNode(typeOfOldMember))); + , createTypeNode(typeOfOldMember) + , /*initializer*/undefined)); break; case SyntaxKind.MethodSignature: case SyntaxKind.CallSignature: diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 14af23e95b..7eb44f93bc 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -248,30 +248,30 @@ namespace ts { : node; } - // TODO: handle qualified names, ie EntityName's. - export function createTypeReferenceNode(typeName: string | EntityName, typeArguments?: NodeArray) { + export function createTypeReferenceNode(typeName: string | EntityName, typeArguments: NodeArray | undefined) { const typeReference = createSynthesizedNode(SyntaxKind.TypeReference) as TypeReferenceNode; - + typeReference.typeName = isQualifiedName(typeName) ? typeName : asName(typeName); typeReference.typeArguments = typeArguments; return typeReference; } + export function updateTypeReferenceNode(node: TypeReferenceNode, typeName: EntityName, typeArguments: NodeArray | undefined) { + return node.typeName !== typeName + || node.typeArguments !== typeArguments + ? updateNode(createTypeReferenceNode(typeName, typeArguments), node) + : node; + } + export function createArrayTypeNode(elementType: TypeNode): ArrayTypeNode { const arrayTypeNode = createSynthesizedNode(SyntaxKind.ArrayType) as ArrayTypeNode; arrayTypeNode.elementType = elementType; return arrayTypeNode; } - + export function updateArrayTypeNode(node: ArrayTypeNode, elementType: TypeNode): ArrayTypeNode { return node.elementType !== elementType - ? updateNode(createArrayTypeNode(elementType), node) - : node; - } - export function updateTypeReferenceNode(node: TypeReferenceNode, typeName: EntityName, typeArguments?: NodeArray) { - return node.typeName !== typeName - || node.typeArguments !== typeArguments - ? updateNode(createTypeReferenceNode(typeName, typeArguments), node) + ? updateNode(createArrayTypeNode(elementType), node) : node; } @@ -316,7 +316,7 @@ namespace ts { // Type Declarations - export function createTypeParameterDeclaration(name: string | Identifier, constraint?: TypeNode, defaultParameter?: TypeNode) { + export function createTypeParameterDeclaration(name: string | Identifier, constraint: TypeNode | undefined, defaultParameter: TypeNode | undefined) { const typeParameter = createSynthesizedNode(SyntaxKind.TypeParameter) as TypeParameterDeclaration; typeParameter.name = asName(name); typeParameter.constraint = constraint; @@ -325,7 +325,7 @@ namespace ts { return typeParameter; } - export function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint?: TypeNode, defaultParameter?: TypeNode) { + export function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultParameter: TypeNode | undefined) { return node.name !== name || node.constraint !== constraint || node.default !== defaultParameter @@ -335,7 +335,7 @@ namespace ts { // Signature elements - export function createPropertySignature(name: PropertyName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression): PropertySignature { + export function createPropertySignature(name: PropertyName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): PropertySignature { const propertySignature = createSynthesizedNode(SyntaxKind.PropertySignature) as PropertySignature; propertySignature.name = name; propertySignature.questionToken = questionToken; @@ -344,7 +344,7 @@ namespace ts { return propertySignature; } - export function updatePropertySignature(node: PropertySignature, name: PropertyName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression) { + export function updatePropertySignature(node: PropertySignature, name: PropertyName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { return node.name !== name || node.questionToken !== questionToken || node.type !== type @@ -353,18 +353,7 @@ namespace ts { : node; } - export function createSignature(kind: SyntaxKind, parameters: NodeArray, name?: PropertyName, typeParameters?: NodeArray, returnType?: TypeNode): SignatureDeclaration { - const signature = createSynthesizedNode(kind) as SignatureDeclaration; - signature.parameters = parameters; - signature.name = name; - signature.typeParameters = typeParameters; - signature.type = returnType; - return signature; - } - - // TODO: check usage of name... - // TODO: create entry in visitor.ts - export function createIndexSignatureDeclaration(parameters: ParameterDeclaration[], type: TypeNode, decorators?: Decorator[], modifiers?: Modifier[]): IndexSignatureDeclaration { + export function createIndexSignatureDeclaration(parameters: ParameterDeclaration[], type: TypeNode, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined): IndexSignatureDeclaration { const indexSignature = createSynthesizedNode(SyntaxKind.IndexSignature) as IndexSignatureDeclaration; // indexSignature.name = asName(name); // type parameters @@ -375,7 +364,7 @@ namespace ts { return indexSignature; } - export function updateIndexSignatureDeclaration(node: IndexSignatureDeclaration, parameters: ParameterDeclaration[], type: TypeNode, decorators?: Decorator[], modifiers?: Modifier[]) { + export function updateIndexSignatureDeclaration(node: IndexSignatureDeclaration, parameters: ParameterDeclaration[], type: TypeNode, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined) { return node.parameters !== parameters || node.type !== type || node.decorators !== decorators diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 547178e3b7..3b75e072a5 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -302,8 +302,8 @@ namespace ts { case SyntaxKind.TypeReference: return updateTypeReferenceNode(node , visitNode((node).typeName, visitor, isEntityName) - , nodesVisitor((node).typeArguments, visitor, isTypeNode) - ); + , nodesVisitor((node).typeArguments, visitor, isTypeNode)); + case SyntaxKind.FunctionType: throw new Error("reached unsupported type in visitor."); case SyntaxKind.ConstructorType: @@ -347,7 +347,8 @@ namespace ts { return updatePropertySignature((node) , visitNode((node).name, visitor, isPropertyName) , visitNode((node).questionToken, visitor, isToken) - , visitNode((node).type, visitor, isTypeNode)); + , visitNode((node).type, visitor, isTypeNode) + , visitNode((node).initializer, visitor, isExpression)); case SyntaxKind.IndexSignature: return updateIndexSignatureDeclaration(node diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index fe12249f17..2403c0adbe 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -62,7 +62,11 @@ namespace ts.codefix { , "x" , /*questionToken*/ undefined , stringTypeNode); - const indexSignature = createIndexSignatureDeclaration([indexingParameter], typeNode); + const indexSignature = createIndexSignatureDeclaration( + [indexingParameter] + , typeNode + , /*decorators*/undefined + , /*modifiers*/ undefined); const indexSignatureChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); indexSignatureChangeTracker.insertNodeAfter(sourceFile, openBrace, indexSignature, { insertTrailingNewLine: true }); diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index f653d21e02..220cea55d6 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -62,7 +62,9 @@ namespace ts.codefix { , getNameFromIndexInfo(indexInfoOfKind) , /*questionToken*/ undefined , kind === IndexKind.String ? createKeywordTypeNode(SyntaxKind.StringKeyword) : createKeywordTypeNode(SyntaxKind.NumberKeyword))] - , typeNode); + , typeNode + , /*decorators*/undefined + , /*modifiers*/ undefined); newNodes.push(newIndexSignatureDeclaration); } diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethod.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethod.ts index b6b408cf5f..7e51e2216d 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractMethod.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethod.ts @@ -5,6 +5,7 @@ //// abstract f(a: number, b: string): this; //// abstract f(a: string, b: number): Function; //// abstract f(a: string): Function; +//// abstract foo(): number; //// } //// //// class C extends A {[| |]} @@ -17,4 +18,7 @@ verify.rangeAfterCodeFix(` f(a: any, b?: any) { throw new Error("Method not implemented."); } + foo(): number { + throw new Error("Method not implemented."); + } `); diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts b/tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts index 83f770b555..701903d677 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractProperty.ts @@ -4,7 +4,6 @@ //// abstract x: number; //// abstract y: this; //// abstract z: A; -//// abstract foo(): number; //// } //// //// class C extends A {[| |]} @@ -13,7 +12,4 @@ verify.rangeAfterCodeFix(` x: number; y: this; z: A; - foo(): number { - throw new Error("Method not implemented."); - } `); From 27fe2df203eb552976c853b704f6682eb8e81ae2 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Mon, 13 Mar 2017 20:20:26 -0700 Subject: [PATCH 19/65] some missed token bugs --- src/compiler/checker.ts | 11 ++---- src/compiler/factory.ts | 9 +++-- src/compiler/transformers/esnext.ts | 1 + src/compiler/types.ts | 2 +- src/compiler/visitor.ts | 1 + src/services/codefixes/fixAddMissingMember.ts | 3 +- src/services/codefixes/helpers.ts | 39 +++++++++---------- src/services/textChanges.ts | 2 +- .../codeFixClassExtendAbstractGetterSetter.ts | 2 +- 9 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ea7c01b973..ca105c4113 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2361,7 +2361,7 @@ namespace ts { return createArrayTypeNode(elementType); } else if (type.target.objectFlags & ObjectFlags.Tuple) { - return createTupleTypeNode(mapToTypeNodeArray(typeArguments.slice(0, getTypeReferenceArity(type)))); + return createTupleTypeNode(typeArguments.length > 0 ? mapToTypeNodeArray(typeArguments.slice(0, getTypeReferenceArity(type))) : undefined); } else { // TODO: handle type parameters in qualified names... @@ -2406,7 +2406,7 @@ namespace ts { entityName = nameIdentifier; } const typeParameterCount = (type.target.typeParameters || emptyArray).length; - const typeArgumentNodes = mapToTypeNodeArray(typeArguments.slice(i, typeParameterCount - i)); + const typeArgumentNodes = mapToTypeNodeArray(typeArguments.length > 0 ? typeArguments.slice(i, typeParameterCount - i) : undefined); return createTypeReferenceNode(entityName, typeArgumentNodes); } } @@ -2478,7 +2478,8 @@ namespace ts { , /*dotDotDotToken*/ undefined , name , /*questionToken*/ undefined - , stringTypeNode); + , stringTypeNode + , /*initializer*/ undefined); const typeNode = createTypeNode(indexInfo.type); return createIndexSignatureDeclaration( [indexingParameter] @@ -7261,10 +7262,6 @@ namespace ts { } } - // export function synthesizeTypeNode(type: Type, enclosingDeclaration: Node): TypeNode { - // throw new Error("Not implemented" + enclosingDeclaration); - // } - function instantiateList(items: T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): T[] { if (items && items.length) { const result: T[] = []; diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 7eb44f93bc..077bc34986 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -355,8 +355,6 @@ namespace ts { export function createIndexSignatureDeclaration(parameters: ParameterDeclaration[], type: TypeNode, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined): IndexSignatureDeclaration { const indexSignature = createSynthesizedNode(SyntaxKind.IndexSignature) as IndexSignatureDeclaration; - // indexSignature.name = asName(name); - // type parameters indexSignature.parameters = asNodeArray(parameters); indexSignature.type = type; indexSignature.decorators = asNodeArray(decorators); @@ -373,6 +371,8 @@ namespace ts { : node; } + // Signature elements + export function createParameter(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression) { const node = createSynthesizedNode(SyntaxKind.Parameter); node.decorators = asNodeArray(decorators); @@ -385,11 +385,12 @@ namespace ts { return node; } - export function updateParameter(node: ParameterDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, dotDotDotToken: DotDotDotToken | undefined, name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined) { + export function updateParameter(node: ParameterDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, dotDotDotToken: DotDotDotToken | undefined, name: string | BindingName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined) { return node.decorators !== decorators || node.modifiers !== modifiers || node.dotDotDotToken !== dotDotDotToken || node.name !== name + || node.questionToken !== questionToken || node.type !== type || node.initializer !== initializer ? updateNode(createParameter(decorators, modifiers, dotDotDotToken, name, node.questionToken, type, initializer), node) @@ -438,7 +439,7 @@ namespace ts { node.asteriskToken = asteriskToken; node.name = asName(name); node.typeParameters = asNodeArray(typeParameters); - node.parameters = createNodeArray(parameters); + node.parameters = asNodeArray(parameters); node.type = type; node.body = body; return node; diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 971dddd20e..f66bf14da5 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -475,6 +475,7 @@ namespace ts { /*modifiers*/ undefined, node.dotDotDotToken, getGeneratedNameForNode(node), + node.questionToken, /*type*/ undefined, visitNode(node.initializer, visitor, isExpression) ); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index e3c5525e06..bccf0f1c4f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -895,7 +895,7 @@ namespace ts { exprName: EntityName; } - /** A TypeLiteral is the declaration node for an anonymous symbol. */ + // A TypeLiteral is the declaration node for an anonymous symbol. export interface TypeLiteralNode extends TypeNode, Declaration { kind: SyntaxKind.TypeLiteral; members: NodeArray; diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 3b75e072a5..e64e59eba3 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -272,6 +272,7 @@ namespace ts { nodesVisitor((node).modifiers, visitor, isModifier), (node).dotDotDotToken, visitNode((node).name, visitor, isBindingName), + visitNode((node).questionToken, visitor, isToken), visitNode((node).type, visitor, isTypeNode), visitNode((node).initializer, visitor, isExpression)); diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 2403c0adbe..61c823908d 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -61,7 +61,8 @@ namespace ts.codefix { , /*dotDotDotToken*/ undefined , "x" , /*questionToken*/ undefined - , stringTypeNode); + , stringTypeNode + , /*initializer*/ undefined); const indexSignature = createIndexSignatureDeclaration( [indexingParameter] , typeNode diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 8c4eb40a3e..2db14f81df 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -126,7 +126,7 @@ namespace ts.codefix { } else { Debug.assert(declarations.length === signatures.length); - const methodImplementingSignatures = createMethodImplementingSignatures(signatures, enclosingDeclaration, name, modifiers); + const methodImplementingSignatures = createMethodImplementingSignatures(signatures, name, modifiers); signatureDeclarations.push(methodImplementingSignatures); } return signatureDeclarations; @@ -135,11 +135,8 @@ namespace ts.codefix { } } - // TODO: infer types of arguments? - function createMethodImplementingSignatures(signatures: Signature[], enclosingDeclaration: ClassLikeDeclaration, name: string, modifiers: Modifier[] | undefined): MethodDeclaration { - const newMethodDeclaration = createNode(SyntaxKind.CallSignature) as SignatureDeclaration; - newMethodDeclaration.parent = enclosingDeclaration; - newMethodDeclaration.name = signatures[0].getDeclaration().name; + function createMethodImplementingSignatures(signatures: Signature[], name: string, modifiers: Modifier[] | undefined): MethodDeclaration { + Debug.assert(signatures && signatures.length > 0); let maxNonRestArgs = -1; let maxArgsIndex = 0; @@ -157,46 +154,45 @@ namespace ts.codefix { } const maxArgsParameterSymbolNames = signatures[maxArgsIndex].getParameters().map(symbol => symbol.getName()); - const parameters = createNodeArray(); + const parameters: ParameterDeclaration[] = []; for (let i = 0; i < maxNonRestArgs; i++) { + const anyType = createKeywordTypeNode(SyntaxKind.AnyKeyword); const newParameter = createParameter( /*decorators*/ undefined , /*modifiers*/ undefined , /*dotDotDotToken*/ undefined , maxArgsParameterSymbolNames[i] , /*questionToken*/ i >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined - , /*type*/ undefined + , anyType , /*initializer*/ undefined); parameters.push(newParameter); } if (hasRestParameter) { + const anyType = createKeywordTypeNode(SyntaxKind.AnyKeyword); const restParameter = createParameter( /*decorators*/ undefined , /*modifiers*/ undefined , createToken(SyntaxKind.DotDotDotToken) , maxArgsParameterSymbolNames[maxNonRestArgs] || "rest" , /*questionToken*/ maxNonRestArgs >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined - , /*type*/ undefined + , anyType , /*initializer*/ undefined); parameters.push(restParameter); } - return createMethod( - /*decorators*/ undefined - , modifiers - , /*asteriskToken*/ undefined + return createStubbedMethod( + modifiers , name , /*typeParameters*/undefined , parameters - , /*type*/ undefined - , /*body*/undefined); + , /*returnType*/ undefined); } - export function createStubbedMethod(modifiers: Modifier[], name: string, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], returnType?: TypeNode) { + export function createStubbedMethod(modifiers: Modifier[], name: string, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], returnType: TypeNode | undefined) { return createMethod( /*decorators*/undefined - , /*modifiers*/modifiers + , modifiers , /*asteriskToken*/undefined , name , typeParameters @@ -231,12 +227,13 @@ namespace ts.codefix { const parameterTypeNode = checker.createTypeNode(parameterType); // TODO: deep cloning of decorators/any node. const parameterNode = createParameter( - parameterDeclaration.decorators && parameterDeclaration.decorators.map(getSynthesizedClone) - , parameterDeclaration.modifiers && parameterDeclaration.modifiers.map(getSynthesizedClone) + parameterDeclaration.decorators && parameterDeclaration.decorators.map(getSynthesizedDeepClone) + , parameterDeclaration.modifiers && parameterDeclaration.modifiers.map(getSynthesizedDeepClone) , parameterDeclaration.dotDotDotToken && createToken(SyntaxKind.DotDotDotToken) - , parameterDeclaration.name + , getSynthesizedDeepClone(parameterDeclaration.name) , parameterDeclaration.questionToken && createToken(SyntaxKind.QuestionToken) - , parameterTypeNode); + , parameterTypeNode + , /*initializer*/ undefined); return parameterNode; } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index cb8c614cb2..71536dc75f 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -360,7 +360,7 @@ namespace ts.textChanges { return visited; } // clone nodearray if necessary - const nodeArray = visited === nodes ? createNodeArray(visited) : visited; + const nodeArray = visited === nodes ? createNodeArray(visited.slice(0)) : visited; nodeArray.pos = getPos(nodes); nodeArray.end = getEnd(nodes); return nodeArray; diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractGetterSetter.ts b/tests/cases/fourslash/codeFixClassExtendAbstractGetterSetter.ts index 4bddfb799f..bc437c93bc 100644 --- a/tests/cases/fourslash/codeFixClassExtendAbstractGetterSetter.ts +++ b/tests/cases/fourslash/codeFixClassExtendAbstractGetterSetter.ts @@ -28,4 +28,4 @@ verify.rangeAfterCodeFix(` e: this; f: A; g: string; -`); \ No newline at end of file +`); From 2fd3229568dad45411c9af7e3ca1234197b29bad Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Tue, 14 Mar 2017 14:35:23 -0700 Subject: [PATCH 20/65] Various fixes * create type reference node from type parameters * expose index signature synthesis * widen types in helpers * format unions * use deep cloning --- src/compiler/checker.ts | 74 ++++++++++--------- src/compiler/types.ts | 2 + src/compiler/utilities.ts | 4 + .../fixClassIncorrectlyImplementsInterface.ts | 13 +--- src/services/codefixes/helpers.ts | 18 ++--- .../codeFixClassImplementInterface36.ts | 18 ----- ...eFixClassImplementInterfaceInNamespace.ts} | 0 ...mplementInterfaceInheritsAbstractMethod.ts | 18 +++++ ...ssImplementInterfaceMultipleImplements1.ts | 4 +- ...ssImplementInterfaceMultipleImplements2.ts | 6 +- 10 files changed, 76 insertions(+), 81 deletions(-) delete mode 100644 tests/cases/fourslash/codeFixClassImplementInterface36.ts rename tests/cases/fourslash/{codeFixClassImplementInterface39.ts => codeFixClassImplementInterfaceInNamespace.ts} (100%) create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfaceInheritsAbstractMethod.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ca105c4113..e9cea047ea 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -108,6 +108,7 @@ namespace ts { getNonNullableType, createTypeNode, createTypeParameterDeclarationFromType, + createIndexSignatureFromIndexInfo, getSymbolsInScope: (location, meaning) => { location = getParseTreeNode(location); return location ? getSymbolsInScope(location, meaning) : []; @@ -2190,16 +2191,13 @@ namespace ts { return result; } - function createTypeParameterDeclarationFromType(type: Type): TypeParameterDeclaration { + function createTypeParameterDeclarationFromType(type: TypeParameter): TypeParameterDeclaration { if (!(type && type.symbol && type.flags & TypeFlags.TypeParameter)) { return undefined; } - const constraint = createTypeNode(getConstraintFromTypeParameter(type)) as TypeNode; - const defaultParameter = createTypeNode(getDefaultFromTypeParameter(type)) as TypeNode; - if (!type.symbol) { - return undefined; - } + const constraint = createTypeNode(getConstraintFromTypeParameter(type)) as TypeNode; + const defaultParameter = createTypeNode(getDefaultFromTypeParameter(type)) as TypeNode; const name = symbolToString(type.symbol); return createTypeParameterDeclaration(name, constraint, defaultParameter); @@ -2212,8 +2210,10 @@ namespace ts { let checkAlias = true; let result = createTypeNodeWorker(type); - (result).__type_source = type; - (result).__type_source_str = typeToString(type); + if (result) { + (result).__type_source = type; + (result).__type_source_str = typeToString(type); + } return result; function createTypeNodeWorker(type: Type): TypeNode { @@ -2299,7 +2299,16 @@ namespace ts { return createTypeReferenceNode(name, /*typeParameters*/undefined); } if (type.flags & TypeFlags.TypeParameter) { - throw new Error("Type Parameter declarations only handled in other worker."); + const constraint = createTypeNode(getConstraintFromTypeParameter(type)) as TypeNode; + const defaultParameter = createTypeNode(getDefaultFromTypeParameter(type)) as TypeNode; + if(constraint || defaultParameter) { + // Type parameters in type position can't have constraints or defaults. + encounteredError = true; + return undefined; + } + // TODO: get qualified name when necessary instead of string. + const name = symbolToString(type.symbol); + return createTypeReferenceNode(name, /*typeArguments*/ undefined); } // accessible type aliasSymbol @@ -2312,7 +2321,7 @@ namespace ts { checkAlias = false; if (type.flags & TypeFlags.Union) { - return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, mapToTypeNodeArray((type as UnionType).types)); + return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, mapToTypeNodeArray(formatUnionTypes((type).types))); } if (type.flags & TypeFlags.Intersection) { return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, mapToTypeNodeArray((type as UnionType).types)); @@ -2467,34 +2476,29 @@ namespace ts { }); return typeElements.length ? typeElements : undefined; } - - function createIndexSignatureFromIndexInfo(indexInfo: IndexInfo, kind: IndexKind) { - const stringTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword); - - const name = (indexInfo.declaration && indexInfo.declaration.name && getTextOfPropertyName(indexInfo.declaration.name)) || "x"; - const indexingParameter = createParameter( - /*decorators*/ undefined - , /*modifiers*/ undefined - , /*dotDotDotToken*/ undefined - , name - , /*questionToken*/ undefined - , stringTypeNode - , /*initializer*/ undefined); - const typeNode = createTypeNode(indexInfo.type); - return createIndexSignatureDeclaration( - [indexingParameter] - , typeNode - , /*decoarators*/ undefined - , indexInfo.isReadonly ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined); - } - - // /** Note that mapToTypeNodeArray(undefined) === undefined. */ - // function mapToTypeParameterArray(types: Type[]): NodeArray { - // return asNodeArray(types && types.map(createTypeParameterDeclarationFromType) as TypeNode[]); - // } } } + function createIndexSignatureFromIndexInfo(indexInfo: IndexInfo, kind: IndexKind): IndexSignatureDeclaration { + const indexerTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword); + + const name = getNameFromIndexInfo(indexInfo); + const indexingParameter = createParameter( + /*decorators*/ undefined + , /*modifiers*/ undefined + , /*dotDotDotToken*/ undefined + , name + , /*questionToken*/ undefined + , indexerTypeNode + , /*initializer*/ undefined); + const typeNode = createTypeNode(indexInfo.type); + return createIndexSignatureDeclaration( + [indexingParameter] + , typeNode + , /*decorators*/ undefined + , indexInfo.isReadonly ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined); + } + function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Declaration, flags?: TypeFormatFlags): string { const writer = getSingleLineStringWriter(); getSymbolDisplayBuilder().buildTypePredicateDisplay(typePredicate, writer, enclosingDeclaration, flags); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index bccf0f1c4f..28c293ae53 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2471,6 +2471,8 @@ namespace ts { createTypeNode(type: Type): TypeNode; /** Note that the resulting type node cannot be checked. */ createTypeParameterDeclarationFromType(type: Type): TypeParameterDeclaration; + /** Note that the resulting type node cannot be checked. */ + createIndexSignatureFromIndexInfo(indexInfo: IndexInfo, kind: IndexKind): IndexSignatureDeclaration getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; getSymbolAtLocation(node: Node): Symbol; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 030b66813a..b4d65958f3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -524,6 +524,10 @@ namespace ts { export function declarationNameToString(name: DeclarationName) { return getFullWidth(name) === 0 ? "(Missing)" : getTextOfNode(name); } + + export function getNameFromIndexInfo(info: IndexInfo) { + return info.declaration ? declarationNameToString(info.declaration.parameters[0].name) : undefined; + } export function getTextOfPropertyName(name: PropertyName): string { switch (name.kind) { diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index 220cea55d6..33b4a10dff 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -53,18 +53,7 @@ namespace ts.codefix { if (!indexInfoOfKind) { return undefined; } - const typeNode = checker.createTypeNode(indexInfoOfKind.type); - const newIndexSignatureDeclaration = createIndexSignatureDeclaration( - [createParameter( - /*decorators*/undefined - , /*modifiers*/ undefined - , /*dotDotDotToken*/ undefined - , getNameFromIndexInfo(indexInfoOfKind) - , /*questionToken*/ undefined - , kind === IndexKind.String ? createKeywordTypeNode(SyntaxKind.StringKeyword) : createKeywordTypeNode(SyntaxKind.NumberKeyword))] - , typeNode - , /*decorators*/undefined - , /*modifiers*/ undefined); + const newIndexSignatureDeclaration = checker.createIndexSignatureFromIndexInfo(indexInfoOfKind, kind); newNodes.push(newIndexSignatureDeclaration); } diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 2db14f81df..c51687962b 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -52,10 +52,10 @@ namespace ts.codefix { const declaration = declarations[0] as Declaration; // TODO: get name as identifier or computer property name, etc. - const name = declaration.name ? declaration.name.getText() : undefined; + const name = declaration.name ? getSynthesizedDeepClone(declaration.name) as PropertyName : undefined; const visibilityModifier = createVisibilityModifier(getModifierFlags(declaration)); const modifiers = visibilityModifier ? [visibilityModifier] : undefined; - const type = checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration); + const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration)); switch (declaration.kind) { case SyntaxKind.GetAccessor: @@ -94,7 +94,7 @@ namespace ts.codefix { const newTypeParameters = signature.typeParameters && signature.typeParameters.map(checker.createTypeParameterDeclarationFromType); const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); - const returnType = checker.createTypeNode(checker.getReturnTypeOfSignature(signature)); + const returnType = checker.createTypeNode(checker.getWidenedType(checker.getReturnTypeOfSignature(signature))); return createStubbedMethod(modifiers, name, newTypeParameters, newParameterNodes, returnType); } @@ -121,7 +121,7 @@ namespace ts.codefix { let signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration); const newTypeParameters = signature.typeParameters && signature.typeParameters.map(checker.createTypeParameterDeclarationFromType); const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); - const returnType = checker.createTypeNode(signature.resolvedReturnType); + const returnType = checker.createTypeNode(checker.getWidenedType(checker.getReturnTypeOfSignature(signature))); signatureDeclarations.push(createStubbedMethod(modifiers, name, newTypeParameters, newParameterNodes, returnType)); } else { @@ -135,7 +135,7 @@ namespace ts.codefix { } } - function createMethodImplementingSignatures(signatures: Signature[], name: string, modifiers: Modifier[] | undefined): MethodDeclaration { + function createMethodImplementingSignatures(signatures: Signature[], name: PropertyName, modifiers: Modifier[] | undefined): MethodDeclaration { Debug.assert(signatures && signatures.length > 0); let maxNonRestArgs = -1; @@ -189,7 +189,7 @@ namespace ts.codefix { , /*returnType*/ undefined); } - export function createStubbedMethod(modifiers: Modifier[], name: string, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], returnType: TypeNode | undefined) { + export function createStubbedMethod(modifiers: Modifier[], name: PropertyName, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], returnType: TypeNode | undefined) { return createMethod( /*decorators*/undefined , modifiers @@ -223,7 +223,7 @@ namespace ts.codefix { function createParameterDeclarationFromSymbol(parameterSymbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker) { const parameterDeclaration = parameterSymbol.getDeclarations()[0] as ParameterDeclaration; - const parameterType = checker.getTypeOfSymbolAtLocation(parameterSymbol, enclosingDeclaration); + const parameterType = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(parameterSymbol, enclosingDeclaration)); const parameterTypeNode = checker.createTypeNode(parameterType); // TODO: deep cloning of decorators/any node. const parameterNode = createParameter( @@ -236,8 +236,4 @@ namespace ts.codefix { , /*initializer*/ undefined); return parameterNode; } - - export function getNameFromIndexInfo(info: IndexInfo) { - return info.declaration ? declarationNameToString(info.declaration.parameters[0].name) : "x" - } } \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassImplementInterface36.ts b/tests/cases/fourslash/codeFixClassImplementInterface36.ts deleted file mode 100644 index 5b0459ab66..0000000000 --- a/tests/cases/fourslash/codeFixClassImplementInterface36.ts +++ /dev/null @@ -1,18 +0,0 @@ -/// - -//// abstract class C1 { -//// -//// } -//// -//// abstract class C2 { -//// abstract f1(); -//// } -//// -//// interface I1 extends C1, C2 {} -//// -//// class C3 implements I1 {[| |]} - -verify.rangeAfterCodeFix(`f1(){ - throw new Error("Method not implemented."); -} -`); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassImplementInterface39.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceInNamespace.ts similarity index 100% rename from tests/cases/fourslash/codeFixClassImplementInterface39.ts rename to tests/cases/fourslash/codeFixClassImplementInterfaceInNamespace.ts diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceInheritsAbstractMethod.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceInheritsAbstractMethod.ts new file mode 100644 index 0000000000..b49d42dab5 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceInheritsAbstractMethod.ts @@ -0,0 +1,18 @@ +/// + +// abstract class C1 { +// +// } +// +// abstract class C2 { +// abstract f1(); +// } +// +// interface I1 extends C1, C2 {} +// +// class C3 implements I1 {[| |]} + +verify.rangeAfterCodeFix(`f1(){ + throw new Error("Method not implemented."); +} +`); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplements1.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplements1.ts index 5043a4d9c2..13d58d0ad6 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplements1.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplements1.ts @@ -7,8 +7,8 @@ //// y: number; //// } //// -//// class C implements I1,I2 {[| |] -//// y: number; +//// class C implements I1,I2 {[| +//// |]y: number; //// } verify.rangeAfterCodeFix(` diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplements2.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplements2.ts index ead2375168..e5100b88f6 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplements2.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMultipleImplements2.ts @@ -7,9 +7,9 @@ //// y: number; //// } //// -//// class C implements I1,I2 {[| |] -//// x: number; -//// |]} +//// class C implements I1,I2 {[| +//// |]x: number; +//// } verify.rangeAfterCodeFix(` y: number; From 92b3e17bc0db3daac25f29a7219de527f8c2b591 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Tue, 14 Mar 2017 15:26:30 -0700 Subject: [PATCH 21/65] Fix deep clone --- src/compiler/factory.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 077bc34986..bd46e20878 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -68,11 +68,18 @@ namespace ts { } /* @internal */ + /** + * Note this implementation is inefficient in that all nodes except leaves are cloned twice, + * First by the explicit call below and then again as part of updateNode. + * We need to clone before visiting the children because otherwise updateNode + * will overwrite the synthesized span with the original node's span. + */ export function getSynthesizedDeepClone(node: T | undefined): T { if (node === undefined) { return undefined; } - return getSynthesizedClone(visitEachChild(node, getSynthesizedClone, nullTransformationContext)); + const clone = getSynthesizedClone(node); + return visitEachChild(clone, getSynthesizedDeepClone, nullTransformationContext); } // Literals From d4bb267ee3618f03fb64c515cfa2a7a02261baac Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 14 Mar 2017 16:13:33 -0700 Subject: [PATCH 22/65] add insertNodeInListAfter functionality --- src/harness/unittests/textChanges.ts | 172 +++++++++++-- .../fixClassSuperMustPrecedeThisAccess.ts | 2 +- .../fixConstructorForDerivedNeedSuperCall.ts | 2 +- src/services/codefixes/importFixes.ts | 103 +++----- .../codefixes/unusedIdentifierFixes.ts | 2 +- src/services/formatting/formattingScanner.ts | 4 +- src/services/textChanges.ts | 242 +++++++++++++++--- .../textChanges/insertNodeInListAfter1.js | 6 + .../textChanges/insertNodeInListAfter10.js | 10 + .../textChanges/insertNodeInListAfter11.js | 11 + .../textChanges/insertNodeInListAfter12.js | 10 + .../textChanges/insertNodeInListAfter13.js | 11 + .../textChanges/insertNodeInListAfter14.js | 13 + .../textChanges/insertNodeInListAfter15.js | 13 + .../textChanges/insertNodeInListAfter16.js | 13 + .../textChanges/insertNodeInListAfter17.js | 13 + .../textChanges/insertNodeInListAfter18.js | 10 + .../textChanges/insertNodeInListAfter2.js | 6 + .../textChanges/insertNodeInListAfter3.js | 6 + .../textChanges/insertNodeInListAfter4.js | 6 + .../textChanges/insertNodeInListAfter5.js | 6 + .../textChanges/insertNodeInListAfter6.js | 9 + .../textChanges/insertNodeInListAfter7.js | 9 + .../textChanges/insertNodeInListAfter8.js | 9 + .../textChanges/insertNodeInListAfter9.js | 9 + .../importNameCodeFixExistingImport10.ts | 2 +- .../importNameCodeFixExistingImport11.ts | 3 +- .../importNameCodeFixExistingImport9.ts | 3 +- 28 files changed, 580 insertions(+), 125 deletions(-) create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter1.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter10.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter11.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter12.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter13.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter14.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter15.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter16.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter17.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter18.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter2.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter3.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter4.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter5.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter6.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter7.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter8.js create mode 100644 tests/baselines/reference/textChanges/insertNodeInListAfter9.js diff --git a/src/harness/unittests/textChanges.ts b/src/harness/unittests/textChanges.ts index b8c9a8e873..61f6d738e5 100644 --- a/src/harness/unittests/textChanges.ts +++ b/src/harness/unittests/textChanges.ts @@ -130,7 +130,7 @@ namespace M /*body */ createBlock(statements) ); - changeTracker.insertNodeBefore(sourceFile, /*before*/findChild("M2", sourceFile), newFunction, { insertTrailingNewLine: true }); + changeTracker.insertNodeBefore(sourceFile, /*before*/findChild("M2", sourceFile), newFunction, { suffix: newLineCharacter }); // replace statements with return statement const newStatement = createReturn( @@ -139,7 +139,7 @@ namespace M /*typeArguments*/ undefined, /*argumentsArray*/ emptyArray )); - changeTracker.replaceNodeRange(sourceFile, statements[0], lastOrUndefined(statements), newStatement, { insertTrailingNewLine: true }); + changeTracker.replaceNodeRange(sourceFile, statements[0], lastOrUndefined(statements), newStatement, { suffix: newLineCharacter }); }); } { @@ -255,10 +255,10 @@ var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; runSingleFileTest("replaceRange", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { insertTrailingNewLine: true }); + changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter }); }); runSingleFileTest("replaceRangeWithForcedIndentation", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { insertTrailingNewLine: true, indentation: 8, delta: 0 }); + changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter, indentation: 8, delta: 0 }); }); runSingleFileTest("replaceRangeNoLineBreakBefore", setNewLineForOpenBraceInFunctions, `const x = 1, y = "2";`, /*validateNodes*/ false, (sourceFile, changeTracker) => { @@ -287,13 +287,13 @@ var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; runSingleFileTest("replaceNode1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { insertTrailingNewLine: true }); + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter }); }); runSingleFileTest("replaceNode2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, insertTrailingNewLine: true, insertLeadingNewLine: true }); + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, suffix: newLineCharacter, prefix: newLineCharacter }); }); runSingleFileTest("replaceNode3", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, insertTrailingNewLine: true }); + changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, suffix: newLineCharacter }); }); runSingleFileTest("replaceNode4", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true }); @@ -312,13 +312,13 @@ var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; runSingleFileTest("replaceNodeRange1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { insertTrailingNewLine: true }); + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { suffix: newLineCharacter }); }); runSingleFileTest("replaceNodeRange2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, insertTrailingNewLine: true, insertLeadingNewLine: true }); + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, suffix: newLineCharacter, prefix: newLineCharacter }); }); runSingleFileTest("replaceNodeRange3", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, insertTrailingNewLine: true }); + changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, suffix: newLineCharacter }); }); runSingleFileTest("replaceNodeRange4", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true }); @@ -334,7 +334,7 @@ var z = 3; // comment 5 // comment 6 var a = 4; // comment 7`; runSingleFileTest("insertNodeAt1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeAt(sourceFile, text.indexOf("var y"), createTestClass(), { insertTrailingNewLine: true }); + changeTracker.insertNodeAt(sourceFile, text.indexOf("var y"), createTestClass(), { suffix: newLineCharacter }); }); runSingleFileTest("insertNodeAt2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { changeTracker.insertNodeAt(sourceFile, text.indexOf("; // comment 4"), createTestVariableDeclaration("z1")); @@ -352,16 +352,16 @@ namespace M { var a = 4; // comment 7 }`; runSingleFileTest("insertNodeBefore1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { insertTrailingNewLine: true }); + changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter }); }); runSingleFileTest("insertNodeBefore2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeBefore(sourceFile, findChild("M", sourceFile), createTestClass(), { insertTrailingNewLine: true }); + changeTracker.insertNodeBefore(sourceFile, findChild("M", sourceFile), createTestClass(), { suffix: newLineCharacter }); }); runSingleFileTest("insertNodeAfter1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { insertTrailingNewLine: true }); + changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter }); }); runSingleFileTest("insertNodeAfter2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findChild("M", sourceFile), createTestClass(), { insertLeadingNewLine: true }); + changeTracker.insertNodeAfter(sourceFile, findChild("M", sourceFile), createTestClass(), { prefix: newLineCharacter }); }); } { @@ -385,7 +385,7 @@ class A { } `; runSingleFileTest("insertNodeAfter3", noop, text1, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { insertTrailingNewLine: true }); + changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { suffix: newLineCharacter }); }); const text2 = ` class A { @@ -395,7 +395,7 @@ class A { } `; runSingleFileTest("insertNodeAfter4", noop, text2, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), createTestSuperCall(), { insertTrailingNewLine: true }); + changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), createTestSuperCall(), { suffix: newLineCharacter }); }); const text3 = ` class A { @@ -405,7 +405,7 @@ class A { } `; runSingleFileTest("insertNodeAfter3-block with newline", noop, text3, /*validateNodes*/ false, (sourceFile, changeTracker) => { - changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { insertTrailingNewLine: true }); + changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { suffix: newLineCharacter }); }); } { @@ -498,7 +498,7 @@ function foo(a: number,b: string,c = true) { changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile)); }); } -{ + { const text = ` function foo( a: number, @@ -516,5 +516,139 @@ function foo( changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile)); }); } + { + const text = ` +const x = 1, y = 2;`; + runSingleFileTest("insertNodeInListAfter1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + runSingleFileTest("insertNodeInListAfter2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + } + { + const text = ` +const /*x*/ x = 1, /*y*/ y = 2;`; + runSingleFileTest("insertNodeInListAfter3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + runSingleFileTest("insertNodeInListAfter4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + } + { + const text = ` +const x = 1;`; + runSingleFileTest("insertNodeInListAfter5", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + } + { + const text = ` +const x = 1, + y = 2;`; + runSingleFileTest("insertNodeInListAfter6", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + runSingleFileTest("insertNodeInListAfter7", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + } + { + const text = ` +const /*x*/ x = 1, + /*y*/ y = 2;`; + runSingleFileTest("insertNodeInListAfter8", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + runSingleFileTest("insertNodeInListAfter9", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1))); + }); + } + { + const text = ` +import { + x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter10", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a"))); + }) + } + { + const text = ` +import { + x // this is x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter11", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a"))); + }) + } + { + const text = ` +import { + x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter12", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a"))); + }) + } + { + const text = ` +import { + x // this is x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter13", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a"))); + }) + } + { + const text = ` +import { + x0, + x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter14", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a"))); + }) + } + { + const text = ` +import { + x0, + x // this is x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter15", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a"))); + }) + } + { + const text = ` +import { + x0, + x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter16", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a"))); + }) + } + { + const text = ` +import { + x0, + x // this is x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter17", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a"))); + }) + } + { + const text = ` +import { + x0, x +} from "bar"`; + runSingleFileTest("insertNodeInListAfter18", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a"))); + }) + } }); } \ No newline at end of file diff --git a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts index 207d039df8..6d7efb64c6 100644 --- a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts +++ b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts @@ -27,7 +27,7 @@ namespace ts.codefix { } } const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); - changeTracker.insertNodeAfter(sourceFile, getOpenBrace(constructor, sourceFile), superCall, { insertTrailingNewLine: true }); + changeTracker.insertNodeAfter(sourceFile, getOpenBrace(constructor, sourceFile), superCall, { suffix: context.newLineCharacter }); changeTracker.deleteNode(sourceFile, superCall); return [{ diff --git a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts index 27b655f19c..822c34842f 100644 --- a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts +++ b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts @@ -12,7 +12,7 @@ namespace ts.codefix { const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); const superCall = createStatement(createCall(createSuper(), /*typeArguments*/ undefined, /*argumentsArray*/ emptyArray)); - changeTracker.insertNodeAfter(sourceFile, getOpenBrace(token.parent, sourceFile), superCall, { insertTrailingNewLine: true }); + changeTracker.insertNodeAfter(sourceFile, getOpenBrace(token.parent, sourceFile), superCall, { suffix: context.newLineCharacter }); return [{ description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 3e07efbc3d..743c13ccf3 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -130,7 +130,7 @@ namespace ts.codefix { // this is a module id -> module import declaration map const cachedImportDeclarations: (ImportDeclaration | ImportEqualsDeclaration)[][] = []; - let cachedNewImportInsertPosition: number; + let lastImportDeclaration: Node; const currentTokenMeaning = getMeaningFromLocation(token); if (context.errorCode === Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code) { @@ -138,14 +138,14 @@ namespace ts.codefix { return getCodeActionForImport(symbol, /*isDefault*/ false, /*isNamespaceImport*/ true); } - const allPotentialModules = checker.getAmbientModules(); + const candidateModules = checker.getAmbientModules(); for (const otherSourceFile of allSourceFiles) { if (otherSourceFile !== sourceFile && isExternalOrCommonJsModule(otherSourceFile)) { - allPotentialModules.push(otherSourceFile.symbol); + candidateModules.push(otherSourceFile.symbol); } } - for (const moduleSymbol of allPotentialModules) { + for (const moduleSymbol of candidateModules) { context.cancellationToken.throwIfCancellationRequested(); // check the default export @@ -277,14 +277,12 @@ namespace ts.codefix { * If the existing import declaration already has a named import list, just * insert the identifier into that list. */ - const textChange = getTextChangeForImportClause(namedImportDeclaration.importClause); + const fileTextChanges = getTextChangeForImportClause(namedImportDeclaration.importClause); const moduleSpecifierWithoutQuotes = stripQuotes(namedImportDeclaration.moduleSpecifier.getText()); actions.push(createCodeAction( Diagnostics.Add_0_to_existing_import_declaration_from_1, [name, moduleSpecifierWithoutQuotes], - textChange.newText, - textChange.span, - sourceFile.fileName, + fileTextChanges, "InsertingIntoExistingImport", moduleSpecifierWithoutQuotes )); @@ -302,49 +300,31 @@ namespace ts.codefix { return declaration.moduleReference.getText(); } - function getTextChangeForImportClause(importClause: ImportClause): TextChange { - const newImportText = isDefault ? `default as ${name}` : name; + function getTextChangeForImportClause(importClause: ImportClause): FileTextChanges[] { + //const newImportText = isDefault ? `default as ${name}` : name; const importList = importClause.namedBindings; + const newImportSpecifier = createImportSpecifier(/*propertyName*/ undefined, createIdentifier(name)); // case 1: // original text: import default from "module" // change to: import default, { name } from "module" - if (!importList && importClause.name) { - const start = importClause.name.getEnd(); - return { - newText: `, { ${newImportText} }`, - span: { start, length: 0 } - }; - } - // case 2: // original text: import {} from "module" // change to: import { name } from "module" - if (importList.elements.length === 0) { - const start = importList.getStart(); - return { - newText: `{ ${newImportText} }`, - span: { start, length: importList.getEnd() - start } - }; + if (!importList || importList.elements.length === 0) { + const newImportClause = createImportClause(importClause.name, createNamedImports([newImportSpecifier])); + return createChangeTracker().replaceNode(sourceFile, importClause, newImportClause).getChanges(); } - // case 3: - // original text: import { foo, bar } from "module" - // change to: import { foo, bar, name } from "module" - const insertPoint = importList.elements[importList.elements.length - 1].getEnd(); /** * If the import list has one import per line, preserve that. Otherwise, insert on same line as last element * import { * foo * } from "./module"; */ - const startLine = getLineOfLocalPosition(sourceFile, importList.getStart()); - const endLine = getLineOfLocalPosition(sourceFile, importList.getEnd()); - const oneImportPerLine = endLine - startLine > importList.elements.length; - - return { - newText: `,${oneImportPerLine ? context.newLineCharacter : " "}${newImportText}`, - span: { start: insertPoint, length: 0 } - }; + return createChangeTracker().insertNodeInListAfter( + sourceFile, + importList.elements[importList.elements.length - 1], + newImportSpecifier).getChanges(); } function getCodeActionForNamespaceImport(declaration: ImportDeclaration | ImportEqualsDeclaration): ImportCodeAction { @@ -370,48 +350,47 @@ namespace ts.codefix { return createCodeAction( Diagnostics.Change_0_to_1, [name, `${namespacePrefix}.${name}`], - `${namespacePrefix}.`, - { start: token.getStart(), length: 0 }, - sourceFile.fileName, + createChangeTracker().replaceNode(sourceFile, token, createPropertyAccess(createIdentifier(namespacePrefix), name)).getChanges(), "CodeChange" ); } } function getCodeActionForNewImport(moduleSpecifier?: string): ImportCodeAction { - if (!cachedNewImportInsertPosition) { + if (!lastImportDeclaration) { // insert after any existing imports - let lastModuleSpecifierEnd = -1; - for (const moduleSpecifier of sourceFile.imports) { - const end = moduleSpecifier.getEnd(); - if (!lastModuleSpecifierEnd || end > lastModuleSpecifierEnd) { - lastModuleSpecifierEnd = end; + for (let i = sourceFile.statements.length - 1; i >= 0; i--) { + const statement = sourceFile.statements[i]; + if (statement.kind === SyntaxKind.ImportEqualsDeclaration || statement.kind === SyntaxKind.ImportDeclaration) { + lastImportDeclaration = statement; + break; } } - cachedNewImportInsertPosition = lastModuleSpecifierEnd > 0 ? sourceFile.getLineEndOfPosition(lastModuleSpecifierEnd) : sourceFile.getStart(); } const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier || getModuleSpecifierForNewImport()); - const importStatementText = isDefault - ? `import ${name} from "${moduleSpecifierWithoutQuotes}"` + const changeTracker = createChangeTracker(); + const importClause = isDefault + ? createImportClause(createIdentifier(name), /*namedBindings*/ undefined) : isNamespaceImport - ? `import * as ${name} from "${moduleSpecifierWithoutQuotes}"` - : `import { ${name} } from "${moduleSpecifierWithoutQuotes}"`; + ? createImportClause(/*name*/ undefined, createNamespaceImport(createIdentifier(name))) + : createImportClause(/*name*/ undefined, createNamedImports([createImportSpecifier(/*propertyName*/ undefined, createIdentifier(name))])); + const importDecl = createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, importClause, createLiteral(moduleSpecifierWithoutQuotes)); + if (!lastImportDeclaration) { + changeTracker.insertNodeAt(sourceFile, sourceFile.getStart(), importDecl, { suffix: `${context.newLineCharacter}${context.newLineCharacter}` }); + } + else { + changeTracker.insertNodeAfter(sourceFile, lastImportDeclaration, importDecl, { suffix: context.newLineCharacter }); + } // if this file doesn't have any import statements, insert an import statement and then insert a new line // between the only import statement and user code. Otherwise just insert the statement because chances // are there are already a new line seperating code and import statements. - const newText = cachedNewImportInsertPosition === sourceFile.getStart() - ? `${importStatementText};${context.newLineCharacter}${context.newLineCharacter}` - : `${context.newLineCharacter}${importStatementText};`; - return createCodeAction( Diagnostics.Import_0_from_1, [name, `"${moduleSpecifierWithoutQuotes}"`], - newText, - { start: cachedNewImportInsertPosition, length: 0 }, - sourceFile.fileName, + changeTracker.getChanges(), "NewImport", moduleSpecifierWithoutQuotes ); @@ -576,17 +555,19 @@ namespace ts.codefix { } + function createChangeTracker() { + return textChanges.ChangeTracker.fromCodeFixContext(context);; + } + function createCodeAction( description: DiagnosticMessage, diagnosticArgs: string[], - newText: string, - span: TextSpan, - fileName: string, + changes: FileTextChanges[], kind: ImportCodeActionKind, moduleSpecifier?: string): ImportCodeAction { return { description: formatMessage.apply(undefined, [undefined, description].concat(diagnosticArgs)), - changes: [{ fileName, textChanges: [{ newText, span }] }], + changes, kind, moduleSpecifier }; diff --git a/src/services/codefixes/unusedIdentifierFixes.ts b/src/services/codefixes/unusedIdentifierFixes.ts index 1773427fce..f77f9723d0 100644 --- a/src/services/codefixes/unusedIdentifierFixes.ts +++ b/src/services/codefixes/unusedIdentifierFixes.ts @@ -132,7 +132,7 @@ namespace ts.codefix { else { const previousToken = getTokenAtPosition(sourceFile, namespaceImport.pos - 1); if (previousToken && previousToken.kind === SyntaxKind.CommaToken) { - const startPosition = textChanges.getAdjustedStartPosition(sourceFile, previousToken, {}, /*forDeleteOperation*/ true); + const startPosition = textChanges.getAdjustedStartPosition(sourceFile, previousToken, {}, textChanges.Position.FullStart); return deleteRange({ pos: startPosition, end: namespaceImport.end }); } return deleteRange(namespaceImport); diff --git a/src/services/formatting/formattingScanner.ts b/src/services/formatting/formattingScanner.ts index 981ae09b2b..20d190de2f 100644 --- a/src/services/formatting/formattingScanner.ts +++ b/src/services/formatting/formattingScanner.ts @@ -276,8 +276,8 @@ namespace ts.formatting { function isOnToken(): boolean { Debug.assert(scanner !== undefined); - const current = (lastTokenInfo && lastTokenInfo.token.kind) || scanner.getToken(); - const startPos = (lastTokenInfo && lastTokenInfo.token.pos) || scanner.getStartPos(); + const current = lastTokenInfo ? lastTokenInfo.token.kind : scanner.getToken(); + const startPos = lastTokenInfo ? lastTokenInfo.token.pos : scanner.getStartPos(); return startPos < endPos && current !== SyntaxKind.EndOfFileToken && !isTrivia(current); } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 5b9f0f5ef4..174f2969a0 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -28,6 +28,28 @@ namespace ts.textChanges { useNonAdjustedEndPosition?: boolean; } + export enum Position { + FullStart, + Start + } + + function skipWhitespacesAndLineBreaks(text: string, start: number) { + return skipTrivia(text, start, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + } + + function hasCommentsBeforeLineBreak(text: string, start: number) { + let i = start; + while (i < text.length) { + const ch = text.charCodeAt(i); + if (isWhiteSpaceSingleLine(ch)) { + i++; + continue; + } + return ch === CharacterCodes.slash; + } + return false; + } + /** * Usually node.pos points to a position immediately after the previous token. * If this position is used as a beginning of the span to remove - it might lead to removing the trailing trivia of the previous node, i.e: @@ -44,13 +66,13 @@ namespace ts.textChanges { export interface InsertNodeOptions { /** - * Set this value to true to make sure that node text of newly inserted node ends with new line + * Text to be inserted before the new node */ - insertTrailingNewLine?: boolean; + prefix?: string; /** - * Set this value to true to make sure that node text of newly inserted node starts with new line + * Text to be inserted after the new node */ - insertLeadingNewLine?: boolean; + suffix?: string; /** * Text of inserted node will be formatted with this indentation, otherwise indentation will be inferred from the old node */ @@ -66,12 +88,16 @@ namespace ts.textChanges { interface Change { readonly sourceFile: SourceFile; readonly range: TextRange; - readonly oldNode?: Node; + readonly useIndentationFromFile?: boolean; readonly node?: Node; readonly options?: ChangeNodeOptions; } - export function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStart, forDeleteOperation: boolean) { + export function getSeparatorCharacter(separator: Token) { + return tokenToString(separator.kind); + } + + export function getAdjustedStartPosition(sourceFile: SourceFile, node: Node, options: ConfigurableStart, position: Position) { if (options.useNonAdjustedStartPosition) { return node.getFullStart(); } @@ -90,12 +116,12 @@ namespace ts.textChanges { // fullstart // when b is replaced - we usually want to keep the leading trvia // when b is deleted - we delete it - return forDeleteOperation ? fullStart : start; + return position === Position.Start ? start : fullStart; } // get start position of the line following the line that contains fullstart position let adjustedStartPosition = getStartPositionOfLine(getLineOfLocalPosition(sourceFile, fullStartLine) + 1, sourceFile); // skip whitespaces/newlines - adjustedStartPosition = skipTrivia(sourceFile.text, adjustedStartPosition, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + adjustedStartPosition = skipWhitespacesAndLineBreaks(sourceFile.text, adjustedStartPosition); return getStartPositionOfLine(getLineOfLocalPosition(sourceFile, adjustedStartPosition), sourceFile); } @@ -112,8 +138,19 @@ namespace ts.textChanges { : end; } - function isSeparator(node: Node, separator: Node): boolean { - return node.parent && (separator.kind === SyntaxKind.CommaToken || (separator.kind === SyntaxKind.SemicolonToken && node.parent.kind === SyntaxKind.ObjectLiteralExpression)); + /** + * Checks if 'candidate' argument is a legal separator in the list that contains 'node' as an element + */ + function isSeparator(node: Node, candidate: Node): candidate is Token { + return candidate && node.parent && (candidate.kind === SyntaxKind.CommaToken || (candidate.kind === SyntaxKind.SemicolonToken && node.parent.kind === SyntaxKind.ObjectLiteralExpression)); + } + + function spaces(count: number) { + let s = ""; + for (let i = 0; i < count; i++) { + s += " "; + } + return s; } export class ChangeTracker { @@ -132,7 +169,7 @@ namespace ts.textChanges { } public deleteNode(sourceFile: SourceFile, node: Node, options: ConfigurableStartEnd = {}) { - const startPosition = getAdjustedStartPosition(sourceFile, node, options, /*forDeleteOperation*/ true); + const startPosition = getAdjustedStartPosition(sourceFile, node, options, Position.FullStart); const endPosition = getAdjustedEndPosition(sourceFile, node, options); this.changes.push({ sourceFile, options, range: { pos: startPosition, end: endPosition } }); return this; @@ -144,7 +181,7 @@ namespace ts.textChanges { } public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = {}) { - const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, /*forDeleteOperation*/ true); + const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart); const endPosition = getAdjustedEndPosition(sourceFile, endNode, options); this.changes.push({ sourceFile, options, range: { pos: startPosition, end: endPosition } }); return this; @@ -153,7 +190,7 @@ namespace ts.textChanges { public deleteNodeInList(sourceFile: SourceFile, node: Node) { const containingList = formatting.SmartIndenter.getContainingList(node, sourceFile); if (!containingList) { - return; + return this; } const index = containingList.indexOf(node); if (index < 0) { @@ -167,10 +204,10 @@ namespace ts.textChanges { const nextToken = getTokenAtPosition(sourceFile, node.end); if (nextToken && isSeparator(node, nextToken)) { // find first non-whitespace position in the leading trivia of the node - const startPosition = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, /*forDeleteOperation*/ true), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + const startPosition = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); const nextElement = containingList[index + 1]; /// find first non-whitespace position in the leading trivia of the next node - const endPosition = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, nextElement, {}, /*forDeleteOperation*/ true), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); + const endPosition = skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, nextElement, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true); // shift next node so its first non-whitespace position will be moved to the first non-whitespace position of the deleted node this.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); } @@ -190,16 +227,16 @@ namespace ts.textChanges { } public replaceNode(sourceFile: SourceFile, oldNode: Node, newNode: Node, options: ChangeNodeOptions = {}) { - const startPosition = getAdjustedStartPosition(sourceFile, oldNode, options, /*forDeleteOperation*/ false); + const startPosition = getAdjustedStartPosition(sourceFile, oldNode, options, Position.Start); const endPosition = getAdjustedEndPosition(sourceFile, oldNode, options); - this.changes.push({ sourceFile, options, oldNode, node: newNode, range: { pos: startPosition, end: endPosition } }); + this.changes.push({ sourceFile, options, useIndentationFromFile: true, node: newNode, range: { pos: startPosition, end: endPosition } }); return this; } public replaceNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, newNode: Node, options: ChangeNodeOptions = {}) { - const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, /*forDeleteOperation*/ false); + const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.Start); const endPosition = getAdjustedEndPosition(sourceFile, endNode, options); - this.changes.push({ sourceFile, options, oldNode: startNode, node: newNode, range: { pos: startPosition, end: endPosition } }); + this.changes.push({ sourceFile, options, useIndentationFromFile: true, node: newNode, range: { pos: startPosition, end: endPosition } }); return this; } @@ -209,14 +246,149 @@ namespace ts.textChanges { } public insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, options: InsertNodeOptions & ConfigurableStart = {}) { - const startPosition = getAdjustedStartPosition(sourceFile, before, options, /*forDeleteOperation*/ false); - this.changes.push({ sourceFile, options, oldNode: before, node: newNode, range: { pos: startPosition, end: startPosition } }); + const startPosition = getAdjustedStartPosition(sourceFile, before, options, Position.Start); + this.changes.push({ sourceFile, options, useIndentationFromFile: true, node: newNode, range: { pos: startPosition, end: startPosition } }); return this; } public insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node, options: InsertNodeOptions & ConfigurableEnd = {}) { const endPosition = getAdjustedEndPosition(sourceFile, after, options); - this.changes.push({ sourceFile, options, oldNode: after, node: newNode, range: { pos: endPosition, end: endPosition } }); + this.changes.push({ sourceFile, options, useIndentationFromFile: true, node: newNode, range: { pos: endPosition, end: endPosition } }); + return this; + } + + public insertNodeInListAfter(sourceFile: SourceFile, after: Node, newNode: Node) { + const containingList = formatting.SmartIndenter.getContainingList(after, sourceFile); + if (!containingList) { + return this; + } + const index = containingList.indexOf(after); + if (index < 0) { + return this; + } + const end = after.getEnd(); + if (index !== containingList.length - 1) { + // any element except the last one + // use next sibling as an anchor + const nextToken = getTokenAtPosition(sourceFile, after.end); + if (nextToken && isSeparator(after, nextToken)) { + // for list + // a, b, c + // create change for adding 'e' after 'a' as + // - find start of next element after a (it is b) + // - use this start as start and end position in final change + // - build text of change by formatting the text of node + separator + whitespace trivia of b + + // in multiline case it will work as + // a, + // b, + // c, + // result - '*' denotes leading trivia that will be inserted after new text (displayed as '#') + // a,* + //***insertedtext# + //###b, + // c, + // find line and character of the next element + const lineAndCharOfNextElement = getLineAndCharacterOfPosition(sourceFile, skipWhitespacesAndLineBreaks(sourceFile.text, containingList[index + 1].getFullStart())); + // find line and character of the token that precedes next element (usually it is separator) + const lineAndCharOfNextToken = getLineAndCharacterOfPosition(sourceFile, nextToken.end); + let prefix: string; + let startPos: number; + if (lineAndCharOfNextToken.line === lineAndCharOfNextElement.line) { + // next element is located on the same line with separator: + // a,$$$$b + // ^ ^ + // | |-next element + // |-separator + // where $$$ is some leading trivia + // for a newly inserted node we'll maintain the same relative position comparing to separator and replace leading trivia with spaces + // a, x,$$$$b + // ^ ^ ^ + // | | |-next element + // | |-new inserted node padded with spaces + // |-separator + startPos = nextToken.end; + prefix = spaces(lineAndCharOfNextElement.character - lineAndCharOfNextToken.character); + } + else { + // next element is located on different line that separator + // let insert position be the beginning of the line that contains next element + startPos = getStartPositionOfLine(lineAndCharOfNextElement.line, sourceFile); + } + + this.changes.push({ + sourceFile, + range: { pos: startPos, end: containingList[index + 1].getStart(sourceFile) }, + node: newNode, + useIndentationFromFile: true, + options: { + prefix, + // write separator and leading trivia of the next element as suffix + suffix: `${tokenToString(nextToken.kind)}${sourceFile.text.substring(nextToken.end, containingList[index + 1].getStart(sourceFile))}` + } + }); + } + } + else { + const afterStart = after.getStart(sourceFile); + const afterStartLinePosition = getLineStartPositionForPosition(afterStart, sourceFile); + + let separator: SyntaxKind.CommaToken | SyntaxKind.SemicolonToken; + let multilineList = false; + + // insert element after the last element in the list that has more than one item + // pick the element preceding the after element to: + // - pick the separator + // - determine if list is a multiline + if (containingList.length === 1) { + // if list has only one element then we'll format is as multiline if node has comment in trailing trivia, or as singleline otherwise + // i.e. var x = 1 // this is x + // | new element will be inserted at this position + separator = SyntaxKind.CommaToken; + } + else { + // element has more than one element, pick separator from the list + const tokenBeforeInsertPosition = findPrecedingToken(after.pos, sourceFile); + separator = isSeparator(after, tokenBeforeInsertPosition) ? tokenBeforeInsertPosition.kind : SyntaxKind.CommaToken; + // determine if list is multiline by checking lines of after element and element that precedes it. + const afterMinusOneStartLinePosition = getLineStartPositionForPosition(containingList[index - 1].getStart(sourceFile), sourceFile); + multilineList = afterMinusOneStartLinePosition !== afterStartLinePosition; + } + if (hasCommentsBeforeLineBreak(sourceFile.text, after.end)) { + // in this case we'll always treat containing list as multiline + multilineList = true; + } + if (multilineList) { + // insert separator immediately following the 'after' node to preserve comments in trailing trivia + this.changes.push({ + sourceFile, + range: { pos: end, end }, + node: createToken(separator), + options: {} + }); + // use the same indentation as 'after' item + const indentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(afterStartLinePosition, afterStart, sourceFile, this.rulesProvider.getFormatOptions()); + // insert element before the line break on the line that contains 'after' element + let insertPos = skipTrivia(sourceFile.text, end, /*stopAfterLineBreak*/ true, /*stopAtComments*/ false); + if (insertPos !== end && isLineBreak(sourceFile.text.charCodeAt(insertPos - 1))) { + insertPos-- + } + this.changes.push({ + sourceFile, + range: { pos: insertPos, end: insertPos }, + node: newNode, + options: { indentation, prefix: this.newLineCharacter } + }); + } + else { + this.changes.push({ + sourceFile, + range: { pos: end, end }, + node: newNode, + options: { prefix: `${tokenToString(separator)} ` } + }); + } + } return this; } @@ -267,13 +439,13 @@ namespace ts.textChanges { const formatOptions = this.rulesProvider.getFormatOptions(); const pos = change.range.pos; - const posStartsLine = getLineStartPositionForPosition(pos, sourceFile) === pos; + const posStartsLine = getLineStartPositionForPosition(pos, sourceFile) === pos; const initialIndentation = change.options.indentation !== undefined ? change.options.indentation - : change.oldNode - ? formatting.SmartIndenter.getIndentation(change.range.pos, sourceFile, formatOptions, posStartsLine || change.options.insertLeadingNewLine) + : change.useIndentationFromFile + ? formatting.SmartIndenter.getIndentation(change.range.pos, sourceFile, formatOptions, posStartsLine || (change.options.prefix == this.newLineCharacter)) : 0; const delta = change.options.delta !== undefined @@ -284,15 +456,9 @@ namespace ts.textChanges { let text = applyFormatting(nonFormattedText, sourceFile, initialIndentation, delta, this.rulesProvider); // strip initial indentation (spaces or tabs) if text will be inserted in the middle of the line - text = posStartsLine ? text : text.replace(/^\s+/, ""); - - if (options.insertLeadingNewLine) { - text = this.newLineCharacter + text; - } - if (options.insertTrailingNewLine) { - text = text + this.newLineCharacter; - } - return text; + // however keep indentation if it is was forced + text = posStartsLine || change.options.indentation !== undefined ? text : text.replace(/^\s+/, ""); + return (options.prefix || "") + text + (options.suffix || ""); } private static normalize(changes: Change[]) { @@ -397,9 +563,13 @@ namespace ts.textChanges { constructor(newLine: string) { this.writer = createTextWriter(newLine); this.onEmitNode = (hint, node, printCallback) => { - setPos(node, this.lastNonTriviaPosition); + if (node) { + setPos(node, this.lastNonTriviaPosition); + } printCallback(hint, node); - setEnd(node, this.lastNonTriviaPosition); + if (node) { + setEnd(node, this.lastNonTriviaPosition); + } }; this.onBeforeEmitNodeArray = nodes => { if (nodes) { diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter1.js b/tests/baselines/reference/textChanges/insertNodeInListAfter1.js new file mode 100644 index 0000000000..9b0ed7b819 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter1.js @@ -0,0 +1,6 @@ +===ORIGINAL=== + +const x = 1, y = 2; +===MODIFIED=== + +const x = 1, z = 1, y = 2; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter10.js b/tests/baselines/reference/textChanges/insertNodeInListAfter10.js new file mode 100644 index 0000000000..300230bf51 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter10.js @@ -0,0 +1,10 @@ +===ORIGINAL=== + +import { + x +} from "bar" +===MODIFIED=== + +import { + x, b as a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter11.js b/tests/baselines/reference/textChanges/insertNodeInListAfter11.js new file mode 100644 index 0000000000..f0ba43a0f3 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter11.js @@ -0,0 +1,11 @@ +===ORIGINAL=== + +import { + x // this is x +} from "bar" +===MODIFIED=== + +import { + x, // this is x + b as a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter12.js b/tests/baselines/reference/textChanges/insertNodeInListAfter12.js new file mode 100644 index 0000000000..79e7bb2c8b --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter12.js @@ -0,0 +1,10 @@ +===ORIGINAL=== + +import { + x +} from "bar" +===MODIFIED=== + +import { + x, a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter13.js b/tests/baselines/reference/textChanges/insertNodeInListAfter13.js new file mode 100644 index 0000000000..a7ac7179ab --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter13.js @@ -0,0 +1,11 @@ +===ORIGINAL=== + +import { + x // this is x +} from "bar" +===MODIFIED=== + +import { + x, // this is x + a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter14.js b/tests/baselines/reference/textChanges/insertNodeInListAfter14.js new file mode 100644 index 0000000000..e44a21723b --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter14.js @@ -0,0 +1,13 @@ +===ORIGINAL=== + +import { + x0, + x +} from "bar" +===MODIFIED=== + +import { + x0, + x, + b as a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter15.js b/tests/baselines/reference/textChanges/insertNodeInListAfter15.js new file mode 100644 index 0000000000..a80bdc2db5 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter15.js @@ -0,0 +1,13 @@ +===ORIGINAL=== + +import { + x0, + x // this is x +} from "bar" +===MODIFIED=== + +import { + x0, + x, // this is x + b as a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter16.js b/tests/baselines/reference/textChanges/insertNodeInListAfter16.js new file mode 100644 index 0000000000..07f8db919f --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter16.js @@ -0,0 +1,13 @@ +===ORIGINAL=== + +import { + x0, + x +} from "bar" +===MODIFIED=== + +import { + x0, + x, + a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter17.js b/tests/baselines/reference/textChanges/insertNodeInListAfter17.js new file mode 100644 index 0000000000..990139408e --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter17.js @@ -0,0 +1,13 @@ +===ORIGINAL=== + +import { + x0, + x // this is x +} from "bar" +===MODIFIED=== + +import { + x0, + x, // this is x + a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter18.js b/tests/baselines/reference/textChanges/insertNodeInListAfter18.js new file mode 100644 index 0000000000..b25a3f4634 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter18.js @@ -0,0 +1,10 @@ +===ORIGINAL=== + +import { + x0, x +} from "bar" +===MODIFIED=== + +import { + x0, x, a +} from "bar" \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter2.js b/tests/baselines/reference/textChanges/insertNodeInListAfter2.js new file mode 100644 index 0000000000..32e8e63c0f --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter2.js @@ -0,0 +1,6 @@ +===ORIGINAL=== + +const x = 1, y = 2; +===MODIFIED=== + +const x = 1, y = 2, z = 1; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter3.js b/tests/baselines/reference/textChanges/insertNodeInListAfter3.js new file mode 100644 index 0000000000..70a0b0bddc --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter3.js @@ -0,0 +1,6 @@ +===ORIGINAL=== + +const /*x*/ x = 1, /*y*/ y = 2; +===MODIFIED=== + +const /*x*/ x = 1, z = 1, /*y*/ y = 2; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter4.js b/tests/baselines/reference/textChanges/insertNodeInListAfter4.js new file mode 100644 index 0000000000..4a30acd707 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter4.js @@ -0,0 +1,6 @@ +===ORIGINAL=== + +const /*x*/ x = 1, /*y*/ y = 2; +===MODIFIED=== + +const /*x*/ x = 1, /*y*/ y = 2, z = 1; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter5.js b/tests/baselines/reference/textChanges/insertNodeInListAfter5.js new file mode 100644 index 0000000000..42d5fe2ca4 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter5.js @@ -0,0 +1,6 @@ +===ORIGINAL=== + +const x = 1; +===MODIFIED=== + +const x = 1, z = 1; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter6.js b/tests/baselines/reference/textChanges/insertNodeInListAfter6.js new file mode 100644 index 0000000000..06eae7372a --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter6.js @@ -0,0 +1,9 @@ +===ORIGINAL=== + +const x = 1, + y = 2; +===MODIFIED=== + +const x = 1, + z = 1, + y = 2; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter7.js b/tests/baselines/reference/textChanges/insertNodeInListAfter7.js new file mode 100644 index 0000000000..bef0150368 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter7.js @@ -0,0 +1,9 @@ +===ORIGINAL=== + +const x = 1, + y = 2; +===MODIFIED=== + +const x = 1, + y = 2, + z = 1; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter8.js b/tests/baselines/reference/textChanges/insertNodeInListAfter8.js new file mode 100644 index 0000000000..e3c44b1427 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter8.js @@ -0,0 +1,9 @@ +===ORIGINAL=== + +const /*x*/ x = 1, + /*y*/ y = 2; +===MODIFIED=== + +const /*x*/ x = 1, + z = 1, + /*y*/ y = 2; \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/insertNodeInListAfter9.js b/tests/baselines/reference/textChanges/insertNodeInListAfter9.js new file mode 100644 index 0000000000..510984b757 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeInListAfter9.js @@ -0,0 +1,9 @@ +===ORIGINAL=== + +const /*x*/ x = 1, + /*y*/ y = 2; +===MODIFIED=== + +const /*x*/ x = 1, + /*y*/ y = 2, + z = 1; \ No newline at end of file diff --git a/tests/cases/fourslash/importNameCodeFixExistingImport10.ts b/tests/cases/fourslash/importNameCodeFixExistingImport10.ts index 25246e7012..8a178e7273 100644 --- a/tests/cases/fourslash/importNameCodeFixExistingImport10.ts +++ b/tests/cases/fourslash/importNameCodeFixExistingImport10.ts @@ -16,6 +16,6 @@ verify.importFixAtPosition([ `{ v1, v2, -f1 + f1 }` ]); \ No newline at end of file diff --git a/tests/cases/fourslash/importNameCodeFixExistingImport11.ts b/tests/cases/fourslash/importNameCodeFixExistingImport11.ts index 304ffb896d..8822356c13 100644 --- a/tests/cases/fourslash/importNameCodeFixExistingImport11.ts +++ b/tests/cases/fourslash/importNameCodeFixExistingImport11.ts @@ -15,6 +15,7 @@ verify.importFixAtPosition([ `{ v1, v2, - v3, f1 + v3, + f1 }` ]); \ No newline at end of file diff --git a/tests/cases/fourslash/importNameCodeFixExistingImport9.ts b/tests/cases/fourslash/importNameCodeFixExistingImport9.ts index 05d1792745..51496abff1 100644 --- a/tests/cases/fourslash/importNameCodeFixExistingImport9.ts +++ b/tests/cases/fourslash/importNameCodeFixExistingImport9.ts @@ -11,7 +11,6 @@ verify.importFixAtPosition([ `{ - v1, -f1 + v1, f1 }` ]); \ No newline at end of file From 79a4557f4c1737ee4dd4151e94f7f2c24b792e8b Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Tue, 14 Mar 2017 16:24:59 -0700 Subject: [PATCH 23/65] visit type literal and dotDotDottoken --- src/compiler/visitor.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index e64e59eba3..ba348a558b 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -270,7 +270,7 @@ namespace ts { return updateParameter(node, nodesVisitor((node).decorators, visitor, isDecorator), nodesVisitor((node).modifiers, visitor, isModifier), - (node).dotDotDotToken, + visitNode((node).dotDotDotToken, visitor), visitNode((node).name, visitor, isBindingName), visitNode((node).questionToken, visitor, isToken), visitNode((node).type, visitor, isTypeNode), @@ -294,7 +294,7 @@ namespace ts { case SyntaxKind.NeverKeyword: case SyntaxKind.NeverKeyword: case SyntaxKind.ThisKeyword: - return node; + throw new Error("should be caught above"); // Types @@ -332,7 +332,8 @@ namespace ts { case SyntaxKind.MappedType: throw new Error("reached unsupported type in visitor."); case SyntaxKind.LiteralType: - throw new Error("reached unsupported type in visitor."); + return updateLiteralTypeNode(node + , visitNode((node).literal, visitor, isExpression)); // Type Declarations From e381ffaac7f0b59caa2a41f9813acf4a487664b5 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Tue, 14 Mar 2017 16:25:51 -0700 Subject: [PATCH 24/65] don't add any typenode for signature return type --- src/services/codefixes/helpers.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index c51687962b..e6f4524c71 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -94,18 +94,17 @@ namespace ts.codefix { const newTypeParameters = signature.typeParameters && signature.typeParameters.map(checker.createTypeParameterDeclarationFromType); const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); - const returnType = checker.createTypeNode(checker.getWidenedType(checker.getReturnTypeOfSignature(signature))); + const returnType = createTypeNodeExceptAny(checker.getReturnTypeOfSignature(signature), checker); return createStubbedMethod(modifiers, name, newTypeParameters, newParameterNodes, returnType); } let signatureDeclarations = []; for (let i = 0; i < signatures.length; i++) { - // const sigString = checker.signatureToString(signatures[i], enclosingDeclaration, TypeFormatFlags.SuppressAnyReturnType, SignatureKind.Call); // TODO: make signatures instead of methods const signature = signatures[i]; const newTypeParameters = signature.typeParameters && signature.typeParameters.map(checker.createTypeParameterDeclarationFromType); const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); - const returnType = checker.createTypeNode(signature.resolvedReturnType); + const returnType = createTypeNodeExceptAny(checker.getReturnTypeOfSignature(signature), checker); signatureDeclarations.push(createMethod( /*decorators*/ undefined , modifiers @@ -121,7 +120,7 @@ namespace ts.codefix { let signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration); const newTypeParameters = signature.typeParameters && signature.typeParameters.map(checker.createTypeParameterDeclarationFromType); const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); - const returnType = checker.createTypeNode(checker.getWidenedType(checker.getReturnTypeOfSignature(signature))); + const returnType = createTypeNodeExceptAny(checker.getReturnTypeOfSignature(signature), checker); signatureDeclarations.push(createStubbedMethod(modifiers, name, newTypeParameters, newParameterNodes, returnType)); } else { @@ -236,4 +235,9 @@ namespace ts.codefix { , /*initializer*/ undefined); return parameterNode; } + + function createTypeNodeExceptAny(type: Type, checker: TypeChecker) { + const typeNode = checker.createTypeNode(type); + return typeNode && typeNode.kind !== SyntaxKind.AnyKeyword ? typeNode : undefined; + } } \ No newline at end of file From b8756597b8208844dcbf724fc69eb84589608fd2 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Tue, 14 Mar 2017 16:45:47 -0700 Subject: [PATCH 25/65] test cleanup --- ...ssImplementInterfaceIndexSignaturesNumber.ts | 1 - ...sImplementInterfaceInheritsAbstractMethod.ts | 17 ++++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesNumber.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesNumber.ts index 41bb7257e4..82cc53570f 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesNumber.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesNumber.ts @@ -3,7 +3,6 @@ //// interface I { //// [x: number]: I; //// } -//// //// class C implements I {[| |]} verify.rangeAfterCodeFix(` diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceInheritsAbstractMethod.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceInheritsAbstractMethod.ts index b49d42dab5..c141592823 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceInheritsAbstractMethod.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceInheritsAbstractMethod.ts @@ -1,16 +1,11 @@ /// -// abstract class C1 { -// -// } -// -// abstract class C2 { -// abstract f1(); -// } -// -// interface I1 extends C1, C2 {} -// -// class C3 implements I1 {[| |]} +//// abstract class C1 { } +//// abstract class C2 { +//// abstract f1(); +//// } +//// interface I1 extends C1, C2 { } +//// class C3 implements I1 {[| |]} verify.rangeAfterCodeFix(`f1(){ throw new Error("Method not implemented."); From 5775d27e32034d2712902011f05c713f95698aba Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 14 Mar 2017 17:08:35 -0700 Subject: [PATCH 26/65] respect initial order of changes --- src/harness/unittests/textChanges.ts | 17 +++++++++++++++ src/services/textChanges.ts | 14 +++++++------ .../insertNodeAfterMultipleNodes.js | 21 +++++++++++++++++++ 3 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 tests/baselines/reference/textChanges/insertNodeAfterMultipleNodes.js diff --git a/src/harness/unittests/textChanges.ts b/src/harness/unittests/textChanges.ts index 61f6d738e5..8724f7aa39 100644 --- a/src/harness/unittests/textChanges.ts +++ b/src/harness/unittests/textChanges.ts @@ -650,5 +650,22 @@ import { changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a"))); }) } + { + const text = ` +class A { + x; +}`; + runSingleFileTest("insertNodeAfterMultipleNodes", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + let newNodes = []; + for (let i = 0; i < 11 /*error doesn't occur with fewer nodes*/; ++i) { + newNodes.push( + createProperty(undefined, undefined, i + "", undefined, undefined, undefined)); + } + const insertAfter = findChild("x", sourceFile); + for (const newNode of newNodes) { + changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode, { suffix: newLineCharacter }); + } + }); + } }); } \ No newline at end of file diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 174f2969a0..bb310657b6 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -407,10 +407,8 @@ namespace ts.textChanges { changesPerFile.forEachValue(path => { const changesInFile = changesPerFile.get(path); const sourceFile = changesInFile[0].sourceFile; - ChangeTracker.normalize(changesInFile); - const fileTextChanges: FileTextChanges = { fileName: sourceFile.fileName, textChanges: [] }; - for (const c of changesInFile) { + for (const c of ChangeTracker.normalize(changesInFile)) { fileTextChanges.textChanges.push({ span: this.computeSpan(c, sourceFile), newText: this.computeNewText(c, sourceFile) @@ -463,11 +461,15 @@ namespace ts.textChanges { private static normalize(changes: Change[]) { // order changes by start position - changes.sort((a, b) => a.range.pos - b.range.pos); + const normalized = changes + .map((c, i) => ({ c, i })) + .sort(({ c: a, i: i1 }, { c: b, i: i2 }) => (a.range.pos - b.range.pos) || i1 - i2) + .map(({ c }) => c); // verify that end position of the change is less than start position of the next change - for (let i = 0; i < changes.length - 2; i++) { - Debug.assert(changes[i].range.end <= changes[i + 1].range.pos); + for (let i = 0; i < normalized.length - 2; i++) { + Debug.assert(normalized[i].range.end <= normalized[i + 1].range.pos); } + return normalized; } } diff --git a/tests/baselines/reference/textChanges/insertNodeAfterMultipleNodes.js b/tests/baselines/reference/textChanges/insertNodeAfterMultipleNodes.js new file mode 100644 index 0000000000..1c3bbe4d3e --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeAfterMultipleNodes.js @@ -0,0 +1,21 @@ +===ORIGINAL=== + +class A { + x; +} +===MODIFIED=== + +class A { + x; + 0; + 1; + 2; + 3; + 4; + 5; + 6; + 7; + 8; + 9; + 10; +} \ No newline at end of file From 323aa3a56c14d76c0ecd2d450572cd120269b41d Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 14 Mar 2017 17:17:16 -0700 Subject: [PATCH 27/65] add asserts for cases when containing list cannot be determined --- src/services/textChanges.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index bb310657b6..4dcb0867d9 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -190,6 +190,7 @@ namespace ts.textChanges { public deleteNodeInList(sourceFile: SourceFile, node: Node) { const containingList = formatting.SmartIndenter.getContainingList(node, sourceFile); if (!containingList) { + Debug.fail("node is not a list element"); return this; } const index = containingList.indexOf(node); @@ -260,6 +261,7 @@ namespace ts.textChanges { public insertNodeInListAfter(sourceFile: SourceFile, after: Node, newNode: Node) { const containingList = formatting.SmartIndenter.getContainingList(after, sourceFile); if (!containingList) { + Debug.fail("node is not a list element"); return this; } const index = containingList.indexOf(after); From e185ebb8d52bac8e0a344629d7cfda8e7e39c2b3 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Tue, 14 Mar 2017 18:24:00 -0700 Subject: [PATCH 28/65] simplify rest param handling --- src/compiler/checker.ts | 6 ++--- src/services/codefixes/fixAddMissingMember.ts | 4 +-- src/services/codefixes/helpers.ts | 25 +++++++++++-------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e9cea047ea..df4bfbb845 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2340,8 +2340,8 @@ namespace ts { // The type is an object literal type. if (!type.symbol) { // Anonymous types without symbols are literals. - // mapToTypeDeclarationsArray(type) - throw new Error("unknown case."); + // TODO: handle this case correctly. + noop(); } return createTypeLiteralNodeFromType(type); @@ -2471,7 +2471,7 @@ namespace ts { case SyntaxKind.IndexSignature: throw new Error("type literal constituent not implemented."); default: - throw new Error("Unknown case."); + throw new Error("Unknown resolved member kind."); } }); return typeElements.length ? typeElements : undefined; diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 61c823908d..ace11cda80 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -52,7 +52,7 @@ namespace ts.codefix { , /*initializer*/ undefined); // TODO: make index signature. const propertyChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); - propertyChangeTracker.insertNodeAfter(sourceFile, openBrace, property, { insertTrailingNewLine: true }); + propertyChangeTracker.insertNodeAfter(sourceFile, openBrace, property, { suffix: context.newLineCharacter }); const stringTypeNode = createKeywordTypeNode(SyntaxKind.StringKeyword); const indexingParameter = createParameter( @@ -70,7 +70,7 @@ namespace ts.codefix { , /*modifiers*/ undefined); const indexSignatureChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); - indexSignatureChangeTracker.insertNodeAfter(sourceFile, openBrace, indexSignature, { insertTrailingNewLine: true }); + indexSignatureChangeTracker.insertNodeAfter(sourceFile, openBrace, indexSignature, { suffix: context.newLineCharacter }); return [{ description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_declaration_for_missing_property_0), [token.getText()]), diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index e6f4524c71..da579cccb1 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -11,7 +11,7 @@ namespace ts.codefix { const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); for (const newNode of newNodes) { - changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode, { insertTrailingNewLine: true }); + changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode, { suffix: context.newLineCharacter }); } return changeTracker.getChanges(); } @@ -137,20 +137,23 @@ namespace ts.codefix { function createMethodImplementingSignatures(signatures: Signature[], name: PropertyName, modifiers: Modifier[] | undefined): MethodDeclaration { Debug.assert(signatures && signatures.length > 0); - let maxNonRestArgs = -1; let maxArgsIndex = 0; + /** This is *a* signature with the maximal number of arguments, + * such that if there is a "maximal" signature without rest arguments, + * this is one of them. + */ + let maxArgsSignature = signatures[0]; let minArgumentCount = signatures[0].minArgumentCount; - let hasRestParameter = false; + let someSigHasRestParameter = false; for (let i = 0; i < signatures.length; i++) { const sig = signatures[i]; minArgumentCount = Math.min(sig.minArgumentCount, minArgumentCount); - hasRestParameter = hasRestParameter || sig.hasRestParameter; - const nonRestLength = sig.parameters.length - (sig.hasRestParameter ? 1 : 0); - if (nonRestLength >= maxNonRestArgs) { - maxNonRestArgs = nonRestLength; - maxArgsIndex = i; + someSigHasRestParameter = someSigHasRestParameter || sig.hasRestParameter; + if (sig.parameters.length >= maxArgsSignature.parameters.length && (!sig.hasRestParameter || maxArgsSignature.hasRestParameter)) { + maxArgsSignature = sig; } } + const maxNonRestArgs = maxArgsSignature.parameters.length - (maxArgsSignature.hasRestParameter ? 1 : 0); const maxArgsParameterSymbolNames = signatures[maxArgsIndex].getParameters().map(symbol => symbol.getName()); const parameters: ParameterDeclaration[] = []; @@ -167,15 +170,15 @@ namespace ts.codefix { parameters.push(newParameter); } - if (hasRestParameter) { - const anyType = createKeywordTypeNode(SyntaxKind.AnyKeyword); + if (someSigHasRestParameter) { + const anyArrayType = createArrayTypeNode(createKeywordTypeNode(SyntaxKind.AnyKeyword)); const restParameter = createParameter( /*decorators*/ undefined , /*modifiers*/ undefined , createToken(SyntaxKind.DotDotDotToken) , maxArgsParameterSymbolNames[maxNonRestArgs] || "rest" , /*questionToken*/ maxNonRestArgs >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined - , anyType + , anyArrayType , /*initializer*/ undefined); parameters.push(restParameter); } From c687add579382a0910e985a36f88434312097dcc Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Tue, 14 Mar 2017 22:36:38 -0700 Subject: [PATCH 29/65] add tests to add and remove members in class --- src/harness/unittests/textChanges.ts | 42 +++++++++++++++++++ .../textChanges/deleteNodeAfterInClass1.js | 12 ++++++ .../textChanges/deleteNodeAfterInClass2.js | 12 ++++++ .../textChanges/insertNodeAfterInClass1.js | 12 ++++++ .../textChanges/insertNodeAfterInClass2.js | 12 ++++++ 5 files changed, 90 insertions(+) create mode 100644 tests/baselines/reference/textChanges/deleteNodeAfterInClass1.js create mode 100644 tests/baselines/reference/textChanges/deleteNodeAfterInClass2.js create mode 100644 tests/baselines/reference/textChanges/insertNodeAfterInClass1.js create mode 100644 tests/baselines/reference/textChanges/insertNodeAfterInClass2.js diff --git a/src/harness/unittests/textChanges.ts b/src/harness/unittests/textChanges.ts index 8724f7aa39..b2fc1ae577 100644 --- a/src/harness/unittests/textChanges.ts +++ b/src/harness/unittests/textChanges.ts @@ -667,5 +667,47 @@ class A { } }); } + { + const text = ` +class A { + x +} +`; + runSingleFileTest("insertNodeAfterInClass1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), createProperty(undefined, undefined, "a", undefined, createKeywordTypeNode(SyntaxKind.BooleanKeyword), undefined), { suffix: newLineCharacter }); + }); + } + { + const text = ` +class A { + x; +} +`; + runSingleFileTest("insertNodeAfterInClass2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), createProperty(undefined, undefined, "a", undefined, createKeywordTypeNode(SyntaxKind.BooleanKeyword), undefined), { suffix: newLineCharacter }); + }); + } + { + const text = ` +class A { + x; + y = 1; +} +`; + runSingleFileTest("deleteNodeAfterInClass1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNode(sourceFile, findChild("x", sourceFile)); + }); + } + { + const text = ` +class A { + x + y = 1; +} +`; + runSingleFileTest("deleteNodeAfterInClass2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => { + changeTracker.deleteNode(sourceFile, findChild("x", sourceFile)); + }); + } }); } \ No newline at end of file diff --git a/tests/baselines/reference/textChanges/deleteNodeAfterInClass1.js b/tests/baselines/reference/textChanges/deleteNodeAfterInClass1.js new file mode 100644 index 0000000000..d8d54153ea --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeAfterInClass1.js @@ -0,0 +1,12 @@ +===ORIGINAL=== + +class A { + x; + y = 1; +} + +===MODIFIED=== + +class A { + y = 1; +} diff --git a/tests/baselines/reference/textChanges/deleteNodeAfterInClass2.js b/tests/baselines/reference/textChanges/deleteNodeAfterInClass2.js new file mode 100644 index 0000000000..8d18d82021 --- /dev/null +++ b/tests/baselines/reference/textChanges/deleteNodeAfterInClass2.js @@ -0,0 +1,12 @@ +===ORIGINAL=== + +class A { + x + y = 1; +} + +===MODIFIED=== + +class A { + y = 1; +} diff --git a/tests/baselines/reference/textChanges/insertNodeAfterInClass1.js b/tests/baselines/reference/textChanges/insertNodeAfterInClass1.js new file mode 100644 index 0000000000..87206e588f --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeAfterInClass1.js @@ -0,0 +1,12 @@ +===ORIGINAL=== + +class A { + x +} + +===MODIFIED=== + +class A { + x + a: boolean; +} diff --git a/tests/baselines/reference/textChanges/insertNodeAfterInClass2.js b/tests/baselines/reference/textChanges/insertNodeAfterInClass2.js new file mode 100644 index 0000000000..1d583d8ac9 --- /dev/null +++ b/tests/baselines/reference/textChanges/insertNodeAfterInClass2.js @@ -0,0 +1,12 @@ +===ORIGINAL=== + +class A { + x; +} + +===MODIFIED=== + +class A { + x; + a: boolean; +} From 758e1ff8b7ee7b87f0d0e55b2e2a446d70eec6d8 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Wed, 15 Mar 2017 09:06:59 -0700 Subject: [PATCH 30/65] temp --- src/compiler/checker.ts | 11 ++--------- src/services/codefixes/helpers.ts | 2 +- .../codeFixUndeclaredIndexSignatureNumericLiteral.ts | 2 +- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index df4bfbb845..bc718e4c1d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2293,25 +2293,18 @@ namespace ts { } if (objectFlags & ObjectFlags.ClassOrInterface) { Debug.assert(!!(type.flags & TypeFlags.Object)); + // TODO: Detect whether class is named and fail if not. const name = getNameOfSymbol(type.symbol); // TODO: handle type arguments. - // TODO: handle anonymous classes. return createTypeReferenceNode(name, /*typeParameters*/undefined); } if (type.flags & TypeFlags.TypeParameter) { - const constraint = createTypeNode(getConstraintFromTypeParameter(type)) as TypeNode; - const defaultParameter = createTypeNode(getDefaultFromTypeParameter(type)) as TypeNode; - if(constraint || defaultParameter) { - // Type parameters in type position can't have constraints or defaults. - encounteredError = true; - return undefined; - } // TODO: get qualified name when necessary instead of string. const name = symbolToString(type.symbol); + // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. return createTypeReferenceNode(name, /*typeArguments*/ undefined); } - // accessible type aliasSymbol // TODO: move back up later on? if (checkAlias && type.aliasSymbol) { const name = getNameOfSymbol(type.aliasSymbol); diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index da579cccb1..37ea8bab1a 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -225,7 +225,7 @@ namespace ts.codefix { function createParameterDeclarationFromSymbol(parameterSymbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker) { const parameterDeclaration = parameterSymbol.getDeclarations()[0] as ParameterDeclaration; - const parameterType = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(parameterSymbol, enclosingDeclaration)); + const parameterType = checker.getTypeOfSymbolAtLocation(parameterSymbol, enclosingDeclaration); const parameterTypeNode = checker.createTypeNode(parameterType); // TODO: deep cloning of decorators/any node. const parameterNode = createParameter( diff --git a/tests/cases/fourslash/codeFixUndeclaredIndexSignatureNumericLiteral.ts b/tests/cases/fourslash/codeFixUndeclaredIndexSignatureNumericLiteral.ts index 2e49a8184e..0a2b0ee799 100644 --- a/tests/cases/fourslash/codeFixUndeclaredIndexSignatureNumericLiteral.ts +++ b/tests/cases/fourslash/codeFixUndeclaredIndexSignatureNumericLiteral.ts @@ -8,7 +8,7 @@ verify.rangeAfterCodeFix(` class A { - [name: string]: number; + [x: string]: number; constructor() { this.x = 10; From 0a2d7a7f94406d6c1a5c8d187171ac60cb7b56ca Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Wed, 15 Mar 2017 18:09:55 -0700 Subject: [PATCH 31/65] temp * added signature factory/visitor entry --- src/compiler/checker.ts | 34 ++++++++--- src/compiler/factory.ts | 34 +++++++++++ src/compiler/types.ts | 1 + src/compiler/utilities.ts | 4 ++ src/compiler/visitor.ts | 20 +++++-- src/harness/fourslash.ts | 2 +- .../codeFixClassImplementInterfaceComments.ts | 14 +++++ ...aceComputedPropertyNameWellKnownSymbols.ts | 1 - ...ClassImplementInterfaceEmptyTypeLiteral.ts | 13 +++++ ...ixClassImplementInterfaceMemberOrdering.ts | 58 +++++++++++++++++++ 10 files changed, 166 insertions(+), 15 deletions(-) create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfaceComments.ts create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfaceEmptyTypeLiteral.ts create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfaceMemberOrdering.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bc718e4c1d..625f4d75eb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2322,10 +2322,18 @@ namespace ts { if (objectFlags & ObjectFlags.Mapped) { Debug.assert(!!(type.flags & TypeFlags.Object)); - // const typeParameter = getTypeParameterFromMappedType(type); + + // TODO: does typeParameter have the same constraint or do we need to overwrite it somehow? + const typeParameter = getTypeParameterFromMappedType(type); // const constraintType = getConstraintTypeFromMappedType(type); - // const templateType = getTemplateTypeFromMappedType(type); - throw new Error("Mapped types not implemented"); + const typeParameterNode = createTypeParameterDeclarationFromType(typeParameter); + + const templateTypeNode = createTypeNode(getTemplateTypeFromMappedType(type)); + const readonlyToken = (type).declaration && (type).declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; + const questionToken = (type).declaration && (type).declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; + + // TODO: test. + return createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); } if (objectFlags & ObjectFlags.Anonymous) { @@ -2334,6 +2342,7 @@ namespace ts { if (!type.symbol) { // Anonymous types without symbols are literals. // TODO: handle this case correctly. + // TODO: test. noop(); } @@ -2343,9 +2352,11 @@ namespace ts { // TODO: string or number literal here or above? if (type.flags & TypeFlags.Index) { + // TODO: implement and test. throw new Error("index not implemented"); } if (type.flags & TypeFlags.IndexedAccess) { + // TODO: implement and test. throw new Error("indexed access not implemented"); } @@ -2414,7 +2425,6 @@ namespace ts { } function createTypeLiteralNodeFromType(type: ObjectType) { - // TODO: do we need to do something for mapped types here??? const resolvedType = resolveStructuredTypeMembers(type); const newMembers = createTypeNodesFromResolvedType(resolvedType); return createTypeLiteralNode(newMembers); @@ -2446,21 +2456,29 @@ namespace ts { } const kind = oldDeclaration.kind; - const memberName = symbolToString(memberSymbol); + const memberName = getSynthesizedDeepClone(oldDeclaration.name); + const memberType = getTypeOfSymbol(memberSymbol); switch (kind) { case SyntaxKind.PropertySignature: const optional = !!oldDeclaration.questionToken; - const typeOfOldMember = getTypeOfSymbol(memberSymbol); typeElements.push(createPropertySignature( - createIdentifier(memberName) + memberName , optional ? createToken(SyntaxKind.QuestionToken) : undefined - , createTypeNode(typeOfOldMember) + , createTypeNode(memberType) , /*initializer*/undefined)); break; case SyntaxKind.MethodSignature: case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: + const signatureType = getSignaturesOfSymbol(memberSymbol); + signatureType + createSignatureDeclaration + throw new Error("signature problems."); + // name ?: PropertyName; + // typeParameters ?: NodeArray; + // parameters: NodeArray; + // type ?: TypeNode; case SyntaxKind.IndexSignature: throw new Error("type literal constituent not implemented."); default: diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index bd46e20878..c9fef5b026 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -255,6 +255,22 @@ namespace ts { : node; } + export function createSignatureDeclaration(kind: SyntaxKind, name: string | PropertyName | undefined, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T { + const signatureDeclaration = createSynthesizedNode(kind) as T; + signatureDeclaration.name = asName(name); + signatureDeclaration.typeParameters = asNodeArray(typeParameters); + signatureDeclaration.type = type; + return signatureDeclaration; + } + + export function updateSignatureDeclaration(node: T, name: string | PropertyName | undefined, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T { + return node.name !== name + || node.typeParameters !== typeParameters + || node.type !== type + ? updateNode(createSignatureDeclaration(node.kind, name, typeParameters, parameters, type), node) + : node; + } + export function createTypeReferenceNode(typeName: string | EntityName, typeArguments: NodeArray | undefined) { const typeReference = createSynthesizedNode(SyntaxKind.TypeReference) as TypeReferenceNode; @@ -321,6 +337,24 @@ namespace ts { : node; } + export function createMappedTypeNode(readonlyToken: ReadonlyToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | undefined, type: TypeNode | undefined): MappedTypeNode { + const mappedTypeNode = createSynthesizedNode(SyntaxKind.MappedType) as MappedTypeNode; + mappedTypeNode.readonlyToken = readonlyToken; + mappedTypeNode.typeParameter = typeParameter; + mappedTypeNode.questionToken = questionToken; + mappedTypeNode.type = type; + return mappedTypeNode; + } + + export function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | undefined, type: TypeNode | undefined): MappedTypeNode { + return node.readonlyToken !== readonlyToken + || node.typeParameter !== typeParameter + || node.questionToken !== questionToken + || node.type !== type + ? updateNode(createMappedTypeNode(readonlyToken, typeParameter, questionToken, type), node) + : node; + } + // Type Declarations export function createTypeParameterDeclaration(name: string | Identifier, constraint: TypeNode | undefined, defaultParameter: TypeNode | undefined) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 28c293ae53..7f6c56aa1f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -856,6 +856,7 @@ namespace ts { | SyntaxKind.BooleanKeyword | SyntaxKind.StringKeyword | SyntaxKind.SymbolKeyword + | SyntaxKind.ThisKeyword | SyntaxKind.VoidKeyword | SyntaxKind.UndefinedKeyword | SyntaxKind.NullKeyword diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index b4d65958f3..47642291c2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3717,10 +3717,14 @@ namespace ts { return (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode) || kind === SyntaxKind.AnyKeyword || kind === SyntaxKind.NumberKeyword + || kind === SyntaxKind.ObjectKeyword || kind === SyntaxKind.BooleanKeyword || kind === SyntaxKind.StringKeyword || kind === SyntaxKind.SymbolKeyword + || kind === SyntaxKind.ThisKeyword || kind === SyntaxKind.VoidKeyword + || kind === SyntaxKind.UndefinedKeyword + || kind === SyntaxKind.NullKeyword || kind === SyntaxKind.NeverKeyword || kind === SyntaxKind.ExpressionWithTypeArguments; } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index ba348a558b..206e269f36 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -258,13 +258,23 @@ namespace ts { return updateComputedPropertyName(node, visitNode((node).expression, visitor, isExpression)); - // Signature elements + // Signatures and Signature Elements + case SyntaxKind.FunctionType: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.MethodSignature: + return updateSignatureDeclaration(node + , visitNode((node).name, visitor, isPropertyName) + , nodesVisitor((node).typeParameters, visitor, isTypeParameter) + , nodesVisitor((node).parameters, visitor, isParameter) + , visitNode((node).type, visitor, isTypeNode)); + case SyntaxKind.IndexSignature: return updateIndexSignatureDeclaration(node - , nodesVisitor((node).parameters, visitor) - , visitNode((node).type, visitor) - , nodesVisitor((node).decorators, visitor, isDecorator) - , nodesVisitor((node).modifiers, visitor, isModifier)); + , nodesVisitor((node).parameters, visitor) + , visitNode((node).type, visitor, isTypeNode) + , nodesVisitor((node).decorators, visitor, isDecorator) + , nodesVisitor((node).modifiers, visitor, isModifier)); case SyntaxKind.Parameter: return updateParameter(node, diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index d7638ffc03..08370480aa 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1673,7 +1673,7 @@ namespace FourSlash { // We get back a set of edits, but langSvc.editScript only accepts one at a time. Use this to keep track // of the incremental offset from each edit to the next. Assumption is that these edit ranges don't overlap let runningOffset = 0; - edits = edits.sort((a, b) => a.span.start - b.span.start); + edits = ts.stableSort(edits, (a, b) => a.span.start - b.span.start); // Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters const oldContent = this.getFileContent(fileName); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceComments.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceComments.ts new file mode 100644 index 0000000000..56d56a5a50 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceComments.ts @@ -0,0 +1,14 @@ +/// + +// @lib: es2017 + +//// /** interface prefix */ +//// interface /**interface name prefix */ I /**open-brace prefix*/{ +//// /** property prefix*/ x /**colon prefix*/: /**number prefix*/ number; +//// +//// /**close-brace prefix*/ } +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` + x: number; +`); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyNameWellKnownSymbols.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyNameWellKnownSymbols.ts index 61b4267e7f..31fc167bb9 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyNameWellKnownSymbols.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceComputedPropertyNameWellKnownSymbols.ts @@ -17,7 +17,6 @@ //// [Symbol.toStringTag]: string; //// [Symbol.unscopables]: any; //// } -//// //// class C implements I {[| |]} verify.rangeAfterCodeFix(` diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceEmptyTypeLiteral.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceEmptyTypeLiteral.ts new file mode 100644 index 0000000000..9805901709 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceEmptyTypeLiteral.ts @@ -0,0 +1,13 @@ +/// + +//// interface I { +//// x: {}; +//// } +//// +//// class C implements I {[| +//// |]constructor() { } +//// } + +verify.rangeAfterCodeFix(` +x: {}; +`); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMemberOrdering.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMemberOrdering.ts new file mode 100644 index 0000000000..dbfd623be1 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMemberOrdering.ts @@ -0,0 +1,58 @@ +/// + +// @lib: es2017 + +//// /** asdf */ +//// interface I { +//// 1; +//// 2; +//// 3; +//// 4; +//// 5; +//// 6; +//// 7; +//// 8; +//// 9; +//// 10; +//// 11; +//// 12; +//// 13; +//// 14; +//// 15; +//// 16; +//// 17; +//// 18; +//// 19; +//// 20; +//// 21; +//// 22; +//// /** a nice safe prime */ +//// 23; +//// } +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` + 1: any; + 2: any; + 3: any; + 4: any; + 5: any; + 6: any; + 7: any; + 8: any; + 9: any; + 10: any; + 11: any; + 12: any; + 13: any; + 14: any; + 15: any; + 16: any; + 17: any; + 18: any; + 19: any; + 20: any; + 21: any; + 22: any; + 23: any; +`); From ec997f329914663b16948eaa06fe1a3ba99f910c Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 16 Mar 2017 09:52:19 -0700 Subject: [PATCH 32/65] more temp --- src/compiler/checker.ts | 233 +++++++++++++++++- src/compiler/factory.ts | 72 ++++-- src/compiler/visitor.ts | 10 +- ...FixClassImplementInterfaceCallSignature.ts | 10 + ...assImplementInterfaceConstructSignature.ts | 10 + ...ImplementInterfacePropertyCallSignature.ts | 13 + ...ntInterfacePropertyConstructSignature.1.ts | 12 + ...plementInterfacePropertyMethodSignature.ts | 11 + 8 files changed, 340 insertions(+), 31 deletions(-) create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfaceCallSignature.ts create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfaceConstructSignature.ts create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfacePropertyCallSignature.ts create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfacePropertyConstructSignature.1.ts create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfacePropertyMethodSignature.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 625f4d75eb..d650865ef7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2208,6 +2208,8 @@ namespace ts { let encounteredError = false; let inObjectTypeLiteral = false; let checkAlias = true; + let enclosingDeclaration: Node = undefined; // TODO: add parameter. + let symbolStack: Symbol[] = undefined; let result = createTypeNodeWorker(type); if (result) { @@ -2224,7 +2226,6 @@ namespace ts { const typeString = typeToString(type); typeString; // TODO: remove. - if (type.flags & TypeFlags.Any) { // TODO: add other case where type ends up being `any`. return createKeywordTypeNode(SyntaxKind.AnyKeyword); @@ -2239,7 +2240,7 @@ namespace ts { return createKeywordTypeNode(SyntaxKind.BooleanKeyword); } if (type.flags & TypeFlags.Enum) { - throw new Error("enum not implemented"); + throw new Error ("enums not implemented") } if (type.flags & (TypeFlags.StringLiteral)) { return createLiteralTypeNode((createLiteral((type).text))); @@ -2316,6 +2317,7 @@ namespace ts { if (type.flags & TypeFlags.Union) { return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, mapToTypeNodeArray(formatUnionTypes((type).types))); } + if (type.flags & TypeFlags.Intersection) { return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, mapToTypeNodeArray((type as UnionType).types)); } @@ -2336,7 +2338,7 @@ namespace ts { return createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); } - if (objectFlags & ObjectFlags.Anonymous) { + if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { Debug.assert(!!(type.flags & TypeFlags.Object)); // The type is an object literal type. if (!type.symbol) { @@ -2346,18 +2348,20 @@ namespace ts { noop(); } - return createTypeLiteralNodeFromType(type); + return createAnonymousTypeNode(type); } - // TODO: string or number literal here or above? - if (type.flags & TypeFlags.Index) { - // TODO: implement and test. - throw new Error("index not implemented"); + // TODO: test. + const indexType = getIndexType(getApparentType((type).type)); + const indexTypeNode = createTypeNodeWorker(indexType); + return createTypeOperatorNode(indexTypeNode); } if (type.flags & TypeFlags.IndexedAccess) { - // TODO: implement and test. - throw new Error("indexed access not implemented"); + // TODO: test. + const objectTypeNode = createTypeNodeWorker((type).objectType); + const indexTypeNode = createTypeNodeWorker((type).indexType); + return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); } Debug.fail("Should be unreachable."); @@ -2367,6 +2371,214 @@ namespace ts { return asNodeArray(types && types.map(createTypeNodeWorker) as TypeNode[]); } + /******** START COPY *********/ + + // function buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]) { + // const globalFlagsToPass = globalFlags & TypeFormatFlags.WriteOwnNameForAnyLike; + // let inObjectTypeLiteral = false; + // return writeType(type, globalFlags); + + // function writeType(type: Type, flags: TypeFormatFlags) { + // // const nextFlags = flags & ~TypeFormatFlags.InTypeAlias; + // // // Write undefined/null type as any + // // if (type.flags & TypeFlags.Intrinsic) { + // // // Special handling for unknown / resolving types, they should show up as any and not unknown or __resolving + // // writer.writeKeyword(!(globalFlags & TypeFormatFlags.WriteOwnNameForAnyLike) && isTypeAny(type) + // // ? "any" + // // : (type).intrinsicName); + // // } + // // else if (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType) { + // // if (inObjectTypeLiteral) { + // // writer.reportInaccessibleThisError(); + // // } + // // writer.writeKeyword("this"); + // // } + // else if (getObjectFlags(type) & ObjectFlags.Reference) { + // writeTypeReference(type, nextFlags); + // } + // else if (type.flags & TypeFlags.EnumLiteral) { + // buildSymbolDisplay(getParentOfSymbol(type.symbol), writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, nextFlags); + // writePunctuation(writer, SyntaxKind.DotToken); + // appendSymbolNameOnly(type.symbol, writer); + // } + // else if (getObjectFlags(type) & ObjectFlags.ClassOrInterface || type.flags & (TypeFlags.Enum | TypeFlags.TypeParameter)) { + // // The specified symbol flags need to be reinterpreted as type flags + // buildSymbolDisplay(type.symbol, writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, nextFlags); + // } + // else if (!(flags & TypeFormatFlags.InTypeAlias) && type.aliasSymbol && + // isSymbolAccessible(type.aliasSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false).accessibility === SymbolAccessibility.Accessible) { + // const typeArguments = type.aliasTypeArguments; + // writeSymbolTypeReference(type.aliasSymbol, typeArguments, 0, length(typeArguments), nextFlags); + // } + // else if (type.flags & TypeFlags.UnionOrIntersection) { + // writeUnionOrIntersectionType(type, nextFlags); + // } + // else if (getObjectFlags(type) & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { + // writeAnonymousType(type, nextFlags); + // } + // else if (type.flags & TypeFlags.StringOrNumberLiteral) { + // writer.writeStringLiteral(literalTypeToString(type)); + // } + // } + + /******** END COPY *********/ + + function createAnonymousTypeNode(type: ObjectType): TypeNode { + const symbol = type.symbol; + if (symbol) { + // Always use 'typeof T' for type of class, enum, and module objects + if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) || + symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || + shouldWriteTypeOfFunctionSymbol()) { + // TODO: test. + // TODO: get entity name from symbol. + return createTypeQueryNodeFromType(type); + } + else if (contains(symbolStack, symbol)) { + // If type is an anonymous type literal in a type alias declaration, use type alias name + const typeAlias = getTypeAliasForTypeLiteral(type); + if (typeAlias) { + // The specified symbol flags need to be reinterpreted as type flags + const entityName = getEntityNameFromSymbol(typeAlias, enclosingDeclaration); + return createTypeReferenceNode(entityName, /*typeArguments*/ undefined); + } + else { + return createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + } + else { + // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead + // of types allows us to catch circular references to instantiations of the same anonymous type + if (!symbolStack) { + symbolStack = []; + } + symbolStack.push(symbol); + let result = createTypeLiteralNodeFromType(type); + symbolStack.pop(); + return result; + } + } + else { + // Anonymous types with no symbol are never circular + return createTypeLiteralNodeFromType(type); + } + + function shouldWriteTypeOfFunctionSymbol() { + const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method && // typeof static method + forEach(symbol.declarations, declaration => getModifierFlags(declaration) & ModifierFlags.Static)); + const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && + (symbol.parent || // is exported function symbol + forEach(symbol.declarations, declaration => + declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); + if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { + // typeof is allowed only for static/non local functions + return contains(symbolStack, symbol); // it is type of the symbol uses itself recursively + } + } + } + + function writeLiteralType(type: ObjectType, flags: TypeFormatFlags) { + if (type.objectFlags & ObjectFlags.Mapped) { + if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { + writeMappedType(type); + return; + } + } + + const resolved = resolveStructuredTypeMembers(type); + if (!resolved.properties.length && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { + if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { + writePunctuation(writer, SyntaxKind.OpenBraceToken); + writePunctuation(writer, SyntaxKind.CloseBraceToken); + return; + } + + if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { + const parenthesizeSignature = shouldAddParenthesisAroundFunctionType(resolved.callSignatures[0], flags); + if (parenthesizeSignature) { + writePunctuation(writer, SyntaxKind.OpenParenToken); + } + buildSignatureDisplay(resolved.callSignatures[0], writer, enclosingDeclaration, globalFlagsToPass | TypeFormatFlags.WriteArrowStyleSignature, /*kind*/ undefined, symbolStack); + if (parenthesizeSignature) { + writePunctuation(writer, SyntaxKind.CloseParenToken); + } + return; + } + if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { + if (flags & TypeFormatFlags.InElementType) { + writePunctuation(writer, SyntaxKind.OpenParenToken); + } + writeKeyword(writer, SyntaxKind.NewKeyword); + writeSpace(writer); + buildSignatureDisplay(resolved.constructSignatures[0], writer, enclosingDeclaration, globalFlagsToPass | TypeFormatFlags.WriteArrowStyleSignature, /*kind*/ undefined, symbolStack); + if (flags & TypeFormatFlags.InElementType) { + writePunctuation(writer, SyntaxKind.CloseParenToken); + } + return; + } + } + + const saveInObjectTypeLiteral = inObjectTypeLiteral; + inObjectTypeLiteral = true; + writePunctuation(writer, SyntaxKind.OpenBraceToken); + writer.writeLine(); + writer.increaseIndent(); + writeObjectLiteralType(resolved); + writer.decreaseIndent(); + writePunctuation(writer, SyntaxKind.CloseBraceToken); + inObjectTypeLiteral = saveInObjectTypeLiteral; + } + + function writeObjectLiteralType(resolved: ResolvedType) { + for (const signature of resolved.callSignatures) { + buildSignatureDisplay(signature, writer, enclosingDeclaration, globalFlagsToPass, /*kind*/ undefined, symbolStack); + writePunctuation(writer, SyntaxKind.SemicolonToken); + writer.writeLine(); + } + for (const signature of resolved.constructSignatures) { + buildSignatureDisplay(signature, writer, enclosingDeclaration, globalFlagsToPass, SignatureKind.Construct, symbolStack); + writePunctuation(writer, SyntaxKind.SemicolonToken); + writer.writeLine(); + } + buildIndexSignatureDisplay(resolved.stringIndexInfo, writer, IndexKind.String, enclosingDeclaration, globalFlags, symbolStack); + buildIndexSignatureDisplay(resolved.numberIndexInfo, writer, IndexKind.Number, enclosingDeclaration, globalFlags, symbolStack); + for (const p of resolved.properties) { + const t = getTypeOfSymbol(p); + if (p.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(t).length) { + const signatures = getSignaturesOfType(t, SignatureKind.Call); + for (const signature of signatures) { + writePropertyWithModifiers(p); + buildSignatureDisplay(signature, writer, enclosingDeclaration, globalFlagsToPass, /*kind*/ undefined, symbolStack); + writePunctuation(writer, SyntaxKind.SemicolonToken); + writer.writeLine(); + } + } + else { + writePropertyWithModifiers(p); + writePunctuation(writer, SyntaxKind.ColonToken); + writeSpace(writer); + writeType(t, TypeFormatFlags.None); + writePunctuation(writer, SyntaxKind.SemicolonToken); + writer.writeLine(); + } + } + } + + function createTypeQueryNodeFromType(type: Type) { + const symbol = type.symbol; + if (symbol) { + // TODO: get entity name instead. + const entityName = createIdentifier(symbolToString(symbol)); + return createTypeQueryNode(entityName); + } + } + + function getEntityNameFromSymbol(symbol: Symbol, enclosingDeclaration: Node): EntityName { + symbol; enclosingDeclaration; + // TODO: actually implement this + return createIdentifier(symbolToString(symbol, enclosingDeclaration)); + } + function createTypeReferenceNodeFromType(type: TypeReference) { const typeArguments: Type[] = type.typeArguments || emptyArray; if (type.target === globalArrayType) { @@ -2778,6 +2990,7 @@ namespace ts { } } + function writeTypeList(types: Type[], delimiter: SyntaxKind) { for (let i = 0; i < types.length; i++) { if (i > 0) { diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index c9fef5b026..ebb4bbe5a9 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -255,25 +255,8 @@ namespace ts { : node; } - export function createSignatureDeclaration(kind: SyntaxKind, name: string | PropertyName | undefined, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T { - const signatureDeclaration = createSynthesizedNode(kind) as T; - signatureDeclaration.name = asName(name); - signatureDeclaration.typeParameters = asNodeArray(typeParameters); - signatureDeclaration.type = type; - return signatureDeclaration; - } - - export function updateSignatureDeclaration(node: T, name: string | PropertyName | undefined, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T { - return node.name !== name - || node.typeParameters !== typeParameters - || node.type !== type - ? updateNode(createSignatureDeclaration(node.kind, name, typeParameters, parameters, type), node) - : node; - } - export function createTypeReferenceNode(typeName: string | EntityName, typeArguments: NodeArray | undefined) { const typeReference = createSynthesizedNode(SyntaxKind.TypeReference) as TypeReferenceNode; - typeReference.typeName = isQualifiedName(typeName) ? typeName : asName(typeName); typeReference.typeArguments = typeArguments; return typeReference; @@ -286,6 +269,16 @@ namespace ts { : node; } + export function createTypeQueryNode(exprName: EntityName) { + const typeQueryNode = createSynthesizedNode(SyntaxKind.TypeQuery) as TypeQueryNode; + typeQueryNode.exprName = exprName; + return typeQueryNode; + } + + export function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName) { + return node.exprName !== exprName ? updateNode(createTypeQueryNode(exprName) , node) : node; + } + export function createArrayTypeNode(elementType: TypeNode): ArrayTypeNode { const arrayTypeNode = createSynthesizedNode(SyntaxKind.ArrayType) as ArrayTypeNode; arrayTypeNode.elementType = elementType; @@ -355,6 +348,33 @@ namespace ts { : node; } + export function createTypeOperatorNode(type: TypeNode) { + const typeOperatorNode = createSynthesizedNode(SyntaxKind.TypeOperator) as TypeOperatorNode; + typeOperatorNode.operator = SyntaxKind.KeyOfKeyword; + typeOperatorNode.type = type + return typeOperatorNode; + } + + export function updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode) { + return node.type !== type ? updateNode(createTypeOperatorNode(type), node) : node; + } + + export function createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode) { + const indexedAccessTypeNode = createSynthesizedNode(SyntaxKind.IndexedAccessType) as IndexedAccessTypeNode; + indexedAccessTypeNode.objectType = objectType; + indexedAccessTypeNode.indexType = indexType; + return indexedAccessTypeNode; + } + + + export function updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode) { + return node.objectType !== objectType + || node.indexType !== indexType + ? updateNode(createIndexedAccessTypeNode(objectType, indexType), node) + : node; + } + + // Type Declarations export function createTypeParameterDeclaration(name: string | Identifier, constraint: TypeNode | undefined, defaultParameter: TypeNode | undefined) { @@ -374,6 +394,24 @@ namespace ts { : node; } + export function createSignatureDeclaration(kind: SyntaxKind, name: string | PropertyName | undefined, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T { + const signatureDeclaration = createSynthesizedNode(kind) as T; + signatureDeclaration.name = asName(name); + signatureDeclaration.typeParameters = asNodeArray(typeParameters); + signatureDeclaration.parameters = asNodeArray(parameters); + signatureDeclaration.type = type; + return signatureDeclaration; + } + + export function updateSignatureDeclaration(node: T, name: string | PropertyName | undefined, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T { + return node.name !== name + || node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? updateNode(createSignatureDeclaration(node.kind, name, typeParameters, parameters, type), node) + : node; + } + // Signature elements export function createPropertySignature(name: PropertyName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): PropertySignature { diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 206e269f36..afe236859a 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -320,13 +320,13 @@ namespace ts { case SyntaxKind.ConstructorType: throw new Error("reached unsupported type in visitor."); case SyntaxKind.TypeQuery: - throw new Error("reached unsupported type in visitor."); + return updateTypeQueryNode((node), visitNode((node).exprName, visitor, isEntityName)); case SyntaxKind.TypeLiteral: return updateTypeLiteralNode((node), nodesVisitor((node).members, visitor)); case SyntaxKind.ArrayType: return updateArrayTypeNode(node, visitNode((node).elementType, visitor, isTypeNode)); case SyntaxKind.TupleType: - throw new Error("reached unsupported type in visitor."); + return updateTypleTypeNode((node), nodesVisitor((node).elementTypes, visitor, isTypeNode)); case SyntaxKind.UnionType: case SyntaxKind.IntersectionType: return updateUnionOrIntersectionTypeNode(node @@ -336,9 +336,11 @@ namespace ts { case SyntaxKind.ThisType: throw new Error("reached unsupported type in visitor."); case SyntaxKind.TypeOperator: - throw new Error("reached unsupported type in visitor."); + return updateTypeOperatorNode(node, visitNode((node).type, visitor, isTypeNode)); case SyntaxKind.IndexedAccessType: - throw new Error("reached unsupported type in visitor."); + return updateIndexedAccessTypeNode((node) + , visitNode((node).objectType, visitor, isTypeNode) + , visitNode((node).indexType, visitor, isTypeNode)); case SyntaxKind.MappedType: throw new Error("reached unsupported type in visitor."); case SyntaxKind.LiteralType: diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceCallSignature.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceCallSignature.ts new file mode 100644 index 0000000000..4b47268396 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceCallSignature.ts @@ -0,0 +1,10 @@ +/// + +//// interface I { +//// (x: number, b: string): number; +//// } +//// class C implements I {[| |]} + +verify.not.codeFixAvailable(); + + diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceConstructSignature.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceConstructSignature.ts new file mode 100644 index 0000000000..1700f7ec88 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceConstructSignature.ts @@ -0,0 +1,10 @@ +/// + +//// interface I { +//// new (x: number, b: string); +//// } +//// class C implements I {[| |]} + +verify.not.codeFixAvailable(); + + diff --git a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyCallSignature.ts b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyCallSignature.ts new file mode 100644 index 0000000000..7f0eddfb14 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyCallSignature.ts @@ -0,0 +1,13 @@ +/// + +//// interface I { +//// a1: { (b1: number, c1: string): number; }; +//// a2: (b2: number, c2: string) => number; +//// } +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` + a1: (b1: number, c1: string) => number; + a2: (b2: number, c2: string) => number; +`); + diff --git a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyConstructSignature.1.ts b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyConstructSignature.1.ts new file mode 100644 index 0000000000..2b27fab376 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyConstructSignature.1.ts @@ -0,0 +1,12 @@ +/// + +//// interface I { +//// a1: { new (b1: number, c1: string): number; }; +//// a2: new (b2: number, c2: string) => number; +//// } +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` + a1: new (b1: number, c1: string) => number; + a2: new (b2: number, c2: string) => number; +`); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyMethodSignature.ts b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyMethodSignature.ts new file mode 100644 index 0000000000..3d3505e1d6 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyMethodSignature.ts @@ -0,0 +1,11 @@ +/// + +//// interface I { +//// x: { a(b: number, c: string): number }; +//// } +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` + a: { (b: number, c: string): number; }; +`); + From 451b2d648fcc1f36fccf0c604c8f20f893f23dc9 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 16 Mar 2017 16:09:54 -0700 Subject: [PATCH 33/65] Add signatures test --- ...ImplementInterfacePropertyCallSignature.ts | 13 ------- ...ntInterfacePropertyConstructSignature.1.ts | 12 ------- ...plementInterfacePropertyMethodSignature.ts | 11 ------ ...assImplementInterfacePropertySignatures.ts | 34 +++++++++++++++++++ 4 files changed, 34 insertions(+), 36 deletions(-) delete mode 100644 tests/cases/fourslash/codeFixClassImplementInterfacePropertyCallSignature.ts delete mode 100644 tests/cases/fourslash/codeFixClassImplementInterfacePropertyConstructSignature.1.ts delete mode 100644 tests/cases/fourslash/codeFixClassImplementInterfacePropertyMethodSignature.ts create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfacePropertySignatures.ts diff --git a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyCallSignature.ts b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyCallSignature.ts deleted file mode 100644 index 7f0eddfb14..0000000000 --- a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyCallSignature.ts +++ /dev/null @@ -1,13 +0,0 @@ -/// - -//// interface I { -//// a1: { (b1: number, c1: string): number; }; -//// a2: (b2: number, c2: string) => number; -//// } -//// class C implements I {[| |]} - -verify.rangeAfterCodeFix(` - a1: (b1: number, c1: string) => number; - a2: (b2: number, c2: string) => number; -`); - diff --git a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyConstructSignature.1.ts b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyConstructSignature.1.ts deleted file mode 100644 index 2b27fab376..0000000000 --- a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyConstructSignature.1.ts +++ /dev/null @@ -1,12 +0,0 @@ -/// - -//// interface I { -//// a1: { new (b1: number, c1: string): number; }; -//// a2: new (b2: number, c2: string) => number; -//// } -//// class C implements I {[| |]} - -verify.rangeAfterCodeFix(` - a1: new (b1: number, c1: string) => number; - a2: new (b2: number, c2: string) => number; -`); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyMethodSignature.ts b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyMethodSignature.ts deleted file mode 100644 index 3d3505e1d6..0000000000 --- a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyMethodSignature.ts +++ /dev/null @@ -1,11 +0,0 @@ -/// - -//// interface I { -//// x: { a(b: number, c: string): number }; -//// } -//// class C implements I {[| |]} - -verify.rangeAfterCodeFix(` - a: { (b: number, c: string): number; }; -`); - diff --git a/tests/cases/fourslash/codeFixClassImplementInterfacePropertySignatures.ts b/tests/cases/fourslash/codeFixClassImplementInterfacePropertySignatures.ts new file mode 100644 index 0000000000..9438241005 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfacePropertySignatures.ts @@ -0,0 +1,34 @@ +/// + +//// interface I { +//// a0: {}; +//// a1: { (b1: number, c1: string): number; }; +//// a2: (b2: number, c2: string) => number; +//// a3: { (b3: number, c3: string): number, x: number }; +//// +//// a4: { new (b1: number, c1: string): number; }; +//// a5: new (b2: number, c2: string) => number; +//// a6: { new (b3: number, c3: string): number, x: number }; +//// +//// a7: { foo(b7: number, c7: string): number }; +//// +//// a8: { (b81: number, c81: string): number, new (b82: number, c82: string): number; }; +//// +//// a9: { (b9: number, c9: string): number; [d9: number]: I }; +//// a10: { (b10: number, c10: string): number; [d10: string]: I }; +//// } +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` + a0: {}; + a1: (b1: number, c1: string) => number; + a2: (b2: number, c2: string) => number; + a3: { (b3: number, c3: string): number; x: number; }; + a4: new (b1: number, c1: string) => number; + a5: new (b2: number, c2: string) => number; + a6: { new (b3: number, c3: string): number; x: number; }; + a7: { foo(b7: number, c7: string): number; }; + a8: { (b81: number, c81: string): number; new (b82: number, c82: string): number; }; + a9: { (b9: number, c9: string): number; [d9: number]: I; }; + a10: { (b10: number, c10: string): number; [d10: string]: I; }; +`); \ No newline at end of file From 0fa4f112a33a8f7fdc12b61e9e5a8418b9bf95eb Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 16 Mar 2017 16:41:26 -0700 Subject: [PATCH 34/65] signatures, parameter, literals, anyonymous types --- src/compiler/checker.ts | 283 +++++++++++------------------- src/compiler/factory.ts | 31 ++-- src/compiler/types.ts | 2 + src/compiler/visitor.ts | 56 +++--- src/services/codefixes/helpers.ts | 66 ++++--- 5 files changed, 181 insertions(+), 257 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d650865ef7..7545430717 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -109,6 +109,7 @@ namespace ts { createTypeNode, createTypeParameterDeclarationFromType, createIndexSignatureFromIndexInfo, + createParameterDeclarationFromSymbol, getSymbolsInScope: (location, meaning) => { location = getParseTreeNode(location); return location ? getSymbolsInScope(location, meaning) : []; @@ -2203,6 +2204,45 @@ namespace ts { return createTypeParameterDeclaration(name, constraint, defaultParameter); } + // TODO: enclosing declaration appears to be unused in getTypeOfSymbolAtLocation + function createParameterDeclarationFromSymbol(parameterSymbol: Symbol): ParameterDeclaration { + const parameterDeclaration = parameterSymbol.declarations[0] as ParameterDeclaration; + const parameterType = getTypeOfSymbol(parameterSymbol); + const parameterTypeNode = checker.createTypeNode(parameterType); + // TODO: clone binding names correctly. + // TODO: copy initialzer in a way that checks whether all symbols used in expression are accessible here, and qualify them appropriately. + const parameterNode = createParameter( + parameterDeclaration.decorators && parameterDeclaration.decorators.map(getSynthesizedDeepClone) + , parameterDeclaration.modifiers && parameterDeclaration.modifiers.map(getSynthesizedDeepClone) + , parameterDeclaration.dotDotDotToken && createToken(SyntaxKind.DotDotDotToken) + , getSynthesizedDeepClone(parameterDeclaration.name) + , parameterDeclaration.questionToken && createToken(SyntaxKind.QuestionToken) + , parameterTypeNode + , /*initializer*/ undefined); + return parameterNode; + } + + /* @internal */ + type SignatureParts = { + typeParameters: TypeParameterDeclaration[] | undefined; + parameters: ParameterDeclaration[]; + type: TypeNode; + } + + // TODO: expose this, remove copy from helper, possibly don't expose createParameter/TypeParameter? + function createSignatureParts(signature: Signature): SignatureParts { + return { + typeParameters: signature.typeParameters && signature.typeParameters.map(createTypeParameterDeclarationFromType), + parameters: signature.parameters.map(createParameterDeclarationFromSymbol), + type: createTypeNodeExceptAny(getReturnTypeOfSignature(signature)) + } + + function createTypeNodeExceptAny(type: Type): TypeNode | undefined { + const typeNode = createTypeNode(type); + return typeNode && typeNode.kind !== SyntaxKind.AnyKeyword ? typeNode : undefined; + } + } + function createTypeNode(type: Type): TypeNode { let undefinedArgumentIsError = true; let encounteredError = false; @@ -2322,22 +2362,6 @@ namespace ts { return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, mapToTypeNodeArray((type as UnionType).types)); } - if (objectFlags & ObjectFlags.Mapped) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - - // TODO: does typeParameter have the same constraint or do we need to overwrite it somehow? - const typeParameter = getTypeParameterFromMappedType(type); - // const constraintType = getConstraintTypeFromMappedType(type); - const typeParameterNode = createTypeParameterDeclarationFromType(typeParameter); - - const templateTypeNode = createTypeNode(getTemplateTypeFromMappedType(type)); - const readonlyToken = (type).declaration && (type).declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; - const questionToken = (type).declaration && (type).declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; - - // TODO: test. - return createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); - } - if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { Debug.assert(!!(type.flags & TypeFlags.Object)); // The type is an object literal type. @@ -2371,57 +2395,25 @@ namespace ts { return asNodeArray(types && types.map(createTypeNodeWorker) as TypeNode[]); } - /******** START COPY *********/ - - // function buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, globalFlags?: TypeFormatFlags, symbolStack?: Symbol[]) { - // const globalFlagsToPass = globalFlags & TypeFormatFlags.WriteOwnNameForAnyLike; - // let inObjectTypeLiteral = false; - // return writeType(type, globalFlags); - - // function writeType(type: Type, flags: TypeFormatFlags) { - // // const nextFlags = flags & ~TypeFormatFlags.InTypeAlias; - // // // Write undefined/null type as any - // // if (type.flags & TypeFlags.Intrinsic) { - // // // Special handling for unknown / resolving types, they should show up as any and not unknown or __resolving - // // writer.writeKeyword(!(globalFlags & TypeFormatFlags.WriteOwnNameForAnyLike) && isTypeAny(type) - // // ? "any" - // // : (type).intrinsicName); - // // } - // // else if (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType) { - // // if (inObjectTypeLiteral) { - // // writer.reportInaccessibleThisError(); - // // } - // // writer.writeKeyword("this"); - // // } - // else if (getObjectFlags(type) & ObjectFlags.Reference) { - // writeTypeReference(type, nextFlags); - // } - // else if (type.flags & TypeFlags.EnumLiteral) { - // buildSymbolDisplay(getParentOfSymbol(type.symbol), writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, nextFlags); - // writePunctuation(writer, SyntaxKind.DotToken); - // appendSymbolNameOnly(type.symbol, writer); - // } - // else if (getObjectFlags(type) & ObjectFlags.ClassOrInterface || type.flags & (TypeFlags.Enum | TypeFlags.TypeParameter)) { - // // The specified symbol flags need to be reinterpreted as type flags - // buildSymbolDisplay(type.symbol, writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, nextFlags); - // } - // else if (!(flags & TypeFormatFlags.InTypeAlias) && type.aliasSymbol && - // isSymbolAccessible(type.aliasSymbol, enclosingDeclaration, SymbolFlags.Type, /*shouldComputeAliasesToMakeVisible*/ false).accessibility === SymbolAccessibility.Accessible) { - // const typeArguments = type.aliasTypeArguments; - // writeSymbolTypeReference(type.aliasSymbol, typeArguments, 0, length(typeArguments), nextFlags); - // } - // else if (type.flags & TypeFlags.UnionOrIntersection) { - // writeUnionOrIntersectionType(type, nextFlags); - // } - // else if (getObjectFlags(type) & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { - // writeAnonymousType(type, nextFlags); - // } + // TODO: implement when this is testable. // else if (type.flags & TypeFlags.StringOrNumberLiteral) { // writer.writeStringLiteral(literalTypeToString(type)); - // } - // } - /******** END COPY *********/ + function createMappedTypeNodeFromType(type: MappedType) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + + // TODO: does typeParameter have the same constraint or do we need to overwrite it somehow? + const typeParameter = getTypeParameterFromMappedType(type); + // const constraintType = getConstraintTypeFromMappedType(type); + const typeParameterNode = createTypeParameterDeclarationFromType(typeParameter); + + const templateTypeNode = createTypeNode(getTemplateTypeFromMappedType(type)); + const readonlyToken = (type).declaration && (type).declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; + const questionToken = (type).declaration && (type).declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; + + // TODO: test. + return createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); + } function createAnonymousTypeNode(type: ObjectType): TypeNode { const symbol = type.symbol; @@ -2439,7 +2431,7 @@ namespace ts { const typeAlias = getTypeAliasForTypeLiteral(type); if (typeAlias) { // The specified symbol flags need to be reinterpreted as type flags - const entityName = getEntityNameFromSymbol(typeAlias, enclosingDeclaration); + const entityName = createNameFromSymbol(typeAlias, enclosingDeclaration); return createTypeReferenceNode(entityName, /*typeArguments*/ undefined); } else { @@ -2453,14 +2445,14 @@ namespace ts { symbolStack = []; } symbolStack.push(symbol); - let result = createTypeLiteralNodeFromType(type); + let result = createTypeNodeFromObjectType(type); symbolStack.pop(); return result; } } else { // Anonymous types with no symbol are never circular - return createTypeLiteralNodeFromType(type); + return createTypeNodeFromObjectType(type); } function shouldWriteTypeOfFunctionSymbol() { @@ -2477,103 +2469,48 @@ namespace ts { } } - function writeLiteralType(type: ObjectType, flags: TypeFormatFlags) { + function createTypeNodeFromObjectType(type: ObjectType): TypeNode { if (type.objectFlags & ObjectFlags.Mapped) { if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { - writeMappedType(type); - return; + return createMappedTypeNodeFromType(type); } } const resolved = resolveStructuredTypeMembers(type); if (!resolved.properties.length && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { - writePunctuation(writer, SyntaxKind.OpenBraceToken); - writePunctuation(writer, SyntaxKind.CloseBraceToken); - return; + return createTypeLiteralNode(/*members*/ undefined); } if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { - const parenthesizeSignature = shouldAddParenthesisAroundFunctionType(resolved.callSignatures[0], flags); - if (parenthesizeSignature) { - writePunctuation(writer, SyntaxKind.OpenParenToken); - } - buildSignatureDisplay(resolved.callSignatures[0], writer, enclosingDeclaration, globalFlagsToPass | TypeFormatFlags.WriteArrowStyleSignature, /*kind*/ undefined, symbolStack); - if (parenthesizeSignature) { - writePunctuation(writer, SyntaxKind.CloseParenToken); - } - return; + const signature = resolved.callSignatures[0]; + const signatureParts = createSignatureParts(signature); + return createSignatureDeclaration(SyntaxKind.FunctionType, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type); } if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { - if (flags & TypeFormatFlags.InElementType) { - writePunctuation(writer, SyntaxKind.OpenParenToken); - } - writeKeyword(writer, SyntaxKind.NewKeyword); - writeSpace(writer); - buildSignatureDisplay(resolved.constructSignatures[0], writer, enclosingDeclaration, globalFlagsToPass | TypeFormatFlags.WriteArrowStyleSignature, /*kind*/ undefined, symbolStack); - if (flags & TypeFormatFlags.InElementType) { - writePunctuation(writer, SyntaxKind.CloseParenToken); - } - return; + const signature = resolved.constructSignatures[0]; + const signatureParts = createSignatureParts(signature); + return createSignatureDeclaration(SyntaxKind.ConstructorType, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type); } } const saveInObjectTypeLiteral = inObjectTypeLiteral; inObjectTypeLiteral = true; - writePunctuation(writer, SyntaxKind.OpenBraceToken); - writer.writeLine(); - writer.increaseIndent(); - writeObjectLiteralType(resolved); - writer.decreaseIndent(); - writePunctuation(writer, SyntaxKind.CloseBraceToken); + const members = createTypeNodesFromResolvedType(resolved); inObjectTypeLiteral = saveInObjectTypeLiteral; - } - - function writeObjectLiteralType(resolved: ResolvedType) { - for (const signature of resolved.callSignatures) { - buildSignatureDisplay(signature, writer, enclosingDeclaration, globalFlagsToPass, /*kind*/ undefined, symbolStack); - writePunctuation(writer, SyntaxKind.SemicolonToken); - writer.writeLine(); - } - for (const signature of resolved.constructSignatures) { - buildSignatureDisplay(signature, writer, enclosingDeclaration, globalFlagsToPass, SignatureKind.Construct, symbolStack); - writePunctuation(writer, SyntaxKind.SemicolonToken); - writer.writeLine(); - } - buildIndexSignatureDisplay(resolved.stringIndexInfo, writer, IndexKind.String, enclosingDeclaration, globalFlags, symbolStack); - buildIndexSignatureDisplay(resolved.numberIndexInfo, writer, IndexKind.Number, enclosingDeclaration, globalFlags, symbolStack); - for (const p of resolved.properties) { - const t = getTypeOfSymbol(p); - if (p.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(t).length) { - const signatures = getSignaturesOfType(t, SignatureKind.Call); - for (const signature of signatures) { - writePropertyWithModifiers(p); - buildSignatureDisplay(signature, writer, enclosingDeclaration, globalFlagsToPass, /*kind*/ undefined, symbolStack); - writePunctuation(writer, SyntaxKind.SemicolonToken); - writer.writeLine(); - } - } - else { - writePropertyWithModifiers(p); - writePunctuation(writer, SyntaxKind.ColonToken); - writeSpace(writer); - writeType(t, TypeFormatFlags.None); - writePunctuation(writer, SyntaxKind.SemicolonToken); - writer.writeLine(); - } - } + return createTypeLiteralNode(members); } function createTypeQueryNodeFromType(type: Type) { const symbol = type.symbol; if (symbol) { // TODO: get entity name instead. - const entityName = createIdentifier(symbolToString(symbol)); + const entityName = createNameFromSymbol(symbol); return createTypeQueryNode(entityName); } } - function getEntityNameFromSymbol(symbol: Symbol, enclosingDeclaration: Node): EntityName { + function createNameFromSymbol(symbol: Symbol): EntityName { symbol; enclosingDeclaration; // TODO: actually implement this return createIdentifier(symbolToString(symbol, enclosingDeclaration)); @@ -2636,67 +2573,53 @@ namespace ts { } } - function createTypeLiteralNodeFromType(type: ObjectType) { - const resolvedType = resolveStructuredTypeMembers(type); - const newMembers = createTypeNodesFromResolvedType(resolvedType); - return createTypeLiteralNode(newMembers); - } - function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] { const typeElements: TypeElement[] = []; - for(const signature of resolvedType.callSignatures) { - signature; - throw new Error("call signatures not implemented"); + for (const signature of resolvedType.callSignatures) { + const signatureParts = createSignatureParts(signature); + typeElements.push(createSignatureDeclaration(SyntaxKind.CallSignature, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type)); } for (const signature of resolvedType.constructSignatures) { - signature; - throw new Error("Construct signatures not implemented"); + const signatureParts = createSignatureParts(signature); + typeElements.push(createSignatureDeclaration(SyntaxKind.ConstructSignature, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type)); } if (resolvedType.stringIndexInfo) { typeElements.push(createIndexSignatureFromIndexInfo(resolvedType.stringIndexInfo, IndexKind.String)); } if (resolvedType.numberIndexInfo) { - typeElements.push(createIndexSignatureFromIndexInfo(resolvedType.stringIndexInfo, IndexKind.Number)); + typeElements.push(createIndexSignatureFromIndexInfo(resolvedType.numberIndexInfo, IndexKind.Number)); } - const members = resolvedType.members; + const properties = resolvedType.properties; + if (!properties) { + return typeElements; + } - members.forEach(memberSymbol => { - const oldDeclaration = memberSymbol.declarations && memberSymbol.declarations[0] as TypeElement; + for (const propertySymbol of properties) { + const propertyType = getTypeOfSymbol(propertySymbol); + const oldDeclaration = propertySymbol.declarations && propertySymbol.declarations[0] as TypeElement; if (!oldDeclaration) { return; } - - const kind = oldDeclaration.kind; - const memberName = getSynthesizedDeepClone(oldDeclaration.name); - const memberType = getTypeOfSymbol(memberSymbol); - - switch (kind) { - case SyntaxKind.PropertySignature: - const optional = !!oldDeclaration.questionToken; - typeElements.push(createPropertySignature( - memberName - , optional ? createToken(SyntaxKind.QuestionToken) : undefined - , createTypeNode(memberType) - , /*initializer*/undefined)); - break; - case SyntaxKind.MethodSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - const signatureType = getSignaturesOfSymbol(memberSymbol); - signatureType - createSignatureDeclaration - throw new Error("signature problems."); - // name ?: PropertyName; - // typeParameters ?: NodeArray; - // parameters: NodeArray; - // type ?: TypeNode; - case SyntaxKind.IndexSignature: - throw new Error("type literal constituent not implemented."); - default: - throw new Error("Unknown resolved member kind."); + const propertyName = getSynthesizedDeepClone(oldDeclaration.name); + const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined;; + if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length) { + const signatures = getSignaturesOfType(propertyType, SignatureKind.Call); + for (const signature of signatures) { + const signatureParts = createSignatureParts(signature); + const methodDeclaration = createSignatureDeclaration(SyntaxKind.MethodSignature, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type, propertyName, optionalToken); + methodDeclaration.questionToken = optionalToken; + typeElements.push(methodDeclaration); + } } - }); + else { + typeElements.push(createPropertySignature( + propertyName + , optionalToken + , createTypeNode(propertyType) + , /*initializer*/undefined)); + } + } return typeElements.length ? typeElements : undefined; } } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index ebb4bbe5a9..d91076dcde 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -394,29 +394,37 @@ namespace ts { : node; } - export function createSignatureDeclaration(kind: SyntaxKind, name: string | PropertyName | undefined, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T { + // TODO: ask if we should have multiple implementations. Some T's can't have question token. + export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): T; + export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined): T; + export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, name?: string | PropertyName, questionToken?: QuestionToken): T { const signatureDeclaration = createSynthesizedNode(kind) as T; - signatureDeclaration.name = asName(name); signatureDeclaration.typeParameters = asNodeArray(typeParameters); signatureDeclaration.parameters = asNodeArray(parameters); signatureDeclaration.type = type; + signatureDeclaration.name = asName(name); + signatureDeclaration.questionToken = questionToken; return signatureDeclaration; } - export function updateSignatureDeclaration(node: T, name: string | PropertyName | undefined, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T { - return node.name !== name - || node.typeParameters !== typeParameters + // TODO: figure out right type annotation for this function. + export function updateSignatureDeclaration(node: T, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T; + export function updateSignatureDeclaration(node: T, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined, name: PropertyName, questionToken: QuestionToken | undefined): T; + export function updateSignatureDeclaration(node: T, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined, name?: PropertyName, questionToken?: QuestionToken): T { + return node.typeParameters !== typeParameters || node.parameters !== parameters || node.type !== type - ? updateNode(createSignatureDeclaration(node.kind, name, typeParameters, parameters, type), node) + || node.name !== name + || node.questionToken !== questionToken + ? updateNode(createSignatureDeclaration(node.kind, typeParameters, parameters, type, name, questionToken), node) : node; } // Signature elements - export function createPropertySignature(name: PropertyName, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): PropertySignature { + export function createPropertySignature(name: PropertyName | string, questionToken: QuestionToken | undefined, type: TypeNode | undefined, initializer: Expression | undefined): PropertySignature { const propertySignature = createSynthesizedNode(SyntaxKind.PropertySignature) as PropertySignature; - propertySignature.name = name; + propertySignature.name = asName(name); propertySignature.questionToken = questionToken; propertySignature.type = type; propertySignature.initializer = initializer; @@ -511,12 +519,13 @@ namespace ts { : node; } - export function createMethod(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: string | PropertyName, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { + export function createMethod(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { const node = createSynthesizedNode(SyntaxKind.MethodDeclaration); node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); node.asteriskToken = asteriskToken; node.name = asName(name); + node.questionToken = questionToken; node.typeParameters = asNodeArray(typeParameters); node.parameters = asNodeArray(parameters); node.type = type; @@ -524,7 +533,7 @@ namespace ts { return node; } - export function updateMethod(node: MethodDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: PropertyName, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { + export function updateMethod(node: MethodDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: PropertyName, questionToken: QuestionToken | undefined, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { return node.decorators !== decorators || node.modifiers !== modifiers || node.asteriskToken !== asteriskToken @@ -533,7 +542,7 @@ namespace ts { || node.parameters !== parameters || node.type !== type || node.body !== body - ? updateNode(createMethod(decorators, modifiers, asteriskToken, name, typeParameters, parameters, type, body), node) + ? updateNode(createMethod(decorators, modifiers, asteriskToken, name, questionToken, typeParameters, parameters, type, body), node) : node; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7f6c56aa1f..acca1b7398 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2474,6 +2474,8 @@ namespace ts { createTypeParameterDeclarationFromType(type: Type): TypeParameterDeclaration; /** Note that the resulting type node cannot be checked. */ createIndexSignatureFromIndexInfo(indexInfo: IndexInfo, kind: IndexKind): IndexSignatureDeclaration + /** Note that the resulting type node cannot be checked. */ + createParameterDeclarationFromSymbol(parameterSymbol: Symbol): ParameterDeclaration; getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; getSymbolAtLocation(node: Node): Symbol; diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index afe236859a..5d63126ad0 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -245,6 +245,8 @@ namespace ts { case SyntaxKind.EmptyStatement: case SyntaxKind.OmittedExpression: case SyntaxKind.DebuggerStatement: + case SyntaxKind.EndOfDeclarationMarker: + case SyntaxKind.MissingDeclaration: // No need to visit nodes with no children. return node; @@ -260,18 +262,25 @@ namespace ts { // Signatures and Signature Elements case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: - case SyntaxKind.MethodSignature: return updateSignatureDeclaration(node - , visitNode((node).name, visitor, isPropertyName) , nodesVisitor((node).typeParameters, visitor, isTypeParameter) - , nodesVisitor((node).parameters, visitor, isParameter) + , visitParameterList((node).parameters, visitor, context, nodesVisitor) , visitNode((node).type, visitor, isTypeNode)); + case SyntaxKind.MethodSignature: + return updateSignatureDeclaration(node + , nodesVisitor((node).typeParameters, visitor, isTypeParameter) + , visitParameterList((node).parameters, visitor, context, nodesVisitor) + , visitNode((node).type, visitor, isTypeNode) + , visitNode((node).name, visitor, isPropertyName) + , visitNode((node).questionToken, visitor, isToken)); + case SyntaxKind.IndexSignature: return updateIndexSignatureDeclaration(node - , nodesVisitor((node).parameters, visitor) + , visitParameterList((node).parameters, visitor, context, nodesVisitor) , visitNode((node).type, visitor, isTypeNode) , nodesVisitor((node).decorators, visitor, isDecorator) , nodesVisitor((node).modifiers, visitor, isModifier)); @@ -290,47 +299,32 @@ namespace ts { return updateDecorator(node, visitNode((node).expression, visitor, isExpression)); - // Keyword Types and This - - case SyntaxKind.AnyKeyword: - case SyntaxKind.NumberKeyword: - case SyntaxKind.ObjectKeyword: - case SyntaxKind.BooleanKeyword: - case SyntaxKind.StringKeyword: - case SyntaxKind.SymbolKeyword: - case SyntaxKind.VoidKeyword: - case SyntaxKind.UndefinedKeyword: - case SyntaxKind.NullKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.NeverKeyword: - case SyntaxKind.ThisKeyword: - throw new Error("should be caught above"); - // Types case SyntaxKind.TypePredicate: throw new Error("reached unsupported type in visitor."); case SyntaxKind.TypeReference: return updateTypeReferenceNode(node - , visitNode((node).typeName, visitor, isEntityName) - , nodesVisitor((node).typeArguments, visitor, isTypeNode)); + , visitNode((node).typeName, visitor, isEntityName) + , nodesVisitor((node).typeArguments, visitor, isTypeNode)); - case SyntaxKind.FunctionType: - throw new Error("reached unsupported type in visitor."); - case SyntaxKind.ConstructorType: - throw new Error("reached unsupported type in visitor."); case SyntaxKind.TypeQuery: return updateTypeQueryNode((node), visitNode((node).exprName, visitor, isEntityName)); + case SyntaxKind.TypeLiteral: return updateTypeLiteralNode((node), nodesVisitor((node).members, visitor)); + case SyntaxKind.ArrayType: return updateArrayTypeNode(node, visitNode((node).elementType, visitor, isTypeNode)); + case SyntaxKind.TupleType: return updateTypleTypeNode((node), nodesVisitor((node).elementTypes, visitor, isTypeNode)); + case SyntaxKind.UnionType: case SyntaxKind.IntersectionType: return updateUnionOrIntersectionTypeNode(node - , nodesVisitor((node).types, visitor, isTypeNode)); + , nodesVisitor((node).types, visitor, isTypeNode)); + case SyntaxKind.ParenthesizedType: throw new Error("reached unsupported type in visitor."); case SyntaxKind.ThisType: @@ -366,7 +360,7 @@ namespace ts { case SyntaxKind.IndexSignature: return updateIndexSignatureDeclaration(node - , nodesVisitor((node).parameters, visitor, isParameter) + , visitParameterList((node).parameters, visitor, context, nodesVisitor) , visitNode((node).type, visitor, isTypeNode) , nodesVisitor((node).decorators, visitor, isDecorator) , nodesVisitor((node).modifiers, visitor, isModifier)); @@ -383,8 +377,9 @@ namespace ts { return updateMethod(node, nodesVisitor((node).decorators, visitor, isDecorator), nodesVisitor((node).modifiers, visitor, isModifier), - (node).asteriskToken, + visitNode((node).asteriskToken, visitor, isToken), visitNode((node).name, visitor, isPropertyName), + visitNode((node).questionToken, visitor, isToken), nodesVisitor((node).typeParameters, visitor, isTypeParameter), visitParameterList((node).parameters, visitor, context, nodesVisitor), visitNode((node).type, visitor, isTypeNode), @@ -857,8 +852,7 @@ namespace ts { visitNode((node).expression, visitor, isExpression)); default: - throw new Error("not handled"); - // return node; + return node; } } diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 37ea8bab1a..e98235c3c8 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -85,56 +85,66 @@ namespace ts.codefix { if (!(signatures && signatures.length > 0)) { return undefined; } + + const optional = !!(symbol.flags & SymbolFlags.Optional); if (declarations.length === 1) { Debug.assert(signatures.length === 1); // TODO: suppress any return type // TODO: get parameters working. // TODO: add support for type parameters. const signature = signatures[0]; - const newTypeParameters = signature.typeParameters && signature.typeParameters.map(checker.createTypeParameterDeclarationFromType); - const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); - - const returnType = createTypeNodeExceptAny(checker.getReturnTypeOfSignature(signature), checker); - return createStubbedMethod(modifiers, name, newTypeParameters, newParameterNodes, returnType); + const signatureParts = getSignatureParts(signature); + return createStubbedMethod(modifiers, name, optional, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type); } let signatureDeclarations = []; for (let i = 0; i < signatures.length; i++) { // TODO: make signatures instead of methods const signature = signatures[i]; - const newTypeParameters = signature.typeParameters && signature.typeParameters.map(checker.createTypeParameterDeclarationFromType); - const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); - const returnType = createTypeNodeExceptAny(checker.getReturnTypeOfSignature(signature), checker); + const signatureParts = getSignatureParts(signature); signatureDeclarations.push(createMethod( /*decorators*/ undefined , modifiers , /*asteriskToken*/ undefined , name - , newTypeParameters - , newParameterNodes - , returnType + , optional ? createToken(SyntaxKind.QuestionToken) : undefined + , signatureParts.typeParameters + , signatureParts.parameters + , signatureParts.type , /*body*/undefined)); } if (declarations.length > signatures.length) { let signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration); - const newTypeParameters = signature.typeParameters && signature.typeParameters.map(checker.createTypeParameterDeclarationFromType); - const newParameterNodes = signature.getParameters().map(symbol => createParameterDeclarationFromSymbol(symbol, enclosingDeclaration, checker)); - const returnType = createTypeNodeExceptAny(checker.getReturnTypeOfSignature(signature), checker); - signatureDeclarations.push(createStubbedMethod(modifiers, name, newTypeParameters, newParameterNodes, returnType)); + const signatureParts = getSignatureParts(signature); + signatureDeclarations.push(createStubbedMethod(modifiers, name, optional, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type)); } else { Debug.assert(declarations.length === signatures.length); - const methodImplementingSignatures = createMethodImplementingSignatures(signatures, name, modifiers); + const methodImplementingSignatures = createMethodImplementingSignatures(signatures, name, optional, modifiers); signatureDeclarations.push(methodImplementingSignatures); } return signatureDeclarations; default: return undefined; } + + type SignatureParts = { + typeParameters: TypeParameterDeclaration[]; + parameters: ParameterDeclaration[]; + type: TypeNode; + } + + function getSignatureParts(signature: Signature): SignatureParts { + return { + typeParameters: signature.typeParameters && signature.typeParameters.map(checker.createTypeParameterDeclarationFromType), + parameters: signature.getParameters().map(symbol => checker.createParameterDeclarationFromSymbol(symbol)), + type: createTypeNodeExceptAny(checker.getReturnTypeOfSignature(signature), checker) + } + } } - function createMethodImplementingSignatures(signatures: Signature[], name: PropertyName, modifiers: Modifier[] | undefined): MethodDeclaration { + function createMethodImplementingSignatures(signatures: Signature[], name: PropertyName, optional: boolean, modifiers: Modifier[] | undefined): MethodDeclaration { Debug.assert(signatures && signatures.length > 0); let maxArgsIndex = 0; @@ -153,7 +163,7 @@ namespace ts.codefix { maxArgsSignature = sig; } } - const maxNonRestArgs = maxArgsSignature.parameters.length - (maxArgsSignature.hasRestParameter ? 1 : 0); + const maxNonRestArgs = maxArgsSignature.parameters.length - (maxArgsSignature.hasRestParameter ? 1 : 0); const maxArgsParameterSymbolNames = signatures[maxArgsIndex].getParameters().map(symbol => symbol.getName()); const parameters: ParameterDeclaration[] = []; @@ -186,17 +196,19 @@ namespace ts.codefix { return createStubbedMethod( modifiers , name + , optional , /*typeParameters*/undefined , parameters , /*returnType*/ undefined); } - export function createStubbedMethod(modifiers: Modifier[], name: PropertyName, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], returnType: TypeNode | undefined) { + export function createStubbedMethod(modifiers: Modifier[], name: PropertyName, optional: boolean, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], returnType: TypeNode | undefined) { return createMethod( /*decorators*/undefined , modifiers , /*asteriskToken*/undefined , name + , optional ? createToken(SyntaxKind.QuestionToken) : undefined , typeParameters , parameters , returnType @@ -223,22 +235,6 @@ namespace ts.codefix { return undefined; } - function createParameterDeclarationFromSymbol(parameterSymbol: Symbol, enclosingDeclaration: ClassLikeDeclaration, checker: TypeChecker) { - const parameterDeclaration = parameterSymbol.getDeclarations()[0] as ParameterDeclaration; - const parameterType = checker.getTypeOfSymbolAtLocation(parameterSymbol, enclosingDeclaration); - const parameterTypeNode = checker.createTypeNode(parameterType); - // TODO: deep cloning of decorators/any node. - const parameterNode = createParameter( - parameterDeclaration.decorators && parameterDeclaration.decorators.map(getSynthesizedDeepClone) - , parameterDeclaration.modifiers && parameterDeclaration.modifiers.map(getSynthesizedDeepClone) - , parameterDeclaration.dotDotDotToken && createToken(SyntaxKind.DotDotDotToken) - , getSynthesizedDeepClone(parameterDeclaration.name) - , parameterDeclaration.questionToken && createToken(SyntaxKind.QuestionToken) - , parameterTypeNode - , /*initializer*/ undefined); - return parameterNode; - } - function createTypeNodeExceptAny(type: Type, checker: TypeChecker) { const typeNode = checker.createTypeNode(type); return typeNode && typeNode.kind !== SyntaxKind.AnyKeyword ? typeNode : undefined; From 69587a286afeebe2fa6456e1e8d99b214a3a26a1 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 16 Mar 2017 16:41:38 -0700 Subject: [PATCH 35/65] transformers tests --- src/compiler/transformers/es2017.ts | 1 + src/compiler/transformers/esnext.ts | 1 + src/compiler/transformers/ts.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index 31aae5055a..9c7bcf5b2a 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -125,6 +125,7 @@ namespace ts { visitNodes(node.modifiers, visitor, isModifier), node.asteriskToken, node.name, + node.questionToken, /*typeParameters*/ undefined, visitParameterList(node.parameters, visitor, context), /*type*/ undefined, diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index f66bf14da5..2475e51119 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -541,6 +541,7 @@ namespace ts { ? undefined : node.asteriskToken, visitNode(node.name, visitor, isPropertyName), + visitNode(node.questionToken, visitor, isToken), /*typeParameters*/ undefined, visitParameterList(node.parameters, visitor, context), /*type*/ undefined, diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 885f8fad89..bbc588ee9f 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -2049,6 +2049,7 @@ namespace ts { visitNodes(node.modifiers, modifierVisitor, isModifier), node.asteriskToken, visitPropertyNameOfClassElement(node), + node.questionToken, /*typeParameters*/ undefined, visitParameterList(node.parameters, visitor, context), /*type*/ undefined, From 41e511efd24dd0f2d143b99121ff0cc261478511 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 16 Mar 2017 16:51:18 -0700 Subject: [PATCH 36/65] fix call --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7545430717..294c499128 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2431,7 +2431,7 @@ namespace ts { const typeAlias = getTypeAliasForTypeLiteral(type); if (typeAlias) { // The specified symbol flags need to be reinterpreted as type flags - const entityName = createNameFromSymbol(typeAlias, enclosingDeclaration); + const entityName = createNameFromSymbol(typeAlias); return createTypeReferenceNode(entityName, /*typeArguments*/ undefined); } else { From a88be7c6f9c93b686562519ef59ecd413bdbcec3 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 16 Mar 2017 16:59:01 -0700 Subject: [PATCH 37/65] consolidate SignatureParts --- src/compiler/checker.ts | 10 +--------- src/compiler/types.ts | 18 +++++++++++------- src/services/codefixes/helpers.ts | 25 +++---------------------- 3 files changed, 15 insertions(+), 38 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 294c499128..4ccc8f84de 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -107,9 +107,8 @@ namespace ts { getReturnTypeOfSignature, getNonNullableType, createTypeNode, - createTypeParameterDeclarationFromType, createIndexSignatureFromIndexInfo, - createParameterDeclarationFromSymbol, + createSignatureParts, getSymbolsInScope: (location, meaning) => { location = getParseTreeNode(location); return location ? getSymbolsInScope(location, meaning) : []; @@ -2222,13 +2221,6 @@ namespace ts { return parameterNode; } - /* @internal */ - type SignatureParts = { - typeParameters: TypeParameterDeclaration[] | undefined; - parameters: ParameterDeclaration[]; - type: TypeNode; - } - // TODO: expose this, remove copy from helper, possibly don't expose createParameter/TypeParameter? function createSignatureParts(signature: Signature): SignatureParts { return { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index acca1b7398..679e11fa25 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2468,14 +2468,12 @@ namespace ts { /* @internal */ getParameterType(signature: Signature, parameterIndex: number): Type; getNonNullableType(type: Type): Type; - /** Note that the resulting type node cannot be checked. */ + /** Note that the resulting nodes cannot be checked. */ createTypeNode(type: Type): TypeNode; - /** Note that the resulting type node cannot be checked. */ - createTypeParameterDeclarationFromType(type: Type): TypeParameterDeclaration; - /** Note that the resulting type node cannot be checked. */ - createIndexSignatureFromIndexInfo(indexInfo: IndexInfo, kind: IndexKind): IndexSignatureDeclaration - /** Note that the resulting type node cannot be checked. */ - createParameterDeclarationFromSymbol(parameterSymbol: Symbol): ParameterDeclaration; + /** Note that the resulting nodes cannot be checked. */ + createIndexSignatureFromIndexInfo(indexInfo: IndexInfo, kind: IndexKind): IndexSignatureDeclaration; + /** Note that the resulting nodes cannot be checked. */ + createSignatureParts(signature: Signature): SignatureParts; getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; getSymbolAtLocation(node: Node): Symbol; @@ -3230,6 +3228,12 @@ namespace ts { declaration?: SignatureDeclaration; } + export interface SignatureParts { + typeParameters: TypeParameterDeclaration[] | undefined; + parameters: ParameterDeclaration[]; + type: TypeNode; + } + /* @internal */ export interface TypeMapper { (t: TypeParameter): Type; diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index e98235c3c8..a8347cb0a9 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -93,7 +93,7 @@ namespace ts.codefix { // TODO: get parameters working. // TODO: add support for type parameters. const signature = signatures[0]; - const signatureParts = getSignatureParts(signature); + const signatureParts = checker.createSignatureParts(signature); return createStubbedMethod(modifiers, name, optional, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type); } @@ -101,7 +101,7 @@ namespace ts.codefix { for (let i = 0; i < signatures.length; i++) { // TODO: make signatures instead of methods const signature = signatures[i]; - const signatureParts = getSignatureParts(signature); + const signatureParts = checker.createSignatureParts(signature); signatureDeclarations.push(createMethod( /*decorators*/ undefined , modifiers @@ -116,7 +116,7 @@ namespace ts.codefix { if (declarations.length > signatures.length) { let signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration); - const signatureParts = getSignatureParts(signature); + const signatureParts = checker.createSignatureParts(signature); signatureDeclarations.push(createStubbedMethod(modifiers, name, optional, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type)); } else { @@ -128,20 +128,6 @@ namespace ts.codefix { default: return undefined; } - - type SignatureParts = { - typeParameters: TypeParameterDeclaration[]; - parameters: ParameterDeclaration[]; - type: TypeNode; - } - - function getSignatureParts(signature: Signature): SignatureParts { - return { - typeParameters: signature.typeParameters && signature.typeParameters.map(checker.createTypeParameterDeclarationFromType), - parameters: signature.getParameters().map(symbol => checker.createParameterDeclarationFromSymbol(symbol)), - type: createTypeNodeExceptAny(checker.getReturnTypeOfSignature(signature), checker) - } - } } function createMethodImplementingSignatures(signatures: Signature[], name: PropertyName, optional: boolean, modifiers: Modifier[] | undefined): MethodDeclaration { @@ -234,9 +220,4 @@ namespace ts.codefix { } return undefined; } - - function createTypeNodeExceptAny(type: Type, checker: TypeChecker) { - const typeNode = checker.createTypeNode(type); - return typeNode && typeNode.kind !== SyntaxKind.AnyKeyword ? typeNode : undefined; - } } \ No newline at end of file From 7318c2c5a9a0a559ab27b5584dd4025d546f33d5 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 16 Mar 2017 17:18:28 -0700 Subject: [PATCH 38/65] some cleanup --- src/compiler/checker.ts | 49 ++++++++----------- src/compiler/factory.ts | 10 +--- ...lassImplementInterfaceNamespaceConflict.ts | 3 +- 3 files changed, 24 insertions(+), 38 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4ccc8f84de..7af5b877b5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2327,22 +2327,22 @@ namespace ts { if (objectFlags & ObjectFlags.ClassOrInterface) { Debug.assert(!!(type.flags & TypeFlags.Object)); // TODO: Detect whether class is named and fail if not. - const name = getNameOfSymbol(type.symbol); + const name = createNameFromSymbol(type.symbol); // TODO: handle type arguments. return createTypeReferenceNode(name, /*typeParameters*/undefined); } if (type.flags & TypeFlags.TypeParameter) { // TODO: get qualified name when necessary instead of string. - const name = symbolToString(type.symbol); + const name = createNameFromSymbol(type.symbol); // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. return createTypeReferenceNode(name, /*typeArguments*/ undefined); } // TODO: move back up later on? if (checkAlias && type.aliasSymbol) { - const name = getNameOfSymbol(type.aliasSymbol); + const name = createNameFromSymbol(type.aliasSymbol); const typeArgumentNodes = mapToTypeNodeArray(type.aliasTypeArguments); - return createTypeReferenceNode(createIdentifier(name), typeArgumentNodes); + return createTypeReferenceNode(name, typeArgumentNodes); } checkAlias = false; @@ -2357,15 +2357,12 @@ namespace ts { if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { Debug.assert(!!(type.flags & TypeFlags.Object)); // The type is an object literal type. - if (!type.symbol) { - // Anonymous types without symbols are literals. - // TODO: handle this case correctly. - // TODO: test. - noop(); - } - return createAnonymousTypeNode(type); } + + // TODO: implement when this is testable. + // else if (type.flags & TypeFlags.StringOrNumberLiteral) { + // writer.writeStringLiteral(literalTypeToString(type)); if (type.flags & TypeFlags.Index) { // TODO: test. @@ -2382,14 +2379,17 @@ namespace ts { Debug.fail("Should be unreachable."); - /** Note that mapToTypeNodeArray(undefined) === undefined. */ function mapToTypeNodeArray(types: Type[]): NodeArray { - return asNodeArray(types && types.map(createTypeNodeWorker) as TypeNode[]); + return types && asNodeArray(types.map(createTypeNodeWorker) as TypeNode[]); } - // TODO: implement when this is testable. - // else if (type.flags & TypeFlags.StringOrNumberLiteral) { - // writer.writeStringLiteral(literalTypeToString(type)); + function createNameFromSymbol(symbol: Symbol): Identifier; + function createNameFromSymbol(symbol: Symbol): EntityName; + function createNameFromSymbol(symbol: Symbol): EntityName { + symbol; enclosingDeclaration; + // TODO: actually implement this + return createIdentifier(symbolToString(symbol, enclosingDeclaration)); + } function createMappedTypeNodeFromType(type: MappedType) { Debug.assert(!!(type.flags & TypeFlags.Object)); @@ -2414,8 +2414,6 @@ namespace ts { if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) || symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || shouldWriteTypeOfFunctionSymbol()) { - // TODO: test. - // TODO: get entity name from symbol. return createTypeQueryNodeFromType(type); } else if (contains(symbolStack, symbol)) { @@ -2443,7 +2441,7 @@ namespace ts { } } else { - // Anonymous types with no symbol are never circular + // Anonymous types without a symbol are never circular. return createTypeNodeFromObjectType(type); } @@ -2496,18 +2494,11 @@ namespace ts { function createTypeQueryNodeFromType(type: Type) { const symbol = type.symbol; if (symbol) { - // TODO: get entity name instead. const entityName = createNameFromSymbol(symbol); return createTypeQueryNode(entityName); } } - function createNameFromSymbol(symbol: Symbol): EntityName { - symbol; enclosingDeclaration; - // TODO: actually implement this - return createIdentifier(symbolToString(symbol, enclosingDeclaration)); - } - function createTypeReferenceNodeFromType(type: TypeReference) { const typeArguments: Type[] = type.typeArguments || emptyArray; if (type.target === globalArrayType) { @@ -2535,8 +2526,8 @@ namespace ts { // the default outer type arguments), we don't show the group. // TODO: figure out how to handle type arguments if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { - const name = symbolToString(parent); - const qualifiedNamePart = createIdentifier(name); // createTypeReferenceNode(name, mapToTypeNodeArray(typeArguments.slice(start, i - start))); + const name = createNameFromSymbol(parent); + const qualifiedNamePart = name; // createTypeReferenceNode(name, mapToTypeNodeArray(typeArguments.slice(start, i - start))); if (!qualifiedName) { qualifiedName = createQualifiedName(qualifiedNamePart, /*right*/undefined); } @@ -2549,7 +2540,7 @@ namespace ts { } } let entityName: EntityName = undefined; - const nameIdentifier = createIdentifier(symbolToString(type.symbol)); + const nameIdentifier = createNameFromSymbol(type.symbol); if (qualifiedName) { // TODO: handle checking of type arguments for qualified names? Debug.assert(!qualifiedName.right); diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index d91076dcde..e60ed0009e 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -68,18 +68,12 @@ namespace ts { } /* @internal */ - /** - * Note this implementation is inefficient in that all nodes except leaves are cloned twice, - * First by the explicit call below and then again as part of updateNode. - * We need to clone before visiting the children because otherwise updateNode - * will overwrite the synthesized span with the original node's span. - */ export function getSynthesizedDeepClone(node: T | undefined): T { if (node === undefined) { return undefined; } - const clone = getSynthesizedClone(node); - return visitEachChild(clone, getSynthesizedDeepClone, nullTransformationContext); + const clone = visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext); + return clone === node ? getSynthesizedClone(node) : clone; } // Literals diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts index 4ca42640c2..b8b389c463 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts @@ -3,14 +3,15 @@ //// namespace N1 { //// export interface I1 { //// x: number; +//// y: I1; //// } //// } //// interface I1 { //// f1(); //// } -//// //// class C1 implements N1.I1 {[| |]} verify.rangeAfterCodeFix(` x: number; +y: N1.I1; `); \ No newline at end of file From 28df2ab2df340d343a477e467f8ce4721d83e47d Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 16 Mar 2017 17:21:12 -0700 Subject: [PATCH 39/65] some cleanup --- src/compiler/checker.ts | 49 +++++++++++++++++------------------------ src/compiler/factory.ts | 10 ++------- 2 files changed, 22 insertions(+), 37 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4ccc8f84de..7af5b877b5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2327,22 +2327,22 @@ namespace ts { if (objectFlags & ObjectFlags.ClassOrInterface) { Debug.assert(!!(type.flags & TypeFlags.Object)); // TODO: Detect whether class is named and fail if not. - const name = getNameOfSymbol(type.symbol); + const name = createNameFromSymbol(type.symbol); // TODO: handle type arguments. return createTypeReferenceNode(name, /*typeParameters*/undefined); } if (type.flags & TypeFlags.TypeParameter) { // TODO: get qualified name when necessary instead of string. - const name = symbolToString(type.symbol); + const name = createNameFromSymbol(type.symbol); // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. return createTypeReferenceNode(name, /*typeArguments*/ undefined); } // TODO: move back up later on? if (checkAlias && type.aliasSymbol) { - const name = getNameOfSymbol(type.aliasSymbol); + const name = createNameFromSymbol(type.aliasSymbol); const typeArgumentNodes = mapToTypeNodeArray(type.aliasTypeArguments); - return createTypeReferenceNode(createIdentifier(name), typeArgumentNodes); + return createTypeReferenceNode(name, typeArgumentNodes); } checkAlias = false; @@ -2357,15 +2357,12 @@ namespace ts { if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { Debug.assert(!!(type.flags & TypeFlags.Object)); // The type is an object literal type. - if (!type.symbol) { - // Anonymous types without symbols are literals. - // TODO: handle this case correctly. - // TODO: test. - noop(); - } - return createAnonymousTypeNode(type); } + + // TODO: implement when this is testable. + // else if (type.flags & TypeFlags.StringOrNumberLiteral) { + // writer.writeStringLiteral(literalTypeToString(type)); if (type.flags & TypeFlags.Index) { // TODO: test. @@ -2382,14 +2379,17 @@ namespace ts { Debug.fail("Should be unreachable."); - /** Note that mapToTypeNodeArray(undefined) === undefined. */ function mapToTypeNodeArray(types: Type[]): NodeArray { - return asNodeArray(types && types.map(createTypeNodeWorker) as TypeNode[]); + return types && asNodeArray(types.map(createTypeNodeWorker) as TypeNode[]); } - // TODO: implement when this is testable. - // else if (type.flags & TypeFlags.StringOrNumberLiteral) { - // writer.writeStringLiteral(literalTypeToString(type)); + function createNameFromSymbol(symbol: Symbol): Identifier; + function createNameFromSymbol(symbol: Symbol): EntityName; + function createNameFromSymbol(symbol: Symbol): EntityName { + symbol; enclosingDeclaration; + // TODO: actually implement this + return createIdentifier(symbolToString(symbol, enclosingDeclaration)); + } function createMappedTypeNodeFromType(type: MappedType) { Debug.assert(!!(type.flags & TypeFlags.Object)); @@ -2414,8 +2414,6 @@ namespace ts { if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) || symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || shouldWriteTypeOfFunctionSymbol()) { - // TODO: test. - // TODO: get entity name from symbol. return createTypeQueryNodeFromType(type); } else if (contains(symbolStack, symbol)) { @@ -2443,7 +2441,7 @@ namespace ts { } } else { - // Anonymous types with no symbol are never circular + // Anonymous types without a symbol are never circular. return createTypeNodeFromObjectType(type); } @@ -2496,18 +2494,11 @@ namespace ts { function createTypeQueryNodeFromType(type: Type) { const symbol = type.symbol; if (symbol) { - // TODO: get entity name instead. const entityName = createNameFromSymbol(symbol); return createTypeQueryNode(entityName); } } - function createNameFromSymbol(symbol: Symbol): EntityName { - symbol; enclosingDeclaration; - // TODO: actually implement this - return createIdentifier(symbolToString(symbol, enclosingDeclaration)); - } - function createTypeReferenceNodeFromType(type: TypeReference) { const typeArguments: Type[] = type.typeArguments || emptyArray; if (type.target === globalArrayType) { @@ -2535,8 +2526,8 @@ namespace ts { // the default outer type arguments), we don't show the group. // TODO: figure out how to handle type arguments if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { - const name = symbolToString(parent); - const qualifiedNamePart = createIdentifier(name); // createTypeReferenceNode(name, mapToTypeNodeArray(typeArguments.slice(start, i - start))); + const name = createNameFromSymbol(parent); + const qualifiedNamePart = name; // createTypeReferenceNode(name, mapToTypeNodeArray(typeArguments.slice(start, i - start))); if (!qualifiedName) { qualifiedName = createQualifiedName(qualifiedNamePart, /*right*/undefined); } @@ -2549,7 +2540,7 @@ namespace ts { } } let entityName: EntityName = undefined; - const nameIdentifier = createIdentifier(symbolToString(type.symbol)); + const nameIdentifier = createNameFromSymbol(type.symbol); if (qualifiedName) { // TODO: handle checking of type arguments for qualified names? Debug.assert(!qualifiedName.right); diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index d91076dcde..e60ed0009e 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -68,18 +68,12 @@ namespace ts { } /* @internal */ - /** - * Note this implementation is inefficient in that all nodes except leaves are cloned twice, - * First by the explicit call below and then again as part of updateNode. - * We need to clone before visiting the children because otherwise updateNode - * will overwrite the synthesized span with the original node's span. - */ export function getSynthesizedDeepClone(node: T | undefined): T { if (node === undefined) { return undefined; } - const clone = getSynthesizedClone(node); - return visitEachChild(clone, getSynthesizedDeepClone, nullTransformationContext); + const clone = visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext); + return clone === node ? getSynthesizedClone(node) : clone; } // Literals From 621c04479077badda6ac777a160db67224b86c8b Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 16 Mar 2017 17:22:57 -0700 Subject: [PATCH 40/65] remove test --- ...xClassImplementInterfaceNamespaceConflict.ts | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts deleted file mode 100644 index b8b389c463..0000000000 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts +++ /dev/null @@ -1,17 +0,0 @@ -/// - -//// namespace N1 { -//// export interface I1 { -//// x: number; -//// y: I1; -//// } -//// } -//// interface I1 { -//// f1(); -//// } -//// class C1 implements N1.I1 {[| |]} - -verify.rangeAfterCodeFix(` -x: number; -y: N1.I1; -`); \ No newline at end of file From dee9ced0608f847962d3c4352608847fa021f898 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 16 Mar 2017 18:02:34 -0700 Subject: [PATCH 41/65] add tests --- .../codeFixClassImplementInterfaceArrayTuple.ts | 15 +++++++++++++++ ...FixClassImplementInterfaceNamespaceConflict.ts | 15 +++++++++++++++ ...codeFixClassImplementInterfaceQualifiedName.ts | 12 ++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfaceArrayTuple.ts create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfaceQualifiedName.ts diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceArrayTuple.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceArrayTuple.ts new file mode 100644 index 0000000000..4550e5ca31 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceArrayTuple.ts @@ -0,0 +1,15 @@ +/// + +//// interface I { +//// x: number[]; +//// y: Array; +//// z: [number, string, I]; +//// } +//// +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` +x: number[]; +y: number[]; +z: [number, string, I]; +`); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts new file mode 100644 index 0000000000..1d71ce3c47 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceNamespaceConflict.ts @@ -0,0 +1,15 @@ +/// + +//// namespace N1 { +//// export interface I1 { +//// x: number; +//// } +//// } +//// interface I1 { +//// f1(); +//// } +//// class C1 implements N1.I1 {[| |]} + +verify.rangeAfterCodeFix(` +x: number; +`); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceQualifiedName.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceQualifiedName.ts new file mode 100644 index 0000000000..e29f4501b1 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceQualifiedName.ts @@ -0,0 +1,12 @@ +/// + +//// namespace N { +//// export interface I { +//// y: I; +//// } +//// } +//// class C1 implements N.I {[| |]} + +verify.rangeAfterCodeFix(` +y: N.I; +`); \ No newline at end of file From 4fa32a29ce42761f9a20172aad47d3bb60ed11b8 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 16 Mar 2017 18:02:57 -0700 Subject: [PATCH 42/65] make enclosingdeclaration arg required --- src/compiler/checker.ts | 54 ++++++++----------- src/compiler/types.ts | 6 +-- src/services/codefixes/fixAddMissingMember.ts | 2 +- .../fixClassIncorrectlyImplementsInterface.ts | 14 ++--- src/services/codefixes/helpers.ts | 11 ++-- 5 files changed, 38 insertions(+), 49 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7af5b877b5..960a36e347 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2191,23 +2191,23 @@ namespace ts { return result; } - function createTypeParameterDeclarationFromType(type: TypeParameter): TypeParameterDeclaration { + function createTypeParameterDeclarationFromType(type: TypeParameter, enclosingDeclaration: Node): TypeParameterDeclaration { if (!(type && type.symbol && type.flags & TypeFlags.TypeParameter)) { return undefined; } - const constraint = createTypeNode(getConstraintFromTypeParameter(type)) as TypeNode; - const defaultParameter = createTypeNode(getDefaultFromTypeParameter(type)) as TypeNode; + const constraint = createTypeNode(getConstraintFromTypeParameter(type), enclosingDeclaration); + const defaultParameter = createTypeNode(getDefaultFromTypeParameter(type), enclosingDeclaration); const name = symbolToString(type.symbol); return createTypeParameterDeclaration(name, constraint, defaultParameter); } // TODO: enclosing declaration appears to be unused in getTypeOfSymbolAtLocation - function createParameterDeclarationFromSymbol(parameterSymbol: Symbol): ParameterDeclaration { + function createParameterDeclarationFromSymbol(parameterSymbol: Symbol, enclosingDeclaration: Node): ParameterDeclaration { const parameterDeclaration = parameterSymbol.declarations[0] as ParameterDeclaration; const parameterType = getTypeOfSymbol(parameterSymbol); - const parameterTypeNode = checker.createTypeNode(parameterType); + const parameterTypeNode = createTypeNode(parameterType, enclosingDeclaration); // TODO: clone binding names correctly. // TODO: copy initialzer in a way that checks whether all symbols used in expression are accessible here, and qualify them appropriately. const parameterNode = createParameter( @@ -2222,25 +2222,24 @@ namespace ts { } // TODO: expose this, remove copy from helper, possibly don't expose createParameter/TypeParameter? - function createSignatureParts(signature: Signature): SignatureParts { + function createSignatureParts(signature: Signature, enclosingDeclaration: Node): SignatureParts { return { - typeParameters: signature.typeParameters && signature.typeParameters.map(createTypeParameterDeclarationFromType), - parameters: signature.parameters.map(createParameterDeclarationFromSymbol), + typeParameters: signature.typeParameters && signature.typeParameters.map(parameter => createTypeParameterDeclarationFromType(parameter,enclosingDeclaration)), + parameters: signature.parameters.map(parameter => createParameterDeclarationFromSymbol(parameter,enclosingDeclaration)), type: createTypeNodeExceptAny(getReturnTypeOfSignature(signature)) } function createTypeNodeExceptAny(type: Type): TypeNode | undefined { - const typeNode = createTypeNode(type); + const typeNode = createTypeNode(type, enclosingDeclaration); return typeNode && typeNode.kind !== SyntaxKind.AnyKeyword ? typeNode : undefined; } } - function createTypeNode(type: Type): TypeNode { + function createTypeNode(type: Type, enclosingDeclaration: Node): TypeNode { let undefinedArgumentIsError = true; let encounteredError = false; let inObjectTypeLiteral = false; let checkAlias = true; - let enclosingDeclaration: Node = undefined; // TODO: add parameter. let symbolStack: Symbol[] = undefined; let result = createTypeNodeWorker(type); @@ -2256,7 +2255,7 @@ namespace ts { return undefined; } - const typeString = typeToString(type); typeString; // TODO: remove. + const typeString = typeToString(type, enclosingDeclaration); typeString; // TODO: remove. if (type.flags & TypeFlags.Any) { // TODO: add other case where type ends up being `any`. @@ -2315,15 +2314,8 @@ namespace ts { if (objectFlags & ObjectFlags.Reference) { Debug.assert(!!(type.flags & TypeFlags.Object)); - // and vice versa. - // this case includes tuple types - // TODO: test empty tuples, see if they are coherent. return createTypeReferenceNodeFromType(type); } - - if (type.flags & TypeFlags.EnumLiteral) { - throw new Error("Enum literal not implemented"); - } if (objectFlags & ObjectFlags.ClassOrInterface) { Debug.assert(!!(type.flags & TypeFlags.Object)); // TODO: Detect whether class is named and fail if not. @@ -2397,9 +2389,9 @@ namespace ts { // TODO: does typeParameter have the same constraint or do we need to overwrite it somehow? const typeParameter = getTypeParameterFromMappedType(type); // const constraintType = getConstraintTypeFromMappedType(type); - const typeParameterNode = createTypeParameterDeclarationFromType(typeParameter); + const typeParameterNode = createTypeParameterDeclarationFromType(typeParameter, enclosingDeclaration); - const templateTypeNode = createTypeNode(getTemplateTypeFromMappedType(type)); + const templateTypeNode = createTypeNode(getTemplateTypeFromMappedType(type), enclosingDeclaration); const readonlyToken = (type).declaration && (type).declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; const questionToken = (type).declaration && (type).declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; @@ -2474,12 +2466,12 @@ namespace ts { if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { const signature = resolved.callSignatures[0]; - const signatureParts = createSignatureParts(signature); + const signatureParts = createSignatureParts(signature, enclosingDeclaration); return createSignatureDeclaration(SyntaxKind.FunctionType, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type); } if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { const signature = resolved.constructSignatures[0]; - const signatureParts = createSignatureParts(signature); + const signatureParts = createSignatureParts(signature, enclosingDeclaration); return createSignatureDeclaration(SyntaxKind.ConstructorType, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type); } } @@ -2559,18 +2551,18 @@ namespace ts { function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] { const typeElements: TypeElement[] = []; for (const signature of resolvedType.callSignatures) { - const signatureParts = createSignatureParts(signature); + const signatureParts = createSignatureParts(signature, enclosingDeclaration); typeElements.push(createSignatureDeclaration(SyntaxKind.CallSignature, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type)); } for (const signature of resolvedType.constructSignatures) { - const signatureParts = createSignatureParts(signature); + const signatureParts = createSignatureParts(signature, enclosingDeclaration); typeElements.push(createSignatureDeclaration(SyntaxKind.ConstructSignature, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type)); } if (resolvedType.stringIndexInfo) { - typeElements.push(createIndexSignatureFromIndexInfo(resolvedType.stringIndexInfo, IndexKind.String)); + typeElements.push(createIndexSignatureFromIndexInfo(resolvedType.stringIndexInfo, IndexKind.String, enclosingDeclaration)); } if (resolvedType.numberIndexInfo) { - typeElements.push(createIndexSignatureFromIndexInfo(resolvedType.numberIndexInfo, IndexKind.Number)); + typeElements.push(createIndexSignatureFromIndexInfo(resolvedType.numberIndexInfo, IndexKind.Number, enclosingDeclaration)); } const properties = resolvedType.properties; @@ -2589,7 +2581,7 @@ namespace ts { if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length) { const signatures = getSignaturesOfType(propertyType, SignatureKind.Call); for (const signature of signatures) { - const signatureParts = createSignatureParts(signature); + const signatureParts = createSignatureParts(signature, enclosingDeclaration); const methodDeclaration = createSignatureDeclaration(SyntaxKind.MethodSignature, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type, propertyName, optionalToken); methodDeclaration.questionToken = optionalToken; typeElements.push(methodDeclaration); @@ -2599,7 +2591,7 @@ namespace ts { typeElements.push(createPropertySignature( propertyName , optionalToken - , createTypeNode(propertyType) + , createTypeNodeWorker(propertyType) , /*initializer*/undefined)); } } @@ -2608,7 +2600,7 @@ namespace ts { } } - function createIndexSignatureFromIndexInfo(indexInfo: IndexInfo, kind: IndexKind): IndexSignatureDeclaration { + function createIndexSignatureFromIndexInfo(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node): IndexSignatureDeclaration { const indexerTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword); const name = getNameFromIndexInfo(indexInfo); @@ -2620,7 +2612,7 @@ namespace ts { , /*questionToken*/ undefined , indexerTypeNode , /*initializer*/ undefined); - const typeNode = createTypeNode(indexInfo.type); + const typeNode = createTypeNode(indexInfo.type, enclosingDeclaration); return createIndexSignatureDeclaration( [indexingParameter] , typeNode diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 679e11fa25..d802adf67e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2469,11 +2469,11 @@ namespace ts { getNonNullableType(type: Type): Type; /** Note that the resulting nodes cannot be checked. */ - createTypeNode(type: Type): TypeNode; + createTypeNode(type: Type, enclosingDeclaration: Node): TypeNode; /** Note that the resulting nodes cannot be checked. */ - createIndexSignatureFromIndexInfo(indexInfo: IndexInfo, kind: IndexKind): IndexSignatureDeclaration; + createIndexSignatureFromIndexInfo(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node): IndexSignatureDeclaration; /** Note that the resulting nodes cannot be checked. */ - createSignatureParts(signature: Signature): SignatureParts; + createSignatureParts(signature: Signature, enclosingDeclaration: Node): SignatureParts; getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; getSymbolAtLocation(node: Node): Symbol; diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index ace11cda80..ab973bb577 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -38,7 +38,7 @@ namespace ts.codefix { const checker = context.program.getTypeChecker(); const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(binaryExpression.right))); - typeNode = checker.createTypeNode(widenedType) || typeNode; + typeNode = checker.createTypeNode(widenedType, classDeclaration) || typeNode; } const openBrace = getOpenBraceOfClassLike(classDeclaration, sourceFile); diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index 33b4a10dff..de34676eda 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -11,14 +11,14 @@ namespace ts.codefix { const token = getTokenAtPosition(sourceFile, start); const checker = context.program.getTypeChecker(); - const classDecl = getContainingClass(token); - if (!classDecl) { + const classDeclaration = getContainingClass(token); + if (!classDeclaration) { return undefined; } - const openBrace = getOpenBraceOfClassLike(classDecl, sourceFile); - const classType = checker.getTypeAtLocation(classDecl) as InterfaceType; - const implementedTypeNodes = getClassImplementsHeritageClauseElements(classDecl); + const openBrace = getOpenBraceOfClassLike(classDeclaration, sourceFile); + const classType = checker.getTypeAtLocation(classDeclaration) as InterfaceType; + const implementedTypeNodes = getClassImplementsHeritageClauseElements(classDeclaration); const hasNumericIndexSignature = !!checker.getIndexTypeOfType(classType, IndexKind.Number); const hasStringIndexSignature = !!checker.getIndexTypeOfType(classType, IndexKind.String); @@ -34,7 +34,7 @@ namespace ts.codefix { let newNodes: Node[] = []; createAndAddMissingIndexSignatureDeclaration(implementedType, IndexKind.Number, hasNumericIndexSignature, newNodes); createAndAddMissingIndexSignatureDeclaration(implementedType, IndexKind.String, hasStringIndexSignature, newNodes); - newNodes = newNodes.concat(createMissingMemberNodes(classDecl, nonPrivateMembers, checker)); + newNodes = newNodes.concat(createMissingMemberNodes(classDeclaration, nonPrivateMembers, checker)); const message = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Implement_interface_0), [implementedTypeNode.getText()]); if (newNodes.length > 0) { pushAction(result, newNodes, message); @@ -53,7 +53,7 @@ namespace ts.codefix { if (!indexInfoOfKind) { return undefined; } - const newIndexSignatureDeclaration = checker.createIndexSignatureFromIndexInfo(indexInfoOfKind, kind); + const newIndexSignatureDeclaration = checker.createIndexSignatureFromIndexInfo(indexInfoOfKind, kind, classDeclaration); newNodes.push(newIndexSignatureDeclaration); } diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index a8347cb0a9..8693cc453b 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -62,7 +62,7 @@ namespace ts.codefix { case SyntaxKind.SetAccessor: case SyntaxKind.PropertySignature: case SyntaxKind.PropertyDeclaration: - const typeNode = checker.createTypeNode(type); + const typeNode = checker.createTypeNode(type, enclosingDeclaration); // TODO: add modifiers. const property = createProperty( /*decorators*/undefined @@ -89,11 +89,8 @@ namespace ts.codefix { const optional = !!(symbol.flags & SymbolFlags.Optional); if (declarations.length === 1) { Debug.assert(signatures.length === 1); - // TODO: suppress any return type - // TODO: get parameters working. - // TODO: add support for type parameters. const signature = signatures[0]; - const signatureParts = checker.createSignatureParts(signature); + const signatureParts = checker.createSignatureParts(signature, enclosingDeclaration); return createStubbedMethod(modifiers, name, optional, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type); } @@ -101,7 +98,7 @@ namespace ts.codefix { for (let i = 0; i < signatures.length; i++) { // TODO: make signatures instead of methods const signature = signatures[i]; - const signatureParts = checker.createSignatureParts(signature); + const signatureParts = checker.createSignatureParts(signature, enclosingDeclaration); signatureDeclarations.push(createMethod( /*decorators*/ undefined , modifiers @@ -116,7 +113,7 @@ namespace ts.codefix { if (declarations.length > signatures.length) { let signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration); - const signatureParts = checker.createSignatureParts(signature); + const signatureParts = checker.createSignatureParts(signature, enclosingDeclaration); signatureDeclarations.push(createStubbedMethod(modifiers, name, optional, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type)); } else { From ac7fc8fe75af8b0bc062fa67ebbe3d8692933545 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Fri, 17 Mar 2017 11:54:51 -0700 Subject: [PATCH 43/65] Handle TODO's --- src/compiler/checker.ts | 113 +++++++++++------- src/compiler/visitor.ts | 13 +- src/services/codefixes/fixAddMissingMember.ts | 1 - src/services/codefixes/helpers.ts | 5 +- ...codeFixClassImplementInterfaceIndexType.ts | 10 ++ ...odeFixClassImplementInterfaceMappedType.ts | 10 ++ 6 files changed, 102 insertions(+), 50 deletions(-) create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfaceIndexType.ts create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfaceMappedType.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 960a36e347..58186560e8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2203,13 +2203,12 @@ namespace ts { return createTypeParameterDeclaration(name, constraint, defaultParameter); } - // TODO: enclosing declaration appears to be unused in getTypeOfSymbolAtLocation function createParameterDeclarationFromSymbol(parameterSymbol: Symbol, enclosingDeclaration: Node): ParameterDeclaration { const parameterDeclaration = parameterSymbol.declarations[0] as ParameterDeclaration; const parameterType = getTypeOfSymbol(parameterSymbol); const parameterTypeNode = createTypeNode(parameterType, enclosingDeclaration); - // TODO: clone binding names correctly. - // TODO: copy initialzer in a way that checks whether all symbols used in expression are accessible here, and qualify them appropriately. + // TODO: how should we clone members/modifiers? + // TODO: check initializer accessibility correctly. const parameterNode = createParameter( parameterDeclaration.decorators && parameterDeclaration.decorators.map(getSynthesizedDeepClone) , parameterDeclaration.modifiers && parameterDeclaration.modifiers.map(getSynthesizedDeepClone) @@ -2217,11 +2216,10 @@ namespace ts { , getSynthesizedDeepClone(parameterDeclaration.name) , parameterDeclaration.questionToken && createToken(SyntaxKind.QuestionToken) , parameterTypeNode - , /*initializer*/ undefined); + , parameterDeclaration.initializer && getSynthesizedDeepClone(parameterDeclaration.initializer)); return parameterNode; } - // TODO: expose this, remove copy from helper, possibly don't expose createParameter/TypeParameter? function createSignatureParts(signature: Signature, enclosingDeclaration: Node): SignatureParts { return { typeParameters: signature.typeParameters && signature.typeParameters.map(parameter => createTypeParameterDeclarationFromType(parameter,enclosingDeclaration)), @@ -2242,12 +2240,8 @@ namespace ts { let checkAlias = true; let symbolStack: Symbol[] = undefined; - let result = createTypeNodeWorker(type); - if (result) { - (result).__type_source = type; - (result).__type_source_str = typeToString(type); - } - return result; + const result = createTypeNodeWorker(type); + return encounteredError ? undefined: result; function createTypeNodeWorker(type: Type): TypeNode { if (!type) { @@ -2255,10 +2249,7 @@ namespace ts { return undefined; } - const typeString = typeToString(type, enclosingDeclaration); typeString; // TODO: remove. - if (type.flags & TypeFlags.Any) { - // TODO: add other case where type ends up being `any`. return createKeywordTypeNode(SyntaxKind.AnyKeyword); } if (type.flags & TypeFlags.String) { @@ -2318,19 +2309,16 @@ namespace ts { } if (objectFlags & ObjectFlags.ClassOrInterface) { Debug.assert(!!(type.flags & TypeFlags.Object)); - // TODO: Detect whether class is named and fail if not. const name = createNameFromSymbol(type.symbol); // TODO: handle type arguments. - return createTypeReferenceNode(name, /*typeParameters*/undefined); + return createTypeReferenceNode(name, /*typeArguments*/ undefined); } if (type.flags & TypeFlags.TypeParameter) { - // TODO: get qualified name when necessary instead of string. const name = createNameFromSymbol(type.symbol); // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. return createTypeReferenceNode(name, /*typeArguments*/ undefined); } - // TODO: move back up later on? if (checkAlias && type.aliasSymbol) { const name = createNameFromSymbol(type.aliasSymbol); const typeArgumentNodes = mapToTypeNodeArray(type.aliasTypeArguments); @@ -2352,18 +2340,14 @@ namespace ts { return createAnonymousTypeNode(type); } - // TODO: implement when this is testable. - // else if (type.flags & TypeFlags.StringOrNumberLiteral) { - // writer.writeStringLiteral(literalTypeToString(type)); + // TODO (aozgaa): implement string and number literals here once there is a testable case. if (type.flags & TypeFlags.Index) { - // TODO: test. - const indexType = getIndexType(getApparentType((type).type)); - const indexTypeNode = createTypeNodeWorker(indexType); + const indexedType = (type).type; + const indexTypeNode = createTypeNodeWorker(indexedType); return createTypeOperatorNode(indexTypeNode); } if (type.flags & TypeFlags.IndexedAccess) { - // TODO: test. const objectTypeNode = createTypeNodeWorker((type).objectType); const indexTypeNode = createTypeNodeWorker((type).indexType); return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); @@ -2375,27 +2359,15 @@ namespace ts { return types && asNodeArray(types.map(createTypeNodeWorker) as TypeNode[]); } - function createNameFromSymbol(symbol: Symbol): Identifier; - function createNameFromSymbol(symbol: Symbol): EntityName; - function createNameFromSymbol(symbol: Symbol): EntityName { - symbol; enclosingDeclaration; - // TODO: actually implement this - return createIdentifier(symbolToString(symbol, enclosingDeclaration)); - } - function createMappedTypeNodeFromType(type: MappedType) { Debug.assert(!!(type.flags & TypeFlags.Object)); - - // TODO: does typeParameter have the same constraint or do we need to overwrite it somehow? const typeParameter = getTypeParameterFromMappedType(type); - // const constraintType = getConstraintTypeFromMappedType(type); const typeParameterNode = createTypeParameterDeclarationFromType(typeParameter, enclosingDeclaration); const templateTypeNode = createTypeNode(getTemplateTypeFromMappedType(type), enclosingDeclaration); const readonlyToken = (type).declaration && (type).declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; const questionToken = (type).declaration && (type).declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; - // TODO: test. return createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); } @@ -2501,7 +2473,6 @@ namespace ts { return createTupleTypeNode(typeArguments.length > 0 ? mapToTypeNodeArray(typeArguments.slice(0, getTypeReferenceArity(type))) : undefined); } else { - // TODO: handle type parameters in qualified names... const outerTypeParameters = type.target.outerTypeParameters; let i = 0; let qualifiedName: QualifiedName | undefined = undefined; @@ -2516,10 +2487,9 @@ namespace ts { } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); // When type parameters are their own type arguments for the whole group (i.e. we have // the default outer type arguments), we don't show the group. - // TODO: figure out how to handle type arguments if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { const name = createNameFromSymbol(parent); - const qualifiedNamePart = name; // createTypeReferenceNode(name, mapToTypeNodeArray(typeArguments.slice(start, i - start))); + const qualifiedNamePart = name; if (!qualifiedName) { qualifiedName = createQualifiedName(qualifiedNamePart, /*right*/undefined); } @@ -2534,7 +2504,6 @@ namespace ts { let entityName: EntityName = undefined; const nameIdentifier = createNameFromSymbol(type.symbol); if (qualifiedName) { - // TODO: handle checking of type arguments for qualified names? Debug.assert(!qualifiedName.right); qualifiedName.right = nameIdentifier; entityName = qualifiedName; @@ -2597,6 +2566,68 @@ namespace ts { } return typeElements.length ? typeElements : undefined; } + + function createNameFromSymbol(symbol: Symbol): Identifier; + function createNameFromSymbol(symbol: Symbol): EntityName; + function createNameFromSymbol(symbol: Symbol): EntityName { + let parentSymbol: Symbol; + symbol; enclosingDeclaration; + let meaning: SymbolFlags; + + // Get qualified name if the symbol is not a type parameter + // and there is an enclosing declaration. + let chain: Symbol[]; + const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; + if (!isTypeParameter && enclosingDeclaration) { + chain = getSymbolChain(symbol, meaning, /*endOfChain*/ true); + Debug.assert(chain && chain.length > 0); + } + else { + chain = [symbol]; + } + + const result = createEntityNameFromSymbolChain(chain, chain.length - 1); + return result; + + function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName { + Debug.assert(chain && 0 <= index && index < chain.length); + const identifier = createIdentifier(getNameOfSymbol(chain[index])); + return index > 0 ? createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; + } + + /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ + function getSymbolChain(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): Symbol[] | undefined { + let accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/false); + + if (!accessibleSymbolChain || + needsQualification(accessibleSymbolChain[0], enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { + + // Go up and add our parent. + const parent = getParentOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol); + if (parent) { + const parentChain = getSymbolChain(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false); + if (parentChain) { + accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [symbol]); + } + } + } + + if (accessibleSymbolChain) { + return accessibleSymbolChain; + } + + else if ( + // If this is the last part of outputting the symbol, always output. The cases apply only to parent symbols. + endOfChain || + // If a parent symbol is an external module, don't write it. (We prefer just `x` vs `"foo/bar".x`.) + !(!parentSymbol && ts.forEach(symbol.declarations, hasExternalModuleSymbol)) && + // If a parent symbol is an anonymous type, don't write it. + !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral))) { + + return [symbol]; + } + } + } } } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 5d63126ad0..1410c7aaab 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -333,13 +333,18 @@ namespace ts { return updateTypeOperatorNode(node, visitNode((node).type, visitor, isTypeNode)); case SyntaxKind.IndexedAccessType: return updateIndexedAccessTypeNode((node) - , visitNode((node).objectType, visitor, isTypeNode) - , visitNode((node).indexType, visitor, isTypeNode)); + , visitNode((node).objectType, visitor, isTypeNode) + , visitNode((node).indexType, visitor, isTypeNode)); case SyntaxKind.MappedType: - throw new Error("reached unsupported type in visitor."); + return updateMappedTypeNode((node) + , visitNode((node).readonlyToken, visitor, isToken) + , visitNode((node).typeParameter, visitor, isTypeParameter) + , visitNode((node).questionToken, visitor, isToken) + , visitNode((node).type, visitor, isTypeNode)); + case SyntaxKind.LiteralType: return updateLiteralTypeNode(node - , visitNode((node).literal, visitor, isExpression)); + , visitNode((node).literal, visitor, isExpression)); // Type Declarations diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index ab973bb577..5399092757 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -50,7 +50,6 @@ namespace ts.codefix { , /*questionToken*/ undefined , typeNode , /*initializer*/ undefined); - // TODO: make index signature. const propertyChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); propertyChangeTracker.insertNodeAfter(sourceFile, openBrace, property, { suffix: context.newLineCharacter }); diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 8693cc453b..e642da2159 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -4,7 +4,6 @@ namespace ts.codefix { export function newNodesToChanges(newNodes: Node[], insertAfter: Node, context: CodeFixContext) { const sourceFile = context.sourceFile; if (!(newNodes)) { - // TODO: make the appropriate value flow through gracefully. throw new Error("newNodesToChanges expects an array"); } @@ -13,6 +12,7 @@ namespace ts.codefix { for (const newNode of newNodes) { changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode, { suffix: context.newLineCharacter }); } + // TODO (aozgaa): concatenate changes into a single change. return changeTracker.getChanges(); } @@ -51,7 +51,6 @@ namespace ts.codefix { } const declaration = declarations[0] as Declaration; - // TODO: get name as identifier or computer property name, etc. const name = declaration.name ? getSynthesizedDeepClone(declaration.name) as PropertyName : undefined; const visibilityModifier = createVisibilityModifier(getModifierFlags(declaration)); const modifiers = visibilityModifier ? [visibilityModifier] : undefined; @@ -63,7 +62,6 @@ namespace ts.codefix { case SyntaxKind.PropertySignature: case SyntaxKind.PropertyDeclaration: const typeNode = checker.createTypeNode(type, enclosingDeclaration); - // TODO: add modifiers. const property = createProperty( /*decorators*/undefined , modifiers @@ -96,7 +94,6 @@ namespace ts.codefix { let signatureDeclarations = []; for (let i = 0; i < signatures.length; i++) { - // TODO: make signatures instead of methods const signature = signatures[i]; const signatureParts = checker.createSignatureParts(signature, enclosingDeclaration); signatureDeclarations.push(createMethod( diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceIndexType.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexType.ts new file mode 100644 index 0000000000..ed5ea06ebb --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexType.ts @@ -0,0 +1,10 @@ +/// + +//// interface I { +//// x: keyof X; +//// } +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` +x: keyof Y; +`); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMappedType.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMappedType.ts new file mode 100644 index 0000000000..64676bce86 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMappedType.ts @@ -0,0 +1,10 @@ +/// + +//// interface I { +//// x: { readonly [K in keyof X]: X[K] }; +//// } +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` +x: { readonly [K in keyof X]: Y[K]; }; +`); From 944b34fd4e96edb9dcb54313aabe0455b3ab51c6 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Fri, 17 Mar 2017 12:08:38 -0700 Subject: [PATCH 44/65] move commas to end of line --- src/compiler/checker.ts | 44 ++++----- src/compiler/visitor.ts | 92 +++++++++--------- src/services/codefixes/fixAddMissingMember.ts | 34 +++---- src/services/codefixes/helpers.ts | 96 +++++++++---------- 4 files changed, 132 insertions(+), 134 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 58186560e8..bb5046a8d6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2210,13 +2210,13 @@ namespace ts { // TODO: how should we clone members/modifiers? // TODO: check initializer accessibility correctly. const parameterNode = createParameter( - parameterDeclaration.decorators && parameterDeclaration.decorators.map(getSynthesizedDeepClone) - , parameterDeclaration.modifiers && parameterDeclaration.modifiers.map(getSynthesizedDeepClone) - , parameterDeclaration.dotDotDotToken && createToken(SyntaxKind.DotDotDotToken) - , getSynthesizedDeepClone(parameterDeclaration.name) - , parameterDeclaration.questionToken && createToken(SyntaxKind.QuestionToken) - , parameterTypeNode - , parameterDeclaration.initializer && getSynthesizedDeepClone(parameterDeclaration.initializer)); + parameterDeclaration.decorators && parameterDeclaration.decorators.map(getSynthesizedDeepClone), + parameterDeclaration.modifiers && parameterDeclaration.modifiers.map(getSynthesizedDeepClone), + parameterDeclaration.dotDotDotToken && createToken(SyntaxKind.DotDotDotToken), + getSynthesizedDeepClone(parameterDeclaration.name), + parameterDeclaration.questionToken && createToken(SyntaxKind.QuestionToken), + parameterTypeNode, + parameterDeclaration.initializer && getSynthesizedDeepClone(parameterDeclaration.initializer)); return parameterNode; } @@ -2558,10 +2558,10 @@ namespace ts { } else { typeElements.push(createPropertySignature( - propertyName - , optionalToken - , createTypeNodeWorker(propertyType) - , /*initializer*/undefined)); + propertyName, + optionalToken, + createTypeNodeWorker(propertyType), + /*initializer*/undefined)); } } return typeElements.length ? typeElements : undefined; @@ -2636,19 +2636,19 @@ namespace ts { const name = getNameFromIndexInfo(indexInfo); const indexingParameter = createParameter( - /*decorators*/ undefined - , /*modifiers*/ undefined - , /*dotDotDotToken*/ undefined - , name - , /*questionToken*/ undefined - , indexerTypeNode - , /*initializer*/ undefined); + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + name, + /*questionToken*/ undefined, + indexerTypeNode, + /*initializer*/ undefined); const typeNode = createTypeNode(indexInfo.type, enclosingDeclaration); return createIndexSignatureDeclaration( - [indexingParameter] - , typeNode - , /*decorators*/ undefined - , indexInfo.isReadonly ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined); + [indexingParameter], + typeNode, + /*decorators*/ undefined, + indexInfo.isReadonly ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined); } function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Declaration, flags?: TypeFormatFlags): string { diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 1410c7aaab..44b994ce57 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -2,9 +2,7 @@ /// /// -namespace ts { - - +namespace ts { export const nullTransformationContext: TransformationContext = { enableEmitNotification: noop, enableSubstitution: noop, @@ -265,25 +263,25 @@ namespace ts { case SyntaxKind.ConstructorType: case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: - return updateSignatureDeclaration(node - , nodesVisitor((node).typeParameters, visitor, isTypeParameter) - , visitParameterList((node).parameters, visitor, context, nodesVisitor) - , visitNode((node).type, visitor, isTypeNode)); + return updateSignatureDeclaration(node, + nodesVisitor((node).typeParameters, visitor, isTypeParameter), + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitNode((node).type, visitor, isTypeNode)); case SyntaxKind.MethodSignature: - return updateSignatureDeclaration(node - , nodesVisitor((node).typeParameters, visitor, isTypeParameter) - , visitParameterList((node).parameters, visitor, context, nodesVisitor) - , visitNode((node).type, visitor, isTypeNode) - , visitNode((node).name, visitor, isPropertyName) - , visitNode((node).questionToken, visitor, isToken)); + return updateSignatureDeclaration(node, + nodesVisitor((node).typeParameters, visitor, isTypeParameter), + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitNode((node).type, visitor, isTypeNode), + visitNode((node).name, visitor, isPropertyName), + visitNode((node).questionToken, visitor, isToken)); case SyntaxKind.IndexSignature: - return updateIndexSignatureDeclaration(node - , visitParameterList((node).parameters, visitor, context, nodesVisitor) - , visitNode((node).type, visitor, isTypeNode) - , nodesVisitor((node).decorators, visitor, isDecorator) - , nodesVisitor((node).modifiers, visitor, isModifier)); + return updateIndexSignatureDeclaration(node, + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitNode((node).type, visitor, isTypeNode), + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier)); case SyntaxKind.Parameter: return updateParameter(node, @@ -304,9 +302,9 @@ namespace ts { case SyntaxKind.TypePredicate: throw new Error("reached unsupported type in visitor."); case SyntaxKind.TypeReference: - return updateTypeReferenceNode(node - , visitNode((node).typeName, visitor, isEntityName) - , nodesVisitor((node).typeArguments, visitor, isTypeNode)); + return updateTypeReferenceNode(node, + visitNode((node).typeName, visitor, isEntityName), + nodesVisitor((node).typeArguments, visitor, isTypeNode)); case SyntaxKind.TypeQuery: return updateTypeQueryNode((node), visitNode((node).exprName, visitor, isEntityName)); @@ -322,8 +320,8 @@ namespace ts { case SyntaxKind.UnionType: case SyntaxKind.IntersectionType: - return updateUnionOrIntersectionTypeNode(node - , nodesVisitor((node).types, visitor, isTypeNode)); + return updateUnionOrIntersectionTypeNode(node, + nodesVisitor((node).types, visitor, isTypeNode)); case SyntaxKind.ParenthesizedType: throw new Error("reached unsupported type in visitor."); @@ -332,43 +330,43 @@ namespace ts { case SyntaxKind.TypeOperator: return updateTypeOperatorNode(node, visitNode((node).type, visitor, isTypeNode)); case SyntaxKind.IndexedAccessType: - return updateIndexedAccessTypeNode((node) - , visitNode((node).objectType, visitor, isTypeNode) - , visitNode((node).indexType, visitor, isTypeNode)); + return updateIndexedAccessTypeNode((node), + visitNode((node).objectType, visitor, isTypeNode), + visitNode((node).indexType, visitor, isTypeNode)); case SyntaxKind.MappedType: - return updateMappedTypeNode((node) - , visitNode((node).readonlyToken, visitor, isToken) - , visitNode((node).typeParameter, visitor, isTypeParameter) - , visitNode((node).questionToken, visitor, isToken) - , visitNode((node).type, visitor, isTypeNode)); + return updateMappedTypeNode((node), + visitNode((node).readonlyToken, visitor, isToken), + visitNode((node).typeParameter, visitor, isTypeParameter), + visitNode((node).questionToken, visitor, isToken), + visitNode((node).type, visitor, isTypeNode)); case SyntaxKind.LiteralType: - return updateLiteralTypeNode(node - , visitNode((node).literal, visitor, isExpression)); + return updateLiteralTypeNode(node, + visitNode((node).literal, visitor, isExpression)); // Type Declarations case SyntaxKind.TypeParameter: - return updateTypeParameterDeclaration(node - , visitNode((node).name, visitor, isIdentifier) - , visitNode((node).constraint, visitor, isTypeNode) - , visitNode((node).default, visitor, isTypeNode)); + return updateTypeParameterDeclaration(node, + visitNode((node).name, visitor, isIdentifier), + visitNode((node).constraint, visitor, isTypeNode), + visitNode((node).default, visitor, isTypeNode)); // Type members case SyntaxKind.PropertySignature: - return updatePropertySignature((node) - , visitNode((node).name, visitor, isPropertyName) - , visitNode((node).questionToken, visitor, isToken) - , visitNode((node).type, visitor, isTypeNode) - , visitNode((node).initializer, visitor, isExpression)); + return updatePropertySignature((node), + visitNode((node).name, visitor, isPropertyName), + visitNode((node).questionToken, visitor, isToken), + visitNode((node).type, visitor, isTypeNode), + visitNode((node).initializer, visitor, isExpression)); case SyntaxKind.IndexSignature: - return updateIndexSignatureDeclaration(node - , visitParameterList((node).parameters, visitor, context, nodesVisitor) - , visitNode((node).type, visitor, isTypeNode) - , nodesVisitor((node).decorators, visitor, isDecorator) - , nodesVisitor((node).modifiers, visitor, isModifier)); + return updateIndexSignatureDeclaration(node, + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitNode((node).type, visitor, isTypeNode), + nodesVisitor((node).decorators, visitor, isDecorator), + nodesVisitor((node).modifiers, visitor, isModifier)); case SyntaxKind.PropertyDeclaration: return updateProperty(node, diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 5399092757..a67c6791ba 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -44,29 +44,29 @@ namespace ts.codefix { const openBrace = getOpenBraceOfClassLike(classDeclaration, sourceFile); const property = createProperty( - /*decorators*/undefined - , /*modifiers*/ undefined - , token.getText(sourceFile) - , /*questionToken*/ undefined - , typeNode - , /*initializer*/ undefined); + /*decorators*/undefined, + /*modifiers*/ undefined, + token.getText(sourceFile), + /*questionToken*/ undefined, + typeNode, + /*initializer*/ undefined); const propertyChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); propertyChangeTracker.insertNodeAfter(sourceFile, openBrace, property, { suffix: context.newLineCharacter }); const stringTypeNode = createKeywordTypeNode(SyntaxKind.StringKeyword); const indexingParameter = createParameter( - /*decorators*/ undefined - , /*modifiers*/ undefined - , /*dotDotDotToken*/ undefined - , "x" - , /*questionToken*/ undefined - , stringTypeNode - , /*initializer*/ undefined); + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + "x", + /*questionToken*/ undefined, + stringTypeNode, + /*initializer*/ undefined); const indexSignature = createIndexSignatureDeclaration( - [indexingParameter] - , typeNode - , /*decorators*/undefined - , /*modifiers*/ undefined); + [indexingParameter], + typeNode, + /*decorators*/undefined, + /*modifiers*/ undefined); const indexSignatureChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); indexSignatureChangeTracker.insertNodeAfter(sourceFile, openBrace, indexSignature, { suffix: context.newLineCharacter }); diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index e642da2159..2d1d76ac8a 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -63,12 +63,12 @@ namespace ts.codefix { case SyntaxKind.PropertyDeclaration: const typeNode = checker.createTypeNode(type, enclosingDeclaration); const property = createProperty( - /*decorators*/undefined - , modifiers - , name - , /*questionToken*/ undefined - , typeNode - , /*initializer*/ undefined); + /*decorators*/undefined, + modifiers, + name, + /*questionToken*/ undefined, + typeNode, + /*initializer*/ undefined); return property; case SyntaxKind.MethodSignature: case SyntaxKind.MethodDeclaration: @@ -97,15 +97,15 @@ namespace ts.codefix { const signature = signatures[i]; const signatureParts = checker.createSignatureParts(signature, enclosingDeclaration); signatureDeclarations.push(createMethod( - /*decorators*/ undefined - , modifiers - , /*asteriskToken*/ undefined - , name - , optional ? createToken(SyntaxKind.QuestionToken) : undefined - , signatureParts.typeParameters - , signatureParts.parameters - , signatureParts.type - , /*body*/undefined)); + /*decorators*/ undefined, + modifiers, + /*asteriskToken*/ undefined, + name, + optional ? createToken(SyntaxKind.QuestionToken) : undefined, + signatureParts.typeParameters, + signatureParts.parameters, + signatureParts.type, + /*body*/undefined)); } if (declarations.length > signatures.length) { @@ -150,59 +150,59 @@ namespace ts.codefix { for (let i = 0; i < maxNonRestArgs; i++) { const anyType = createKeywordTypeNode(SyntaxKind.AnyKeyword); const newParameter = createParameter( - /*decorators*/ undefined - , /*modifiers*/ undefined - , /*dotDotDotToken*/ undefined - , maxArgsParameterSymbolNames[i] - , /*questionToken*/ i >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined - , anyType - , /*initializer*/ undefined); + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + maxArgsParameterSymbolNames[i], + /*questionToken*/ i >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined, + anyType, + /*initializer*/ undefined); parameters.push(newParameter); } if (someSigHasRestParameter) { const anyArrayType = createArrayTypeNode(createKeywordTypeNode(SyntaxKind.AnyKeyword)); const restParameter = createParameter( - /*decorators*/ undefined - , /*modifiers*/ undefined - , createToken(SyntaxKind.DotDotDotToken) - , maxArgsParameterSymbolNames[maxNonRestArgs] || "rest" - , /*questionToken*/ maxNonRestArgs >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined - , anyArrayType - , /*initializer*/ undefined); + /*decorators*/ undefined, + /*modifiers*/ undefined, + createToken(SyntaxKind.DotDotDotToken), + maxArgsParameterSymbolNames[maxNonRestArgs] || "rest", + /*questionToken*/ maxNonRestArgs >= minArgumentCount ? createToken(SyntaxKind.QuestionToken) : undefined, + anyArrayType, + /*initializer*/ undefined); parameters.push(restParameter); } return createStubbedMethod( - modifiers - , name - , optional - , /*typeParameters*/undefined - , parameters - , /*returnType*/ undefined); + modifiers, + name, + optional, + /*typeParameters*/undefined, + parameters, + /*returnType*/ undefined); } export function createStubbedMethod(modifiers: Modifier[], name: PropertyName, optional: boolean, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], returnType: TypeNode | undefined) { return createMethod( - /*decorators*/undefined - , modifiers - , /*asteriskToken*/undefined - , name - , optional ? createToken(SyntaxKind.QuestionToken) : undefined - , typeParameters - , parameters - , returnType - , createStubbedMethodBody()); + /*decorators*/undefined, + modifiers, + /*asteriskToken*/undefined, + name, + optional ? createToken(SyntaxKind.QuestionToken) : undefined, + typeParameters, + parameters, + returnType, + createStubbedMethodBody()); } function createStubbedMethodBody() { return createBlock( [createThrow( createNew( - createIdentifier('Error') - , /*typeArguments*/undefined - , [createLiteral('Method not implemented.')]))] - , /*multiline*/true); + createIdentifier('Error'), + /*typeArguments*/undefined, + [createLiteral('Method not implemented.')]))], + /*multiline*/true); } function createVisibilityModifier(flags: ModifierFlags) { From d3661f5b6ad801922e11f937c658ce002d2d0109 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Fri, 17 Mar 2017 13:55:39 -0700 Subject: [PATCH 45/65] add tokenVisitor --- src/compiler/core.ts | 3 +++ src/compiler/visitor.ts | 28 ++++++++++++++-------------- src/services/textChanges.ts | 2 +- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index c380a39611..3f466e726d 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1067,6 +1067,9 @@ namespace ts { /** Does nothing. */ export function noop(): void {} + /** Returns its first argument. */ + export function identity(x: X): X { return x; } + /** Throws an error because a function is not implemented. */ export function notImplemented(): never { throw new Error("Not implemented"); diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 44b994ce57..57de104b0c 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -224,9 +224,9 @@ namespace ts { * @param visitor The callback used to visit each child. * @param context A lexical environment context for the visitor. */ - export function visitEachChild(node: T | undefined, visitor: Visitor, context: TransformationContext, nodesVisitor?: typeof visitNodes): T | undefined; + export function visitEachChild(node: T | undefined, visitor: Visitor, context: TransformationContext, nodesVisitor?: typeof visitNodes, tokenVisitor?: Visitor): T | undefined; - export function visitEachChild(node: Node, visitor: Visitor, context: TransformationContext, nodesVisitor = visitNodes): Node { + export function visitEachChild(node: Node, visitor: Visitor, context: TransformationContext, nodesVisitor = visitNodes, tokenVisitor?: Visitor): Node { if (node === undefined) { return undefined; } @@ -274,7 +274,7 @@ namespace ts { visitParameterList((node).parameters, visitor, context, nodesVisitor), visitNode((node).type, visitor, isTypeNode), visitNode((node).name, visitor, isPropertyName), - visitNode((node).questionToken, visitor, isToken)); + visitNode((node).questionToken, tokenVisitor, isToken)); case SyntaxKind.IndexSignature: return updateIndexSignatureDeclaration(node, @@ -287,9 +287,9 @@ namespace ts { return updateParameter(node, nodesVisitor((node).decorators, visitor, isDecorator), nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).dotDotDotToken, visitor), + visitNode((node).dotDotDotToken, tokenVisitor, isToken), visitNode((node).name, visitor, isBindingName), - visitNode((node).questionToken, visitor, isToken), + visitNode((node).questionToken, tokenVisitor, isToken), visitNode((node).type, visitor, isTypeNode), visitNode((node).initializer, visitor, isExpression)); @@ -335,9 +335,9 @@ namespace ts { visitNode((node).indexType, visitor, isTypeNode)); case SyntaxKind.MappedType: return updateMappedTypeNode((node), - visitNode((node).readonlyToken, visitor, isToken), + visitNode((node).readonlyToken, tokenVisitor, isToken), visitNode((node).typeParameter, visitor, isTypeParameter), - visitNode((node).questionToken, visitor, isToken), + visitNode((node).questionToken, tokenVisitor, isToken), visitNode((node).type, visitor, isTypeNode)); case SyntaxKind.LiteralType: @@ -357,7 +357,7 @@ namespace ts { case SyntaxKind.PropertySignature: return updatePropertySignature((node), visitNode((node).name, visitor, isPropertyName), - visitNode((node).questionToken, visitor, isToken), + visitNode((node).questionToken, tokenVisitor, isToken), visitNode((node).type, visitor, isTypeNode), visitNode((node).initializer, visitor, isExpression)); @@ -380,9 +380,9 @@ namespace ts { return updateMethod(node, nodesVisitor((node).decorators, visitor, isDecorator), nodesVisitor((node).modifiers, visitor, isModifier), - visitNode((node).asteriskToken, visitor, isToken), + visitNode((node).asteriskToken, tokenVisitor, isToken), visitNode((node).name, visitor, isPropertyName), - visitNode((node).questionToken, visitor, isToken), + visitNode((node).questionToken, tokenVisitor, isToken), nodesVisitor((node).typeParameters, visitor, isTypeParameter), visitParameterList((node).parameters, visitor, context, nodesVisitor), visitNode((node).type, visitor, isTypeNode), @@ -423,7 +423,7 @@ namespace ts { case SyntaxKind.BindingElement: return updateBindingElement(node, - (node).dotDotDotToken, + visitNode((node).dotDotDotToken, tokenVisitor, isToken), visitNode((node).propertyName, visitor, isPropertyName), visitNode((node).name, visitor, isBindingName), visitNode((node).initializer, visitor, isExpression)); @@ -476,7 +476,7 @@ namespace ts { case SyntaxKind.FunctionExpression: return updateFunctionExpression(node, nodesVisitor((node).modifiers, visitor, isModifier), - (node).asteriskToken, + visitNode((node).asteriskToken, tokenVisitor, isToken), visitNode((node).name, visitor, isIdentifier), nodesVisitor((node).typeParameters, visitor, isTypeParameter), visitParameterList((node).parameters, visitor, context, nodesVisitor), @@ -533,7 +533,7 @@ namespace ts { case SyntaxKind.YieldExpression: return updateYield(node, - (node).asteriskToken, + visitNode((node).asteriskToken, tokenVisitor, isToken), visitNode((node).expression, visitor, isExpression)); case SyntaxKind.SpreadElement: @@ -669,7 +669,7 @@ namespace ts { return updateFunctionDeclaration(node, nodesVisitor((node).decorators, visitor, isDecorator), nodesVisitor((node).modifiers, visitor, isModifier), - (node).asteriskToken, + visitNode((node).asteriskToken, tokenVisitor, isToken), visitNode((node).name, visitor, isIdentifier), nodesVisitor((node).typeParameters, visitor, isTypeParameter), visitParameterList((node).parameters, visitor, context, nodesVisitor), diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index b7a8b101c0..ddcbf2fa2f 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -529,7 +529,7 @@ namespace ts.textChanges { } function assignPositionsToNode(node: Node): Node { - const visited = visitEachChild(node, assignPositionsToNode, nullTransformationContext, assignPositionsToNodeArray); + const visited = visitEachChild(node, assignPositionsToNode, nullTransformationContext, assignPositionsToNodeArray, assignPositionsToNode); // create proxy node for non synthesized nodes const newNode = nodeIsSynthesized(visited) ? visited From ff061a1b934406a41f0fe5f3d95be5fae5793743 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Fri, 17 Mar 2017 14:56:04 -0700 Subject: [PATCH 46/65] consolidate edits --- ...sDoesntImplementInheritedAbstractMember.ts | 2 +- .../fixClassIncorrectlyImplementsInterface.ts | 4 ++-- src/services/codefixes/helpers.ts | 21 ++++++++++++++----- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts index 513853e461..62f89b1a21 100644 --- a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts +++ b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts @@ -31,7 +31,7 @@ namespace ts.codefix { const newNodes = createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, checker); const changes = newNodesToChanges(newNodes, getOpenBraceOfClassLike(classDeclaration, sourceFile), context); - if(changes && changes.length > 0) { + if (changes && changes.length > 0) { return [{ description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class), changes diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index de34676eda..365fd35801 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -45,13 +45,13 @@ namespace ts.codefix { function createAndAddMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind, hasIndexSigOfKind: boolean, newNodes: Node[]): void { if (hasIndexSigOfKind) { - return undefined; + return; } const indexInfoOfKind = checker.getIndexInfoOfType(type, kind); if (!indexInfoOfKind) { - return undefined; + return; } const newIndexSignatureDeclaration = checker.createIndexSignatureFromIndexInfo(indexInfoOfKind, kind, classDeclaration); newNodes.push(newIndexSignatureDeclaration); diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 2d1d76ac8a..4a9ee25228 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -3,17 +3,28 @@ namespace ts.codefix { export function newNodesToChanges(newNodes: Node[], insertAfter: Node, context: CodeFixContext) { const sourceFile = context.sourceFile; - if (!(newNodes)) { - throw new Error("newNodesToChanges expects an array"); - } const changeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); for (const newNode of newNodes) { changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode, { suffix: context.newLineCharacter }); } - // TODO (aozgaa): concatenate changes into a single change. - return changeTracker.getChanges(); + + const changes = changeTracker.getChanges(); + if (!(changes && changes.length > 0)) { + return changes; + } + + Debug.assert(changes.length === 1); + const consolidatedChanges: FileTextChanges[] = [{ + fileName: changes[0].fileName, + textChanges: [{ + span: changes[0].textChanges[0].span, + newText: changes[0].textChanges.reduce((prev, cur) => prev + cur.newText, "") + }] + + }] + return consolidatedChanges; } /** From 5b739cf78ce901ae556c725221873801b45cd1a4 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Tue, 21 Mar 2017 22:54:06 -0700 Subject: [PATCH 47/65] respond to comments --- src/compiler/checker.ts | 118 +++++++++++++----- src/compiler/core.ts | 3 - src/compiler/transformers/es2017.ts | 2 +- src/compiler/transformers/esnext.ts | 4 +- src/compiler/transformers/ts.ts | 2 +- src/compiler/types.ts | 6 +- src/compiler/utilities.ts | 6 +- src/services/codefixes/fixAddMissingMember.ts | 2 +- .../fixClassIncorrectlyImplementsInterface.ts | 2 +- src/services/codefixes/helpers.ts | 40 +++--- ...assExtendAbstractMethodAnonymousClass.1.ts | 33 +++++ ...ClassImplementClassMemberAnonymousClass.ts | 20 +++ ...FixClassImplementClassPropertyTypeQuery.ts | 10 ++ 13 files changed, 180 insertions(+), 68 deletions(-) create mode 100644 tests/cases/fourslash/codeFixClassExtendAbstractMethodAnonymousClass.1.ts create mode 100644 tests/cases/fourslash/codeFixClassImplementClassMemberAnonymousClass.ts create mode 100644 tests/cases/fourslash/codeFixClassImplementClassPropertyTypeQuery.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bb5046a8d6..dabc5407e1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -106,9 +106,9 @@ namespace ts { getParameterType: getTypeAtPosition, getReturnTypeOfSignature, getNonNullableType, - createTypeNode, - createIndexSignatureFromIndexInfo, - createSignatureParts, + typeToTypeNode, + indexInfoToIndexSignatureDeclaration, + signatureToSignatureDeclaration, getSymbolsInScope: (location, meaning) => { location = getParseTreeNode(location); return location ? getSymbolsInScope(location, meaning) : []; @@ -2196,8 +2196,8 @@ namespace ts { return undefined; } - const constraint = createTypeNode(getConstraintFromTypeParameter(type), enclosingDeclaration); - const defaultParameter = createTypeNode(getDefaultFromTypeParameter(type), enclosingDeclaration); + const constraint = typeToTypeNode(getConstraintFromTypeParameter(type), enclosingDeclaration); + const defaultParameter = typeToTypeNode(getDefaultFromTypeParameter(type), enclosingDeclaration); const name = symbolToString(type.symbol); return createTypeParameterDeclaration(name, constraint, defaultParameter); @@ -2206,7 +2206,7 @@ namespace ts { function createParameterDeclarationFromSymbol(parameterSymbol: Symbol, enclosingDeclaration: Node): ParameterDeclaration { const parameterDeclaration = parameterSymbol.declarations[0] as ParameterDeclaration; const parameterType = getTypeOfSymbol(parameterSymbol); - const parameterTypeNode = createTypeNode(parameterType, enclosingDeclaration); + const parameterTypeNode = typeToTypeNode(parameterType, enclosingDeclaration); // TODO: how should we clone members/modifiers? // TODO: check initializer accessibility correctly. const parameterNode = createParameter( @@ -2220,32 +2220,35 @@ namespace ts { return parameterNode; } - function createSignatureParts(signature: Signature, enclosingDeclaration: Node): SignatureParts { - return { - typeParameters: signature.typeParameters && signature.typeParameters.map(parameter => createTypeParameterDeclarationFromType(parameter,enclosingDeclaration)), - parameters: signature.parameters.map(parameter => createParameterDeclarationFromSymbol(parameter,enclosingDeclaration)), - type: createTypeNodeExceptAny(getReturnTypeOfSignature(signature)) - } + function signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration: Node): SignatureDeclaration { + const typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => createTypeParameterDeclarationFromType(parameter, enclosingDeclaration)); + const parameters = signature.parameters.map(parameter => createParameterDeclarationFromSymbol(parameter, enclosingDeclaration)); + const type = createTypeNodeExceptAny(getReturnTypeOfSignature(signature)); + + return createSignatureDeclaration(kind, typeParameters, parameters, type); function createTypeNodeExceptAny(type: Type): TypeNode | undefined { - const typeNode = createTypeNode(type, enclosingDeclaration); + const typeNode = typeToTypeNode(type, enclosingDeclaration); return typeNode && typeNode.kind !== SyntaxKind.AnyKeyword ? typeNode : undefined; } } - function createTypeNode(type: Type, enclosingDeclaration: Node): TypeNode { - let undefinedArgumentIsError = true; + + // function typeToDisplayParts + + function typeToTypeNode(type: Type, enclosingDeclaration: Node, returnNodeOnError?: boolean): TypeNode { let encounteredError = false; let inObjectTypeLiteral = false; let checkAlias = true; let symbolStack: Symbol[] = undefined; const result = createTypeNodeWorker(type); - return encounteredError ? undefined: result; + // returnNodeOnError = true; // TODO: unset. + return encounteredError && !returnNodeOnError ? undefined: result; function createTypeNodeWorker(type: Type): TypeNode { if (!type) { - if (undefinedArgumentIsError) { encounteredError = true; } + encounteredError = true; return undefined; } @@ -2364,7 +2367,7 @@ namespace ts { const typeParameter = getTypeParameterFromMappedType(type); const typeParameterNode = createTypeParameterDeclarationFromType(typeParameter, enclosingDeclaration); - const templateTypeNode = createTypeNode(getTemplateTypeFromMappedType(type), enclosingDeclaration); + const templateTypeNode = typeToTypeNode(getTemplateTypeFromMappedType(type), enclosingDeclaration); const readonlyToken = (type).declaration && (type).declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; const questionToken = (type).declaration && (type).declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; @@ -2438,13 +2441,11 @@ namespace ts { if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { const signature = resolved.callSignatures[0]; - const signatureParts = createSignatureParts(signature, enclosingDeclaration); - return createSignatureDeclaration(SyntaxKind.FunctionType, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type); + return signatureToSignatureDeclaration(signature, SyntaxKind.FunctionType, enclosingDeclaration); } if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { const signature = resolved.constructSignatures[0]; - const signatureParts = createSignatureParts(signature, enclosingDeclaration); - return createSignatureDeclaration(SyntaxKind.ConstructorType, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type); + return signatureToSignatureDeclaration(signature, SyntaxKind.ConstructorType, enclosingDeclaration); } } @@ -2520,18 +2521,16 @@ namespace ts { function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] { const typeElements: TypeElement[] = []; for (const signature of resolvedType.callSignatures) { - const signatureParts = createSignatureParts(signature, enclosingDeclaration); - typeElements.push(createSignatureDeclaration(SyntaxKind.CallSignature, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type)); + typeElements.push(signatureToSignatureDeclaration(signature, SyntaxKind.CallSignature, enclosingDeclaration)); } for (const signature of resolvedType.constructSignatures) { - const signatureParts = createSignatureParts(signature, enclosingDeclaration); - typeElements.push(createSignatureDeclaration(SyntaxKind.ConstructSignature, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type)); + typeElements.push(signatureToSignatureDeclaration(signature, SyntaxKind.ConstructSignature, enclosingDeclaration)); } if (resolvedType.stringIndexInfo) { - typeElements.push(createIndexSignatureFromIndexInfo(resolvedType.stringIndexInfo, IndexKind.String, enclosingDeclaration)); + typeElements.push(indexInfoToIndexSignatureDeclaration(resolvedType.stringIndexInfo, IndexKind.String, enclosingDeclaration)); } if (resolvedType.numberIndexInfo) { - typeElements.push(createIndexSignatureFromIndexInfo(resolvedType.numberIndexInfo, IndexKind.Number, enclosingDeclaration)); + typeElements.push(indexInfoToIndexSignatureDeclaration(resolvedType.numberIndexInfo, IndexKind.Number, enclosingDeclaration)); } const properties = resolvedType.properties; @@ -2550,8 +2549,8 @@ namespace ts { if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length) { const signatures = getSignaturesOfType(propertyType, SignatureKind.Call); for (const signature of signatures) { - const signatureParts = createSignatureParts(signature, enclosingDeclaration); - const methodDeclaration = createSignatureDeclaration(SyntaxKind.MethodSignature, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type, propertyName, optionalToken); + const methodDeclaration = signatureToSignatureDeclaration(signature, SyntaxKind.MethodSignature, enclosingDeclaration); + methodDeclaration.name = propertyName; methodDeclaration.questionToken = optionalToken; typeElements.push(methodDeclaration); } @@ -2571,7 +2570,6 @@ namespace ts { function createNameFromSymbol(symbol: Symbol): EntityName; function createNameFromSymbol(symbol: Symbol): EntityName { let parentSymbol: Symbol; - symbol; enclosingDeclaration; let meaning: SymbolFlags; // Get qualified name if the symbol is not a type parameter @@ -2580,18 +2578,49 @@ namespace ts { const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; if (!isTypeParameter && enclosingDeclaration) { chain = getSymbolChain(symbol, meaning, /*endOfChain*/ true); + // TODO: check whether type pointed to by symbol requires type arguments to be printed. Debug.assert(chain && chain.length > 0); } else { chain = [symbol]; } + parentSymbol = undefined; const result = createEntityNameFromSymbolChain(chain, chain.length - 1); return result; function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName { Debug.assert(chain && 0 <= index && index < chain.length); - const identifier = createIdentifier(getNameOfSymbol(chain[index])); + // const parentIndex = index - 1; + const symbol = chain[index]; + let typeParameterString = ""; + if(index > 0) { + // TODO: is the parentSymbol wrong? + const parentSymbol = chain[index - 1]; + let typeParameters: TypeParameter[]; + if(getCheckFlags(symbol) & CheckFlags.Instantiated) { + typeParameters = getTypeParametersOfClassOrInterface(parentSymbol); + } + else { + const targetSymbol = getTargetSymbol(parentSymbol); + if(targetSymbol.flags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeAlias)) { + typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); + } + } + if(typeParameters && typeParameters.length > 0) { + encounteredError = true; + const writer = getSingleLineStringWriter(); + const displayBuilder = getSymbolDisplayBuilder(); + displayBuilder.buildDisplayForTypeParametersAndDelimiters(typeParameters, writer, enclosingDeclaration, 0); + typeParameterString = writer.string(); + releaseStringWriter(writer); + + } + } + const symbolName = getNameOfSymbol(symbol); + const symbolNameWithTypeParameters = typeParameterString.length > 0 ? `${symbolName}<${typeParameterString}>` : symbolName; + let identifier = createIdentifier(symbolNameWithTypeParameters); + return index > 0 ? createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; } @@ -2628,10 +2657,31 @@ namespace ts { } } } + + function getNameOfSymbol(symbol: Symbol): string { + if (symbol.declarations && symbol.declarations.length) { + const declaration = symbol.declarations[0]; + if (declaration.name) { + return declarationNameToString(declaration.name); + } + if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) { + return declarationNameToString((declaration.parent).name); + } + encounteredError = true; + switch (declaration.kind) { + case SyntaxKind.ClassExpression: + return "(Anonymous class)"; + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return "(Anonymous function)"; + } + } + return symbol.name; + } } } - function createIndexSignatureFromIndexInfo(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node): IndexSignatureDeclaration { + function indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node): IndexSignatureDeclaration { const indexerTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword); const name = getNameFromIndexInfo(indexInfo); @@ -2643,7 +2693,7 @@ namespace ts { /*questionToken*/ undefined, indexerTypeNode, /*initializer*/ undefined); - const typeNode = createTypeNode(indexInfo.type, enclosingDeclaration); + const typeNode = typeToTypeNode(indexInfo.type, enclosingDeclaration); return createIndexSignatureDeclaration( [indexingParameter], typeNode, diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 3f466e726d..c380a39611 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1067,9 +1067,6 @@ namespace ts { /** Does nothing. */ export function noop(): void {} - /** Returns its first argument. */ - export function identity(x: X): X { return x; } - /** Throws an error because a function is not implemented. */ export function notImplemented(): never { throw new Error("Not implemented"); diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index 9c7bcf5b2a..088c57866d 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -125,7 +125,7 @@ namespace ts { visitNodes(node.modifiers, visitor, isModifier), node.asteriskToken, node.name, - node.questionToken, + /*questionToken*/ undefined, /*typeParameters*/ undefined, visitParameterList(node.parameters, visitor, context), /*type*/ undefined, diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 2475e51119..83e84e6eab 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -475,7 +475,7 @@ namespace ts { /*modifiers*/ undefined, node.dotDotDotToken, getGeneratedNameForNode(node), - node.questionToken, + /*questionToken*/ undefined, /*type*/ undefined, visitNode(node.initializer, visitor, isExpression) ); @@ -541,7 +541,7 @@ namespace ts { ? undefined : node.asteriskToken, visitNode(node.name, visitor, isPropertyName), - visitNode(node.questionToken, visitor, isToken), + visitNode(/*questionToken*/ undefined, visitor, isToken), /*typeParameters*/ undefined, visitParameterList(node.parameters, visitor, context), /*type*/ undefined, diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index bbc588ee9f..6dc643b954 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -2049,7 +2049,7 @@ namespace ts { visitNodes(node.modifiers, modifierVisitor, isModifier), node.asteriskToken, visitPropertyNameOfClassElement(node), - node.questionToken, + /*questionToken*/ undefined, /*typeParameters*/ undefined, visitParameterList(node.parameters, visitor, context), /*type*/ undefined, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2579ee2eaf..5ccdd79f19 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2473,11 +2473,11 @@ namespace ts { getNonNullableType(type: Type): Type; /** Note that the resulting nodes cannot be checked. */ - createTypeNode(type: Type, enclosingDeclaration: Node): TypeNode; + typeToTypeNode(type: Type, enclosingDeclaration: Node): TypeNode; /** Note that the resulting nodes cannot be checked. */ - createIndexSignatureFromIndexInfo(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node): IndexSignatureDeclaration; + indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node): IndexSignatureDeclaration; /** Note that the resulting nodes cannot be checked. */ - createSignatureParts(signature: Signature, enclosingDeclaration: Node): SignatureParts; + signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration: Node): SignatureDeclaration; getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; getSymbolAtLocation(node: Node): Symbol; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 47642291c2..656b3c5db4 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -744,8 +744,8 @@ namespace ts { // // let a: A.B.C; // - // Calling isPartOfTypeNode would consider the qualified name A.B a type node. Only C or - // A.B.C is a type node. + // Calling isPartOfTypeNode would consider the qualified name A.B a type node. + // Only C and A.B.C are type nodes. if (SyntaxKind.FirstTypeNode <= parent.kind && parent.kind <= SyntaxKind.LastTypeNode) { return true; } @@ -3735,7 +3735,7 @@ namespace ts { * of a TypeNode. */ export function isTypeNode(node: Node): node is TypeNode { - return isTypeNodeKind(node.kind); + return node && isTypeNodeKind(node.kind) && (!node.parent || isPartOfTypeNode(node)); } // Binding patterns diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index a67c6791ba..bf706e7ded 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -38,7 +38,7 @@ namespace ts.codefix { const checker = context.program.getTypeChecker(); const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(binaryExpression.right))); - typeNode = checker.createTypeNode(widenedType, classDeclaration) || typeNode; + typeNode = checker.typeToTypeNode(widenedType, classDeclaration) || typeNode; } const openBrace = getOpenBraceOfClassLike(classDeclaration, sourceFile); diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index 365fd35801..3b1b99febb 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -53,7 +53,7 @@ namespace ts.codefix { if (!indexInfoOfKind) { return; } - const newIndexSignatureDeclaration = checker.createIndexSignatureFromIndexInfo(indexInfoOfKind, kind, classDeclaration); + const newIndexSignatureDeclaration = checker.indexInfoToIndexSignatureDeclaration(indexInfoOfKind, kind, classDeclaration); newNodes.push(newIndexSignatureDeclaration); } diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 4a9ee25228..3b4a75caa7 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -64,7 +64,7 @@ namespace ts.codefix { const declaration = declarations[0] as Declaration; const name = declaration.name ? getSynthesizedDeepClone(declaration.name) as PropertyName : undefined; const visibilityModifier = createVisibilityModifier(getModifierFlags(declaration)); - const modifiers = visibilityModifier ? [visibilityModifier] : undefined; + const modifiers = visibilityModifier ? createNodeArray([visibilityModifier]) : undefined; const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration)); switch (declaration.kind) { @@ -72,7 +72,7 @@ namespace ts.codefix { case SyntaxKind.SetAccessor: case SyntaxKind.PropertySignature: case SyntaxKind.PropertyDeclaration: - const typeNode = checker.createTypeNode(type, enclosingDeclaration); + const typeNode = checker.typeToTypeNode(type, enclosingDeclaration); const property = createProperty( /*decorators*/undefined, modifiers, @@ -99,30 +99,32 @@ namespace ts.codefix { if (declarations.length === 1) { Debug.assert(signatures.length === 1); const signature = signatures[0]; - const signatureParts = checker.createSignatureParts(signature, enclosingDeclaration); - return createStubbedMethod(modifiers, name, optional, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type); + const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration) as MethodDeclaration; + signatureDeclaration.modifiers = modifiers; + signatureDeclaration.name = name; + signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; + signatureDeclaration.body = createStubbedMethodBody(); + return signatureDeclaration; } let signatureDeclarations = []; for (let i = 0; i < signatures.length; i++) { const signature = signatures[i]; - const signatureParts = checker.createSignatureParts(signature, enclosingDeclaration); - signatureDeclarations.push(createMethod( - /*decorators*/ undefined, - modifiers, - /*asteriskToken*/ undefined, - name, - optional ? createToken(SyntaxKind.QuestionToken) : undefined, - signatureParts.typeParameters, - signatureParts.parameters, - signatureParts.type, - /*body*/undefined)); + const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration) as MethodDeclaration; + signatureDeclaration.modifiers = modifiers; + signatureDeclaration.name = name; + signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; + signatureDeclarations.push(signatureDeclaration); } if (declarations.length > signatures.length) { let signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration); - const signatureParts = checker.createSignatureParts(signature, enclosingDeclaration); - signatureDeclarations.push(createStubbedMethod(modifiers, name, optional, signatureParts.typeParameters, signatureParts.parameters, signatureParts.type)); + const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration) as MethodDeclaration; + signatureDeclaration.modifiers = modifiers; + signatureDeclaration.name = name; + signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; + signatureDeclaration.body = createStubbedMethodBody(); + signatureDeclarations.push(signatureDeclaration); } else { Debug.assert(declarations.length === signatures.length); @@ -195,9 +197,9 @@ namespace ts.codefix { export function createStubbedMethod(modifiers: Modifier[], name: PropertyName, optional: boolean, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], returnType: TypeNode | undefined) { return createMethod( - /*decorators*/undefined, + /*decorators*/ undefined, modifiers, - /*asteriskToken*/undefined, + /*asteriskToken*/ undefined, name, optional ? createToken(SyntaxKind.QuestionToken) : undefined, typeParameters, diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethodAnonymousClass.1.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethodAnonymousClass.1.ts new file mode 100644 index 0000000000..134ce17376 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassExtendAbstractMethodAnonymousClass.1.ts @@ -0,0 +1,33 @@ +/// + +class A { + foo() { + return class { x: number; } + } + bar() { + return new class { x: number; } + } +} +class B { + foo() { + return class { + x: X; + } + } +} + + +class D extends A { } + +verify.rangeAfterCodeFix(` + f(a: number, b: string): boolean; + f(a: number, b: string): this; + f(a: string, b: number): Function; + f(a: string): Function; + f(a: any, b?: any) { + throw new Error("Method not implemented."); + } + foo(): number { + throw new Error("Method not implemented."); + } +`); diff --git a/tests/cases/fourslash/codeFixClassImplementClassMemberAnonymousClass.ts b/tests/cases/fourslash/codeFixClassImplementClassMemberAnonymousClass.ts new file mode 100644 index 0000000000..a8a024d194 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementClassMemberAnonymousClass.ts @@ -0,0 +1,20 @@ +/// + +//// class A { +//// foo() { +//// return class { x: number; } +//// } +//// bar() { +//// return new class { x: number; } +//// } +//// } +//// class C implements A {[| |]} + +verify.rangeAfterCodeFix(` + foo() { + throw new Error("Method not implemented."); + } + bar() { + throw new Error("Method not implemented."); + } +`); diff --git a/tests/cases/fourslash/codeFixClassImplementClassPropertyTypeQuery.ts b/tests/cases/fourslash/codeFixClassImplementClassPropertyTypeQuery.ts new file mode 100644 index 0000000000..4a5663a8ba --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementClassPropertyTypeQuery.ts @@ -0,0 +1,10 @@ +/// + +//// class A { +//// A: typeof A; +//// } +//// class D implements A {[| |]} + +verify.rangeAfterCodeFix(` + A: typeof A; +`); From c4ddc5bffad8ab3609270a17ac6be40aaaa1ab7b Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Wed, 22 Mar 2017 12:54:51 -0700 Subject: [PATCH 48/65] Respond to more comments --- src/compiler/checker.ts | 72 ++++++++++--------- src/compiler/factory.ts | 20 ++---- src/services/codefixes/fixAddMissingMember.ts | 4 +- src/services/codefixes/helpers.ts | 2 +- src/services/textChanges.ts | 6 +- 5 files changed, 50 insertions(+), 54 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dabc5407e1..c4eaed3fde 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2191,7 +2191,7 @@ namespace ts { return result; } - function createTypeParameterDeclarationFromType(type: TypeParameter, enclosingDeclaration: Node): TypeParameterDeclaration { + function typeParameterToDeclaration(type: TypeParameter, enclosingDeclaration?: Node): TypeParameterDeclaration { if (!(type && type.symbol && type.flags & TypeFlags.TypeParameter)) { return undefined; } @@ -2199,35 +2199,36 @@ namespace ts { const constraint = typeToTypeNode(getConstraintFromTypeParameter(type), enclosingDeclaration); const defaultParameter = typeToTypeNode(getDefaultFromTypeParameter(type), enclosingDeclaration); + // TODO: use method internal to typeToTypeNode. const name = symbolToString(type.symbol); return createTypeParameterDeclaration(name, constraint, defaultParameter); } - function createParameterDeclarationFromSymbol(parameterSymbol: Symbol, enclosingDeclaration: Node): ParameterDeclaration { + function symbolToParameterDeclaration(parameterSymbol: Symbol, enclosingDeclaration?: Node): ParameterDeclaration { const parameterDeclaration = parameterSymbol.declarations[0] as ParameterDeclaration; const parameterType = getTypeOfSymbol(parameterSymbol); const parameterTypeNode = typeToTypeNode(parameterType, enclosingDeclaration); // TODO: how should we clone members/modifiers? // TODO: check initializer accessibility correctly. const parameterNode = createParameter( - parameterDeclaration.decorators && parameterDeclaration.decorators.map(getSynthesizedDeepClone), - parameterDeclaration.modifiers && parameterDeclaration.modifiers.map(getSynthesizedDeepClone), + parameterDeclaration.decorators, + parameterDeclaration.modifiers, parameterDeclaration.dotDotDotToken && createToken(SyntaxKind.DotDotDotToken), - getSynthesizedDeepClone(parameterDeclaration.name), + parameterDeclaration.name, parameterDeclaration.questionToken && createToken(SyntaxKind.QuestionToken), parameterTypeNode, - parameterDeclaration.initializer && getSynthesizedDeepClone(parameterDeclaration.initializer)); + parameterDeclaration.initializer); return parameterNode; } - function signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration: Node): SignatureDeclaration { - const typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => createTypeParameterDeclarationFromType(parameter, enclosingDeclaration)); - const parameters = signature.parameters.map(parameter => createParameterDeclarationFromSymbol(parameter, enclosingDeclaration)); - const type = createTypeNodeExceptAny(getReturnTypeOfSignature(signature)); + function signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node): SignatureDeclaration { + const typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, enclosingDeclaration)); + const parameters = signature.parameters.map(parameter => symbolToParameterDeclaration(parameter, enclosingDeclaration)); + const type = typeToTypeNodeExceptAny(getReturnTypeOfSignature(signature)); return createSignatureDeclaration(kind, typeParameters, parameters, type); - function createTypeNodeExceptAny(type: Type): TypeNode | undefined { + function typeToTypeNodeExceptAny(type: Type): TypeNode | undefined { const typeNode = typeToTypeNode(type, enclosingDeclaration); return typeNode && typeNode.kind !== SyntaxKind.AnyKeyword ? typeNode : undefined; } @@ -2236,17 +2237,17 @@ namespace ts { // function typeToDisplayParts - function typeToTypeNode(type: Type, enclosingDeclaration: Node, returnNodeOnError?: boolean): TypeNode { + function typeToTypeNode(type: Type, enclosingDeclaration?: Node, returnNodeOnError?: boolean): TypeNode { let encounteredError = false; let inObjectTypeLiteral = false; let checkAlias = true; let symbolStack: Symbol[] = undefined; - const result = createTypeNodeWorker(type); + const result = typeToTypeNodeWorker(type); // returnNodeOnError = true; // TODO: unset. return encounteredError && !returnNodeOnError ? undefined: result; - function createTypeNodeWorker(type: Type): TypeNode { + function typeToTypeNodeWorker(type: Type): TypeNode { if (!type) { encounteredError = true; return undefined; @@ -2308,7 +2309,7 @@ namespace ts { if (objectFlags & ObjectFlags.Reference) { Debug.assert(!!(type.flags & TypeFlags.Object)); - return createTypeReferenceNodeFromType(type); + return typeReferenceToTypeReferenceNode(type); } if (objectFlags & ObjectFlags.ClassOrInterface) { Debug.assert(!!(type.flags & TypeFlags.Object)); @@ -2347,25 +2348,25 @@ namespace ts { if (type.flags & TypeFlags.Index) { const indexedType = (type).type; - const indexTypeNode = createTypeNodeWorker(indexedType); + const indexTypeNode = typeToTypeNodeWorker(indexedType); return createTypeOperatorNode(indexTypeNode); } if (type.flags & TypeFlags.IndexedAccess) { - const objectTypeNode = createTypeNodeWorker((type).objectType); - const indexTypeNode = createTypeNodeWorker((type).indexType); + const objectTypeNode = typeToTypeNodeWorker((type).objectType); + const indexTypeNode = typeToTypeNodeWorker((type).indexType); return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); } Debug.fail("Should be unreachable."); function mapToTypeNodeArray(types: Type[]): NodeArray { - return types && asNodeArray(types.map(createTypeNodeWorker) as TypeNode[]); + return types && asNodeArray(types.map(typeToTypeNodeWorker) as TypeNode[]); } function createMappedTypeNodeFromType(type: MappedType) { Debug.assert(!!(type.flags & TypeFlags.Object)); const typeParameter = getTypeParameterFromMappedType(type); - const typeParameterNode = createTypeParameterDeclarationFromType(typeParameter, enclosingDeclaration); + const typeParameterNode = typeParameterToDeclaration(typeParameter, enclosingDeclaration); const templateTypeNode = typeToTypeNode(getTemplateTypeFromMappedType(type), enclosingDeclaration); const readonlyToken = (type).declaration && (type).declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; @@ -2464,10 +2465,10 @@ namespace ts { } } - function createTypeReferenceNodeFromType(type: TypeReference) { + function typeReferenceToTypeReferenceNode(type: TypeReference) { const typeArguments: Type[] = type.typeArguments || emptyArray; if (type.target === globalArrayType) { - const elementType = createTypeNodeWorker(typeArguments[0]); + const elementType = typeToTypeNodeWorker(typeArguments[0]); return createArrayTypeNode(elementType); } else if (type.target.objectFlags & ObjectFlags.Tuple) { @@ -2489,21 +2490,20 @@ namespace ts { // When type parameters are their own type arguments for the whole group (i.e. we have // the default outer type arguments), we don't show the group. if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { - const name = createNameFromSymbol(parent); - const qualifiedNamePart = name; + const qualifiedNamePart = createNameFromSymbol(parent, /*mustBeIdentifier*/ true); if (!qualifiedName) { - qualifiedName = createQualifiedName(qualifiedNamePart, /*right*/undefined); + qualifiedName = createQualifiedName(qualifiedNamePart, /*right*/ undefined); } else { Debug.assert(!qualifiedName.right); qualifiedName.right = qualifiedNamePart; - qualifiedName = createQualifiedName(qualifiedName, /*right*/undefined); + qualifiedName = createQualifiedName(qualifiedName, /*right*/ undefined); } } } } let entityName: EntityName = undefined; - const nameIdentifier = createNameFromSymbol(type.symbol); + const nameIdentifier = createNameFromSymbol(type.symbol, /*mustBeIdentifier*/ true); if (qualifiedName) { Debug.assert(!qualifiedName.right); qualifiedName.right = nameIdentifier; @@ -2544,7 +2544,7 @@ namespace ts { if (!oldDeclaration) { return; } - const propertyName = getSynthesizedDeepClone(oldDeclaration.name); + const propertyName = oldDeclaration.name; const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined;; if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length) { const signatures = getSignaturesOfType(propertyType, SignatureKind.Call); @@ -2559,16 +2559,16 @@ namespace ts { typeElements.push(createPropertySignature( propertyName, optionalToken, - createTypeNodeWorker(propertyType), + typeToTypeNodeWorker(propertyType), /*initializer*/undefined)); } } return typeElements.length ? typeElements : undefined; } - function createNameFromSymbol(symbol: Symbol): Identifier; - function createNameFromSymbol(symbol: Symbol): EntityName; - function createNameFromSymbol(symbol: Symbol): EntityName { + function createNameFromSymbol(symbol: Symbol, mustBeIdentifier: true): Identifier; + function createNameFromSymbol(symbol: Symbol, mustBeIdentifier?: false): EntityName; + function createNameFromSymbol(symbol: Symbol, mustBeIdentifier: boolean | undefined): EntityName { let parentSymbol: Symbol; let meaning: SymbolFlags; @@ -2586,8 +2586,12 @@ namespace ts { } parentSymbol = undefined; - const result = createEntityNameFromSymbolChain(chain, chain.length - 1); - return result; + if(mustBeIdentifier && chain.length !== 1) { + encounteredError = true; + // TODO: failing to get an identifier when we expect one generates an unprintable node. + // Should error handling be more severe? + } + return createEntityNameFromSymbolChain(chain, chain.length - 1); function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName { Debug.assert(chain && 0 <= index && index < chain.length); diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index e60ed0009e..0ae4245970 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -67,15 +67,6 @@ namespace ts { return clone; } - /* @internal */ - export function getSynthesizedDeepClone(node: T | undefined): T { - if (node === undefined) { - return undefined; - } - const clone = visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext); - return clone === node ? getSynthesizedClone(node) : clone; - } - // Literals export function createLiteral(value: string): StringLiteral; @@ -227,9 +218,7 @@ namespace ts { // Type Elements - export function createConstructSignature() { - throw new Error("not implemented."); - } + // TODO: add signatures // Types @@ -251,7 +240,7 @@ namespace ts { export function createTypeReferenceNode(typeName: string | EntityName, typeArguments: NodeArray | undefined) { const typeReference = createSynthesizedNode(SyntaxKind.TypeReference) as TypeReferenceNode; - typeReference.typeName = isQualifiedName(typeName) ? typeName : asName(typeName); + typeReference.typeName = asName(typeName); typeReference.typeArguments = typeArguments; return typeReference; } @@ -388,7 +377,7 @@ namespace ts { : node; } - // TODO: ask if we should have multiple implementations. Some T's can't have question token. + // TODO: Split according to AST nodes. export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): T; export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined): T; export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, name?: string | PropertyName, questionToken?: QuestionToken): T { @@ -2030,7 +2019,8 @@ namespace ts { function asName(name: string | Identifier): Identifier; function asName(name: string | BindingName): BindingName; function asName(name: string | PropertyName): PropertyName; - function asName(name: string | Identifier | BindingName | PropertyName) { + function asName(name: string | EntityName): EntityName; + function asName(name: string | Identifier | BindingName | PropertyName | QualifiedName) { return typeof name === "string" ? createIdentifier(name) : name; } diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index bf706e7ded..4ef9fce067 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -31,7 +31,7 @@ namespace ts.codefix { return undefined; } - let typeNode: TypeNode = createKeywordTypeNode(SyntaxKind.AnyKeyword); + let typeNode: TypeNode; if (token.parent.parent.kind === SyntaxKind.BinaryExpression) { const binaryExpression = token.parent.parent as BinaryExpression; @@ -41,6 +41,8 @@ namespace ts.codefix { typeNode = checker.typeToTypeNode(widenedType, classDeclaration) || typeNode; } + typeNode = typeNode ? typeNode : createKeywordTypeNode(SyntaxKind.AnyKeyword); + const openBrace = getOpenBraceOfClassLike(classDeclaration, sourceFile); const property = createProperty( diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 3b4a75caa7..b3635f89f6 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -62,7 +62,7 @@ namespace ts.codefix { } const declaration = declarations[0] as Declaration; - const name = declaration.name ? getSynthesizedDeepClone(declaration.name) as PropertyName : undefined; + const name = declaration.name; const visibilityModifier = createVisibilityModifier(getModifierFlags(declaration)); const modifiers = visibilityModifier ? createNodeArray([visibilityModifier]) : undefined; const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration)); diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index ddcbf2fa2f..cc304c0751 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -275,8 +275,8 @@ namespace ts.textChanges { /** * This function should be used to insert nodes in lists when nodes don't carry separators as the part of the node range, - * i.e. arguments in arguments lists, parameters in parameter lists etc. Statements or class elements are different in sense that - * for them separators are treated as the part of the node. + * i.e. arguments in arguments lists, parameters in parameter lists etc. + * Separators are treated as part of the node for statements and class elements. */ public insertNodeInListAfter(sourceFile: SourceFile, after: Node, newNode: Node) { const containingList = formatting.SmartIndenter.getContainingList(after, sourceFile); @@ -484,7 +484,7 @@ namespace ts.textChanges { private static normalize(changes: Change[]) { // order changes by start position const normalized = stableSort(changes, (a, b) => a.range.pos - b.range.pos); - // verify that end position of the change is less than start position of the next change + // verify that change intervals to not overlap, except possible at end points. for (let i = 0; i < normalized.length - 2; i++) { Debug.assert(normalized[i].range.end <= normalized[i + 1].range.pos); } From 2958649a9594641fe6d59f8342888372871405fa Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Wed, 22 Mar 2017 14:33:49 -0700 Subject: [PATCH 49/65] Enum and enum literal --- src/compiler/checker.ts | 6 ++++-- .../codeFixClassImplementInterfacePropertyEnum.ts | 13 +++++++++++++ ...FixClassImplementInterfacePropertyEnumLiteral.ts | 13 +++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfacePropertyEnum.ts create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfacePropertyEnumLiteral.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c4eaed3fde..2790d03934 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2266,7 +2266,8 @@ namespace ts { return createKeywordTypeNode(SyntaxKind.BooleanKeyword); } if (type.flags & TypeFlags.Enum) { - throw new Error ("enums not implemented") + const name = createNameFromSymbol(type.symbol); + return createTypeReferenceNode(name, /*typeArguments*/ undefined); } if (type.flags & (TypeFlags.StringLiteral)) { return createLiteralTypeNode((createLiteral((type).text))); @@ -2278,7 +2279,8 @@ namespace ts { return (type).intrinsicName === "true" ? createTrue() : createFalse(); } if (type.flags & TypeFlags.EnumLiteral) { - throw new Error("enum literal not implemented"); + const name = createNameFromSymbol(type.symbol); + return createTypeReferenceNode(name, /*typeArguments*/ undefined); } if (type.flags & TypeFlags.Void) { return createKeywordTypeNode(SyntaxKind.VoidKeyword); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyEnum.ts b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyEnum.ts new file mode 100644 index 0000000000..8b04e4a4b4 --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyEnum.ts @@ -0,0 +1,13 @@ +/// + +// @lib: es2017 + +//// enum E { a,b,c } +//// interface I { +//// a: E; +//// } +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` + a: E; +`); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyEnumLiteral.ts b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyEnumLiteral.ts new file mode 100644 index 0000000000..720acc1ccc --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyEnumLiteral.ts @@ -0,0 +1,13 @@ +/// + +// @lib: es2017 + +//// enum E { a,b,c } +//// interface I { +//// a: E.a +//// } +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` + a: E.a; +`); \ No newline at end of file From 43e01f282b5172152a9b9c3cac2c5b058fa94a8e Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Wed, 22 Mar 2017 14:50:01 -0700 Subject: [PATCH 50/65] remove trivia --- src/compiler/checker.ts | 4 +-- src/services/codefixes/helpers.ts | 11 ++++++- ...assExtendAbstractMethodAnonymousClass.1.ts | 33 ------------------- .../codeFixClassImplementInterfaceComments.ts | 25 ++++++++++---- 4 files changed, 31 insertions(+), 42 deletions(-) delete mode 100644 tests/cases/fourslash/codeFixClassExtendAbstractMethodAnonymousClass.1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2790d03934..b681d1298d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2208,13 +2208,13 @@ namespace ts { const parameterDeclaration = parameterSymbol.declarations[0] as ParameterDeclaration; const parameterType = getTypeOfSymbol(parameterSymbol); const parameterTypeNode = typeToTypeNode(parameterType, enclosingDeclaration); - // TODO: how should we clone members/modifiers? // TODO: check initializer accessibility correctly. const parameterNode = createParameter( parameterDeclaration.decorators, parameterDeclaration.modifiers, parameterDeclaration.dotDotDotToken && createToken(SyntaxKind.DotDotDotToken), - parameterDeclaration.name, + // Clone name to remove trivia. + getSynthesizedClone(parameterDeclaration.name), parameterDeclaration.questionToken && createToken(SyntaxKind.QuestionToken), parameterTypeNode, parameterDeclaration.initializer); diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index b3635f89f6..da72921508 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -62,7 +62,8 @@ namespace ts.codefix { } const declaration = declarations[0] as Declaration; - const name = declaration.name; + // Clone name to remove leading trivia. + const name = getSynthesizedClone(declaration.name); const visibilityModifier = createVisibilityModifier(getModifierFlags(declaration)); const modifiers = visibilityModifier ? createNodeArray([visibilityModifier]) : undefined; const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration)); @@ -227,4 +228,12 @@ namespace ts.codefix { } return undefined; } + + function stripComments(node: Node): Node { + if(node === undefined) { + return node; + } + const strippedChildren = visitEachChild(node, stripComments, nullTransformationContext); + return strippedChildren === node ? getSynthesizedClone(strippedChildren) : strippedChildren; + } } \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassExtendAbstractMethodAnonymousClass.1.ts b/tests/cases/fourslash/codeFixClassExtendAbstractMethodAnonymousClass.1.ts deleted file mode 100644 index 134ce17376..0000000000 --- a/tests/cases/fourslash/codeFixClassExtendAbstractMethodAnonymousClass.1.ts +++ /dev/null @@ -1,33 +0,0 @@ -/// - -class A { - foo() { - return class { x: number; } - } - bar() { - return new class { x: number; } - } -} -class B { - foo() { - return class { - x: X; - } - } -} - - -class D extends A { } - -verify.rangeAfterCodeFix(` - f(a: number, b: string): boolean; - f(a: number, b: string): this; - f(a: string, b: number): Function; - f(a: string): Function; - f(a: any, b?: any) { - throw new Error("Method not implemented."); - } - foo(): number { - throw new Error("Method not implemented."); - } -`); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceComments.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceComments.ts index 56d56a5a50..f082074f16 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceComments.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceComments.ts @@ -2,13 +2,26 @@ // @lib: es2017 -//// /** interface prefix */ -//// interface /**interface name prefix */ I /**open-brace prefix*/{ -//// /** property prefix*/ x /**colon prefix*/: /**number prefix*/ number; -//// +//// namespace N { +//// /**enum prefix */ +//// export enum /**enum identifier prefix */ E /**open-brace prefix*/ { +//// /* literal prefix */ a /** comma prefix */, +//// /* literal prefix */ b /** comma prefix */, +//// /* literal prefix */ c +//// /** close brace prefix */ } +//// /** interface prefix */ +//// export interface /**interface name prefix */ I /**open-brace prefix*/ { +//// /** property prefix */ a /** colon prefix */: /** enum literal prefix 1*/ E /** dot prefix */. /** enum literal prefix 2*/a; +//// /** property prefix */ b /** colon prefix */: /** enum prefix */ E; +//// /**method signature prefix */foo /**open angle prefix */< /**type parameter name prefix */ X /** closing angle prefix */> /**open paren prefix */(/** parameter prefix */ a/** colon prefix */: /** parameter type prefix */ X /** close paren prefix */) /** colon prefix */: /** return type prefix */ string /** semicolon prefix */; +//// /**close-brace prefix*/ } //// /**close-brace prefix*/ } -//// class C implements I {[| |]} +//// class C implements N.I {[| |]} verify.rangeAfterCodeFix(` - x: number; + a: N.E.a; + b: N.E; + foo(a: X): string { + throw new Error("Method not implemented."); + } `); From abd14ef18586c91c1d08c7fd87e0a82b4746e9fc Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Wed, 22 Mar 2017 19:10:56 -0700 Subject: [PATCH 51/65] temp --- src/compiler/checker.ts | 955 +++++++++++++++++++++------------------- src/compiler/types.ts | 20 +- 2 files changed, 510 insertions(+), 465 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b681d1298d..e004a0ca39 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -61,6 +61,7 @@ namespace ts { const noImplicitThis = compilerOptions.noImplicitThis === undefined ? compilerOptions.strict : compilerOptions.noImplicitThis; const emitResolver = createResolver(); + let nodeBuilderCache: NodeBuilder | undefined; const undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined"); undefinedSymbol.declarations = []; @@ -106,9 +107,9 @@ namespace ts { getParameterType: getTypeAtPosition, getReturnTypeOfSignature, getNonNullableType, - typeToTypeNode, - indexInfoToIndexSignatureDeclaration, - signatureToSignatureDeclaration, + typeToTypeNode: getNodeBuilder().typeToTypeNode, + indexInfoToIndexSignatureDeclaration: getNodeBuilder().indexInfoToIndexSignatureDeclaration, + signatureToSignatureDeclaration: getNodeBuilder().signatureToSignatureDeclaration, getSymbolsInScope: (location, meaning) => { location = getParseTreeNode(location); return location ? getSymbolsInScope(location, meaning) : []; @@ -2191,476 +2192,526 @@ namespace ts { return result; } - function typeParameterToDeclaration(type: TypeParameter, enclosingDeclaration?: Node): TypeParameterDeclaration { - if (!(type && type.symbol && type.flags & TypeFlags.TypeParameter)) { - return undefined; + function unwrapNodeBuilderResult(result: NodeBuilderResult, returnValueOnError: boolean): T | undefined { + return !result.error || returnValueOnError ? result.value : undefined; + } + + function getNodeBuilder(): NodeBuilder { + if (nodeBuilderCache) { + return nodeBuilderCache; } - const constraint = typeToTypeNode(getConstraintFromTypeParameter(type), enclosingDeclaration); - const defaultParameter = typeToTypeNode(getDefaultFromTypeParameter(type), enclosingDeclaration); - - // TODO: use method internal to typeToTypeNode. - const name = symbolToString(type.symbol); - return createTypeParameterDeclaration(name, constraint, defaultParameter); - } - - function symbolToParameterDeclaration(parameterSymbol: Symbol, enclosingDeclaration?: Node): ParameterDeclaration { - const parameterDeclaration = parameterSymbol.declarations[0] as ParameterDeclaration; - const parameterType = getTypeOfSymbol(parameterSymbol); - const parameterTypeNode = typeToTypeNode(parameterType, enclosingDeclaration); - // TODO: check initializer accessibility correctly. - const parameterNode = createParameter( - parameterDeclaration.decorators, - parameterDeclaration.modifiers, - parameterDeclaration.dotDotDotToken && createToken(SyntaxKind.DotDotDotToken), - // Clone name to remove trivia. - getSynthesizedClone(parameterDeclaration.name), - parameterDeclaration.questionToken && createToken(SyntaxKind.QuestionToken), - parameterTypeNode, - parameterDeclaration.initializer); - return parameterNode; - } - - function signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node): SignatureDeclaration { - const typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, enclosingDeclaration)); - const parameters = signature.parameters.map(parameter => symbolToParameterDeclaration(parameter, enclosingDeclaration)); - const type = typeToTypeNodeExceptAny(getReturnTypeOfSignature(signature)); - - return createSignatureDeclaration(kind, typeParameters, parameters, type); - - function typeToTypeNodeExceptAny(type: Type): TypeNode | undefined { - const typeNode = typeToTypeNode(type, enclosingDeclaration); - return typeNode && typeNode.kind !== SyntaxKind.AnyKeyword ? typeNode : undefined; - } - } - - - // function typeToDisplayParts - - function typeToTypeNode(type: Type, enclosingDeclaration?: Node, returnNodeOnError?: boolean): TypeNode { let encounteredError = false; - let inObjectTypeLiteral = false; - let checkAlias = true; - let symbolStack: Symbol[] = undefined; + nodeBuilderCache = { + typeToTypeNode, + indexInfoToIndexSignatureDeclaration, + signatureToSignatureDeclaration + } - const result = typeToTypeNodeWorker(type); - // returnNodeOnError = true; // TODO: unset. - return encounteredError && !returnNodeOnError ? undefined: result; + return nodeBuilderCache; - function typeToTypeNodeWorker(type: Type): TypeNode { - if (!type) { - encounteredError = true; + function typeToTypeNode(type: Type, enclosingDeclaration?: Node, emitNodeOnError?: boolean): NodeBuilderResult { + let toNodeEncounteredErrorCache = encounteredError; + encounteredError = false; + let inObjectTypeLiteral = false; + let checkAlias = true; + let symbolStack: Symbol[] = undefined; + + const resultingNode = typeToTypeNodeWorker(type); + const result = encounteredError && !emitNodeOnError ? undefined : resultingNode; + encounteredError = toNodeEncounteredErrorCache; + return result; + + function typeToTypeNodeWorker(type: Type): NodeBuilderResult { + if (!type) { + encounteredError = true; + return undefined; + } + + if (type.flags & TypeFlags.Any) { + return createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + if (type.flags & TypeFlags.String) { + return createKeywordTypeNode(SyntaxKind.StringKeyword); + } + if (type.flags & TypeFlags.Number) { + return createKeywordTypeNode(SyntaxKind.NumberKeyword); + } + if (type.flags & TypeFlags.Boolean) { + return createKeywordTypeNode(SyntaxKind.BooleanKeyword); + } + if (type.flags & TypeFlags.Enum) { + const name = symbolToName(type.symbol, enclosingDeclaration); + return createTypeReferenceNode(name, /*typeArguments*/ undefined); + } + if (type.flags & (TypeFlags.StringLiteral)) { + return createLiteralTypeNode((createLiteral((type).text))); + } + if (type.flags & (TypeFlags.NumberLiteral)) { + return createLiteralTypeNode((createNumericLiteral((type).text))); + } + if (type.flags & TypeFlags.BooleanLiteral) { + return (type).intrinsicName === "true" ? createTrue() : createFalse(); + } + if (type.flags & TypeFlags.EnumLiteral) { + const name = symbolToName(type.symbol, enclosingDeclaration); + return createTypeReferenceNode(name, /*typeArguments*/ undefined); + } + if (type.flags & TypeFlags.Void) { + return createKeywordTypeNode(SyntaxKind.VoidKeyword); + } + if (type.flags & TypeFlags.Undefined) { + return createKeywordTypeNode(SyntaxKind.UndefinedKeyword); + } + if (type.flags & TypeFlags.Null) { + return createKeywordTypeNode(SyntaxKind.NullKeyword); + } + if (type.flags & TypeFlags.Never) { + return createKeywordTypeNode(SyntaxKind.NeverKeyword); + } + if (type.flags & TypeFlags.ESSymbol) { + throw new Error("ESSymbol not implemented"); + } + if (type.flags & TypeFlags.NonPrimitive) { + throw new Error("Non primitive not implemented"); + } + if (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType) { + if (inObjectTypeLiteral) { + encounteredError = true; + } + return createThis(); + } + + const objectFlags = getObjectFlags(type); + + if (objectFlags & ObjectFlags.Reference) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + return typeReferenceToTypeReferenceNode(type); + } + if (objectFlags & ObjectFlags.ClassOrInterface) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + const name = symbolToName(type.symbol, enclosingDeclaration); + // TODO(aozgaa): handle type arguments. + return createTypeReferenceNode(name, /*typeArguments*/ undefined); + } + if (type.flags & TypeFlags.TypeParameter) { + const name = symbolToName(type.symbol, enclosingDeclaration); + // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. + return createTypeReferenceNode(name, /*typeArguments*/ undefined); + } + + if (checkAlias && type.aliasSymbol) { + const name = symbolToName(type.aliasSymbol, enclosingDeclaration); + const typeArgumentNodes = mapToTypeNodeArray(type.aliasTypeArguments); + return createTypeReferenceNode(name, typeArgumentNodes); + } + checkAlias = false; + + if (type.flags & TypeFlags.Union) { + return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, mapToTypeNodeArray(formatUnionTypes((type).types))); + } + + if (type.flags & TypeFlags.Intersection) { + return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, mapToTypeNodeArray((type as UnionType).types)); + } + + if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + // The type is an object literal type. + return createAnonymousTypeNode(type); + } + + if (type.flags & TypeFlags.Index) { + const indexedType = (type).type; + const indexTypeNode = typeToTypeNodeWorker(indexedType); + return createTypeOperatorNode(indexTypeNode); + } + if (type.flags & TypeFlags.IndexedAccess) { + const objectTypeNode = typeToTypeNodeWorker((type).objectType); + const indexTypeNode = typeToTypeNodeWorker((type).indexType); + return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); + } + + Debug.fail("Should be unreachable."); + + function mapToTypeNodeArray(types: Type[]): NodeArray { + return types && asNodeArray(types.map(typeToTypeNodeWorker) as TypeNode[]); + } + + function createMappedTypeNodeFromType(type: MappedType) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + const typeParameter = getTypeParameterFromMappedType(type); + const typeParameterNode = typeParameterToDeclaration(typeParameter, enclosingDeclaration); + + const templateTypeNode = typeToTypeNodeWorker(getTemplateTypeFromMappedType(type)); + const readonlyToken = (type).declaration && (type).declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; + const questionToken = (type).declaration && (type).declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; + + return createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); + } + + function createAnonymousTypeNode(type: ObjectType): TypeNode { + const symbol = type.symbol; + if (symbol) { + // Always use 'typeof T' for type of class, enum, and module objects + if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) || + symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || + shouldWriteTypeOfFunctionSymbol()) { + return createTypeQueryNodeFromType(type); + } + else if (contains(symbolStack, symbol)) { + // If type is an anonymous type literal in a type alias declaration, use type alias name + const typeAlias = getTypeAliasForTypeLiteral(type); + if (typeAlias) { + // The specified symbol flags need to be reinterpreted as type flags + const entityName = symbolToName(typeAlias, enclosingDeclaration); + return createTypeReferenceNode(entityName, /*typeArguments*/ undefined); + } + else { + return createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + } + else { + // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead + // of types allows us to catch circular references to instantiations of the same anonymous type + if (!symbolStack) { + symbolStack = []; + } + symbolStack.push(symbol); + let result = createTypeNodeFromObjectType(type); + symbolStack.pop(); + return result; + } + } + else { + // Anonymous types without a symbol are never circular. + return createTypeNodeFromObjectType(type); + } + + function shouldWriteTypeOfFunctionSymbol() { + const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method && // typeof static method + forEach(symbol.declarations, declaration => getModifierFlags(declaration) & ModifierFlags.Static)); + const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && + (symbol.parent || // is exported function symbol + forEach(symbol.declarations, declaration => + declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); + if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { + // typeof is allowed only for static/non local functions + return contains(symbolStack, symbol); // it is type of the symbol uses itself recursively + } + } + } + + function createTypeNodeFromObjectType(type: ObjectType): TypeNode { + if (type.objectFlags & ObjectFlags.Mapped) { + if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { + return createMappedTypeNodeFromType(type); + } + } + + const resolved = resolveStructuredTypeMembers(type); + if (!resolved.properties.length && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { + if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { + return createTypeLiteralNode(/*members*/ undefined); + } + + if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { + const signature = resolved.callSignatures[0]; + return signatureToSignatureDeclaration(signature, SyntaxKind.FunctionType, enclosingDeclaration, emitNodeOnError); + } + if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { + const signature = resolved.constructSignatures[0]; + return signatureToSignatureDeclaration(signature, SyntaxKind.ConstructorType, enclosingDeclaration, emitNodeOnError); + } + } + + const saveInObjectTypeLiteral = inObjectTypeLiteral; + inObjectTypeLiteral = true; + const members = createTypeNodesFromResolvedType(resolved); + inObjectTypeLiteral = saveInObjectTypeLiteral; + return createTypeLiteralNode(members); + } + + function createTypeQueryNodeFromType(type: Type) { + const symbol = type.symbol; + if (symbol) { + const entityName = symbolToName(symbol, enclosingDeclaration); + return createTypeQueryNode(entityName); + } + } + + function typeReferenceToTypeReferenceNode(type: TypeReference) { + const typeArguments: Type[] = type.typeArguments || emptyArray; + if (type.target === globalArrayType) { + const elementType = typeToTypeNodeWorker(typeArguments[0]); + return createArrayTypeNode(elementType); + } + else if (type.target.objectFlags & ObjectFlags.Tuple) { + return createTupleTypeNode(typeArguments.length > 0 ? mapToTypeNodeArray(typeArguments.slice(0, getTypeReferenceArity(type))) : undefined); + } + else { + const outerTypeParameters = type.target.outerTypeParameters; + let i = 0; + let qualifiedName: QualifiedName | undefined = undefined; + if (outerTypeParameters) { + const length = outerTypeParameters.length; + while (i < length) { + // Find group of type arguments for type parameters with the same declaring container. + const start = i; + const parent = getParentSymbolOfTypeParameter(outerTypeParameters[i]); + do { + i++; + } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); + // When type parameters are their own type arguments for the whole group (i.e. we have + // the default outer type arguments), we don't show the group. + if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { + const qualifiedNamePart = symbolToName(parent, enclosingDeclaration, /*mustBeIdentifier*/ true); + if (!qualifiedName) { + qualifiedName = createQualifiedName(qualifiedNamePart, /*right*/ undefined); + } + else { + Debug.assert(!qualifiedName.right); + qualifiedName.right = qualifiedNamePart; + qualifiedName = createQualifiedName(qualifiedName, /*right*/ undefined); + } + } + } + } + let entityName: EntityName = undefined; + const nameIdentifier = symbolToName(type.symbol, enclosingDeclaration, /*mustBeIdentifier*/ true); + if (qualifiedName) { + Debug.assert(!qualifiedName.right); + qualifiedName.right = nameIdentifier; + entityName = qualifiedName; + } + else { + entityName = nameIdentifier; + } + const typeParameterCount = (type.target.typeParameters || emptyArray).length; + const typeArgumentNodes = mapToTypeNodeArray(typeArguments.length > 0 ? typeArguments.slice(i, typeParameterCount - i) : undefined); + return createTypeReferenceNode(entityName, typeArgumentNodes); + } + } + + function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] { + const typeElements: TypeElement[] = []; + for (const signature of resolvedType.callSignatures) { + typeElements.push(signatureToSignatureDeclaration(signature, SyntaxKind.CallSignature, enclosingDeclaration, emitNodeOnError)); + } + for (const signature of resolvedType.constructSignatures) { + typeElements.push(signatureToSignatureDeclaration(signature, SyntaxKind.ConstructSignature, enclosingDeclaration, emitNodeOnError)); + } + if (resolvedType.stringIndexInfo) { + typeElements.push(indexInfoToIndexSignatureDeclaration(resolvedType.stringIndexInfo, IndexKind.String, enclosingDeclaration, emitNodeOnError)); + } + if (resolvedType.numberIndexInfo) { + typeElements.push(indexInfoToIndexSignatureDeclaration(resolvedType.numberIndexInfo, IndexKind.Number, enclosingDeclaration, emitNodeOnError)); + } + + const properties = resolvedType.properties; + if (!properties) { + return typeElements; + } + + for (const propertySymbol of properties) { + const propertyType = getTypeOfSymbol(propertySymbol); + const oldDeclaration = propertySymbol.declarations && propertySymbol.declarations[0] as TypeElement; + if (!oldDeclaration) { + return; + } + const propertyName = oldDeclaration.name; + const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined;; + if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length) { + const signatures = getSignaturesOfType(propertyType, SignatureKind.Call); + for (const signature of signatures) { + const methodDeclaration = signatureToSignatureDeclaration(signature, SyntaxKind.MethodSignature, enclosingDeclaration, emitNodeOnError); + methodDeclaration.name = propertyName; + methodDeclaration.questionToken = optionalToken; + typeElements.push(methodDeclaration); + } + } + else { + typeElements.push(createPropertySignature( + propertyName, + optionalToken, + typeToTypeNodeWorker(propertyType), + /*initializer*/undefined)); + } + } + return typeElements.length ? typeElements : undefined; + } + } + } + + function indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node, emitNodeOnError?: boolean): IndexSignatureDeclaration { + const encounteredErrorCache = encounteredError; + encounteredError = false; + + const indexerTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword); + + const name = getNameFromIndexInfo(indexInfo); + const indexingParameter = createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + name, + /*questionToken*/ undefined, + indexerTypeNode, + /*initializer*/ undefined); + const typeNode = typeToTypeNode(indexInfo.type, enclosingDeclaration, emitNodeOnError); + const result = !encounteredError || emitNodeOnError ? createIndexSignatureDeclaration( + [indexingParameter], + typeNode, + /*decorators*/ undefined, + indexInfo.isReadonly ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined) + : undefined; + encounteredError = encounteredErrorCache; + return result; + } + + function signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, emitNodeOnError?: boolean): SignatureDeclaration { + const encounteredErrorCache = encounteredError; + encounteredError = false; + + const typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, enclosingDeclaration)); + const parameters = signature.parameters.map(parameter => symbolToParameterDeclaration(parameter, enclosingDeclaration)); + const type = typeToTypeNodeExceptAny(getReturnTypeOfSignature(signature)); + + const result = !encounteredError || emitNodeOnError ? createSignatureDeclaration(kind, typeParameters, parameters, type) : undefined; + encounteredError = encounteredErrorCache; + return result; + + function typeToTypeNodeExceptAny(type: Type): TypeNode | undefined { + const typeNode = typeToTypeNode(type, enclosingDeclaration, emitNodeOnError); + return typeNode && typeNode.kind !== SyntaxKind.AnyKeyword ? typeNode : undefined; + } + } + + function typeParameterToDeclaration(type: TypeParameter, enclosingDeclaration?: Node, returnNodeOnError?: boolean): TypeParameterDeclaration { + if (!(type && type.symbol && type.flags & TypeFlags.TypeParameter)) { return undefined; } - if (type.flags & TypeFlags.Any) { - return createKeywordTypeNode(SyntaxKind.AnyKeyword); + const constraint = typeToTypeNode(getConstraintFromTypeParameter(type), enclosingDeclaration); + const defaultParameter = typeToTypeNode(getDefaultFromTypeParameter(type), enclosingDeclaration); + + let toNodeEncounteredErrorCache = encounteredError; + encounteredError = false; + const name = symbolToName(type.symbol, enclosingDeclaration, /*mustBeIdentifier*/ true); + let result = encounteredError && !returnNodeOnError ? undefined : createTypeParameterDeclaration(name, constraint, defaultParameter); + encounteredError = toNodeEncounteredErrorCache; + return result; + } + + function symbolToParameterDeclaration(parameterSymbol: Symbol, enclosingDeclaration?: Node): ParameterDeclaration { + const parameterDeclaration = parameterSymbol.declarations[0] as ParameterDeclaration; + const parameterType = getTypeOfSymbol(parameterSymbol); + const parameterTypeNode = typeToTypeNode(parameterType, enclosingDeclaration); + // TODO(aozgaa): check initializer accessibility correctly. + const parameterNode = createParameter( + parameterDeclaration.decorators, + parameterDeclaration.modifiers, + parameterDeclaration.dotDotDotToken && createToken(SyntaxKind.DotDotDotToken), + // Clone name to remove trivia. + getSynthesizedClone(parameterDeclaration.name), + parameterDeclaration.questionToken && createToken(SyntaxKind.QuestionToken), + parameterTypeNode, + parameterDeclaration.initializer); + return parameterNode; + } + + function symbolToName(symbol: Symbol, enclosingDeclaration: Node | undefined, mustBeIdentifier: true): Identifier; + function symbolToName(symbol: Symbol, enclosingDeclaration?: Node, mustBeIdentifier?: false): EntityName; + function symbolToName(symbol: Symbol, enclosingDeclaration: Node | undefined, mustBeIdentifier: boolean | undefined): EntityName { + let parentSymbol: Symbol; + let meaning: SymbolFlags; + + // Get qualified name if the symbol is not a type parameter + // and there is an enclosing declaration. + let chain: Symbol[]; + const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; + if (!isTypeParameter && enclosingDeclaration) { + chain = getSymbolChain(symbol, meaning, /*endOfChain*/ true); + // TODO: check whether type pointed to by symbol requires type arguments to be printed. + Debug.assert(chain && chain.length > 0); } - if (type.flags & TypeFlags.String) { - return createKeywordTypeNode(SyntaxKind.StringKeyword); - } - if (type.flags & TypeFlags.Number) { - return createKeywordTypeNode(SyntaxKind.NumberKeyword); - } - if(type.flags & TypeFlags.Boolean) { - return createKeywordTypeNode(SyntaxKind.BooleanKeyword); - } - if (type.flags & TypeFlags.Enum) { - const name = createNameFromSymbol(type.symbol); - return createTypeReferenceNode(name, /*typeArguments*/ undefined); - } - if (type.flags & (TypeFlags.StringLiteral)) { - return createLiteralTypeNode((createLiteral((type).text))); - } - if (type.flags & (TypeFlags.NumberLiteral)) { - return createLiteralTypeNode((createNumericLiteral((type).text))); - } - if(type.flags & TypeFlags.BooleanLiteral) { - return (type).intrinsicName === "true" ? createTrue() : createFalse(); - } - if (type.flags & TypeFlags.EnumLiteral) { - const name = createNameFromSymbol(type.symbol); - return createTypeReferenceNode(name, /*typeArguments*/ undefined); - } - if (type.flags & TypeFlags.Void) { - return createKeywordTypeNode(SyntaxKind.VoidKeyword); - } - if (type.flags & TypeFlags.Undefined) { - return createKeywordTypeNode(SyntaxKind.UndefinedKeyword); - } - if (type.flags & TypeFlags.Null) { - return createKeywordTypeNode(SyntaxKind.NullKeyword); - } - if (type.flags & TypeFlags.Never) { - return createKeywordTypeNode(SyntaxKind.NeverKeyword); - } - if (type.flags & TypeFlags.ESSymbol) { - throw new Error("ESSymbol not implemented"); - } - if (type.flags & TypeFlags.NonPrimitive) { - throw new Error("Non primitive not implemented"); - } - if (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType) { - if (inObjectTypeLiteral) { - encounteredError = true; - } - return createThis(); + else { + chain = [symbol]; } - const objectFlags = getObjectFlags(type); - - if (objectFlags & ObjectFlags.Reference) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - return typeReferenceToTypeReferenceNode(type); - } - if (objectFlags & ObjectFlags.ClassOrInterface) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - const name = createNameFromSymbol(type.symbol); - // TODO: handle type arguments. - return createTypeReferenceNode(name, /*typeArguments*/ undefined); - } - if (type.flags & TypeFlags.TypeParameter) { - const name = createNameFromSymbol(type.symbol); - // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. - return createTypeReferenceNode(name, /*typeArguments*/ undefined); + parentSymbol = undefined; + if (mustBeIdentifier && chain.length !== 1) { + encounteredError = true; + // TODO: failing to get an identifier when we expect one generates an unprintable node. + // Should error handling be more severe? } + return createEntityNameFromSymbolChain(chain, chain.length - 1); - if (checkAlias && type.aliasSymbol) { - const name = createNameFromSymbol(type.aliasSymbol); - const typeArgumentNodes = mapToTypeNodeArray(type.aliasTypeArguments); - return createTypeReferenceNode(name, typeArgumentNodes); - } - checkAlias = false; - - if (type.flags & TypeFlags.Union) { - return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, mapToTypeNodeArray(formatUnionTypes((type).types))); - } - - if (type.flags & TypeFlags.Intersection) { - return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, mapToTypeNodeArray((type as UnionType).types)); - } - - if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - // The type is an object literal type. - return createAnonymousTypeNode(type); - } - - // TODO (aozgaa): implement string and number literals here once there is a testable case. - - if (type.flags & TypeFlags.Index) { - const indexedType = (type).type; - const indexTypeNode = typeToTypeNodeWorker(indexedType); - return createTypeOperatorNode(indexTypeNode); - } - if (type.flags & TypeFlags.IndexedAccess) { - const objectTypeNode = typeToTypeNodeWorker((type).objectType); - const indexTypeNode = typeToTypeNodeWorker((type).indexType); - return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); - } - - Debug.fail("Should be unreachable."); - - function mapToTypeNodeArray(types: Type[]): NodeArray { - return types && asNodeArray(types.map(typeToTypeNodeWorker) as TypeNode[]); - } - - function createMappedTypeNodeFromType(type: MappedType) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - const typeParameter = getTypeParameterFromMappedType(type); - const typeParameterNode = typeParameterToDeclaration(typeParameter, enclosingDeclaration); - - const templateTypeNode = typeToTypeNode(getTemplateTypeFromMappedType(type), enclosingDeclaration); - const readonlyToken = (type).declaration && (type).declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; - const questionToken = (type).declaration && (type).declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; - - return createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); - } - - function createAnonymousTypeNode(type: ObjectType): TypeNode { - const symbol = type.symbol; - if (symbol) { - // Always use 'typeof T' for type of class, enum, and module objects - if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) || - symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || - shouldWriteTypeOfFunctionSymbol()) { - return createTypeQueryNodeFromType(type); - } - else if (contains(symbolStack, symbol)) { - // If type is an anonymous type literal in a type alias declaration, use type alias name - const typeAlias = getTypeAliasForTypeLiteral(type); - if (typeAlias) { - // The specified symbol flags need to be reinterpreted as type flags - const entityName = createNameFromSymbol(typeAlias); - return createTypeReferenceNode(entityName, /*typeArguments*/ undefined); - } - else { - return createKeywordTypeNode(SyntaxKind.AnyKeyword); - } + function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName { + Debug.assert(chain && 0 <= index && index < chain.length); + // const parentIndex = index - 1; + const symbol = chain[index]; + let typeParameterString = ""; + if (index > 0) { + // TODO: is the parentSymbol wrong? + const parentSymbol = chain[index - 1]; + let typeParameters: TypeParameter[]; + if (getCheckFlags(symbol) & CheckFlags.Instantiated) { + typeParameters = getTypeParametersOfClassOrInterface(parentSymbol); } else { - // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead - // of types allows us to catch circular references to instantiations of the same anonymous type - if (!symbolStack) { - symbolStack = []; + const targetSymbol = getTargetSymbol(parentSymbol); + if (targetSymbol.flags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeAlias)) { + typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); } - symbolStack.push(symbol); - let result = createTypeNodeFromObjectType(type); - symbolStack.pop(); - return result; } - } - else { - // Anonymous types without a symbol are never circular. - return createTypeNodeFromObjectType(type); - } + if (typeParameters && typeParameters.length > 0) { + encounteredError = true; + const writer = getSingleLineStringWriter(); + const displayBuilder = getSymbolDisplayBuilder(); + displayBuilder.buildDisplayForTypeParametersAndDelimiters(typeParameters, writer, enclosingDeclaration, 0); + typeParameterString = writer.string(); + releaseStringWriter(writer); - function shouldWriteTypeOfFunctionSymbol() { - const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method && // typeof static method - forEach(symbol.declarations, declaration => getModifierFlags(declaration) & ModifierFlags.Static)); - const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && - (symbol.parent || // is exported function symbol - forEach(symbol.declarations, declaration => - declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); - if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { - // typeof is allowed only for static/non local functions - return contains(symbolStack, symbol); // it is type of the symbol uses itself recursively } } + const symbolName = getNameOfSymbol(symbol); + const symbolNameWithTypeParameters = typeParameterString.length > 0 ? `${symbolName}<${typeParameterString}>` : symbolName; + let identifier = createIdentifier(symbolNameWithTypeParameters); + + return index > 0 ? createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; } - function createTypeNodeFromObjectType(type: ObjectType): TypeNode { - if (type.objectFlags & ObjectFlags.Mapped) { - if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { - return createMappedTypeNodeFromType(type); - } - } + /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ + function getSymbolChain(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): Symbol[] | undefined { + let accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/false); - const resolved = resolveStructuredTypeMembers(type); - if (!resolved.properties.length && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { - if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { - return createTypeLiteralNode(/*members*/ undefined); - } + if (!accessibleSymbolChain || + needsQualification(accessibleSymbolChain[0], enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { - if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { - const signature = resolved.callSignatures[0]; - return signatureToSignatureDeclaration(signature, SyntaxKind.FunctionType, enclosingDeclaration); - } - if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { - const signature = resolved.constructSignatures[0]; - return signatureToSignatureDeclaration(signature, SyntaxKind.ConstructorType, enclosingDeclaration); - } - } - - const saveInObjectTypeLiteral = inObjectTypeLiteral; - inObjectTypeLiteral = true; - const members = createTypeNodesFromResolvedType(resolved); - inObjectTypeLiteral = saveInObjectTypeLiteral; - return createTypeLiteralNode(members); - } - - function createTypeQueryNodeFromType(type: Type) { - const symbol = type.symbol; - if (symbol) { - const entityName = createNameFromSymbol(symbol); - return createTypeQueryNode(entityName); - } - } - - function typeReferenceToTypeReferenceNode(type: TypeReference) { - const typeArguments: Type[] = type.typeArguments || emptyArray; - if (type.target === globalArrayType) { - const elementType = typeToTypeNodeWorker(typeArguments[0]); - return createArrayTypeNode(elementType); - } - else if (type.target.objectFlags & ObjectFlags.Tuple) { - return createTupleTypeNode(typeArguments.length > 0 ? mapToTypeNodeArray(typeArguments.slice(0, getTypeReferenceArity(type))) : undefined); - } - else { - const outerTypeParameters = type.target.outerTypeParameters; - let i = 0; - let qualifiedName: QualifiedName | undefined = undefined; - if (outerTypeParameters) { - const length = outerTypeParameters.length; - while (i < length) { - // Find group of type arguments for type parameters with the same declaring container. - const start = i; - const parent = getParentSymbolOfTypeParameter(outerTypeParameters[i]); - do { - i++; - } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); - // When type parameters are their own type arguments for the whole group (i.e. we have - // the default outer type arguments), we don't show the group. - if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { - const qualifiedNamePart = createNameFromSymbol(parent, /*mustBeIdentifier*/ true); - if (!qualifiedName) { - qualifiedName = createQualifiedName(qualifiedNamePart, /*right*/ undefined); - } - else { - Debug.assert(!qualifiedName.right); - qualifiedName.right = qualifiedNamePart; - qualifiedName = createQualifiedName(qualifiedName, /*right*/ undefined); - } - } + // Go up and add our parent. + const parent = getParentOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol); + if (parent) { + const parentChain = getSymbolChain(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false); + if (parentChain) { + accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [symbol]); } } - let entityName: EntityName = undefined; - const nameIdentifier = createNameFromSymbol(type.symbol, /*mustBeIdentifier*/ true); - if (qualifiedName) { - Debug.assert(!qualifiedName.right); - qualifiedName.right = nameIdentifier; - entityName = qualifiedName; - } - else { - entityName = nameIdentifier; - } - const typeParameterCount = (type.target.typeParameters || emptyArray).length; - const typeArgumentNodes = mapToTypeNodeArray(typeArguments.length > 0 ? typeArguments.slice(i, typeParameterCount - i) : undefined); - return createTypeReferenceNode(entityName, typeArgumentNodes); - } - } - - function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] { - const typeElements: TypeElement[] = []; - for (const signature of resolvedType.callSignatures) { - typeElements.push(signatureToSignatureDeclaration(signature, SyntaxKind.CallSignature, enclosingDeclaration)); - } - for (const signature of resolvedType.constructSignatures) { - typeElements.push(signatureToSignatureDeclaration(signature, SyntaxKind.ConstructSignature, enclosingDeclaration)); - } - if (resolvedType.stringIndexInfo) { - typeElements.push(indexInfoToIndexSignatureDeclaration(resolvedType.stringIndexInfo, IndexKind.String, enclosingDeclaration)); - } - if (resolvedType.numberIndexInfo) { - typeElements.push(indexInfoToIndexSignatureDeclaration(resolvedType.numberIndexInfo, IndexKind.Number, enclosingDeclaration)); } - const properties = resolvedType.properties; - if (!properties) { - return typeElements; + if (accessibleSymbolChain) { + return accessibleSymbolChain; } - for (const propertySymbol of properties) { - const propertyType = getTypeOfSymbol(propertySymbol); - const oldDeclaration = propertySymbol.declarations && propertySymbol.declarations[0] as TypeElement; - if (!oldDeclaration) { - return; - } - const propertyName = oldDeclaration.name; - const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined;; - if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length) { - const signatures = getSignaturesOfType(propertyType, SignatureKind.Call); - for (const signature of signatures) { - const methodDeclaration = signatureToSignatureDeclaration(signature, SyntaxKind.MethodSignature, enclosingDeclaration); - methodDeclaration.name = propertyName; - methodDeclaration.questionToken = optionalToken; - typeElements.push(methodDeclaration); - } - } - else { - typeElements.push(createPropertySignature( - propertyName, - optionalToken, - typeToTypeNodeWorker(propertyType), - /*initializer*/undefined)); - } - } - return typeElements.length ? typeElements : undefined; - } + else if ( + // If this is the last part of outputting the symbol, always output. The cases apply only to parent symbols. + endOfChain || + // If a parent symbol is an external module, don't write it. (We prefer just `x` vs `"foo/bar".x`.) + !(!parentSymbol && ts.forEach(symbol.declarations, hasExternalModuleSymbol)) && + // If a parent symbol is an anonymous type, don't write it. + !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral))) { - function createNameFromSymbol(symbol: Symbol, mustBeIdentifier: true): Identifier; - function createNameFromSymbol(symbol: Symbol, mustBeIdentifier?: false): EntityName; - function createNameFromSymbol(symbol: Symbol, mustBeIdentifier: boolean | undefined): EntityName { - let parentSymbol: Symbol; - let meaning: SymbolFlags; - - // Get qualified name if the symbol is not a type parameter - // and there is an enclosing declaration. - let chain: Symbol[]; - const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; - if (!isTypeParameter && enclosingDeclaration) { - chain = getSymbolChain(symbol, meaning, /*endOfChain*/ true); - // TODO: check whether type pointed to by symbol requires type arguments to be printed. - Debug.assert(chain && chain.length > 0); - } - else { - chain = [symbol]; - } - - parentSymbol = undefined; - if(mustBeIdentifier && chain.length !== 1) { - encounteredError = true; - // TODO: failing to get an identifier when we expect one generates an unprintable node. - // Should error handling be more severe? - } - return createEntityNameFromSymbolChain(chain, chain.length - 1); - - function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName { - Debug.assert(chain && 0 <= index && index < chain.length); - // const parentIndex = index - 1; - const symbol = chain[index]; - let typeParameterString = ""; - if(index > 0) { - // TODO: is the parentSymbol wrong? - const parentSymbol = chain[index - 1]; - let typeParameters: TypeParameter[]; - if(getCheckFlags(symbol) & CheckFlags.Instantiated) { - typeParameters = getTypeParametersOfClassOrInterface(parentSymbol); - } - else { - const targetSymbol = getTargetSymbol(parentSymbol); - if(targetSymbol.flags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeAlias)) { - typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - } - } - if(typeParameters && typeParameters.length > 0) { - encounteredError = true; - const writer = getSingleLineStringWriter(); - const displayBuilder = getSymbolDisplayBuilder(); - displayBuilder.buildDisplayForTypeParametersAndDelimiters(typeParameters, writer, enclosingDeclaration, 0); - typeParameterString = writer.string(); - releaseStringWriter(writer); - - } - } - const symbolName = getNameOfSymbol(symbol); - const symbolNameWithTypeParameters = typeParameterString.length > 0 ? `${symbolName}<${typeParameterString}>` : symbolName; - let identifier = createIdentifier(symbolNameWithTypeParameters); - - return index > 0 ? createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; - } - - /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ - function getSymbolChain(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): Symbol[] | undefined { - let accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/false); - - if (!accessibleSymbolChain || - needsQualification(accessibleSymbolChain[0], enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { - - // Go up and add our parent. - const parent = getParentOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol); - if (parent) { - const parentChain = getSymbolChain(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false); - if (parentChain) { - accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [symbol]); - } - } - } - - if (accessibleSymbolChain) { - return accessibleSymbolChain; - } - - else if ( - // If this is the last part of outputting the symbol, always output. The cases apply only to parent symbols. - endOfChain || - // If a parent symbol is an external module, don't write it. (We prefer just `x` vs `"foo/bar".x`.) - !(!parentSymbol && ts.forEach(symbol.declarations, hasExternalModuleSymbol)) && - // If a parent symbol is an anonymous type, don't write it. - !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral))) { - - return [symbol]; - } + return [symbol]; } } @@ -2687,26 +2738,6 @@ namespace ts { } } - function indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node): IndexSignatureDeclaration { - const indexerTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword); - - const name = getNameFromIndexInfo(indexInfo); - const indexingParameter = createParameter( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - name, - /*questionToken*/ undefined, - indexerTypeNode, - /*initializer*/ undefined); - const typeNode = typeToTypeNode(indexInfo.type, enclosingDeclaration); - return createIndexSignatureDeclaration( - [indexingParameter], - typeNode, - /*decorators*/ undefined, - indexInfo.isReadonly ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined); - } - function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Declaration, flags?: TypeFormatFlags): string { const writer = getSingleLineStringWriter(); getSymbolDisplayBuilder().buildTypePredicateDisplay(typePredicate, writer, enclosingDeclaration, flags); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5ccdd79f19..288d0942b7 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2473,11 +2473,11 @@ namespace ts { getNonNullableType(type: Type): Type; /** Note that the resulting nodes cannot be checked. */ - typeToTypeNode(type: Type, enclosingDeclaration: Node): TypeNode; + typeToTypeNode(type: Type, enclosingDeclaration?: Node, returnNodeOnError?: boolean): TypeNode; /** Note that the resulting nodes cannot be checked. */ - indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node): IndexSignatureDeclaration; + signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, returnNodeOnError?: boolean): SignatureDeclaration; /** Note that the resulting nodes cannot be checked. */ - signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration: Node): SignatureDeclaration; + indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, returnNodeOnError?: boolean): IndexSignatureDeclaration; getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; getSymbolAtLocation(node: Node): Symbol; @@ -2530,6 +2530,20 @@ namespace ts { /* @internal */ getTypeCount(): number; } + /** Note that the resulting nodes cannot be checked. */ + /* @internal */ + export interface NodeBuilder { + typeToTypeNode(type: Type, enclosingDeclaration?: Node, returnNodeOnError?: boolean): NodeBuilderResult; + signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node): NodeBuilderResult; + indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node): NodeBuilderResult; + } + + /* @internal */ + export interface NodeBuilderResult { + value: T; + error?: boolean; + } + export interface SymbolDisplayBuilder { buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; buildSymbolDisplay(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): void; From 1f3029d4f15e8d6fa8617318a8655c71756ce2bf Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 23 Mar 2017 08:34:58 -0700 Subject: [PATCH 52/65] error handling --- src/compiler/checker.ts | 960 +++++++++++++++++++++------------------- src/compiler/types.ts | 23 +- 2 files changed, 517 insertions(+), 466 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b681d1298d..c981d0673b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -61,6 +61,7 @@ namespace ts { const noImplicitThis = compilerOptions.noImplicitThis === undefined ? compilerOptions.strict : compilerOptions.noImplicitThis; const emitResolver = createResolver(); + let nodeBuilderCache: NodeBuilder | undefined; const undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined"); undefinedSymbol.declarations = []; @@ -106,9 +107,9 @@ namespace ts { getParameterType: getTypeAtPosition, getReturnTypeOfSignature, getNonNullableType, - typeToTypeNode, - indexInfoToIndexSignatureDeclaration, - signatureToSignatureDeclaration, + typeToTypeNode: getNodeBuilder().typeToTypeNode, + indexInfoToIndexSignatureDeclaration: getNodeBuilder().indexInfoToIndexSignatureDeclaration, + signatureToSignatureDeclaration: getNodeBuilder().signatureToSignatureDeclaration, getSymbolsInScope: (location, meaning) => { location = getParseTreeNode(location); return location ? getSymbolsInScope(location, meaning) : []; @@ -2191,476 +2192,529 @@ namespace ts { return result; } - function typeParameterToDeclaration(type: TypeParameter, enclosingDeclaration?: Node): TypeParameterDeclaration { - if (!(type && type.symbol && type.flags & TypeFlags.TypeParameter)) { - return undefined; + function getNodeBuilder(): NodeBuilder { + if (nodeBuilderCache) { + return nodeBuilderCache; } - const constraint = typeToTypeNode(getConstraintFromTypeParameter(type), enclosingDeclaration); - const defaultParameter = typeToTypeNode(getDefaultFromTypeParameter(type), enclosingDeclaration); - - // TODO: use method internal to typeToTypeNode. - const name = symbolToString(type.symbol); - return createTypeParameterDeclaration(name, constraint, defaultParameter); - } - - function symbolToParameterDeclaration(parameterSymbol: Symbol, enclosingDeclaration?: Node): ParameterDeclaration { - const parameterDeclaration = parameterSymbol.declarations[0] as ParameterDeclaration; - const parameterType = getTypeOfSymbol(parameterSymbol); - const parameterTypeNode = typeToTypeNode(parameterType, enclosingDeclaration); - // TODO: check initializer accessibility correctly. - const parameterNode = createParameter( - parameterDeclaration.decorators, - parameterDeclaration.modifiers, - parameterDeclaration.dotDotDotToken && createToken(SyntaxKind.DotDotDotToken), - // Clone name to remove trivia. - getSynthesizedClone(parameterDeclaration.name), - parameterDeclaration.questionToken && createToken(SyntaxKind.QuestionToken), - parameterTypeNode, - parameterDeclaration.initializer); - return parameterNode; - } - - function signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node): SignatureDeclaration { - const typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, enclosingDeclaration)); - const parameters = signature.parameters.map(parameter => symbolToParameterDeclaration(parameter, enclosingDeclaration)); - const type = typeToTypeNodeExceptAny(getReturnTypeOfSignature(signature)); - - return createSignatureDeclaration(kind, typeParameters, parameters, type); - - function typeToTypeNodeExceptAny(type: Type): TypeNode | undefined { - const typeNode = typeToTypeNode(type, enclosingDeclaration); - return typeNode && typeNode.kind !== SyntaxKind.AnyKeyword ? typeNode : undefined; - } - } - - - // function typeToDisplayParts - - function typeToTypeNode(type: Type, enclosingDeclaration?: Node, returnNodeOnError?: boolean): TypeNode { let encounteredError = false; - let inObjectTypeLiteral = false; - let checkAlias = true; - let symbolStack: Symbol[] = undefined; - const result = typeToTypeNodeWorker(type); - // returnNodeOnError = true; // TODO: unset. - return encounteredError && !returnNodeOnError ? undefined: result; + nodeBuilderCache = { + typeToTypeNode, + indexInfoToIndexSignatureDeclaration, + signatureToSignatureDeclaration + } - function typeToTypeNodeWorker(type: Type): TypeNode { - if (!type) { - encounteredError = true; + return nodeBuilderCache; + + function typeToTypeNode(type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): TypeNode { + const helper = () => typeToTypeNodeHelper(type, enclosingDeclaration, flags); + return callHelperWithErrorHandling(helper); + } + + function indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): IndexSignatureDeclaration { + const helper = () => indexInfoToIndexSignatureDeclarationHelper(indexInfo, kind, enclosingDeclaration, flags); + return callHelperWithErrorHandling(helper); + } + + function signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): SignatureDeclaration { + const helper = () => signatureToSignatureDeclarationHelper(signature, kind, enclosingDeclaration, flags); + return callHelperWithErrorHandling(helper); + } + + function callHelperWithErrorHandling(nodeBuilderHelper: () => T): T | undefined { + const encounteredErrorCache = encounteredError; + const resultingNode = nodeBuilderHelper(); + const result = encounteredError ? undefined : resultingNode; + encounteredError = encounteredErrorCache; + return result; + } + + function typeToTypeNodeHelper(type: Type, enclosingDeclaration: Node, flags: NodeBuilderFlags): TypeNode { + let inObjectTypeLiteral = false; + let checkAlias = true; + let symbolStack: Symbol[] = undefined; + + return typeToTypeNodeWorker(type, flags); + + function typeToTypeNodeWorker(type: Type, flags: NodeBuilderFlags): TypeNode { + if (!type) { + encounteredError = encounteredError || !(flags & NodeBuilderFlags.allowUndefinedNode); + return undefined; + } + flags = flags & ~NodeBuilderFlags.allowUndefinedNode; + + if (type.flags & TypeFlags.Any) { + return createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + if (type.flags & TypeFlags.String) { + return createKeywordTypeNode(SyntaxKind.StringKeyword); + } + if (type.flags & TypeFlags.Number) { + return createKeywordTypeNode(SyntaxKind.NumberKeyword); + } + if (type.flags & TypeFlags.Boolean) { + return createKeywordTypeNode(SyntaxKind.BooleanKeyword); + } + if (type.flags & TypeFlags.Enum) { + const name = symbolToName(type.symbol, enclosingDeclaration, /*mustBeIdentifier*/ false, flags); + return createTypeReferenceNode(name, /*typeArguments*/ undefined); + } + if (type.flags & (TypeFlags.StringLiteral)) { + return createLiteralTypeNode((createLiteral((type).text))); + } + if (type.flags & (TypeFlags.NumberLiteral)) { + return createLiteralTypeNode((createNumericLiteral((type).text))); + } + if (type.flags & TypeFlags.BooleanLiteral) { + return (type).intrinsicName === "true" ? createTrue() : createFalse(); + } + if (type.flags & TypeFlags.EnumLiteral) { + const name = symbolToName(type.symbol, enclosingDeclaration, /*mustBeIdentifier*/ false, flags); + return createTypeReferenceNode(name, /*typeArguments*/ undefined); + } + if (type.flags & TypeFlags.Void) { + return createKeywordTypeNode(SyntaxKind.VoidKeyword); + } + if (type.flags & TypeFlags.Undefined) { + return createKeywordTypeNode(SyntaxKind.UndefinedKeyword); + } + if (type.flags & TypeFlags.Null) { + return createKeywordTypeNode(SyntaxKind.NullKeyword); + } + if (type.flags & TypeFlags.Never) { + return createKeywordTypeNode(SyntaxKind.NeverKeyword); + } + if (type.flags & TypeFlags.ESSymbol) { + throw new Error("ESSymbol not implemented"); + } + if (type.flags & TypeFlags.NonPrimitive) { + throw new Error("Non primitive not implemented"); + } + if (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType) { + if (inObjectTypeLiteral) { + encounteredError = encounteredError || !(flags & NodeBuilderFlags.allowThisInObjectLiteral); + } + return createThis(); + } + + const objectFlags = getObjectFlags(type); + + if (objectFlags & ObjectFlags.Reference) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + return typeReferenceToTypeReferenceNode(type); + } + if (objectFlags & ObjectFlags.ClassOrInterface) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + const name = symbolToName(type.symbol, enclosingDeclaration, /*mustBeIdentifier*/ false, flags); + // TODO(aozgaa): handle type arguments. + return createTypeReferenceNode(name, /*typeArguments*/ undefined); + } + if (type.flags & TypeFlags.TypeParameter) { + const name = symbolToName(type.symbol, enclosingDeclaration, /*mustBeIdentifier*/ false, flags); + // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. + return createTypeReferenceNode(name, /*typeArguments*/ undefined); + } + + if (checkAlias && type.aliasSymbol) { + const name = symbolToName(type.aliasSymbol, enclosingDeclaration, /*mustBeIdentifier*/ false, flags); + const typeArgumentNodes = mapToTypeNodeArray(type.aliasTypeArguments); + return createTypeReferenceNode(name, typeArgumentNodes); + } + checkAlias = false; + + if (type.flags & TypeFlags.Union) { + return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, mapToTypeNodeArray(formatUnionTypes((type).types))); + } + + if (type.flags & TypeFlags.Intersection) { + return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, mapToTypeNodeArray((type as UnionType).types)); + } + + if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + // The type is an object literal type. + return createAnonymousTypeNode(type); + } + + if (type.flags & TypeFlags.Index) { + const indexedType = (type).type; + const indexTypeNode = typeToTypeNodeWorker(indexedType, flags); + return createTypeOperatorNode(indexTypeNode); + } + if (type.flags & TypeFlags.IndexedAccess) { + const objectTypeNode = typeToTypeNodeWorker((type).objectType, flags); + const indexTypeNode = typeToTypeNodeWorker((type).indexType, flags); + return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); + } + + Debug.fail("Should be unreachable."); + + function mapToTypeNodeArray(types: Type[]): NodeArray { + return types && asNodeArray(types.map(typeToTypeNodeWorker) as TypeNode[]); + } + + function createMappedTypeNodeFromType(type: MappedType) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + const typeParameter = getTypeParameterFromMappedType(type); + const typeParameterNode = typeParameterToDeclaration(typeParameter, enclosingDeclaration, flags); + + const templateTypeNode = typeToTypeNodeWorker(getTemplateTypeFromMappedType(type), flags | NodeBuilderFlags.allowUndefinedNode); + const readonlyToken = (type).declaration && (type).declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; + const questionToken = (type).declaration && (type).declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; + + return createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); + } + + function createAnonymousTypeNode(type: ObjectType): TypeNode { + const symbol = type.symbol; + if (symbol) { + // Always use 'typeof T' for type of class, enum, and module objects + if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) || + symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || + shouldWriteTypeOfFunctionSymbol()) { + return createTypeQueryNodeFromType(type); + } + else if (contains(symbolStack, symbol)) { + // If type is an anonymous type literal in a type alias declaration, use type alias name + const typeAlias = getTypeAliasForTypeLiteral(type); + if (typeAlias) { + // The specified symbol flags need to be reinterpreted as type flags + const entityName = symbolToName(typeAlias, enclosingDeclaration, /*mustBeIdentifier*/ false, flags); + return createTypeReferenceNode(entityName, /*typeArguments*/ undefined); + } + else { + return createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + } + else { + // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead + // of types allows us to catch circular references to instantiations of the same anonymous type + if (!symbolStack) { + symbolStack = []; + } + symbolStack.push(symbol); + let result = createTypeNodeFromObjectType(type); + symbolStack.pop(); + return result; + } + } + else { + // Anonymous types without a symbol are never circular. + return createTypeNodeFromObjectType(type); + } + + function shouldWriteTypeOfFunctionSymbol() { + const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method && // typeof static method + forEach(symbol.declarations, declaration => getModifierFlags(declaration) & ModifierFlags.Static)); + const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && + (symbol.parent || // is exported function symbol + forEach(symbol.declarations, declaration => + declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); + if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { + // typeof is allowed only for static/non local functions + return contains(symbolStack, symbol); // it is type of the symbol uses itself recursively + } + } + } + + function createTypeNodeFromObjectType(type: ObjectType): TypeNode { + if (type.objectFlags & ObjectFlags.Mapped) { + if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { + return createMappedTypeNodeFromType(type); + } + } + + const resolved = resolveStructuredTypeMembers(type); + if (!resolved.properties.length && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { + if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { + return createTypeLiteralNode(/*members*/ undefined); + } + + if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { + const signature = resolved.callSignatures[0]; + return signatureToSignatureDeclarationHelper(signature, SyntaxKind.FunctionType, enclosingDeclaration, flags); + } + if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { + const signature = resolved.constructSignatures[0]; + return signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructorType, enclosingDeclaration, flags); + } + } + + const saveInObjectTypeLiteral = inObjectTypeLiteral; + inObjectTypeLiteral = true; + const members = createTypeNodesFromResolvedType(resolved); + inObjectTypeLiteral = saveInObjectTypeLiteral; + return createTypeLiteralNode(members); + } + + function createTypeQueryNodeFromType(type: Type) { + const symbol = type.symbol; + if (symbol) { + const entityName = symbolToName(symbol, enclosingDeclaration, /*mustBeIdentifier*/ false, flags); + return createTypeQueryNode(entityName); + } + } + + function typeReferenceToTypeReferenceNode(type: TypeReference) { + const typeArguments: Type[] = type.typeArguments || emptyArray; + if (type.target === globalArrayType) { + const elementType = typeToTypeNodeWorker(typeArguments[0], flags); + return createArrayTypeNode(elementType); + } + else if (type.target.objectFlags & ObjectFlags.Tuple) { + return createTupleTypeNode(typeArguments.length > 0 ? mapToTypeNodeArray(typeArguments.slice(0, getTypeReferenceArity(type))) : undefined); + } + else { + const outerTypeParameters = type.target.outerTypeParameters; + let i = 0; + let qualifiedName: QualifiedName | undefined = undefined; + if (outerTypeParameters) { + const length = outerTypeParameters.length; + while (i < length) { + // Find group of type arguments for type parameters with the same declaring container. + const start = i; + const parent = getParentSymbolOfTypeParameter(outerTypeParameters[i]); + do { + i++; + } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); + // When type parameters are their own type arguments for the whole group (i.e. we have + // the default outer type arguments), we don't show the group. + if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { + const qualifiedNamePart = symbolToName(parent, enclosingDeclaration, /*mustBeIdentifier*/ true, flags); + if (!qualifiedName) { + qualifiedName = createQualifiedName(qualifiedNamePart, /*right*/ undefined); + } + else { + Debug.assert(!qualifiedName.right); + qualifiedName.right = qualifiedNamePart; + qualifiedName = createQualifiedName(qualifiedName, /*right*/ undefined); + } + } + } + } + let entityName: EntityName = undefined; + const nameIdentifier = symbolToName(type.symbol, enclosingDeclaration, /*mustBeIdentifier*/ true, flags); + if (qualifiedName) { + Debug.assert(!qualifiedName.right); + qualifiedName.right = nameIdentifier; + entityName = qualifiedName; + } + else { + entityName = nameIdentifier; + } + const typeParameterCount = (type.target.typeParameters || emptyArray).length; + const typeArgumentNodes = mapToTypeNodeArray(typeArguments.length > 0 ? typeArguments.slice(i, typeParameterCount - i) : undefined); + return createTypeReferenceNode(entityName, typeArgumentNodes); + } + } + + function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] { + const typeElements: TypeElement[] = []; + for (const signature of resolvedType.callSignatures) { + typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.CallSignature, enclosingDeclaration, flags)); + } + for (const signature of resolvedType.constructSignatures) { + typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, enclosingDeclaration, flags)); + } + if (resolvedType.stringIndexInfo) { + typeElements.push(indexInfoToIndexSignatureDeclarationHelper(resolvedType.stringIndexInfo, IndexKind.String, enclosingDeclaration, flags)); + } + if (resolvedType.numberIndexInfo) { + typeElements.push(indexInfoToIndexSignatureDeclarationHelper(resolvedType.numberIndexInfo, IndexKind.Number, enclosingDeclaration, flags)); + } + + const properties = resolvedType.properties; + if (!properties) { + return typeElements; + } + + for (const propertySymbol of properties) { + const propertyType = getTypeOfSymbol(propertySymbol); + const oldDeclaration = propertySymbol.declarations && propertySymbol.declarations[0] as TypeElement; + if (!oldDeclaration) { + return; + } + const propertyName = oldDeclaration.name; + const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined;; + if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length) { + const signatures = getSignaturesOfType(propertyType, SignatureKind.Call); + for (const signature of signatures) { + const methodDeclaration = signatureToSignatureDeclarationHelper(signature, SyntaxKind.MethodSignature, enclosingDeclaration, flags); + methodDeclaration.name = propertyName; + methodDeclaration.questionToken = optionalToken; + typeElements.push(methodDeclaration); + } + } + else { + typeElements.push(createPropertySignature( + propertyName, + optionalToken, + typeToTypeNodeWorker(propertyType, flags), + /*initializer*/undefined)); + } + } + return typeElements.length ? typeElements : undefined; + } + } + } + + function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node, flags: NodeBuilderFlags): IndexSignatureDeclaration { + const indexerTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword); + + const name = getNameFromIndexInfo(indexInfo); + const indexingParameter = createParameter( + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + name, + /*questionToken*/ undefined, + indexerTypeNode, + /*initializer*/ undefined); + const typeNode = typeToTypeNodeHelper(indexInfo.type, enclosingDeclaration, flags); + return createIndexSignatureDeclaration( + [indexingParameter], + typeNode, + /*decorators*/ undefined, + indexInfo.isReadonly ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined); + } + + function signatureToSignatureDeclarationHelper(signature: Signature, kind: SyntaxKind, enclosingDeclaration: Node, flags: NodeBuilderFlags): SignatureDeclaration { + + const typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, enclosingDeclaration, flags)); + const parameters = signature.parameters.map(parameter => symbolToParameterDeclaration(parameter, enclosingDeclaration, flags)); + const type = typeToTypeNodeExceptAny(getReturnTypeOfSignature(signature)); + + return createSignatureDeclaration(kind, typeParameters, parameters, type); + + function typeToTypeNodeExceptAny(type: Type): TypeNode | undefined { + // Note, this call will *not* mark encounteredError. + const typeNode = typeToTypeNode(type, enclosingDeclaration); + return typeNode && typeNode.kind !== SyntaxKind.AnyKeyword ? typeNode : undefined; + } + } + + function typeParameterToDeclaration(type: TypeParameter, enclosingDeclaration: Node, flags: NodeBuilderFlags): TypeParameterDeclaration { + if (!(type && type.symbol && type.flags & TypeFlags.TypeParameter)) { return undefined; } - if (type.flags & TypeFlags.Any) { - return createKeywordTypeNode(SyntaxKind.AnyKeyword); + const constraint = typeToTypeNodeHelper(getConstraintFromTypeParameter(type), enclosingDeclaration, flags | NodeBuilderFlags.allowUndefinedNode); + const defaultParameter = typeToTypeNodeHelper(getDefaultFromTypeParameter(type), enclosingDeclaration, flags | NodeBuilderFlags.allowUndefinedNode); +; + const name = symbolToName(type.symbol, enclosingDeclaration, /*mustBeIdentifier*/ true, flags); + return createTypeParameterDeclaration(name, constraint, defaultParameter); + } + + function symbolToParameterDeclaration(parameterSymbol: Symbol, enclosingDeclaration: Node, flags: NodeBuilderFlags): ParameterDeclaration { + const parameterDeclaration = parameterSymbol.declarations[0] as ParameterDeclaration; + const parameterType = getTypeOfSymbol(parameterSymbol); + const parameterTypeNode = typeToTypeNodeHelper(parameterType, enclosingDeclaration, flags); + // TODO(aozgaa): check initializer accessibility correctly. + const parameterNode = createParameter( + parameterDeclaration.decorators, + parameterDeclaration.modifiers, + parameterDeclaration.dotDotDotToken && createToken(SyntaxKind.DotDotDotToken), + // Clone name to remove trivia. + getSynthesizedClone(parameterDeclaration.name), + parameterDeclaration.questionToken && createToken(SyntaxKind.QuestionToken), + parameterTypeNode, + parameterDeclaration.initializer); + return parameterNode; + } + + function symbolToName(symbol: Symbol, enclosingDeclaration: Node | undefined, mustBeIdentifier: true, flags: NodeBuilderFlags): Identifier; + function symbolToName(symbol: Symbol, enclosingDeclaration: Node, mustBeIdentifier: false, flags: NodeBuilderFlags): EntityName; + function symbolToName(symbol: Symbol, enclosingDeclaration: Node | undefined, mustBeIdentifier: boolean, flags: NodeBuilderFlags): EntityName { + let parentSymbol: Symbol; + let meaning: SymbolFlags; + + // Get qualified name if the symbol is not a type parameter + // and there is an enclosing declaration. + let chain: Symbol[]; + const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; + if (!isTypeParameter && enclosingDeclaration) { + chain = getSymbolChain(symbol, meaning, /*endOfChain*/ true); + // TODO: check whether type pointed to by symbol requires type arguments to be printed. + Debug.assert(chain && chain.length > 0); } - if (type.flags & TypeFlags.String) { - return createKeywordTypeNode(SyntaxKind.StringKeyword); - } - if (type.flags & TypeFlags.Number) { - return createKeywordTypeNode(SyntaxKind.NumberKeyword); - } - if(type.flags & TypeFlags.Boolean) { - return createKeywordTypeNode(SyntaxKind.BooleanKeyword); - } - if (type.flags & TypeFlags.Enum) { - const name = createNameFromSymbol(type.symbol); - return createTypeReferenceNode(name, /*typeArguments*/ undefined); - } - if (type.flags & (TypeFlags.StringLiteral)) { - return createLiteralTypeNode((createLiteral((type).text))); - } - if (type.flags & (TypeFlags.NumberLiteral)) { - return createLiteralTypeNode((createNumericLiteral((type).text))); - } - if(type.flags & TypeFlags.BooleanLiteral) { - return (type).intrinsicName === "true" ? createTrue() : createFalse(); - } - if (type.flags & TypeFlags.EnumLiteral) { - const name = createNameFromSymbol(type.symbol); - return createTypeReferenceNode(name, /*typeArguments*/ undefined); - } - if (type.flags & TypeFlags.Void) { - return createKeywordTypeNode(SyntaxKind.VoidKeyword); - } - if (type.flags & TypeFlags.Undefined) { - return createKeywordTypeNode(SyntaxKind.UndefinedKeyword); - } - if (type.flags & TypeFlags.Null) { - return createKeywordTypeNode(SyntaxKind.NullKeyword); - } - if (type.flags & TypeFlags.Never) { - return createKeywordTypeNode(SyntaxKind.NeverKeyword); - } - if (type.flags & TypeFlags.ESSymbol) { - throw new Error("ESSymbol not implemented"); - } - if (type.flags & TypeFlags.NonPrimitive) { - throw new Error("Non primitive not implemented"); - } - if (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType) { - if (inObjectTypeLiteral) { - encounteredError = true; - } - return createThis(); + else { + chain = [symbol]; } - const objectFlags = getObjectFlags(type); - - if (objectFlags & ObjectFlags.Reference) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - return typeReferenceToTypeReferenceNode(type); - } - if (objectFlags & ObjectFlags.ClassOrInterface) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - const name = createNameFromSymbol(type.symbol); - // TODO: handle type arguments. - return createTypeReferenceNode(name, /*typeArguments*/ undefined); - } - if (type.flags & TypeFlags.TypeParameter) { - const name = createNameFromSymbol(type.symbol); - // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. - return createTypeReferenceNode(name, /*typeArguments*/ undefined); + parentSymbol = undefined; + if (mustBeIdentifier && chain.length !== 1) { + encounteredError = encounteredError || !(flags & NodeBuilderFlags.allowQualifedNameInPlaceOfIdentifier); + // TODO: failing to get an identifier when we expect one generates an unprintable node. + // Should error handling be more severe? } + return createEntityNameFromSymbolChain(chain, chain.length - 1); - if (checkAlias && type.aliasSymbol) { - const name = createNameFromSymbol(type.aliasSymbol); - const typeArgumentNodes = mapToTypeNodeArray(type.aliasTypeArguments); - return createTypeReferenceNode(name, typeArgumentNodes); - } - checkAlias = false; - - if (type.flags & TypeFlags.Union) { - return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, mapToTypeNodeArray(formatUnionTypes((type).types))); - } - - if (type.flags & TypeFlags.Intersection) { - return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, mapToTypeNodeArray((type as UnionType).types)); - } - - if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - // The type is an object literal type. - return createAnonymousTypeNode(type); - } - - // TODO (aozgaa): implement string and number literals here once there is a testable case. - - if (type.flags & TypeFlags.Index) { - const indexedType = (type).type; - const indexTypeNode = typeToTypeNodeWorker(indexedType); - return createTypeOperatorNode(indexTypeNode); - } - if (type.flags & TypeFlags.IndexedAccess) { - const objectTypeNode = typeToTypeNodeWorker((type).objectType); - const indexTypeNode = typeToTypeNodeWorker((type).indexType); - return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); - } - - Debug.fail("Should be unreachable."); - - function mapToTypeNodeArray(types: Type[]): NodeArray { - return types && asNodeArray(types.map(typeToTypeNodeWorker) as TypeNode[]); - } - - function createMappedTypeNodeFromType(type: MappedType) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - const typeParameter = getTypeParameterFromMappedType(type); - const typeParameterNode = typeParameterToDeclaration(typeParameter, enclosingDeclaration); - - const templateTypeNode = typeToTypeNode(getTemplateTypeFromMappedType(type), enclosingDeclaration); - const readonlyToken = (type).declaration && (type).declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; - const questionToken = (type).declaration && (type).declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; - - return createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); - } - - function createAnonymousTypeNode(type: ObjectType): TypeNode { - const symbol = type.symbol; - if (symbol) { - // Always use 'typeof T' for type of class, enum, and module objects - if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) || - symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || - shouldWriteTypeOfFunctionSymbol()) { - return createTypeQueryNodeFromType(type); - } - else if (contains(symbolStack, symbol)) { - // If type is an anonymous type literal in a type alias declaration, use type alias name - const typeAlias = getTypeAliasForTypeLiteral(type); - if (typeAlias) { - // The specified symbol flags need to be reinterpreted as type flags - const entityName = createNameFromSymbol(typeAlias); - return createTypeReferenceNode(entityName, /*typeArguments*/ undefined); - } - else { - return createKeywordTypeNode(SyntaxKind.AnyKeyword); - } + function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName { + Debug.assert(chain && 0 <= index && index < chain.length); + // const parentIndex = index - 1; + const symbol = chain[index]; + let typeParameterString = ""; + if (index > 0) { + // TODO: is the parentSymbol wrong? + const parentSymbol = chain[index - 1]; + let typeParameters: TypeParameter[]; + if (getCheckFlags(symbol) & CheckFlags.Instantiated) { + typeParameters = getTypeParametersOfClassOrInterface(parentSymbol); } else { - // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead - // of types allows us to catch circular references to instantiations of the same anonymous type - if (!symbolStack) { - symbolStack = []; + const targetSymbol = getTargetSymbol(parentSymbol); + if (targetSymbol.flags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeAlias)) { + typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); } - symbolStack.push(symbol); - let result = createTypeNodeFromObjectType(type); - symbolStack.pop(); - return result; } - } - else { - // Anonymous types without a symbol are never circular. - return createTypeNodeFromObjectType(type); - } + if (typeParameters && typeParameters.length > 0) { + encounteredError = encounteredError || !(flags & NodeBuilderFlags.allowTypeParameterInQualifiedName);; + const writer = getSingleLineStringWriter(); + const displayBuilder = getSymbolDisplayBuilder(); + displayBuilder.buildDisplayForTypeParametersAndDelimiters(typeParameters, writer, enclosingDeclaration, 0); + typeParameterString = writer.string(); + releaseStringWriter(writer); - function shouldWriteTypeOfFunctionSymbol() { - const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method && // typeof static method - forEach(symbol.declarations, declaration => getModifierFlags(declaration) & ModifierFlags.Static)); - const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && - (symbol.parent || // is exported function symbol - forEach(symbol.declarations, declaration => - declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); - if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { - // typeof is allowed only for static/non local functions - return contains(symbolStack, symbol); // it is type of the symbol uses itself recursively } } + const symbolName = getNameOfSymbol(symbol); + const symbolNameWithTypeParameters = typeParameterString.length > 0 ? `${symbolName}<${typeParameterString}>` : symbolName; + let identifier = createIdentifier(symbolNameWithTypeParameters); + + return index > 0 ? createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; } - function createTypeNodeFromObjectType(type: ObjectType): TypeNode { - if (type.objectFlags & ObjectFlags.Mapped) { - if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { - return createMappedTypeNodeFromType(type); - } - } + /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ + function getSymbolChain(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): Symbol[] | undefined { + let accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/false); - const resolved = resolveStructuredTypeMembers(type); - if (!resolved.properties.length && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { - if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { - return createTypeLiteralNode(/*members*/ undefined); - } + if (!accessibleSymbolChain || + needsQualification(accessibleSymbolChain[0], enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { - if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { - const signature = resolved.callSignatures[0]; - return signatureToSignatureDeclaration(signature, SyntaxKind.FunctionType, enclosingDeclaration); - } - if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { - const signature = resolved.constructSignatures[0]; - return signatureToSignatureDeclaration(signature, SyntaxKind.ConstructorType, enclosingDeclaration); - } - } - - const saveInObjectTypeLiteral = inObjectTypeLiteral; - inObjectTypeLiteral = true; - const members = createTypeNodesFromResolvedType(resolved); - inObjectTypeLiteral = saveInObjectTypeLiteral; - return createTypeLiteralNode(members); - } - - function createTypeQueryNodeFromType(type: Type) { - const symbol = type.symbol; - if (symbol) { - const entityName = createNameFromSymbol(symbol); - return createTypeQueryNode(entityName); - } - } - - function typeReferenceToTypeReferenceNode(type: TypeReference) { - const typeArguments: Type[] = type.typeArguments || emptyArray; - if (type.target === globalArrayType) { - const elementType = typeToTypeNodeWorker(typeArguments[0]); - return createArrayTypeNode(elementType); - } - else if (type.target.objectFlags & ObjectFlags.Tuple) { - return createTupleTypeNode(typeArguments.length > 0 ? mapToTypeNodeArray(typeArguments.slice(0, getTypeReferenceArity(type))) : undefined); - } - else { - const outerTypeParameters = type.target.outerTypeParameters; - let i = 0; - let qualifiedName: QualifiedName | undefined = undefined; - if (outerTypeParameters) { - const length = outerTypeParameters.length; - while (i < length) { - // Find group of type arguments for type parameters with the same declaring container. - const start = i; - const parent = getParentSymbolOfTypeParameter(outerTypeParameters[i]); - do { - i++; - } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); - // When type parameters are their own type arguments for the whole group (i.e. we have - // the default outer type arguments), we don't show the group. - if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { - const qualifiedNamePart = createNameFromSymbol(parent, /*mustBeIdentifier*/ true); - if (!qualifiedName) { - qualifiedName = createQualifiedName(qualifiedNamePart, /*right*/ undefined); - } - else { - Debug.assert(!qualifiedName.right); - qualifiedName.right = qualifiedNamePart; - qualifiedName = createQualifiedName(qualifiedName, /*right*/ undefined); - } - } + // Go up and add our parent. + const parent = getParentOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol); + if (parent) { + const parentChain = getSymbolChain(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false); + if (parentChain) { + accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [symbol]); } } - let entityName: EntityName = undefined; - const nameIdentifier = createNameFromSymbol(type.symbol, /*mustBeIdentifier*/ true); - if (qualifiedName) { - Debug.assert(!qualifiedName.right); - qualifiedName.right = nameIdentifier; - entityName = qualifiedName; - } - else { - entityName = nameIdentifier; - } - const typeParameterCount = (type.target.typeParameters || emptyArray).length; - const typeArgumentNodes = mapToTypeNodeArray(typeArguments.length > 0 ? typeArguments.slice(i, typeParameterCount - i) : undefined); - return createTypeReferenceNode(entityName, typeArgumentNodes); - } - } - - function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] { - const typeElements: TypeElement[] = []; - for (const signature of resolvedType.callSignatures) { - typeElements.push(signatureToSignatureDeclaration(signature, SyntaxKind.CallSignature, enclosingDeclaration)); - } - for (const signature of resolvedType.constructSignatures) { - typeElements.push(signatureToSignatureDeclaration(signature, SyntaxKind.ConstructSignature, enclosingDeclaration)); - } - if (resolvedType.stringIndexInfo) { - typeElements.push(indexInfoToIndexSignatureDeclaration(resolvedType.stringIndexInfo, IndexKind.String, enclosingDeclaration)); - } - if (resolvedType.numberIndexInfo) { - typeElements.push(indexInfoToIndexSignatureDeclaration(resolvedType.numberIndexInfo, IndexKind.Number, enclosingDeclaration)); } - const properties = resolvedType.properties; - if (!properties) { - return typeElements; + if (accessibleSymbolChain) { + return accessibleSymbolChain; } - for (const propertySymbol of properties) { - const propertyType = getTypeOfSymbol(propertySymbol); - const oldDeclaration = propertySymbol.declarations && propertySymbol.declarations[0] as TypeElement; - if (!oldDeclaration) { - return; - } - const propertyName = oldDeclaration.name; - const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined;; - if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length) { - const signatures = getSignaturesOfType(propertyType, SignatureKind.Call); - for (const signature of signatures) { - const methodDeclaration = signatureToSignatureDeclaration(signature, SyntaxKind.MethodSignature, enclosingDeclaration); - methodDeclaration.name = propertyName; - methodDeclaration.questionToken = optionalToken; - typeElements.push(methodDeclaration); - } - } - else { - typeElements.push(createPropertySignature( - propertyName, - optionalToken, - typeToTypeNodeWorker(propertyType), - /*initializer*/undefined)); - } - } - return typeElements.length ? typeElements : undefined; - } + else if ( + // If this is the last part of outputting the symbol, always output. The cases apply only to parent symbols. + endOfChain || + // If a parent symbol is an external module, don't write it. (We prefer just `x` vs `"foo/bar".x`.) + !(!parentSymbol && ts.forEach(symbol.declarations, hasExternalModuleSymbol)) && + // If a parent symbol is an anonymous type, don't write it. + !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral))) { - function createNameFromSymbol(symbol: Symbol, mustBeIdentifier: true): Identifier; - function createNameFromSymbol(symbol: Symbol, mustBeIdentifier?: false): EntityName; - function createNameFromSymbol(symbol: Symbol, mustBeIdentifier: boolean | undefined): EntityName { - let parentSymbol: Symbol; - let meaning: SymbolFlags; - - // Get qualified name if the symbol is not a type parameter - // and there is an enclosing declaration. - let chain: Symbol[]; - const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; - if (!isTypeParameter && enclosingDeclaration) { - chain = getSymbolChain(symbol, meaning, /*endOfChain*/ true); - // TODO: check whether type pointed to by symbol requires type arguments to be printed. - Debug.assert(chain && chain.length > 0); - } - else { - chain = [symbol]; - } - - parentSymbol = undefined; - if(mustBeIdentifier && chain.length !== 1) { - encounteredError = true; - // TODO: failing to get an identifier when we expect one generates an unprintable node. - // Should error handling be more severe? - } - return createEntityNameFromSymbolChain(chain, chain.length - 1); - - function createEntityNameFromSymbolChain(chain: Symbol[], index: number): EntityName { - Debug.assert(chain && 0 <= index && index < chain.length); - // const parentIndex = index - 1; - const symbol = chain[index]; - let typeParameterString = ""; - if(index > 0) { - // TODO: is the parentSymbol wrong? - const parentSymbol = chain[index - 1]; - let typeParameters: TypeParameter[]; - if(getCheckFlags(symbol) & CheckFlags.Instantiated) { - typeParameters = getTypeParametersOfClassOrInterface(parentSymbol); - } - else { - const targetSymbol = getTargetSymbol(parentSymbol); - if(targetSymbol.flags & (SymbolFlags.Class | SymbolFlags.Interface | SymbolFlags.TypeAlias)) { - typeParameters = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); - } - } - if(typeParameters && typeParameters.length > 0) { - encounteredError = true; - const writer = getSingleLineStringWriter(); - const displayBuilder = getSymbolDisplayBuilder(); - displayBuilder.buildDisplayForTypeParametersAndDelimiters(typeParameters, writer, enclosingDeclaration, 0); - typeParameterString = writer.string(); - releaseStringWriter(writer); - - } - } - const symbolName = getNameOfSymbol(symbol); - const symbolNameWithTypeParameters = typeParameterString.length > 0 ? `${symbolName}<${typeParameterString}>` : symbolName; - let identifier = createIdentifier(symbolNameWithTypeParameters); - - return index > 0 ? createQualifiedName(createEntityNameFromSymbolChain(chain, index - 1), identifier) : identifier; - } - - /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ - function getSymbolChain(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): Symbol[] | undefined { - let accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/false); - - if (!accessibleSymbolChain || - needsQualification(accessibleSymbolChain[0], enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { - - // Go up and add our parent. - const parent = getParentOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol); - if (parent) { - const parentChain = getSymbolChain(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false); - if (parentChain) { - accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [symbol]); - } - } - } - - if (accessibleSymbolChain) { - return accessibleSymbolChain; - } - - else if ( - // If this is the last part of outputting the symbol, always output. The cases apply only to parent symbols. - endOfChain || - // If a parent symbol is an external module, don't write it. (We prefer just `x` vs `"foo/bar".x`.) - !(!parentSymbol && ts.forEach(symbol.declarations, hasExternalModuleSymbol)) && - // If a parent symbol is an anonymous type, don't write it. - !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral))) { - - return [symbol]; - } + return [symbol]; } } @@ -2673,7 +2727,7 @@ namespace ts { if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) { return declarationNameToString((declaration.parent).name); } - encounteredError = true; + encounteredError = encounteredError || !(flags & NodeBuilderFlags.allowAnonymousIdentifier); switch (declaration.kind) { case SyntaxKind.ClassExpression: return "(Anonymous class)"; @@ -2687,26 +2741,6 @@ namespace ts { } } - function indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node): IndexSignatureDeclaration { - const indexerTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword); - - const name = getNameFromIndexInfo(indexInfo); - const indexingParameter = createParameter( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, - name, - /*questionToken*/ undefined, - indexerTypeNode, - /*initializer*/ undefined); - const typeNode = typeToTypeNode(indexInfo.type, enclosingDeclaration); - return createIndexSignatureDeclaration( - [indexingParameter], - typeNode, - /*decorators*/ undefined, - indexInfo.isReadonly ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined); - } - function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Declaration, flags?: TypeFormatFlags): string { const writer = getSingleLineStringWriter(); getSymbolDisplayBuilder().buildTypePredicateDisplay(typePredicate, writer, enclosingDeclaration, flags); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5ccdd79f19..d6faf8cc64 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2473,11 +2473,11 @@ namespace ts { getNonNullableType(type: Type): Type; /** Note that the resulting nodes cannot be checked. */ - typeToTypeNode(type: Type, enclosingDeclaration: Node): TypeNode; + typeToTypeNode(type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): TypeNode; /** Note that the resulting nodes cannot be checked. */ - indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node): IndexSignatureDeclaration; + signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): SignatureDeclaration; /** Note that the resulting nodes cannot be checked. */ - signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration: Node): SignatureDeclaration; + indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): IndexSignatureDeclaration; getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[]; getSymbolAtLocation(node: Node): Symbol; @@ -2530,6 +2530,23 @@ namespace ts { /* @internal */ getTypeCount(): number; } + /** Note that any resulting nodes cannot be checked. */ + /* @internal */ + export interface NodeBuilder { + typeToTypeNode(type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): TypeNode; + signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): SignatureDeclaration; + indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): IndexSignatureDeclaration; + } + + export enum NodeBuilderFlags { + None = 0, + allowUndefinedNode = 1 << 0, + allowThisInObjectLiteral = 1 << 1, + allowQualifedNameInPlaceOfIdentifier = 1 << 2, + allowTypeParameterInQualifiedName = 1 << 3, + allowAnonymousIdentifier = 1 << 4 + } + export interface SymbolDisplayBuilder { buildTypeDisplay(type: Type, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags): void; buildSymbolDisplay(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): void; From dfb4df92c8f7e5d80af3213f27e70187dd9287a9 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 23 Mar 2017 11:17:25 -0700 Subject: [PATCH 53/65] move undefined handling to caller --- src/compiler/checker.ts | 44 ++++++++++++++++++++++------------------- src/compiler/types.ts | 9 ++++----- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c981d0673b..f996f738f1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2235,14 +2235,14 @@ namespace ts { let checkAlias = true; let symbolStack: Symbol[] = undefined; - return typeToTypeNodeWorker(type, flags); + return typeToTypeNodeWorker(type); - function typeToTypeNodeWorker(type: Type, flags: NodeBuilderFlags): TypeNode { + function typeToTypeNodeWorker(type: Type): TypeNode { if (!type) { - encounteredError = encounteredError || !(flags & NodeBuilderFlags.allowUndefinedNode); + encounteredError = true; + // TODO(aozgaa): should we return implict any (undefined) or explicit any (keywordtypenode)? return undefined; } - flags = flags & ~NodeBuilderFlags.allowUndefinedNode; if (type.flags & TypeFlags.Any) { return createKeywordTypeNode(SyntaxKind.AnyKeyword); @@ -2302,7 +2302,7 @@ namespace ts { if (objectFlags & ObjectFlags.Reference) { Debug.assert(!!(type.flags & TypeFlags.Object)); - return typeReferenceToTypeReferenceNode(type); + return typeReferenceToTypeNode (type); } if (objectFlags & ObjectFlags.ClassOrInterface) { Debug.assert(!!(type.flags & TypeFlags.Object)); @@ -2339,19 +2339,19 @@ namespace ts { if (type.flags & TypeFlags.Index) { const indexedType = (type).type; - const indexTypeNode = typeToTypeNodeWorker(indexedType, flags); + const indexTypeNode = typeToTypeNodeWorker(indexedType); return createTypeOperatorNode(indexTypeNode); } if (type.flags & TypeFlags.IndexedAccess) { - const objectTypeNode = typeToTypeNodeWorker((type).objectType, flags); - const indexTypeNode = typeToTypeNodeWorker((type).indexType, flags); + const objectTypeNode = typeToTypeNodeWorker((type).objectType); + const indexTypeNode = typeToTypeNodeWorker((type).indexType); return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); } Debug.fail("Should be unreachable."); function mapToTypeNodeArray(types: Type[]): NodeArray { - return types && asNodeArray(types.map(typeToTypeNodeWorker) as TypeNode[]); + return types && asNodeArray(types.map(typeToTypeNodeWorker).filter(node => !!node)); } function createMappedTypeNodeFromType(type: MappedType) { @@ -2359,7 +2359,8 @@ namespace ts { const typeParameter = getTypeParameterFromMappedType(type); const typeParameterNode = typeParameterToDeclaration(typeParameter, enclosingDeclaration, flags); - const templateTypeNode = typeToTypeNodeWorker(getTemplateTypeFromMappedType(type), flags | NodeBuilderFlags.allowUndefinedNode); + const templateType = getTemplateTypeFromMappedType(type) + const templateTypeNode = templateType && typeToTypeNodeWorker(templateType); const readonlyToken = (type).declaration && (type).declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; const questionToken = (type).declaration && (type).declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; @@ -2456,10 +2457,10 @@ namespace ts { } } - function typeReferenceToTypeReferenceNode(type: TypeReference) { + function typeReferenceToTypeNode(type: TypeReference) { const typeArguments: Type[] = type.typeArguments || emptyArray; if (type.target === globalArrayType) { - const elementType = typeToTypeNodeWorker(typeArguments[0], flags); + const elementType = typeToTypeNodeWorker(typeArguments[0]); return createArrayTypeNode(elementType); } else if (type.target.objectFlags & ObjectFlags.Tuple) { @@ -2547,10 +2548,12 @@ namespace ts { } } else { + // TODO(aozgaa): should we create a node with explicit or implict any? + const propertyTypeNode = propertyType ? typeToTypeNodeWorker(propertyType) : createKeywordTypeNode(SyntaxKind.AnyKeyword); typeElements.push(createPropertySignature( propertyName, optionalToken, - typeToTypeNodeWorker(propertyType, flags), + propertyTypeNode, /*initializer*/undefined)); } } @@ -2599,11 +2602,12 @@ namespace ts { return undefined; } - const constraint = typeToTypeNodeHelper(getConstraintFromTypeParameter(type), enclosingDeclaration, flags | NodeBuilderFlags.allowUndefinedNode); - const defaultParameter = typeToTypeNodeHelper(getDefaultFromTypeParameter(type), enclosingDeclaration, flags | NodeBuilderFlags.allowUndefinedNode); -; + const constraint = getConstraintFromTypeParameter(type); + const constraintNode = constraint && typeToTypeNodeHelper(constraint, enclosingDeclaration, flags); + const defaultParameter = getDefaultFromTypeParameter(type); + const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, enclosingDeclaration, flags); const name = symbolToName(type.symbol, enclosingDeclaration, /*mustBeIdentifier*/ true, flags); - return createTypeParameterDeclaration(name, constraint, defaultParameter); + return createTypeParameterDeclaration(name, constraintNode, defaultParameterNode); } function symbolToParameterDeclaration(parameterSymbol: Symbol, enclosingDeclaration: Node, flags: NodeBuilderFlags): ParameterDeclaration { @@ -2635,7 +2639,7 @@ namespace ts { const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; if (!isTypeParameter && enclosingDeclaration) { chain = getSymbolChain(symbol, meaning, /*endOfChain*/ true); - // TODO: check whether type pointed to by symbol requires type arguments to be printed. + // TODO(aozgaa): check whether type pointed to by symbol requires type arguments to be printed. Debug.assert(chain && chain.length > 0); } else { @@ -2645,7 +2649,7 @@ namespace ts { parentSymbol = undefined; if (mustBeIdentifier && chain.length !== 1) { encounteredError = encounteredError || !(flags & NodeBuilderFlags.allowQualifedNameInPlaceOfIdentifier); - // TODO: failing to get an identifier when we expect one generates an unprintable node. + // TODO(aozgaa): failing to get an identifier when we expect one generates an unprintable node. // Should error handling be more severe? } return createEntityNameFromSymbolChain(chain, chain.length - 1); @@ -2656,7 +2660,7 @@ namespace ts { const symbol = chain[index]; let typeParameterString = ""; if (index > 0) { - // TODO: is the parentSymbol wrong? + // TODO(aozgaa): is the parentSymbol wrong? const parentSymbol = chain[index - 1]; let typeParameters: TypeParameter[]; if (getCheckFlags(symbol) & CheckFlags.Instantiated) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d6faf8cc64..03569788d2 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2540,11 +2540,10 @@ namespace ts { export enum NodeBuilderFlags { None = 0, - allowUndefinedNode = 1 << 0, - allowThisInObjectLiteral = 1 << 1, - allowQualifedNameInPlaceOfIdentifier = 1 << 2, - allowTypeParameterInQualifiedName = 1 << 3, - allowAnonymousIdentifier = 1 << 4 + allowThisInObjectLiteral = 1 << 0, + allowQualifedNameInPlaceOfIdentifier = 1 << 1, + allowTypeParameterInQualifiedName = 1 << 2, + allowAnonymousIdentifier = 1 << 3 } export interface SymbolDisplayBuilder { From 8915cb9a0f5f12c03ddb054ab20f4a5eb00bbc7a Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 23 Mar 2017 13:36:38 -0700 Subject: [PATCH 54/65] Split signature factory methods --- src/compiler/factory.ts | 63 ++++++++++++++++++++++++------- src/compiler/visitor.ts | 35 ++++++++++++----- src/services/codefixes/helpers.ts | 8 ++-- 3 files changed, 79 insertions(+), 27 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 0ae4245970..0d2dee8cfd 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -378,28 +378,65 @@ namespace ts { } // TODO: Split according to AST nodes. - export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): T; - export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined): T; - export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, name?: string | PropertyName, questionToken?: QuestionToken): T { - const signatureDeclaration = createSynthesizedNode(kind) as T; + export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined) { + const signatureDeclaration = createSynthesizedNode(kind) as SignatureDeclaration; signatureDeclaration.typeParameters = asNodeArray(typeParameters); signatureDeclaration.parameters = asNodeArray(parameters); signatureDeclaration.type = type; - signatureDeclaration.name = asName(name); - signatureDeclaration.questionToken = questionToken; return signatureDeclaration; } - // TODO: figure out right type annotation for this function. - export function updateSignatureDeclaration(node: T, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): T; - export function updateSignatureDeclaration(node: T, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined, name: PropertyName, questionToken: QuestionToken | undefined): T; - export function updateSignatureDeclaration(node: T, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined, name?: PropertyName, questionToken?: QuestionToken): T { + export function updateSignatureDeclaration(node: SignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { + return node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? updateNode(createSignatureDeclaration(node.kind, typeParameters, parameters, type), node) + : node; + } + + export function createFunctionTypeNode(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): FunctionTypeNode { + return createSignatureDeclaration(SyntaxKind.FunctionType, typeParameters, parameters, type) as FunctionTypeNode; + } + + export function updateFunctionTypeNode(node: FunctionTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): FunctionTypeNode { + return updateSignatureDeclaration(node, typeParameters, parameters, type); + } + + export function createConstructorTypeNode(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): ConstructorTypeNode { + return createSignatureDeclaration(SyntaxKind.ConstructorType, typeParameters, parameters, type) as ConstructorTypeNode; + } + export function updateConstructorTypeNode(node: ConstructorTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): ConstructorTypeNode { + return updateSignatureDeclaration(node, typeParameters, parameters, type); + } + + export function createCallSignatureDeclaration(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): CallSignatureDeclaration { + return createSignatureDeclaration(SyntaxKind.CallSignature, typeParameters, parameters, type) as CallSignatureDeclaration; + } + export function updateCallSignatureDeclaration(node: CallSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): CallSignatureDeclaration { + return updateSignatureDeclaration(node, typeParameters, parameters, type); + } + + export function createConstructSignatureDeclaration(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): ConstructSignatureDeclaration { + return createSignatureDeclaration(SyntaxKind.ConstructSignature, typeParameters, parameters, type) as ConstructSignatureDeclaration; + } + export function updateConstructSignatureDeclaration(node: ConstructSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): ConstructSignatureDeclaration { + return updateSignatureDeclaration(node, typeParameters, parameters, type); + } + + export function createMethodSignature(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined): MethodSignature{ + const methodSignature = createSignatureDeclaration(SyntaxKind.MethodSignature, typeParameters, parameters, type) as MethodSignature; + methodSignature.name = asName(name); + methodSignature.questionToken = questionToken; + return methodSignature; + } + + export function updateMethodSignature(node: MethodSignature, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined, name?: PropertyName, questionToken?: QuestionToken): MethodSignature { return node.typeParameters !== typeParameters || node.parameters !== parameters || node.type !== type || node.name !== name || node.questionToken !== questionToken - ? updateNode(createSignatureDeclaration(node.kind, typeParameters, parameters, type, name, questionToken), node) + ? updateNode(createMethodSignature(typeParameters, parameters, type, name, questionToken), node) : node; } @@ -502,7 +539,7 @@ namespace ts { : node; } - export function createMethod(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { + export function createMethodDeclaration(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, asteriskToken: AsteriskToken | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, body: Block | undefined) { const node = createSynthesizedNode(SyntaxKind.MethodDeclaration); node.decorators = asNodeArray(decorators); node.modifiers = asNodeArray(modifiers); @@ -525,7 +562,7 @@ namespace ts { || node.parameters !== parameters || node.type !== type || node.body !== body - ? updateNode(createMethod(decorators, modifiers, asteriskToken, name, questionToken, typeParameters, parameters, type, body), node) + ? updateNode(createMethodDeclaration(decorators, modifiers, asteriskToken, name, questionToken, typeParameters, parameters, type, body), node) : node; } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 57de104b0c..e6df9c211e 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -260,21 +260,36 @@ namespace ts { // Signatures and Signature Elements case SyntaxKind.FunctionType: + return updateFunctionTypeNode(node, + nodesVisitor((node).typeParameters, visitor, isTypeParameter), + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitNode((node).type, visitor, isTypeNode)); + case SyntaxKind.ConstructorType: + return updateConstructorTypeNode(node, + nodesVisitor((node).typeParameters, visitor, isTypeParameter), + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitNode((node).type, visitor, isTypeNode)); + case SyntaxKind.CallSignature: + return updateCallSignatureDeclaration(node, + nodesVisitor((node).typeParameters, visitor, isTypeParameter), + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitNode((node).type, visitor, isTypeNode)); + case SyntaxKind.ConstructSignature: - return updateSignatureDeclaration(node, - nodesVisitor((node).typeParameters, visitor, isTypeParameter), - visitParameterList((node).parameters, visitor, context, nodesVisitor), - visitNode((node).type, visitor, isTypeNode)); + return updateConstructSignatureDeclaration(node, + nodesVisitor((node).typeParameters, visitor, isTypeParameter), + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitNode((node).type, visitor, isTypeNode)); case SyntaxKind.MethodSignature: - return updateSignatureDeclaration(node, - nodesVisitor((node).typeParameters, visitor, isTypeParameter), - visitParameterList((node).parameters, visitor, context, nodesVisitor), - visitNode((node).type, visitor, isTypeNode), - visitNode((node).name, visitor, isPropertyName), - visitNode((node).questionToken, tokenVisitor, isToken)); + return updateMethodSignature(node, + nodesVisitor((node).typeParameters, visitor, isTypeParameter), + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitNode((node).type, visitor, isTypeNode), + visitNode((node).name, visitor, isPropertyName), + visitNode((node).questionToken, tokenVisitor, isToken)); case SyntaxKind.IndexSignature: return updateIndexSignatureDeclaration(node, diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index da72921508..0000bd97c5 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -100,7 +100,7 @@ namespace ts.codefix { if (declarations.length === 1) { Debug.assert(signatures.length === 1); const signature = signatures[0]; - const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration) as MethodDeclaration; + const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration); signatureDeclaration.modifiers = modifiers; signatureDeclaration.name = name; signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; @@ -111,7 +111,7 @@ namespace ts.codefix { let signatureDeclarations = []; for (let i = 0; i < signatures.length; i++) { const signature = signatures[i]; - const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration) as MethodDeclaration; + const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration); signatureDeclaration.modifiers = modifiers; signatureDeclaration.name = name; signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; @@ -120,7 +120,7 @@ namespace ts.codefix { if (declarations.length > signatures.length) { let signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration); - const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration) as MethodDeclaration; + const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration); signatureDeclaration.modifiers = modifiers; signatureDeclaration.name = name; signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; @@ -197,7 +197,7 @@ namespace ts.codefix { } export function createStubbedMethod(modifiers: Modifier[], name: PropertyName, optional: boolean, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], returnType: TypeNode | undefined) { - return createMethod( + return createMethodDeclaration( /*decorators*/ undefined, modifiers, /*asteriskToken*/ undefined, From ee7e9fc502ec45f93812ab0f4844a9765d7149a7 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 23 Mar 2017 14:00:49 -0700 Subject: [PATCH 55/65] handle todo's --- src/compiler/checker.ts | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f996f738f1..e98ac3be4d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2257,7 +2257,7 @@ namespace ts { return createKeywordTypeNode(SyntaxKind.BooleanKeyword); } if (type.flags & TypeFlags.Enum) { - const name = symbolToName(type.symbol, enclosingDeclaration, /*mustBeIdentifier*/ false, flags); + const name = symbolToName(type.symbol, enclosingDeclaration, /*expectsIdentifier*/ false, flags); return createTypeReferenceNode(name, /*typeArguments*/ undefined); } if (type.flags & (TypeFlags.StringLiteral)) { @@ -2270,7 +2270,7 @@ namespace ts { return (type).intrinsicName === "true" ? createTrue() : createFalse(); } if (type.flags & TypeFlags.EnumLiteral) { - const name = symbolToName(type.symbol, enclosingDeclaration, /*mustBeIdentifier*/ false, flags); + const name = symbolToName(type.symbol, enclosingDeclaration, /*expectsIdentifier*/ false, flags); return createTypeReferenceNode(name, /*typeArguments*/ undefined); } if (type.flags & TypeFlags.Void) { @@ -2306,18 +2306,18 @@ namespace ts { } if (objectFlags & ObjectFlags.ClassOrInterface) { Debug.assert(!!(type.flags & TypeFlags.Object)); - const name = symbolToName(type.symbol, enclosingDeclaration, /*mustBeIdentifier*/ false, flags); + const name = symbolToName(type.symbol, enclosingDeclaration, /*expectsIdentifier*/ false, flags); // TODO(aozgaa): handle type arguments. return createTypeReferenceNode(name, /*typeArguments*/ undefined); } if (type.flags & TypeFlags.TypeParameter) { - const name = symbolToName(type.symbol, enclosingDeclaration, /*mustBeIdentifier*/ false, flags); + const name = symbolToName(type.symbol, enclosingDeclaration, /*expectsIdentifier*/ false, flags); // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. return createTypeReferenceNode(name, /*typeArguments*/ undefined); } if (checkAlias && type.aliasSymbol) { - const name = symbolToName(type.aliasSymbol, enclosingDeclaration, /*mustBeIdentifier*/ false, flags); + const name = symbolToName(type.aliasSymbol, enclosingDeclaration, /*expectsIdentifier*/ false, flags); const typeArgumentNodes = mapToTypeNodeArray(type.aliasTypeArguments); return createTypeReferenceNode(name, typeArgumentNodes); } @@ -2381,7 +2381,7 @@ namespace ts { const typeAlias = getTypeAliasForTypeLiteral(type); if (typeAlias) { // The specified symbol flags need to be reinterpreted as type flags - const entityName = symbolToName(typeAlias, enclosingDeclaration, /*mustBeIdentifier*/ false, flags); + const entityName = symbolToName(typeAlias, enclosingDeclaration, /*expectsIdentifier*/ false, flags); return createTypeReferenceNode(entityName, /*typeArguments*/ undefined); } else { @@ -2452,7 +2452,7 @@ namespace ts { function createTypeQueryNodeFromType(type: Type) { const symbol = type.symbol; if (symbol) { - const entityName = symbolToName(symbol, enclosingDeclaration, /*mustBeIdentifier*/ false, flags); + const entityName = symbolToName(symbol, enclosingDeclaration, /*expectsIdentifier*/ false, flags); return createTypeQueryNode(entityName); } } @@ -2482,7 +2482,7 @@ namespace ts { // When type parameters are their own type arguments for the whole group (i.e. we have // the default outer type arguments), we don't show the group. if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { - const qualifiedNamePart = symbolToName(parent, enclosingDeclaration, /*mustBeIdentifier*/ true, flags); + const qualifiedNamePart = symbolToName(parent, enclosingDeclaration, /*expectsIdentifier*/ true, flags); if (!qualifiedName) { qualifiedName = createQualifiedName(qualifiedNamePart, /*right*/ undefined); } @@ -2495,7 +2495,7 @@ namespace ts { } } let entityName: EntityName = undefined; - const nameIdentifier = symbolToName(type.symbol, enclosingDeclaration, /*mustBeIdentifier*/ true, flags); + const nameIdentifier = symbolToName(type.symbol, enclosingDeclaration, /*expectsIdentifier*/ true, flags); if (qualifiedName) { Debug.assert(!qualifiedName.right); qualifiedName.right = nameIdentifier; @@ -2606,7 +2606,7 @@ namespace ts { const constraintNode = constraint && typeToTypeNodeHelper(constraint, enclosingDeclaration, flags); const defaultParameter = getDefaultFromTypeParameter(type); const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, enclosingDeclaration, flags); - const name = symbolToName(type.symbol, enclosingDeclaration, /*mustBeIdentifier*/ true, flags); + const name = symbolToName(type.symbol, enclosingDeclaration, /*expectsIdentifier*/ true, flags); return createTypeParameterDeclaration(name, constraintNode, defaultParameterNode); } @@ -2627,19 +2627,17 @@ namespace ts { return parameterNode; } - function symbolToName(symbol: Symbol, enclosingDeclaration: Node | undefined, mustBeIdentifier: true, flags: NodeBuilderFlags): Identifier; - function symbolToName(symbol: Symbol, enclosingDeclaration: Node, mustBeIdentifier: false, flags: NodeBuilderFlags): EntityName; - function symbolToName(symbol: Symbol, enclosingDeclaration: Node | undefined, mustBeIdentifier: boolean, flags: NodeBuilderFlags): EntityName { + function symbolToName(symbol: Symbol, enclosingDeclaration: Node | undefined, expectsIdentifier: true, flags: NodeBuilderFlags): Identifier; + function symbolToName(symbol: Symbol, enclosingDeclaration: Node | undefined, expectsIdentifier: false, flags: NodeBuilderFlags): EntityName; + function symbolToName(symbol: Symbol, enclosingDeclaration: Node | undefined, expectsIdentifier: boolean, flags: NodeBuilderFlags): EntityName { let parentSymbol: Symbol; let meaning: SymbolFlags; - // Get qualified name if the symbol is not a type parameter - // and there is an enclosing declaration. + // Try to get qualified name if the symbol is not a type parameter and there is an enclosing declaration. let chain: Symbol[]; const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; if (!isTypeParameter && enclosingDeclaration) { chain = getSymbolChain(symbol, meaning, /*endOfChain*/ true); - // TODO(aozgaa): check whether type pointed to by symbol requires type arguments to be printed. Debug.assert(chain && chain.length > 0); } else { @@ -2647,10 +2645,8 @@ namespace ts { } parentSymbol = undefined; - if (mustBeIdentifier && chain.length !== 1) { + if (expectsIdentifier && chain.length !== 1) { encounteredError = encounteredError || !(flags & NodeBuilderFlags.allowQualifedNameInPlaceOfIdentifier); - // TODO(aozgaa): failing to get an identifier when we expect one generates an unprintable node. - // Should error handling be more severe? } return createEntityNameFromSymbolChain(chain, chain.length - 1); @@ -2660,7 +2656,6 @@ namespace ts { const symbol = chain[index]; let typeParameterString = ""; if (index > 0) { - // TODO(aozgaa): is the parentSymbol wrong? const parentSymbol = chain[index - 1]; let typeParameters: TypeParameter[]; if (getCheckFlags(symbol) & CheckFlags.Instantiated) { From 5421812f1e1f3f926e01fd3bcd3a36aef75accad Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 23 Mar 2017 16:54:13 -0700 Subject: [PATCH 56/65] build builder --- src/compiler/checker.ts | 72 ++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b02b33e56b..9dd1fda7f5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -61,7 +61,7 @@ namespace ts { const noImplicitThis = compilerOptions.noImplicitThis === undefined ? compilerOptions.strict : compilerOptions.noImplicitThis; const emitResolver = createResolver(); - let nodeBuilderCache: NodeBuilder | undefined; + const nodeBuilder = getNodeBuilder(); const undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined"); undefinedSymbol.declarations = []; @@ -107,9 +107,9 @@ namespace ts { getParameterType: getTypeAtPosition, getReturnTypeOfSignature, getNonNullableType, - typeToTypeNode: getNodeBuilder().typeToTypeNode, - indexInfoToIndexSignatureDeclaration: getNodeBuilder().indexInfoToIndexSignatureDeclaration, - signatureToSignatureDeclaration: getNodeBuilder().signatureToSignatureDeclaration, + typeToTypeNode: nodeBuilder.typeToTypeNode, + indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration, + signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration, getSymbolsInScope: (location, meaning) => { location = getParseTreeNode(location); return location ? getSymbolsInScope(location, meaning) : []; @@ -2203,43 +2203,36 @@ namespace ts { } function getNodeBuilder(): NodeBuilder { - if (nodeBuilderCache) { - return nodeBuilderCache; - } let encounteredError = false; - nodeBuilderCache = { - typeToTypeNode, - indexInfoToIndexSignatureDeclaration, - signatureToSignatureDeclaration + return { + typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags) => { + Debug.assert(encounteredError === false, "Nested call into nodeBuilder are forbidden."); + encounteredError = false; + const resultingNode = typeToTypeNodeHelper(type, enclosingDeclaration, flags); + const result = encounteredError ? undefined : resultingNode; + encounteredError = false; + return result; + }, + indexInfoToIndexSignatureDeclaration: (indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags) => { + Debug.assert(encounteredError === false, "Nested call into nodeBuilder are forbidden."); + encounteredError = false; + const resultingNode = indexInfoToIndexSignatureDeclarationHelper(indexInfo, kind, enclosingDeclaration, flags) + const result = encounteredError ? undefined : resultingNode; + encounteredError = false; + return result; + }, + signatureToSignatureDeclaration: (signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags) => { + Debug.assert(encounteredError === false, "Nested call into nodeBuilder are forbidden."); + encounteredError = false; + const resultingNode = signatureToSignatureDeclarationHelper(signature, kind, enclosingDeclaration, flags); + const result = encounteredError ? undefined : resultingNode; + encounteredError = false; + return result; + } }; - return nodeBuilderCache; - - function typeToTypeNode(type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): TypeNode { - const helper = () => typeToTypeNodeHelper(type, enclosingDeclaration, flags); - return callHelperWithErrorHandling(helper); - } - - function indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): IndexSignatureDeclaration { - const helper = () => indexInfoToIndexSignatureDeclarationHelper(indexInfo, kind, enclosingDeclaration, flags); - return callHelperWithErrorHandling(helper); - } - - function signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): SignatureDeclaration { - const helper = () => signatureToSignatureDeclarationHelper(signature, kind, enclosingDeclaration, flags); - return callHelperWithErrorHandling(helper); - } - - function callHelperWithErrorHandling(nodeBuilderHelper: () => T): T | undefined { - const encounteredErrorCache = encounteredError; - const resultingNode = nodeBuilderHelper(); - const result = encounteredError ? undefined : resultingNode; - encounteredError = encounteredErrorCache; - return result; - } - function typeToTypeNodeHelper(type: Type, enclosingDeclaration: Node, flags: NodeBuilderFlags): TypeNode { let inObjectTypeLiteral = false; let checkAlias = true; @@ -2597,13 +2590,12 @@ namespace ts { const typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, enclosingDeclaration, flags)); const parameters = signature.parameters.map(parameter => symbolToParameterDeclaration(parameter, enclosingDeclaration, flags)); - const type = typeToTypeNodeExceptAny(getReturnTypeOfSignature(signature)); + const returnTypeNode = typeToTypeNodeExceptAny(getReturnTypeOfSignature(signature)); - return createSignatureDeclaration(kind, typeParameters, parameters, type); + return createSignatureDeclaration(kind, typeParameters, parameters, returnTypeNode); function typeToTypeNodeExceptAny(type: Type): TypeNode | undefined { - // Note, this call will *not* mark encounteredError. - const typeNode = typeToTypeNode(type, enclosingDeclaration); + const typeNode = type && typeToTypeNodeHelper(type, enclosingDeclaration, flags); return typeNode && typeNode.kind !== SyntaxKind.AnyKeyword ? typeNode : undefined; } } From a7c166c4a5ecea91565c7512aa0ea0b7f0c70efb Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Fri, 24 Mar 2017 12:56:54 -0700 Subject: [PATCH 57/65] optional signature --- src/compiler/checker.ts | 4 +- src/services/codefixes/helpers.ts | 41 ++++++++++--------- ...ClassImplementClassMemberAnonymousClass.ts | 9 +--- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9dd1fda7f5..7c5e8153a6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2218,7 +2218,7 @@ namespace ts { indexInfoToIndexSignatureDeclaration: (indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags) => { Debug.assert(encounteredError === false, "Nested call into nodeBuilder are forbidden."); encounteredError = false; - const resultingNode = indexInfoToIndexSignatureDeclarationHelper(indexInfo, kind, enclosingDeclaration, flags) + const resultingNode = indexInfoToIndexSignatureDeclarationHelper(indexInfo, kind, enclosingDeclaration, flags); const result = encounteredError ? undefined : resultingNode; encounteredError = false; return result; @@ -2568,8 +2568,8 @@ namespace ts { function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node, flags: NodeBuilderFlags): IndexSignatureDeclaration { const indexerTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword); - const name = getNameFromIndexInfo(indexInfo); + const indexingParameter = createParameter( /*decorators*/ undefined, /*modifiers*/ undefined, diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index d26edbac17..49f95243c9 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -67,6 +67,7 @@ namespace ts.codefix { const visibilityModifier = createVisibilityModifier(getModifierFlags(declaration)); const modifiers = visibilityModifier ? createNodeArray([visibilityModifier]) : undefined; const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration)); + const optional = !!(symbol.flags & SymbolFlags.Optional); switch (declaration.kind) { case SyntaxKind.GetAccessor: @@ -78,7 +79,7 @@ namespace ts.codefix { /*decorators*/undefined, modifiers, name, - /*questionToken*/ undefined, + optional ? createToken(SyntaxKind.QuestionToken) : undefined, typeNode, /*initializer*/ undefined); return property; @@ -96,36 +97,27 @@ namespace ts.codefix { return undefined; } - const optional = !!(symbol.flags & SymbolFlags.Optional); if (declarations.length === 1) { Debug.assert(signatures.length === 1); const signature = signatures[0]; - const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration); - signatureDeclaration.modifiers = modifiers; - signatureDeclaration.name = name; - signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; - signatureDeclaration.body = createStubbedMethodBody(); - return signatureDeclaration; + return signatureToMethodDeclaration(signature, enclosingDeclaration, createStubbedMethodBody()); } const signatureDeclarations = []; for (let i = 0; i < signatures.length; i++) { const signature = signatures[i]; - const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration); - signatureDeclaration.modifiers = modifiers; - signatureDeclaration.name = name; - signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; - signatureDeclarations.push(signatureDeclaration); + const methodDeclaration = signatureToMethodDeclaration(signature, enclosingDeclaration); + if (methodDeclaration) { + signatureDeclarations.push(methodDeclaration); + } } if (declarations.length > signatures.length) { const signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration); - const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration); - signatureDeclaration.modifiers = modifiers; - signatureDeclaration.name = name; - signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; - signatureDeclaration.body = createStubbedMethodBody(); - signatureDeclarations.push(signatureDeclaration); + const methodDeclaration = signatureToMethodDeclaration(signature, enclosingDeclaration, createStubbedMethodBody()); + if (methodDeclaration) { + signatureDeclarations.push(methodDeclaration); + } } else { Debug.assert(declarations.length === signatures.length); @@ -136,6 +128,17 @@ namespace ts.codefix { default: return undefined; } + + function signatureToMethodDeclaration(signature: Signature, enclosingDeclaration: Node, body?: Block) { + const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration); + if (signatureDeclaration) { + signatureDeclaration.modifiers = modifiers; + signatureDeclaration.name = name; + signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; + signatureDeclaration.body = body; + } + return signatureDeclaration; + } } function createMethodImplementingSignatures(signatures: Signature[], name: PropertyName, optional: boolean, modifiers: Modifier[] | undefined): MethodDeclaration { diff --git a/tests/cases/fourslash/codeFixClassImplementClassMemberAnonymousClass.ts b/tests/cases/fourslash/codeFixClassImplementClassMemberAnonymousClass.ts index a8a024d194..ee8b8cafff 100644 --- a/tests/cases/fourslash/codeFixClassImplementClassMemberAnonymousClass.ts +++ b/tests/cases/fourslash/codeFixClassImplementClassMemberAnonymousClass.ts @@ -10,11 +10,4 @@ //// } //// class C implements A {[| |]} -verify.rangeAfterCodeFix(` - foo() { - throw new Error("Method not implemented."); - } - bar() { - throw new Error("Method not implemented."); - } -`); +verify.not.codeFixAvailable(); From 797f6dac028fb3f4f8a16b60053a8610f2ee6aca Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Fri, 24 Mar 2017 13:01:27 -0700 Subject: [PATCH 58/65] rename helper --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7c5e8153a6..694763f6c0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -61,7 +61,7 @@ namespace ts { const noImplicitThis = compilerOptions.noImplicitThis === undefined ? compilerOptions.strict : compilerOptions.noImplicitThis; const emitResolver = createResolver(); - const nodeBuilder = getNodeBuilder(); + const nodeBuilder = createNodeBuilder(); const undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined"); undefinedSymbol.declarations = []; @@ -2202,7 +2202,7 @@ namespace ts { return result; } - function getNodeBuilder(): NodeBuilder { + function createNodeBuilder(): NodeBuilder { let encounteredError = false; From 85986ddaee1d9df121bdcfb30b31c4b33ac7e475 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Fri, 24 Mar 2017 13:12:07 -0700 Subject: [PATCH 59/65] dont check isPartOfTypeNode --- src/compiler/utilities.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 57c00ed8fb..f4f288d0cb 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3735,7 +3735,7 @@ namespace ts { * of a TypeNode. */ export function isTypeNode(node: Node): node is TypeNode { - return node && isTypeNodeKind(node.kind) && (!node.parent || isPartOfTypeNode(node)); + return node && isTypeNodeKind(node.kind); } // Binding patterns From a94f874b06e687b6856c9a9e315a28af2598642b Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Sat, 25 Mar 2017 14:14:36 -0700 Subject: [PATCH 60/65] use NodeBuilderContext --- src/compiler/checker.ts | 697 +++++++++++++++++++++------------------- src/compiler/factory.ts | 4 +- src/compiler/types.ts | 4 +- 3 files changed, 373 insertions(+), 332 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 694763f6c0..a45f3424ef 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2204,369 +2204,403 @@ namespace ts { function createNodeBuilder(): NodeBuilder { - let encounteredError = false; + interface NodeBuilderContext { + readonly enclosingDeclaration: Node | undefined; + readonly flags: NodeBuilderFlags | undefined; + encounteredError: boolean; + inObjectTypeLiteral: boolean; + checkAlias: boolean; + symbolStack: Symbol[] | undefined; + } + + function createNodeBuilderContext(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): NodeBuilderContext { + return { + enclosingDeclaration, + flags, + encounteredError: false, + inObjectTypeLiteral: false, + checkAlias: true, + symbolStack: undefined + }; + } + + let context: NodeBuilderContext; return { typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags) => { - Debug.assert(encounteredError === false, "Nested call into nodeBuilder are forbidden."); - encounteredError = false; - const resultingNode = typeToTypeNodeHelper(type, enclosingDeclaration, flags); - const result = encounteredError ? undefined : resultingNode; - encounteredError = false; + context = createNodeBuilderContext(enclosingDeclaration, flags); + const resultingNode = typeToTypeNodeHelper(type); + const result = context.encounteredError ? undefined : resultingNode; return result; }, indexInfoToIndexSignatureDeclaration: (indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags) => { - Debug.assert(encounteredError === false, "Nested call into nodeBuilder are forbidden."); - encounteredError = false; - const resultingNode = indexInfoToIndexSignatureDeclarationHelper(indexInfo, kind, enclosingDeclaration, flags); - const result = encounteredError ? undefined : resultingNode; - encounteredError = false; + context = createNodeBuilderContext(enclosingDeclaration, flags); + const resultingNode = indexInfoToIndexSignatureDeclarationHelper(indexInfo, kind); + const result = context.encounteredError ? undefined : resultingNode; return result; }, signatureToSignatureDeclaration: (signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags) => { - Debug.assert(encounteredError === false, "Nested call into nodeBuilder are forbidden."); - encounteredError = false; - const resultingNode = signatureToSignatureDeclarationHelper(signature, kind, enclosingDeclaration, flags); - const result = encounteredError ? undefined : resultingNode; - encounteredError = false; + context = createNodeBuilderContext(enclosingDeclaration, flags); + const resultingNode = signatureToSignatureDeclarationHelper(signature, kind); + const result = context.encounteredError ? undefined : resultingNode; return result; } }; - function typeToTypeNodeHelper(type: Type, enclosingDeclaration: Node, flags: NodeBuilderFlags): TypeNode { - let inObjectTypeLiteral = false; - let checkAlias = true; - let symbolStack: Symbol[] = undefined; + function typeToTypeNodeHelper(type: Type): TypeNode { + if (!type) { + context.encounteredError = true; + // TODO(aozgaa): should we return implict any (undefined) or explicit any (keywordtypenode)? + return undefined; + } - return typeToTypeNodeWorker(type); + if (type.flags & TypeFlags.Any) { + return createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + if (type.flags & TypeFlags.String) { + return createKeywordTypeNode(SyntaxKind.StringKeyword); + } + if (type.flags & TypeFlags.Number) { + return createKeywordTypeNode(SyntaxKind.NumberKeyword); + } + if (type.flags & TypeFlags.Boolean) { + return createKeywordTypeNode(SyntaxKind.BooleanKeyword); + } + if (type.flags & TypeFlags.Enum) { + const name = symbolToName(type.symbol, /*expectsIdentifier*/ false); + return createTypeReferenceNode(name, /*typeArguments*/ undefined); + } + if (type.flags & (TypeFlags.StringLiteral)) { + return createLiteralTypeNode((createLiteral((type).text))); + } + if (type.flags & (TypeFlags.NumberLiteral)) { + return createLiteralTypeNode((createNumericLiteral((type).text))); + } + if (type.flags & TypeFlags.BooleanLiteral) { + return (type).intrinsicName === "true" ? createTrue() : createFalse(); + } + if (type.flags & TypeFlags.EnumLiteral) { + const name = symbolToName(type.symbol, /*expectsIdentifier*/ false); + return createTypeReferenceNode(name, /*typeArguments*/ undefined); + } + if (type.flags & TypeFlags.Void) { + return createKeywordTypeNode(SyntaxKind.VoidKeyword); + } + if (type.flags & TypeFlags.Undefined) { + return createKeywordTypeNode(SyntaxKind.UndefinedKeyword); + } + if (type.flags & TypeFlags.Null) { + return createKeywordTypeNode(SyntaxKind.NullKeyword); + } + if (type.flags & TypeFlags.Never) { + return createKeywordTypeNode(SyntaxKind.NeverKeyword); + } + if (type.flags & TypeFlags.ESSymbol) { + throw new Error("ESSymbol not implemented"); + } + if (type.flags & TypeFlags.NonPrimitive) { + throw new Error("Non primitive not implemented"); + } + if (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType) { + if (context.inObjectTypeLiteral) { + if (!context.encounteredError && !(context.flags & NodeBuilderFlags.allowThisInObjectLiteral)) { + context.encounteredError = true; + } + } + return createThis(); + } - function typeToTypeNodeWorker(type: Type): TypeNode { - if (!type) { - encounteredError = true; - // TODO(aozgaa): should we return implict any (undefined) or explicit any (keywordtypenode)? + const objectFlags = getObjectFlags(type); + + if (objectFlags & ObjectFlags.Reference) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + return typeReferenceToTypeNode(type); + } + if (objectFlags & ObjectFlags.ClassOrInterface) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + const name = symbolToName(type.symbol, /*expectsIdentifier*/ false); + // TODO(aozgaa): handle type arguments. + return createTypeReferenceNode(name, /*typeArguments*/ undefined); + } + if (type.flags & TypeFlags.TypeParameter) { + const name = symbolToName(type.symbol, /*expectsIdentifier*/ false); + // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. + return createTypeReferenceNode(name, /*typeArguments*/ undefined); + } + + if (context.checkAlias && type.aliasSymbol) { + const name = symbolToName(type.aliasSymbol, /*expectsIdentifier*/ false); + const typeArgumentNodes = type.aliasTypeArguments && mapToTypeNodeArray(type.aliasTypeArguments); + return createTypeReferenceNode(name, typeArgumentNodes); + } + context.checkAlias = false; + + if (type.flags & TypeFlags.Union) { + const formattedUnionTypes = formatUnionTypes((type).types); + const unionTypeNodes = formattedUnionTypes && mapToTypeNodeArray(formattedUnionTypes); + if (unionTypeNodes && unionTypeNodes.length > 0) { + return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, unionTypeNodes); + } + else { + if (!context.encounteredError && !(context.flags & NodeBuilderFlags.allowEmptyUnionOrIntersection)) { + context.encounteredError = true; + } return undefined; } + } - if (type.flags & TypeFlags.Any) { - return createKeywordTypeNode(SyntaxKind.AnyKeyword); - } - if (type.flags & TypeFlags.String) { - return createKeywordTypeNode(SyntaxKind.StringKeyword); - } - if (type.flags & TypeFlags.Number) { - return createKeywordTypeNode(SyntaxKind.NumberKeyword); - } - if (type.flags & TypeFlags.Boolean) { - return createKeywordTypeNode(SyntaxKind.BooleanKeyword); - } - if (type.flags & TypeFlags.Enum) { - const name = symbolToName(type.symbol, enclosingDeclaration, /*expectsIdentifier*/ false, flags); - return createTypeReferenceNode(name, /*typeArguments*/ undefined); - } - if (type.flags & (TypeFlags.StringLiteral)) { - return createLiteralTypeNode((createLiteral((type).text))); - } - if (type.flags & (TypeFlags.NumberLiteral)) { - return createLiteralTypeNode((createNumericLiteral((type).text))); - } - if (type.flags & TypeFlags.BooleanLiteral) { - return (type).intrinsicName === "true" ? createTrue() : createFalse(); - } - if (type.flags & TypeFlags.EnumLiteral) { - const name = symbolToName(type.symbol, enclosingDeclaration, /*expectsIdentifier*/ false, flags); - return createTypeReferenceNode(name, /*typeArguments*/ undefined); - } - if (type.flags & TypeFlags.Void) { - return createKeywordTypeNode(SyntaxKind.VoidKeyword); - } - if (type.flags & TypeFlags.Undefined) { - return createKeywordTypeNode(SyntaxKind.UndefinedKeyword); - } - if (type.flags & TypeFlags.Null) { - return createKeywordTypeNode(SyntaxKind.NullKeyword); - } - if (type.flags & TypeFlags.Never) { - return createKeywordTypeNode(SyntaxKind.NeverKeyword); - } - if (type.flags & TypeFlags.ESSymbol) { - throw new Error("ESSymbol not implemented"); - } - if (type.flags & TypeFlags.NonPrimitive) { - throw new Error("Non primitive not implemented"); - } - if (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType) { - if (inObjectTypeLiteral) { - encounteredError = encounteredError || !(flags & NodeBuilderFlags.allowThisInObjectLiteral); + if (type.flags & TypeFlags.Intersection) { + return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, mapToTypeNodeArray((type as UnionType).types)); + } + + if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + // The type is an object literal type. + return createAnonymousTypeNode(type); + } + + if (type.flags & TypeFlags.Index) { + const indexedType = (type).type; + const indexTypeNode = typeToTypeNodeHelper(indexedType); + return createTypeOperatorNode(indexTypeNode); + } + if (type.flags & TypeFlags.IndexedAccess) { + const objectTypeNode = typeToTypeNodeHelper((type).objectType); + const indexTypeNode = typeToTypeNodeHelper((type).indexType); + return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); + } + + Debug.fail("Should be unreachable."); + + function mapToTypeNodeArray(types: Type[]): TypeNode[] { + const result = []; + for (const type of types) { + const typeNode = typeToTypeNodeHelper(type); + if (typeNode) { + result.push(typeNode); } - return createThis(); } + return result; + } - const objectFlags = getObjectFlags(type); + function createMappedTypeNodeFromType(type: MappedType) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + const typeParameter = getTypeParameterFromMappedType(type); + const typeParameterNode = typeParameterToDeclaration(typeParameter); - if (objectFlags & ObjectFlags.Reference) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - return typeReferenceToTypeNode (type); - } - if (objectFlags & ObjectFlags.ClassOrInterface) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - const name = symbolToName(type.symbol, enclosingDeclaration, /*expectsIdentifier*/ false, flags); - // TODO(aozgaa): handle type arguments. - return createTypeReferenceNode(name, /*typeArguments*/ undefined); - } - if (type.flags & TypeFlags.TypeParameter) { - const name = symbolToName(type.symbol, enclosingDeclaration, /*expectsIdentifier*/ false, flags); - // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. - return createTypeReferenceNode(name, /*typeArguments*/ undefined); - } + const templateType = getTemplateTypeFromMappedType(type); + const templateTypeNode = templateType && typeToTypeNodeHelper(templateType); + const readonlyToken = (type).declaration && (type).declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; + const questionToken = (type).declaration && (type).declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; - if (checkAlias && type.aliasSymbol) { - const name = symbolToName(type.aliasSymbol, enclosingDeclaration, /*expectsIdentifier*/ false, flags); - const typeArgumentNodes = mapToTypeNodeArray(type.aliasTypeArguments); - return createTypeReferenceNode(name, typeArgumentNodes); - } - checkAlias = false; + return createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); + } - if (type.flags & TypeFlags.Union) { - return createUnionOrIntersectionTypeNode(SyntaxKind.UnionType, mapToTypeNodeArray(formatUnionTypes((type).types))); - } - - if (type.flags & TypeFlags.Intersection) { - return createUnionOrIntersectionTypeNode(SyntaxKind.IntersectionType, mapToTypeNodeArray((type as UnionType).types)); - } - - if (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.Mapped)) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - // The type is an object literal type. - return createAnonymousTypeNode(type); - } - - if (type.flags & TypeFlags.Index) { - const indexedType = (type).type; - const indexTypeNode = typeToTypeNodeWorker(indexedType); - return createTypeOperatorNode(indexTypeNode); - } - if (type.flags & TypeFlags.IndexedAccess) { - const objectTypeNode = typeToTypeNodeWorker((type).objectType); - const indexTypeNode = typeToTypeNodeWorker((type).indexType); - return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); - } - - Debug.fail("Should be unreachable."); - - function mapToTypeNodeArray(types: Type[]): NodeArray { - return types && asNodeArray(types.map(typeToTypeNodeWorker).filter(node => !!node)); - } - - function createMappedTypeNodeFromType(type: MappedType) { - Debug.assert(!!(type.flags & TypeFlags.Object)); - const typeParameter = getTypeParameterFromMappedType(type); - const typeParameterNode = typeParameterToDeclaration(typeParameter, enclosingDeclaration, flags); - - const templateType = getTemplateTypeFromMappedType(type); - const templateTypeNode = templateType && typeToTypeNodeWorker(templateType); - const readonlyToken = (type).declaration && (type).declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; - const questionToken = (type).declaration && (type).declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; - - return createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); - } - - function createAnonymousTypeNode(type: ObjectType): TypeNode { - const symbol = type.symbol; - if (symbol) { - // Always use 'typeof T' for type of class, enum, and module objects - if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) || - symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || - shouldWriteTypeOfFunctionSymbol()) { - return createTypeQueryNodeFromType(type); - } - else if (contains(symbolStack, symbol)) { - // If type is an anonymous type literal in a type alias declaration, use type alias name - const typeAlias = getTypeAliasForTypeLiteral(type); - if (typeAlias) { - // The specified symbol flags need to be reinterpreted as type flags - const entityName = symbolToName(typeAlias, enclosingDeclaration, /*expectsIdentifier*/ false, flags); - return createTypeReferenceNode(entityName, /*typeArguments*/ undefined); - } - else { - return createKeywordTypeNode(SyntaxKind.AnyKeyword); - } + function createAnonymousTypeNode(type: ObjectType): TypeNode { + const symbol = type.symbol; + if (symbol) { + // Always use 'typeof T' for type of class, enum, and module objects + if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) || + symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || + shouldWriteTypeOfFunctionSymbol()) { + return createTypeQueryNodeFromType(type); + } + else if (contains(context.symbolStack, symbol)) { + // If type is an anonymous type literal in a type alias declaration, use type alias name + const typeAlias = getTypeAliasForTypeLiteral(type); + if (typeAlias) { + // The specified symbol flags need to be reinterpreted as type flags + const entityName = symbolToName(typeAlias, /*expectsIdentifier*/ false); + return createTypeReferenceNode(entityName, /*typeArguments*/ undefined); } else { - // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead - // of types allows us to catch circular references to instantiations of the same anonymous type - if (!symbolStack) { - symbolStack = []; - } - symbolStack.push(symbol); - const result = createTypeNodeFromObjectType(type); - symbolStack.pop(); - return result; + return createKeywordTypeNode(SyntaxKind.AnyKeyword); } } else { - // Anonymous types without a symbol are never circular. - return createTypeNodeFromObjectType(type); - } - - function shouldWriteTypeOfFunctionSymbol() { - const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method && // typeof static method - forEach(symbol.declarations, declaration => getModifierFlags(declaration) & ModifierFlags.Static)); - const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && - (symbol.parent || // is exported function symbol - forEach(symbol.declarations, declaration => - declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); - if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { - // typeof is allowed only for static/non local functions - return contains(symbolStack, symbol); // it is type of the symbol uses itself recursively + // Since instantiations of the same anonymous type have the same symbol, tracking symbols instead + // of types allows us to catch circular references to instantiations of the same anonymous type + if (!context.symbolStack) { + context.symbolStack = []; } + context.symbolStack.push(symbol); + const result = createTypeNodeFromObjectType(type); + context.symbolStack.pop(); + return result; + } + } + else { + // Anonymous types without a symbol are never circular. + return createTypeNodeFromObjectType(type); + } + + function shouldWriteTypeOfFunctionSymbol() { + const isStaticMethodSymbol = !!(symbol.flags & SymbolFlags.Method && // typeof static method + forEach(symbol.declarations, declaration => getModifierFlags(declaration) & ModifierFlags.Static)); + const isNonLocalFunctionSymbol = !!(symbol.flags & SymbolFlags.Function) && + (symbol.parent || // is exported function symbol + forEach(symbol.declarations, declaration => + declaration.parent.kind === SyntaxKind.SourceFile || declaration.parent.kind === SyntaxKind.ModuleBlock)); + if (isStaticMethodSymbol || isNonLocalFunctionSymbol) { + // typeof is allowed only for static/non local functions + return contains(context.symbolStack, symbol); // it is type of the symbol uses itself recursively + } + } + } + + function createTypeNodeFromObjectType(type: ObjectType): TypeNode { + if (type.objectFlags & ObjectFlags.Mapped) { + if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { + return createMappedTypeNodeFromType(type); } } - function createTypeNodeFromObjectType(type: ObjectType): TypeNode { - if (type.objectFlags & ObjectFlags.Mapped) { - if (getConstraintTypeFromMappedType(type).flags & (TypeFlags.TypeParameter | TypeFlags.Index)) { - return createMappedTypeNodeFromType(type); - } + const resolved = resolveStructuredTypeMembers(type); + if (!resolved.properties.length && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { + if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { + return createTypeLiteralNode(/*members*/ undefined); } - const resolved = resolveStructuredTypeMembers(type); - if (!resolved.properties.length && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { - if (!resolved.callSignatures.length && !resolved.constructSignatures.length) { - return createTypeLiteralNode(/*members*/ undefined); - } - - if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { - const signature = resolved.callSignatures[0]; - return signatureToSignatureDeclarationHelper(signature, SyntaxKind.FunctionType, enclosingDeclaration, flags); - } - if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { - const signature = resolved.constructSignatures[0]; - return signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructorType, enclosingDeclaration, flags); - } + if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) { + const signature = resolved.callSignatures[0]; + return signatureToSignatureDeclarationHelper(signature, SyntaxKind.FunctionType); } - - const saveInObjectTypeLiteral = inObjectTypeLiteral; - inObjectTypeLiteral = true; - const members = createTypeNodesFromResolvedType(resolved); - inObjectTypeLiteral = saveInObjectTypeLiteral; - return createTypeLiteralNode(members); - } - - function createTypeQueryNodeFromType(type: Type) { - const symbol = type.symbol; - if (symbol) { - const entityName = symbolToName(symbol, enclosingDeclaration, /*expectsIdentifier*/ false, flags); - return createTypeQueryNode(entityName); + if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { + const signature = resolved.constructSignatures[0]; + return signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructorType); } } - function typeReferenceToTypeNode(type: TypeReference) { - const typeArguments: Type[] = type.typeArguments || emptyArray; - if (type.target === globalArrayType) { - const elementType = typeToTypeNodeWorker(typeArguments[0]); - return createArrayTypeNode(elementType); + const saveInObjectTypeLiteral = context.inObjectTypeLiteral; + context.inObjectTypeLiteral = true; + const members = createTypeNodesFromResolvedType(resolved); + context.inObjectTypeLiteral = saveInObjectTypeLiteral; + return createTypeLiteralNode(members); + } + + function createTypeQueryNodeFromType(type: Type) { + const symbol = type.symbol; + if (symbol) { + const entityName = symbolToName(symbol, /*expectsIdentifier*/ false); + return createTypeQueryNode(entityName); + } + } + + function typeReferenceToTypeNode(type: TypeReference) { + const typeArguments: Type[] = type.typeArguments || emptyArray; + if (type.target === globalArrayType) { + const elementType = typeToTypeNodeHelper(typeArguments[0]); + return createArrayTypeNode(elementType); + } + else if (type.target.objectFlags & ObjectFlags.Tuple) { + if (typeArguments.length > 0) { + const tupleConstituentNodes = mapToTypeNodeArray(typeArguments.slice(0, getTypeReferenceArity(type))); + if (tupleConstituentNodes && tupleConstituentNodes.length > 0) { + return createTupleTypeNode(tupleConstituentNodes); + } } - else if (type.target.objectFlags & ObjectFlags.Tuple) { - return createTupleTypeNode(typeArguments.length > 0 ? mapToTypeNodeArray(typeArguments.slice(0, getTypeReferenceArity(type))) : undefined); + if (!context.encounteredError && !(context.flags & NodeBuilderFlags.allowEmptyTuple)) { + context.encounteredError = true; } - else { - const outerTypeParameters = type.target.outerTypeParameters; - let i = 0; - let qualifiedName: QualifiedName | undefined = undefined; - if (outerTypeParameters) { - const length = outerTypeParameters.length; - while (i < length) { - // Find group of type arguments for type parameters with the same declaring container. - const start = i; - const parent = getParentSymbolOfTypeParameter(outerTypeParameters[i]); - do { - i++; - } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); - // When type parameters are their own type arguments for the whole group (i.e. we have - // the default outer type arguments), we don't show the group. - if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { - const qualifiedNamePart = symbolToName(parent, enclosingDeclaration, /*expectsIdentifier*/ true, flags); - if (!qualifiedName) { - qualifiedName = createQualifiedName(qualifiedNamePart, /*right*/ undefined); - } - else { - Debug.assert(!qualifiedName.right); - qualifiedName.right = qualifiedNamePart; - qualifiedName = createQualifiedName(qualifiedName, /*right*/ undefined); - } + return undefined; + } + else { + const outerTypeParameters = type.target.outerTypeParameters; + let i = 0; + let qualifiedName: QualifiedName | undefined = undefined; + if (outerTypeParameters) { + const length = outerTypeParameters.length; + while (i < length) { + // Find group of type arguments for type parameters with the same declaring container. + const start = i; + const parent = getParentSymbolOfTypeParameter(outerTypeParameters[i]); + do { + i++; + } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); + // When type parameters are their own type arguments for the whole group (i.e. we have + // the default outer type arguments), we don't show the group. + if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { + const qualifiedNamePart = symbolToName(parent, /*expectsIdentifier*/ true); + if (!qualifiedName) { + qualifiedName = createQualifiedName(qualifiedNamePart, /*right*/ undefined); + } + else { + Debug.assert(!qualifiedName.right); + qualifiedName.right = qualifiedNamePart; + qualifiedName = createQualifiedName(qualifiedName, /*right*/ undefined); } } } - let entityName: EntityName = undefined; - const nameIdentifier = symbolToName(type.symbol, enclosingDeclaration, /*expectsIdentifier*/ true, flags); - if (qualifiedName) { - Debug.assert(!qualifiedName.right); - qualifiedName.right = nameIdentifier; - entityName = qualifiedName; - } - else { - entityName = nameIdentifier; - } - const typeParameterCount = (type.target.typeParameters || emptyArray).length; - const typeArgumentNodes = mapToTypeNodeArray(typeArguments.length > 0 ? typeArguments.slice(i, typeParameterCount - i) : undefined); - return createTypeReferenceNode(entityName, typeArgumentNodes); } + let entityName: EntityName = undefined; + const nameIdentifier = symbolToName(type.symbol, /*expectsIdentifier*/ true); + if (qualifiedName) { + Debug.assert(!qualifiedName.right); + qualifiedName.right = nameIdentifier; + entityName = qualifiedName; + } + else { + entityName = nameIdentifier; + } + const typeParameterCount = (type.target.typeParameters || emptyArray).length; + const typeArgumentNodes = typeArguments.length > 0 ? mapToTypeNodeArray(typeArguments.slice(i, typeParameterCount - i)) : undefined; + return createTypeReferenceNode(entityName, typeArgumentNodes); + } + } + + function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] { + const typeElements: TypeElement[] = []; + for (const signature of resolvedType.callSignatures) { + typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.CallSignature)); + } + for (const signature of resolvedType.constructSignatures) { + typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature)); + } + if (resolvedType.stringIndexInfo) { + typeElements.push(indexInfoToIndexSignatureDeclarationHelper(resolvedType.stringIndexInfo, IndexKind.String)); + } + if (resolvedType.numberIndexInfo) { + typeElements.push(indexInfoToIndexSignatureDeclarationHelper(resolvedType.numberIndexInfo, IndexKind.Number)); } - function createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] { - const typeElements: TypeElement[] = []; - for (const signature of resolvedType.callSignatures) { - typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.CallSignature, enclosingDeclaration, flags)); - } - for (const signature of resolvedType.constructSignatures) { - typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, enclosingDeclaration, flags)); - } - if (resolvedType.stringIndexInfo) { - typeElements.push(indexInfoToIndexSignatureDeclarationHelper(resolvedType.stringIndexInfo, IndexKind.String, enclosingDeclaration, flags)); - } - if (resolvedType.numberIndexInfo) { - typeElements.push(indexInfoToIndexSignatureDeclarationHelper(resolvedType.numberIndexInfo, IndexKind.Number, enclosingDeclaration, flags)); - } + const properties = resolvedType.properties; + if (!properties) { + return typeElements; + } - const properties = resolvedType.properties; - if (!properties) { - return typeElements; + for (const propertySymbol of properties) { + const propertyType = getTypeOfSymbol(propertySymbol); + const oldDeclaration = propertySymbol.declarations && propertySymbol.declarations[0] as TypeElement; + if (!oldDeclaration) { + return; } - - for (const propertySymbol of properties) { - const propertyType = getTypeOfSymbol(propertySymbol); - const oldDeclaration = propertySymbol.declarations && propertySymbol.declarations[0] as TypeElement; - if (!oldDeclaration) { - return; + const propertyName = oldDeclaration.name; + const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined; + if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length) { + const signatures = getSignaturesOfType(propertyType, SignatureKind.Call); + for (const signature of signatures) { + const methodDeclaration = signatureToSignatureDeclarationHelper(signature, SyntaxKind.MethodSignature); + methodDeclaration.name = propertyName; + methodDeclaration.questionToken = optionalToken; + typeElements.push(methodDeclaration); } - const propertyName = oldDeclaration.name; - const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? createToken(SyntaxKind.QuestionToken) : undefined; - if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length) { - const signatures = getSignaturesOfType(propertyType, SignatureKind.Call); - for (const signature of signatures) { - const methodDeclaration = signatureToSignatureDeclarationHelper(signature, SyntaxKind.MethodSignature, enclosingDeclaration, flags); - methodDeclaration.name = propertyName; - methodDeclaration.questionToken = optionalToken; - typeElements.push(methodDeclaration); - } - } - else { + } + else { - // TODO(aozgaa): should we create a node with explicit or implict any? - const propertyTypeNode = propertyType ? typeToTypeNodeWorker(propertyType) : createKeywordTypeNode(SyntaxKind.AnyKeyword); - typeElements.push(createPropertySignature( - propertyName, - optionalToken, - propertyTypeNode, + // TODO(aozgaa): should we create a node with explicit or implict any? + const propertyTypeNode = propertyType ? typeToTypeNodeHelper(propertyType) : createKeywordTypeNode(SyntaxKind.AnyKeyword); + typeElements.push(createPropertySignature( + propertyName, + optionalToken, + propertyTypeNode, /*initializer*/undefined)); - } } - return typeElements.length ? typeElements : undefined; } + return typeElements.length ? typeElements : undefined; } } - function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration: Node, flags: NodeBuilderFlags): IndexSignatureDeclaration { + function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, kind: IndexKind): IndexSignatureDeclaration { const indexerTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword); const name = getNameFromIndexInfo(indexInfo); @@ -2578,7 +2612,7 @@ namespace ts { /*questionToken*/ undefined, indexerTypeNode, /*initializer*/ undefined); - const typeNode = typeToTypeNodeHelper(indexInfo.type, enclosingDeclaration, flags); + const typeNode = typeToTypeNodeHelper(indexInfo.type); return createIndexSignatureDeclaration( [indexingParameter], typeNode, @@ -2586,37 +2620,37 @@ namespace ts { indexInfo.isReadonly ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined); } - function signatureToSignatureDeclarationHelper(signature: Signature, kind: SyntaxKind, enclosingDeclaration: Node, flags: NodeBuilderFlags): SignatureDeclaration { + function signatureToSignatureDeclarationHelper(signature: Signature, kind: SyntaxKind): SignatureDeclaration { - const typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter, enclosingDeclaration, flags)); - const parameters = signature.parameters.map(parameter => symbolToParameterDeclaration(parameter, enclosingDeclaration, flags)); + const typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter)); + const parameters = signature.parameters.map(parameter => symbolToParameterDeclaration(parameter)); const returnTypeNode = typeToTypeNodeExceptAny(getReturnTypeOfSignature(signature)); return createSignatureDeclaration(kind, typeParameters, parameters, returnTypeNode); function typeToTypeNodeExceptAny(type: Type): TypeNode | undefined { - const typeNode = type && typeToTypeNodeHelper(type, enclosingDeclaration, flags); + const typeNode = type && typeToTypeNodeHelper(type); return typeNode && typeNode.kind !== SyntaxKind.AnyKeyword ? typeNode : undefined; } } - function typeParameterToDeclaration(type: TypeParameter, enclosingDeclaration: Node, flags: NodeBuilderFlags): TypeParameterDeclaration { + function typeParameterToDeclaration(type: TypeParameter): TypeParameterDeclaration { if (!(type && type.symbol && type.flags & TypeFlags.TypeParameter)) { return undefined; } const constraint = getConstraintFromTypeParameter(type); - const constraintNode = constraint && typeToTypeNodeHelper(constraint, enclosingDeclaration, flags); + const constraintNode = constraint && typeToTypeNodeHelper(constraint); const defaultParameter = getDefaultFromTypeParameter(type); - const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, enclosingDeclaration, flags); - const name = symbolToName(type.symbol, enclosingDeclaration, /*expectsIdentifier*/ true, flags); + const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter); + const name = symbolToName(type.symbol, /*expectsIdentifier*/ true); return createTypeParameterDeclaration(name, constraintNode, defaultParameterNode); } - function symbolToParameterDeclaration(parameterSymbol: Symbol, enclosingDeclaration: Node, flags: NodeBuilderFlags): ParameterDeclaration { + function symbolToParameterDeclaration(parameterSymbol: Symbol): ParameterDeclaration { const parameterDeclaration = parameterSymbol.declarations[0] as ParameterDeclaration; const parameterType = getTypeOfSymbol(parameterSymbol); - const parameterTypeNode = typeToTypeNodeHelper(parameterType, enclosingDeclaration, flags); + const parameterTypeNode = typeToTypeNodeHelper(parameterType); // TODO(aozgaa): check initializer accessibility correctly. const parameterNode = createParameter( parameterDeclaration.decorators, @@ -2630,15 +2664,15 @@ namespace ts { return parameterNode; } - function symbolToName(symbol: Symbol, enclosingDeclaration: Node | undefined, expectsIdentifier: true, flags: NodeBuilderFlags): Identifier; - function symbolToName(symbol: Symbol, enclosingDeclaration: Node | undefined, expectsIdentifier: false, flags: NodeBuilderFlags): EntityName; - function symbolToName(symbol: Symbol, enclosingDeclaration: Node | undefined, expectsIdentifier: boolean, flags: NodeBuilderFlags): EntityName { + function symbolToName(symbol: Symbol, expectsIdentifier: true): Identifier; + function symbolToName(symbol: Symbol, expectsIdentifier: false): EntityName; + function symbolToName(symbol: Symbol, expectsIdentifier: boolean): EntityName { let parentSymbol: Symbol; // Try to get qualified name if the symbol is not a type parameter and there is an enclosing declaration. let chain: Symbol[]; const isTypeParameter = symbol.flags & SymbolFlags.TypeParameter; - if (!isTypeParameter && enclosingDeclaration) { + if (!isTypeParameter && context.enclosingDeclaration) { chain = getSymbolChain(symbol, SymbolFlags.None, /*endOfChain*/ true); Debug.assert(chain && chain.length > 0); } @@ -2648,8 +2682,10 @@ namespace ts { parentSymbol = undefined; - if (expectsIdentifier && chain.length !== 1) { - encounteredError = encounteredError || !(flags & NodeBuilderFlags.allowQualifedNameInPlaceOfIdentifier); + if (expectsIdentifier && chain.length !== 1 + && !context.encounteredError + && !(context.flags & NodeBuilderFlags.allowQualifedNameInPlaceOfIdentifier)) { + context.encounteredError = true; } return createEntityNameFromSymbolChain(chain, chain.length - 1); @@ -2672,11 +2708,12 @@ namespace ts { } } if (typeParameters && typeParameters.length > 0) { - encounteredError = encounteredError || !(flags & NodeBuilderFlags.allowTypeParameterInQualifiedName); - + if (!context.encounteredError && !(context.flags & NodeBuilderFlags.allowTypeParameterInQualifiedName)) { + context.encounteredError = true; + } const writer = getSingleLineStringWriter(); const displayBuilder = getSymbolDisplayBuilder(); - displayBuilder.buildDisplayForTypeParametersAndDelimiters(typeParameters, writer, enclosingDeclaration, 0); + displayBuilder.buildDisplayForTypeParametersAndDelimiters(typeParameters, writer, context.enclosingDeclaration, 0); typeParameterString = writer.string(); releaseStringWriter(writer); @@ -2691,10 +2728,10 @@ namespace ts { /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ function getSymbolChain(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): Symbol[] | undefined { - let accessibleSymbolChain = getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/false); + let accessibleSymbolChain = getAccessibleSymbolChain(symbol, context.enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/false); if (!accessibleSymbolChain || - needsQualification(accessibleSymbolChain[0], enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { + needsQualification(accessibleSymbolChain[0], context.enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { // Go up and add our parent. const parent = getParentOfSymbol(accessibleSymbolChain ? accessibleSymbolChain[0] : symbol); @@ -2731,7 +2768,9 @@ namespace ts { if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) { return declarationNameToString((declaration.parent).name); } - encounteredError = encounteredError || !(flags & NodeBuilderFlags.allowAnonymousIdentifier); + if (!context.encounteredError && !(context.flags & NodeBuilderFlags.allowAnonymousIdentifier)) { + context.encounteredError = true; + } switch (declaration.kind) { case SyntaxKind.ClassExpression: return "(Anonymous class)"; diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 08dc91c6b2..e1e4e2c4ae 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -238,10 +238,10 @@ namespace ts { : node; } - export function createTypeReferenceNode(typeName: string | EntityName, typeArguments: NodeArray | undefined) { + export function createTypeReferenceNode(typeName: string | EntityName, typeArguments: TypeNode[] | undefined) { const typeReference = createSynthesizedNode(SyntaxKind.TypeReference) as TypeReferenceNode; typeReference.typeName = asName(typeName); - typeReference.typeArguments = typeArguments; + typeReference.typeArguments = asNodeArray(typeArguments); return typeReference; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 461e33426e..178e6fe779 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2548,7 +2548,9 @@ namespace ts { allowThisInObjectLiteral = 1 << 0, allowQualifedNameInPlaceOfIdentifier = 1 << 1, allowTypeParameterInQualifiedName = 1 << 2, - allowAnonymousIdentifier = 1 << 3 + allowAnonymousIdentifier = 1 << 3, + allowEmptyUnionOrIntersection = 1 << 4, + allowEmptyTuple = 1 << 5 } export interface SymbolDisplayBuilder { From e2b3c9c66374365381d3704c642bb3f4f59424bf Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Sun, 26 Mar 2017 22:29:17 -0700 Subject: [PATCH 61/65] remove NodeBuilder --- src/compiler/checker.ts | 43 ++++++++++++++++++++--------------------- src/compiler/types.ts | 8 -------- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a45f3424ef..3c2eb8c513 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2202,28 +2202,7 @@ namespace ts { return result; } - function createNodeBuilder(): NodeBuilder { - - interface NodeBuilderContext { - readonly enclosingDeclaration: Node | undefined; - readonly flags: NodeBuilderFlags | undefined; - encounteredError: boolean; - inObjectTypeLiteral: boolean; - checkAlias: boolean; - symbolStack: Symbol[] | undefined; - } - - function createNodeBuilderContext(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): NodeBuilderContext { - return { - enclosingDeclaration, - flags, - encounteredError: false, - inObjectTypeLiteral: false, - checkAlias: true, - symbolStack: undefined - }; - } - + function createNodeBuilder() { let context: NodeBuilderContext; return { @@ -2247,6 +2226,26 @@ namespace ts { } }; + interface NodeBuilderContext { + readonly enclosingDeclaration: Node | undefined; + readonly flags: NodeBuilderFlags | undefined; + encounteredError: boolean; + inObjectTypeLiteral: boolean; + checkAlias: boolean; + symbolStack: Symbol[] | undefined; + } + + function createNodeBuilderContext(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): NodeBuilderContext { + return { + enclosingDeclaration, + flags, + encounteredError: false, + inObjectTypeLiteral: false, + checkAlias: true, + symbolStack: undefined + }; + } + function typeToTypeNodeHelper(type: Type): TypeNode { if (!type) { context.encounteredError = true; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 178e6fe779..a93e8bddef 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2535,14 +2535,6 @@ namespace ts { /* @internal */ getTypeCount(): number; } - /** Note that any resulting nodes cannot be checked. */ - /* @internal */ - export interface NodeBuilder { - typeToTypeNode(type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): TypeNode; - signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): SignatureDeclaration; - indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): IndexSignatureDeclaration; - } - export enum NodeBuilderFlags { None = 0, allowThisInObjectLiteral = 1 << 0, From fb4e3d87bd93a3299735271e659ca3e410ee1c2b Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Sun, 26 Mar 2017 22:45:13 -0700 Subject: [PATCH 62/65] essymbol and object --- src/compiler/checker.ts | 4 ++-- ...ts => codeFixClassImplementInterfaceProperty.ts} | 10 ++++++++-- ...FixClassImplementInterfacePropertyEnumLiteral.ts | 13 ------------- 3 files changed, 10 insertions(+), 17 deletions(-) rename tests/cases/fourslash/{codeFixClassImplementInterfacePropertyEnum.ts => codeFixClassImplementInterfaceProperty.ts} (57%) delete mode 100644 tests/cases/fourslash/codeFixClassImplementInterfacePropertyEnumLiteral.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3c2eb8c513..7a7ce2536f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2295,10 +2295,10 @@ namespace ts { return createKeywordTypeNode(SyntaxKind.NeverKeyword); } if (type.flags & TypeFlags.ESSymbol) { - throw new Error("ESSymbol not implemented"); + return createKeywordTypeNode(SyntaxKind.SymbolKeyword); } if (type.flags & TypeFlags.NonPrimitive) { - throw new Error("Non primitive not implemented"); + return createKeywordTypeNode(SyntaxKind.ObjectKeyword); } if (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType) { if (context.inObjectTypeLiteral) { diff --git a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyEnum.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceProperty.ts similarity index 57% rename from tests/cases/fourslash/codeFixClassImplementInterfacePropertyEnum.ts rename to tests/cases/fourslash/codeFixClassImplementInterfaceProperty.ts index 8b04e4a4b4..53cfce5ff6 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyEnum.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceProperty.ts @@ -4,10 +4,16 @@ //// enum E { a,b,c } //// interface I { -//// a: E; +//// x: E; +//// y: E.a +//// z: symbol; +//// w: object; //// } //// class C implements I {[| |]} verify.rangeAfterCodeFix(` - a: E; + x: E; + y: E.a; + z: symbol; + w: object; `); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyEnumLiteral.ts b/tests/cases/fourslash/codeFixClassImplementInterfacePropertyEnumLiteral.ts deleted file mode 100644 index 720acc1ccc..0000000000 --- a/tests/cases/fourslash/codeFixClassImplementInterfacePropertyEnumLiteral.ts +++ /dev/null @@ -1,13 +0,0 @@ -/// - -// @lib: es2017 - -//// enum E { a,b,c } -//// interface I { -//// a: E.a -//// } -//// class C implements I {[| |]} - -verify.rangeAfterCodeFix(` - a: E.a; -`); \ No newline at end of file From a39bb0aaaaa161daeaac2c84ca0aaa1ade913418 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Mon, 27 Mar 2017 00:34:48 -0700 Subject: [PATCH 63/65] midway through response to ron's comments --- src/compiler/checker.ts | 64 +++++++-------- src/compiler/factory.ts | 167 +++++++++++++++++++--------------------- src/compiler/types.ts | 6 +- 3 files changed, 110 insertions(+), 127 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7a7ce2536f..6c24def150 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2384,13 +2384,13 @@ namespace ts { function createMappedTypeNodeFromType(type: MappedType) { Debug.assert(!!(type.flags & TypeFlags.Object)); - const typeParameter = getTypeParameterFromMappedType(type); + const typeParameter = getTypeParameterFromMappedType(type); const typeParameterNode = typeParameterToDeclaration(typeParameter); - const templateType = getTemplateTypeFromMappedType(type); - const templateTypeNode = templateType && typeToTypeNodeHelper(templateType); - const readonlyToken = (type).declaration && (type).declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; - const questionToken = (type).declaration && (type).declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; + const templateType = getTemplateTypeFromMappedType(type); + const templateTypeNode = typeToTypeNodeHelper(templateType); + const readonlyToken = type.declaration && type.declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; + const questionToken = type.declaration && type.declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; return createMappedTypeNode(readonlyToken, typeParameterNode, questionToken, templateTypeNode); } @@ -2402,7 +2402,7 @@ namespace ts { if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) || symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || shouldWriteTypeOfFunctionSymbol()) { - return createTypeQueryNodeFromType(type); + return createTypeQueryNodeFromSymbol(symbol); } else if (contains(context.symbolStack, symbol)) { // If type is an anonymous type literal in a type alias declaration, use type alias name @@ -2477,12 +2477,9 @@ namespace ts { return createTypeLiteralNode(members); } - function createTypeQueryNodeFromType(type: Type) { - const symbol = type.symbol; - if (symbol) { - const entityName = symbolToName(symbol, /*expectsIdentifier*/ false); - return createTypeQueryNode(entityName); - } + function createTypeQueryNodeFromSymbol(symbol: Symbol) { + const entityName = symbolToName(symbol, /*expectsIdentifier*/ false); + return createTypeQueryNode(entityName); } function typeReferenceToTypeNode(type: TypeReference) { @@ -2542,7 +2539,7 @@ namespace ts { entityName = nameIdentifier; } const typeParameterCount = (type.target.typeParameters || emptyArray).length; - const typeArgumentNodes = typeArguments.length > 0 ? mapToTypeNodeArray(typeArguments.slice(i, typeParameterCount - i)) : undefined; + const typeArgumentNodes = some(typeArguments) ? mapToTypeNodeArray(typeArguments.slice(i, typeParameterCount - i)) : undefined; return createTypeReferenceNode(entityName, typeArgumentNodes); } } @@ -2604,13 +2601,13 @@ namespace ts { const name = getNameFromIndexInfo(indexInfo); const indexingParameter = createParameter( - /*decorators*/ undefined, - /*modifiers*/ undefined, - /*dotDotDotToken*/ undefined, + /*decorators*/ undefined, + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, name, - /*questionToken*/ undefined, + /*questionToken*/ undefined, indexerTypeNode, - /*initializer*/ undefined); + /*initializer*/ undefined); const typeNode = typeToTypeNodeHelper(indexInfo.type); return createIndexSignatureDeclaration( [indexingParameter], @@ -2623,21 +2620,14 @@ namespace ts { const typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter)); const parameters = signature.parameters.map(parameter => symbolToParameterDeclaration(parameter)); - const returnTypeNode = typeToTypeNodeExceptAny(getReturnTypeOfSignature(signature)); + const returnType = getReturnTypeOfSignature(signature); + const returnTypeNode = returnType && typeToTypeNodeHelper(returnType); + const returnTypeNodeExceptAny = returnTypeNode && returnTypeNode.kind !== SyntaxKind.AnyKeyword ? returnTypeNode : undefined; - return createSignatureDeclaration(kind, typeParameters, parameters, returnTypeNode); - - function typeToTypeNodeExceptAny(type: Type): TypeNode | undefined { - const typeNode = type && typeToTypeNodeHelper(type); - return typeNode && typeNode.kind !== SyntaxKind.AnyKeyword ? typeNode : undefined; - } + return createSignatureDeclaration(kind, typeParameters, parameters, returnTypeNodeExceptAny); } function typeParameterToDeclaration(type: TypeParameter): TypeParameterDeclaration { - if (!(type && type.symbol && type.flags & TypeFlags.TypeParameter)) { - return undefined; - } - const constraint = getConstraintFromTypeParameter(type); const constraintNode = constraint && typeToTypeNodeHelper(constraint); const defaultParameter = getDefaultFromTypeParameter(type); @@ -2647,10 +2637,10 @@ namespace ts { } function symbolToParameterDeclaration(parameterSymbol: Symbol): ParameterDeclaration { - const parameterDeclaration = parameterSymbol.declarations[0] as ParameterDeclaration; + const parameterDeclaration = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); const parameterType = getTypeOfSymbol(parameterSymbol); const parameterTypeNode = typeToTypeNodeHelper(parameterType); - // TODO(aozgaa): check initializer accessibility correctly. + // TODO(aozgaa): In the future, check initializer accessibility. const parameterNode = createParameter( parameterDeclaration.decorators, parameterDeclaration.modifiers, @@ -2666,7 +2656,6 @@ namespace ts { function symbolToName(symbol: Symbol, expectsIdentifier: true): Identifier; function symbolToName(symbol: Symbol, expectsIdentifier: false): EntityName; function symbolToName(symbol: Symbol, expectsIdentifier: boolean): EntityName { - let parentSymbol: Symbol; // Try to get qualified name if the symbol is not a type parameter and there is an enclosing declaration. let chain: Symbol[]; @@ -2679,8 +2668,6 @@ namespace ts { chain = [symbol]; } - parentSymbol = undefined; - if (expectsIdentifier && chain.length !== 1 && !context.encounteredError && !(context.flags & NodeBuilderFlags.allowQualifedNameInPlaceOfIdentifier)) { @@ -2728,6 +2715,7 @@ namespace ts { /** @param endOfChain Set to false for recursive calls; non-recursive calls should always output something. */ function getSymbolChain(symbol: Symbol, meaning: SymbolFlags, endOfChain: boolean): Symbol[] | undefined { let accessibleSymbolChain = getAccessibleSymbolChain(symbol, context.enclosingDeclaration, meaning, /*useOnlyExternalAliasing*/false); + let parentSymbol: Symbol; if (!accessibleSymbolChain || needsQualification(accessibleSymbolChain[0], context.enclosingDeclaration, accessibleSymbolChain.length === 1 ? meaning : getQualifiedLeftMeaning(meaning))) { @@ -2737,6 +2725,7 @@ namespace ts { if (parent) { const parentChain = getSymbolChain(parent, getQualifiedLeftMeaning(meaning), /*endOfChain*/ false); if (parentChain) { + parentSymbol = parent; accessibleSymbolChain = parentChain.concat(accessibleSymbolChain || [symbol]); } } @@ -2745,8 +2734,7 @@ namespace ts { if (accessibleSymbolChain) { return accessibleSymbolChain; } - - else if ( + if ( // If this is the last part of outputting the symbol, always output. The cases apply only to parent symbols. endOfChain || // If a parent symbol is an external module, don't write it. (We prefer just `x` vs `"foo/bar".x`.) @@ -2759,8 +2747,8 @@ namespace ts { } function getNameOfSymbol(symbol: Symbol): string { - if (symbol.declarations && symbol.declarations.length) { - const declaration = symbol.declarations[0]; + const declaration = firstOrUndefined(symbol.declarations); + if (declaration) { if (declaration.name) { return declarationNameToString(declaration.name); } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index e1e4e2c4ae..4c5a4b4067 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -46,9 +46,6 @@ namespace ts { */ /* @internal */ export function getSynthesizedClone(node: T | undefined): T { - if (node === undefined) { - return undefined; - } // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of // the original node. We also need to exclude specific properties and only include own- // properties (to skip members already defined on the shared prototype). @@ -169,23 +166,23 @@ namespace ts { // Reserved words export function createSuper() { - return createSynthesizedNode(SyntaxKind.SuperKeyword); + return createSynthesizedNode(SyntaxKind.SuperKeyword); } export function createThis() { - return createSynthesizedNode(SyntaxKind.ThisKeyword); + return >createSynthesizedNode(SyntaxKind.ThisKeyword); } export function createNull() { - return createSynthesizedNode(SyntaxKind.NullKeyword); + return >createSynthesizedNode(SyntaxKind.NullKeyword); } export function createTrue() { - return createSynthesizedNode(SyntaxKind.TrueKeyword); + return >createSynthesizedNode(SyntaxKind.TrueKeyword); } export function createFalse() { - return createSynthesizedNode(SyntaxKind.FalseKeyword); + return >createSynthesizedNode(SyntaxKind.FalseKeyword); } // Names @@ -218,11 +215,74 @@ namespace ts { // Type Elements - // TODO: add signatures + export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined) { + const signatureDeclaration = createSynthesizedNode(kind) as SignatureDeclaration; + signatureDeclaration.typeParameters = asNodeArray(typeParameters); + signatureDeclaration.parameters = asNodeArray(parameters); + signatureDeclaration.type = type; + return signatureDeclaration; + } + + function updateSignatureDeclaration(node: SignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { + return node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + ? updateNode(createSignatureDeclaration(node.kind, typeParameters, parameters, type), node) + : node; + } + + export function createFunctionTypeNode(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined) { + return createSignatureDeclaration(SyntaxKind.FunctionType, typeParameters, parameters, type) as FunctionTypeNode; + } + + export function updateFunctionTypeNode(node: FunctionTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { + return updateSignatureDeclaration(node, typeParameters, parameters, type); + } + + export function createConstructorTypeNode(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined) { + return createSignatureDeclaration(SyntaxKind.ConstructorType, typeParameters, parameters, type) as ConstructorTypeNode; + } + + export function updateConstructorTypeNode(node: ConstructorTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { + return updateSignatureDeclaration(node, typeParameters, parameters, type); + } + + export function createCallSignatureDeclaration(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined) { + return createSignatureDeclaration(SyntaxKind.CallSignature, typeParameters, parameters, type) as CallSignatureDeclaration; + } + + export function updateCallSignatureDeclaration(node: CallSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { + return updateSignatureDeclaration(node, typeParameters, parameters, type); + } + + export function createConstructSignatureDeclaration(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined) { + return createSignatureDeclaration(SyntaxKind.ConstructSignature, typeParameters, parameters, type) as ConstructSignatureDeclaration; + } + + export function updateConstructSignatureDeclaration(node: ConstructSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { + return updateSignatureDeclaration(node, typeParameters, parameters, type); + } + + export function createMethodSignature(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined) { + const methodSignature = createSignatureDeclaration(SyntaxKind.MethodSignature, typeParameters, parameters, type) as MethodSignature; + methodSignature.name = asName(name); + methodSignature.questionToken = questionToken; + return methodSignature; + } + + export function updateMethodSignature(node: MethodSignature, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined, name: PropertyName, questionToken: QuestionToken | undefined) { + return node.typeParameters !== typeParameters + || node.parameters !== parameters + || node.type !== type + || node.name !== name + || node.questionToken !== questionToken + ? updateNode(createMethodSignature(typeParameters, parameters, type, name, questionToken), node) + : node; + } // Types - export function createKeywordTypeNode(kind: KeywordTypeNode["kind"]): KeywordTypeNode { + export function createKeywordTypeNode(kind: KeywordTypeNode["kind"]) { return createSynthesizedNode(kind); } @@ -259,10 +319,10 @@ namespace ts { } export function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName) { - return node.exprName !== exprName ? updateNode(createTypeQueryNode(exprName) , node) : node; + return node.exprName !== exprName ? updateNode(createTypeQueryNode(exprName), node) : node; } - export function createArrayTypeNode(elementType: TypeNode): ArrayTypeNode { + export function createArrayTypeNode(elementType: TypeNode) { const arrayTypeNode = createSynthesizedNode(SyntaxKind.ArrayType) as ArrayTypeNode; arrayTypeNode.elementType = elementType; return arrayTypeNode; @@ -277,9 +337,9 @@ namespace ts { export function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType, types: TypeNode[]): UnionTypeNode; export function createUnionOrIntersectionTypeNode(kind: SyntaxKind.IntersectionType, types: TypeNode[]): IntersectionTypeNode; export function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: TypeNode[]): UnionOrIntersectionTypeNode; - export function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: TypeNode[]): UnionOrIntersectionTypeNode { + export function createUnionOrIntersectionTypeNode(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, types: TypeNode[]) { const unionTypeNode = createSynthesizedNode(kind) as UnionTypeNode | IntersectionTypeNode; - unionTypeNode.types = asNodeArray(types); + unionTypeNode.types = createNodeArray(types); return unionTypeNode; } @@ -291,7 +351,7 @@ namespace ts { export function createTypeLiteralNode(members: TypeElement[]) { const typeLiteralNode = createSynthesizedNode(SyntaxKind.TypeLiteral) as TypeLiteralNode; - typeLiteralNode.members = asNodeArray(members); + typeLiteralNode.members = createNodeArray(members); return typeLiteralNode; } @@ -303,7 +363,7 @@ namespace ts { export function createTupleTypeNode(elementTypes: TypeNode[]) { const tupleTypeNode = createSynthesizedNode(SyntaxKind.TupleType) as TupleTypeNode; - tupleTypeNode.elementTypes = asNodeArray(elementTypes); + tupleTypeNode.elementTypes = createNodeArray(elementTypes); return tupleTypeNode; } @@ -349,7 +409,6 @@ namespace ts { return indexedAccessTypeNode; } - export function updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode) { return node.objectType !== objectType || node.indexType !== indexType @@ -357,86 +416,22 @@ namespace ts { : node; } - // Type Declarations - export function createTypeParameterDeclaration(name: string | Identifier, constraint: TypeNode | undefined, defaultParameter: TypeNode | undefined) { + export function createTypeParameterDeclaration(name: string | Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined) { const typeParameter = createSynthesizedNode(SyntaxKind.TypeParameter) as TypeParameterDeclaration; typeParameter.name = asName(name); typeParameter.constraint = constraint; - typeParameter.default = defaultParameter; + typeParameter.default = defaultType; return typeParameter; } - export function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultParameter: TypeNode | undefined) { + export function updateTypeParameterDeclaration(node: TypeParameterDeclaration, name: Identifier, constraint: TypeNode | undefined, defaultType: TypeNode | undefined) { return node.name !== name || node.constraint !== constraint - || node.default !== defaultParameter - ? updateNode(createTypeParameterDeclaration(name, constraint, defaultParameter), node) - : node; - } - - // TODO: Split according to AST nodes. - export function createSignatureDeclaration(kind: SyntaxKind, typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined) { - const signatureDeclaration = createSynthesizedNode(kind) as SignatureDeclaration; - signatureDeclaration.typeParameters = asNodeArray(typeParameters); - signatureDeclaration.parameters = asNodeArray(parameters); - signatureDeclaration.type = type; - return signatureDeclaration; - } - - export function updateSignatureDeclaration(node: SignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined) { - return node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - ? updateNode(createSignatureDeclaration(node.kind, typeParameters, parameters, type), node) - : node; - } - - export function createFunctionTypeNode(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): FunctionTypeNode { - return createSignatureDeclaration(SyntaxKind.FunctionType, typeParameters, parameters, type) as FunctionTypeNode; - } - - export function updateFunctionTypeNode(node: FunctionTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): FunctionTypeNode { - return updateSignatureDeclaration(node, typeParameters, parameters, type); - } - - export function createConstructorTypeNode(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): ConstructorTypeNode { - return createSignatureDeclaration(SyntaxKind.ConstructorType, typeParameters, parameters, type) as ConstructorTypeNode; - } - export function updateConstructorTypeNode(node: ConstructorTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): ConstructorTypeNode { - return updateSignatureDeclaration(node, typeParameters, parameters, type); - } - - export function createCallSignatureDeclaration(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): CallSignatureDeclaration { - return createSignatureDeclaration(SyntaxKind.CallSignature, typeParameters, parameters, type) as CallSignatureDeclaration; - } - export function updateCallSignatureDeclaration(node: CallSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): CallSignatureDeclaration { - return updateSignatureDeclaration(node, typeParameters, parameters, type); - } - - export function createConstructSignatureDeclaration(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined): ConstructSignatureDeclaration { - return createSignatureDeclaration(SyntaxKind.ConstructSignature, typeParameters, parameters, type) as ConstructSignatureDeclaration; - } - export function updateConstructSignatureDeclaration(node: ConstructSignatureDeclaration, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined): ConstructSignatureDeclaration { - return updateSignatureDeclaration(node, typeParameters, parameters, type); - } - - export function createMethodSignature(typeParameters: TypeParameterDeclaration[] | undefined, parameters: ParameterDeclaration[], type: TypeNode | undefined, name: string | PropertyName, questionToken: QuestionToken | undefined): MethodSignature { - const methodSignature = createSignatureDeclaration(SyntaxKind.MethodSignature, typeParameters, parameters, type) as MethodSignature; - methodSignature.name = asName(name); - methodSignature.questionToken = questionToken; - return methodSignature; - } - - export function updateMethodSignature(node: MethodSignature, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode | undefined, name?: PropertyName, questionToken?: QuestionToken): MethodSignature { - return node.typeParameters !== typeParameters - || node.parameters !== parameters - || node.type !== type - || node.name !== name - || node.questionToken !== questionToken - ? updateNode(createMethodSignature(typeParameters, parameters, type, name, questionToken), node) + || node.default !== defaultType + ? updateNode(createTypeParameterDeclaration(name, constraint, defaultType), node) : node; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a93e8bddef..45a4de01c9 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1034,15 +1034,15 @@ namespace ts { _primaryExpressionBrand: any; } - export interface NullLiteral extends PrimaryExpression { + export interface NullLiteral extends PrimaryExpression, TypeNode { kind: SyntaxKind.NullKeyword; } - export interface BooleanLiteral extends PrimaryExpression { + export interface BooleanLiteral extends PrimaryExpression, TypeNode { kind: SyntaxKind.TrueKeyword | SyntaxKind.FalseKeyword; } - export interface ThisExpression extends PrimaryExpression { + export interface ThisExpression extends PrimaryExpression, TypeNode { kind: SyntaxKind.ThisKeyword; } From 7340c4ca1ef40521b66074ece970cbfeec037ba3 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Mon, 27 Mar 2017 11:53:44 -0700 Subject: [PATCH 64/65] type predicate support --- src/compiler/checker.ts | 19 ++++++-- src/compiler/factory.ts | 35 ++++++++++---- src/compiler/types.ts | 8 +--- src/compiler/utilities.ts | 2 +- src/compiler/visitor.ts | 47 +++++-------------- src/services/codefixes/fixAddMissingMember.ts | 6 +-- src/services/codefixes/helpers.ts | 8 ---- src/services/textChanges.ts | 20 ++++++++ ...ssImplementInterfaceMethodTypePredicate.ts | 16 +++++++ 9 files changed, 93 insertions(+), 68 deletions(-) create mode 100644 tests/cases/fourslash/codeFixClassImplementInterfaceMethodTypePredicate.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6c24def150..2471ebcb52 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2610,18 +2610,27 @@ namespace ts { /*initializer*/ undefined); const typeNode = typeToTypeNodeHelper(indexInfo.type); return createIndexSignatureDeclaration( - [indexingParameter], - typeNode, /*decorators*/ undefined, - indexInfo.isReadonly ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined); + indexInfo.isReadonly ? [createToken(SyntaxKind.ReadonlyKeyword)] : undefined, + [indexingParameter], + typeNode); } function signatureToSignatureDeclarationHelper(signature: Signature, kind: SyntaxKind): SignatureDeclaration { const typeParameters = signature.typeParameters && signature.typeParameters.map(parameter => typeParameterToDeclaration(parameter)); const parameters = signature.parameters.map(parameter => symbolToParameterDeclaration(parameter)); - const returnType = getReturnTypeOfSignature(signature); - const returnTypeNode = returnType && typeToTypeNodeHelper(returnType); + let returnTypeNode: TypeNode | TypePredicate; + if (signature.typePredicate) { + const typePredicate = signature.typePredicate; + const parameterName = typePredicate.kind === TypePredicateKind.Identifier ? createIdentifier((typePredicate).parameterName) : createThisTypeNode(); + const typeNode = typeToTypeNodeHelper(typePredicate.type); + returnTypeNode = createTypePredicateNode(parameterName, typeNode); + } + else { + const returnType = getReturnTypeOfSignature(signature); + returnTypeNode = returnType && typeToTypeNodeHelper(returnType); + } const returnTypeNodeExceptAny = returnTypeNode && returnTypeNode.kind !== SyntaxKind.AnyKeyword ? returnTypeNode : undefined; return createSignatureDeclaration(kind, typeParameters, parameters, returnTypeNodeExceptAny); diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 4c5a4b4067..66b3eb713e 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -286,6 +286,10 @@ namespace ts { return createSynthesizedNode(kind); } + export function createThisTypeNode() { + return createSynthesizedNode(SyntaxKind.ThisType); + } + export function createLiteralTypeNode(literal: Expression) { const literalTypeNode = createSynthesizedNode(SyntaxKind.LiteralType) as LiteralTypeNode; literalTypeNode.literal = literal; @@ -312,6 +316,20 @@ namespace ts { : node; } + export function createTypePredicateNode(parameterName: Identifier | ThisTypeNode | string, type: TypeNode) { + const typePredicateNode = createSynthesizedNode(SyntaxKind.TypePredicate) as TypePredicateNode; + typePredicateNode.parameterName = asName(parameterName); + typePredicateNode.type = type; + return typePredicateNode; + } + + export function updateTypePredicateNode(node: TypePredicateNode, parameterName: Identifier | ThisTypeNode, type: TypeNode) { + return node.parameterName !== parameterName + || node.type !== type + ? updateNode(createTypePredicateNode(parameterName, type), node) + : node; + } + export function createTypeQueryNode(exprName: EntityName) { const typeQueryNode = createSynthesizedNode(SyntaxKind.TypeQuery) as TypeQueryNode; typeQueryNode.exprName = exprName; @@ -455,21 +473,21 @@ namespace ts { : node; } - export function createIndexSignatureDeclaration(parameters: ParameterDeclaration[], type: TypeNode, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined): IndexSignatureDeclaration { + export function createIndexSignatureDeclaration(decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, parameters: ParameterDeclaration[], type: TypeNode): IndexSignatureDeclaration { const indexSignature = createSynthesizedNode(SyntaxKind.IndexSignature) as IndexSignatureDeclaration; - indexSignature.parameters = asNodeArray(parameters); - indexSignature.type = type; indexSignature.decorators = asNodeArray(decorators); indexSignature.modifiers = asNodeArray(modifiers); + indexSignature.parameters = createNodeArray(parameters); + indexSignature.type = type; return indexSignature; } - export function updateIndexSignatureDeclaration(node: IndexSignatureDeclaration, parameters: ParameterDeclaration[], type: TypeNode, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined) { + export function updateIndexSignatureDeclaration(node: IndexSignatureDeclaration, decorators: Decorator[] | undefined, modifiers: Modifier[] | undefined, parameters: ParameterDeclaration[], type: TypeNode) { return node.parameters !== parameters || node.type !== type || node.decorators !== decorators || node.modifiers !== modifiers - ? updateNode(createIndexSignatureDeclaration(parameters, type, decorators, modifiers), node) + ? updateNode(createIndexSignatureDeclaration(decorators, modifiers, parameters, type), node) : node; } @@ -542,7 +560,7 @@ namespace ts { node.name = asName(name); node.questionToken = questionToken; node.typeParameters = asNodeArray(typeParameters); - node.parameters = asNodeArray(parameters); + node.parameters = createNodeArray(parameters); node.type = type; node.body = body; return node; @@ -2052,7 +2070,8 @@ namespace ts { function asName(name: string | BindingName): BindingName; function asName(name: string | PropertyName): PropertyName; function asName(name: string | EntityName): EntityName; - function asName(name: string | Identifier | BindingName | PropertyName | QualifiedName) { + function asName(name: string | Identifier | ThisTypeNode): Identifier | ThisTypeNode; + function asName(name: string | Identifier | BindingName | PropertyName | QualifiedName | ThisTypeNode) { return typeof name === "string" ? createIdentifier(name) : name; } @@ -2060,7 +2079,7 @@ namespace ts { return typeof value === "string" || typeof value === "number" ? createLiteral(value) : value; } - export function asNodeArray(array: T[] | undefined): NodeArray | undefined { + function asNodeArray(array: T[] | undefined): NodeArray | undefined { return array ? createNodeArray(array) : undefined; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 45a4de01c9..038f8bf316 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1042,7 +1042,7 @@ namespace ts { kind: SyntaxKind.TrueKeyword | SyntaxKind.FalseKeyword; } - export interface ThisExpression extends PrimaryExpression, TypeNode { + export interface ThisExpression extends PrimaryExpression, KeywordTypeNode { kind: SyntaxKind.ThisKeyword; } @@ -3248,12 +3248,6 @@ namespace ts { declaration?: SignatureDeclaration; } - export interface SignatureParts { - typeParameters: TypeParameterDeclaration[] | undefined; - parameters: ParameterDeclaration[]; - type: TypeNode; - } - /* @internal */ export interface TypeMapper { (t: TypeParameter): Type; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index f4f288d0cb..33cb0d8f48 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3735,7 +3735,7 @@ namespace ts { * of a TypeNode. */ export function isTypeNode(node: Node): node is TypeNode { - return node && isTypeNodeKind(node.kind); + return isTypeNodeKind(node.kind); } // Binding patterns diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index b8eb0210d0..c110ea4227 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -3,26 +3,6 @@ /// namespace ts { - export const nullTransformationContext: TransformationContext = { - enableEmitNotification: noop, - enableSubstitution: noop, - endLexicalEnvironment: () => undefined, - getCompilerOptions: notImplemented, - getEmitHost: notImplemented, - getEmitResolver: notImplemented, - hoistFunctionDeclaration: noop, - hoistVariableDeclaration: noop, - isEmitNotificationEnabled: notImplemented, - isSubstitutionEnabled: notImplemented, - onEmitNode: noop, - onSubstituteNode: notImplemented, - readEmitHelpers: notImplemented, - requestEmitHelper: noop, - resumeLexicalEnvironment: noop, - startLexicalEnvironment: noop, - suspendLexicalEnvironment: noop - }; - /** * Visits a Node using the supplied visitor, possibly returning a new Node in its place. * @@ -234,7 +214,7 @@ namespace ts { const kind = node.kind; // No need to visit nodes with no children. - if ((kind > SyntaxKind.FirstToken && kind <= SyntaxKind.LastToken)) { + if ((kind > SyntaxKind.FirstToken && kind <= SyntaxKind.LastToken) || kind === SyntaxKind.ThisType) { return node; } @@ -293,10 +273,10 @@ namespace ts { case SyntaxKind.IndexSignature: return updateIndexSignatureDeclaration(node, - visitParameterList((node).parameters, visitor, context, nodesVisitor), - visitNode((node).type, visitor, isTypeNode), nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier)); + nodesVisitor((node).modifiers, visitor, isModifier), + visitParameterList((node).parameters, visitor, context, nodesVisitor), + visitNode((node).type, visitor, isTypeNode)); case SyntaxKind.Parameter: return updateParameter(node, @@ -314,13 +294,16 @@ namespace ts { // Types - case SyntaxKind.TypePredicate: - throw new Error("reached unsupported type in visitor."); case SyntaxKind.TypeReference: return updateTypeReferenceNode(node, visitNode((node).typeName, visitor, isEntityName), nodesVisitor((node).typeArguments, visitor, isTypeNode)); + case SyntaxKind.TypePredicate: + return updateTypePredicateNode(node, + visitNode((node).parameterName, visitor), + visitNode((node).type, visitor, isTypeNode)); + case SyntaxKind.TypeQuery: return updateTypeQueryNode((node), visitNode((node).exprName, visitor, isEntityName)); @@ -339,9 +322,8 @@ namespace ts { nodesVisitor((node).types, visitor, isTypeNode)); case SyntaxKind.ParenthesizedType: - throw new Error("reached unsupported type in visitor."); - case SyntaxKind.ThisType: - throw new Error("reached unsupported type in visitor."); + Debug.fail("not implemented."); + case SyntaxKind.TypeOperator: return updateTypeOperatorNode(node, visitNode((node).type, visitor, isTypeNode)); case SyntaxKind.IndexedAccessType: @@ -376,13 +358,6 @@ namespace ts { visitNode((node).type, visitor, isTypeNode), visitNode((node).initializer, visitor, isExpression)); - case SyntaxKind.IndexSignature: - return updateIndexSignatureDeclaration(node, - visitParameterList((node).parameters, visitor, context, nodesVisitor), - visitNode((node).type, visitor, isTypeNode), - nodesVisitor((node).decorators, visitor, isDecorator), - nodesVisitor((node).modifiers, visitor, isModifier)); - case SyntaxKind.PropertyDeclaration: return updateProperty(node, nodesVisitor((node).decorators, visitor, isDecorator), diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 4ef9fce067..bfb931e866 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -65,10 +65,10 @@ namespace ts.codefix { stringTypeNode, /*initializer*/ undefined); const indexSignature = createIndexSignatureDeclaration( - [indexingParameter], - typeNode, /*decorators*/undefined, - /*modifiers*/ undefined); + /*modifiers*/ undefined, + [indexingParameter], + typeNode); const indexSignatureChangeTracker = textChanges.ChangeTracker.fromCodeFixContext(context); indexSignatureChangeTracker.insertNodeAfter(sourceFile, openBrace, indexSignature, { suffix: context.newLineCharacter }); diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 49f95243c9..a7809cdd21 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -230,12 +230,4 @@ namespace ts.codefix { } return undefined; } - - function stripComments(node: Node): Node { - if (node === undefined) { - return node; - } - const strippedChildren = visitEachChild(node, stripComments, nullTransformationContext); - return strippedChildren === node ? getSynthesizedClone(strippedChildren) : strippedChildren; - } } \ No newline at end of file diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index f5b0ec678d..73d5fc61bc 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -528,6 +528,26 @@ namespace ts.textChanges { return skipTrivia(s, 0) === s.length; } + const nullTransformationContext: TransformationContext = { + enableEmitNotification: noop, + enableSubstitution: noop, + endLexicalEnvironment: () => undefined, + getCompilerOptions: notImplemented, + getEmitHost: notImplemented, + getEmitResolver: notImplemented, + hoistFunctionDeclaration: noop, + hoistVariableDeclaration: noop, + isEmitNotificationEnabled: notImplemented, + isSubstitutionEnabled: notImplemented, + onEmitNode: noop, + onSubstituteNode: notImplemented, + readEmitHelpers: notImplemented, + requestEmitHelper: noop, + resumeLexicalEnvironment: noop, + startLexicalEnvironment: noop, + suspendLexicalEnvironment: noop + }; + function assignPositionsToNode(node: Node): Node { const visited = visitEachChild(node, assignPositionsToNode, nullTransformationContext, assignPositionsToNodeArray, assignPositionsToNode); // create proxy node for non synthesized nodes diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceMethodTypePredicate.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceMethodTypePredicate.ts new file mode 100644 index 0000000000..2eb6c2fabc --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceMethodTypePredicate.ts @@ -0,0 +1,16 @@ +/// + +//// interface I { +//// f(i: any): i is I; +//// f(): this is I; +//// } +//// +//// class C implements I {[| |]} + +verify.rangeAfterCodeFix(` +f(i: any): i is I; +f(): this is I; +f(i?: any) { + throw new Error("Method not implemented."); +} +`); From 4329b4524eb4b215752fc2a81d1676d8d4830aea Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Mon, 27 Mar 2017 12:01:56 -0700 Subject: [PATCH 65/65] Cleanup --- src/compiler/visitor.ts | 2 ++ src/harness/fourslash.ts | 2 +- src/services/codefixes/helpers.ts | 13 +++++++------ src/services/textChanges.ts | 4 ++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index c110ea4227..297d2cc0dd 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -326,10 +326,12 @@ namespace ts { case SyntaxKind.TypeOperator: return updateTypeOperatorNode(node, visitNode((node).type, visitor, isTypeNode)); + case SyntaxKind.IndexedAccessType: return updateIndexedAccessTypeNode((node), visitNode((node).objectType, visitor, isTypeNode), visitNode((node).indexType, visitor, isTypeNode)); + case SyntaxKind.MappedType: return updateMappedTypeNode((node), visitNode((node).readonlyToken, tokenVisitor, isToken), diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 4ef6b54e1b..98ebacd49b 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1676,7 +1676,7 @@ namespace FourSlash { // We get back a set of edits, but langSvc.editScript only accepts one at a time. Use this to keep track // of the incremental offset from each edit to the next. Assumption is that these edit ranges don't overlap let runningOffset = 0; - edits = ts.stableSort(edits, (a, b) => a.span.start - b.span.start); + edits = edits.sort((a, b) => a.span.start - b.span.start); // Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters const oldContent = this.getFileContent(fileName); diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index a7809cdd21..b836815fc2 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -11,7 +11,7 @@ namespace ts.codefix { } const changes = changeTracker.getChanges(); - if (!(changes && changes.length > 0)) { + if (!some(changes)) { return changes; } @@ -93,7 +93,7 @@ namespace ts.codefix { // (eg: an abstract method or interface declaration), there is a 1-1 // correspondence of declarations and signatures. const signatures = checker.getSignaturesOfType(type, SignatureKind.Call); - if (!(signatures && signatures.length > 0)) { + if (!some(signatures)) { return undefined; } @@ -103,7 +103,7 @@ namespace ts.codefix { return signatureToMethodDeclaration(signature, enclosingDeclaration, createStubbedMethodBody()); } - const signatureDeclarations = []; + const signatureDeclarations: MethodDeclaration[] = []; for (let i = 0; i < signatures.length; i++) { const signature = signatures[i]; const methodDeclaration = signatureToMethodDeclaration(signature, enclosingDeclaration); @@ -132,6 +132,7 @@ namespace ts.codefix { function signatureToMethodDeclaration(signature: Signature, enclosingDeclaration: Node, body?: Block) { const signatureDeclaration = checker.signatureToSignatureDeclaration(signature, SyntaxKind.MethodDeclaration, enclosingDeclaration); if (signatureDeclaration) { + signatureDeclaration.decorators = undefined; signatureDeclaration.modifiers = modifiers; signatureDeclaration.name = name; signatureDeclaration.questionToken = optional ? createToken(SyntaxKind.QuestionToken) : undefined; @@ -142,8 +143,6 @@ namespace ts.codefix { } function createMethodImplementingSignatures(signatures: Signature[], name: PropertyName, optional: boolean, modifiers: Modifier[] | undefined): MethodDeclaration { - Debug.assert(signatures && signatures.length > 0); - /** This is *a* signature with the maximal number of arguments, * such that if there is a "maximal" signature without rest arguments, * this is one of them. @@ -154,7 +153,9 @@ namespace ts.codefix { for (let i = 0; i < signatures.length; i++) { const sig = signatures[i]; minArgumentCount = Math.min(sig.minArgumentCount, minArgumentCount); - someSigHasRestParameter = someSigHasRestParameter || sig.hasRestParameter; + if (sig.hasRestParameter) { + someSigHasRestParameter = true; + } if (sig.parameters.length >= maxArgsSignature.parameters.length && (!sig.hasRestParameter || maxArgsSignature.hasRestParameter)) { maxArgsSignature = sig; } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 73d5fc61bc..afc9892749 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -276,7 +276,7 @@ namespace ts.textChanges { /** * This function should be used to insert nodes in lists when nodes don't carry separators as the part of the node range, * i.e. arguments in arguments lists, parameters in parameter lists etc. - * Note that separators are art of the node in statements and class elements. + * Note that separators are part of the node in statements and class elements. */ public insertNodeInListAfter(sourceFile: SourceFile, after: Node, newNode: Node) { const containingList = formatting.SmartIndenter.getContainingList(after, sourceFile); @@ -484,7 +484,7 @@ namespace ts.textChanges { private static normalize(changes: Change[]) { // order changes by start position const normalized = stableSort(changes, (a, b) => a.range.pos - b.range.pos); - // verify that change intervals to not overlap, except possible at end points. + // verify that change intervals do not overlap, except possibly at end points. for (let i = 0; i < normalized.length - 2; i++) { Debug.assert(normalized[i].range.end <= normalized[i + 1].range.pos); }