From d1ac4515c8a8977043a6503e0f1e7bca6094c654 Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Wed, 23 Dec 2020 06:30:45 +0000 Subject: [PATCH 01/18] Update package-lock.json --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a5e85983a51..018e03392b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7912,9 +7912,9 @@ "dev": true }, "uglify-js": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.2.tgz", - "integrity": "sha512-rWYleAvfJPjduYCt+ELvzybNah/zIkRteGXIBO8X0lteRZPGladF61hFi8tU7qKTsF7u6DUQCtT9k00VlFOgkg==", + "version": "3.12.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.3.tgz", + "integrity": "sha512-feZzR+kIcSVuLi3s/0x0b2Tx4Iokwqt+8PJM7yRHKuldg4MLdam4TCFeICv+lgDtuYiCtdmrtIP+uN9LWvDasw==", "dev": true, "optional": true }, From 8bbef818edfa7298fd25af94cddcbc36902667f3 Mon Sep 17 00:00:00 2001 From: Jesse Trinity Date: Wed, 23 Dec 2020 12:50:03 -0800 Subject: [PATCH 02/18] Hierarchical refactorings (#41975) * add hierarchical refactoring strings * fourslash tests * extractSymbol filters returned actions * move refactorKind check to utilities * rename parameters * messaging for addOrRemoveBracesToArrowFunction * fix up inferFunctionReturnType * fix up convertArrowFunctionOrFunctionExpression * add preferences to fourslash method * fix up convert string * fix up moveToNewFile * fix lint errors * remove extra arrow braces diagnostics * break out tests * add refactor helpers * refactor refactors * keep list of actions * address PR comments * response protocol * address more comments --- src/compiler/diagnosticMessages.json | 28 ++++ src/harness/fourslashImpl.ts | 18 ++- src/harness/fourslashInterfaceImpl.ts | 4 + src/server/protocol.ts | 8 +- src/server/session.ts | 2 +- src/services/codefixes/generateAccessors.ts | 37 ++--- src/services/refactorProvider.ts | 4 +- .../addOrRemoveBracesToArrowFunction.ts | 85 ++++------- ...onvertArrowFunctionOrFunctionExpression.ts | 87 ++++++++---- src/services/refactors/convertExport.ts | 58 ++++---- src/services/refactors/convertImport.ts | 49 ++++--- .../convertOverloadListToSingleSignature.ts | 16 ++- .../convertParamsToDestructuredObject.ts | 20 ++- .../convertStringOrTemplateLiteral.ts | 19 ++- .../convertToOptionalChainExpression.ts | 52 +++---- src/services/refactors/extractSymbol.ts | 133 +++++++++++------- src/services/refactors/extractType.ts | 66 +++++---- .../generateGetAccessorAndSetAccessor.ts | 28 ++-- src/services/refactors/helpers.ts | 25 ++++ .../refactors/inferFunctionReturnType.ts | 43 ++++-- src/services/refactors/moveToNewFile.ts | 21 ++- src/services/services.ts | 7 +- src/services/tsconfig.json | 1 + src/services/types.ts | 12 +- .../reference/api/tsserverlibrary.d.ts | 11 +- tests/baselines/reference/api/typescript.d.ts | 6 +- tests/cases/fourslash/fourslash.ts | 1 + tests/cases/fourslash/refactorKind_extract.ts | 16 +++ .../refactorKind_generateGetAndSetAccessor.ts | 11 ++ .../fourslash/refactorKind_moveToNewFile.ts | 12 ++ .../fourslash/refactorKind_rewriteExport.ts | 9 ++ .../fourslash/refactorKind_rewriteFunction.ts | 12 ++ ...efactorKind_rewriteFunctionOverloadList.ts | 10 ++ .../fourslash/refactorKind_rewriteImport.ts | 9 ++ .../refactorKind_rewriteOptionalChain.ts | 10 ++ ...torKind_rewriteParametersToDestructured.ts | 9 ++ .../fourslash/refactorKind_rewriteString.ts | 9 ++ 37 files changed, 625 insertions(+), 323 deletions(-) create mode 100644 src/services/refactors/helpers.ts create mode 100644 tests/cases/fourslash/refactorKind_extract.ts create mode 100644 tests/cases/fourslash/refactorKind_generateGetAndSetAccessor.ts create mode 100644 tests/cases/fourslash/refactorKind_moveToNewFile.ts create mode 100644 tests/cases/fourslash/refactorKind_rewriteExport.ts create mode 100644 tests/cases/fourslash/refactorKind_rewriteFunction.ts create mode 100644 tests/cases/fourslash/refactorKind_rewriteFunctionOverloadList.ts create mode 100644 tests/cases/fourslash/refactorKind_rewriteImport.ts create mode 100644 tests/cases/fourslash/refactorKind_rewriteOptionalChain.ts create mode 100644 tests/cases/fourslash/refactorKind_rewriteParametersToDestructured.ts create mode 100644 tests/cases/fourslash/refactorKind_rewriteString.ts diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 527f1737cec..37f12224c75 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -6143,6 +6143,34 @@ "category": "Message", "code": 95148 }, + "Return type must be inferred from a function": { + "category": "Message", + "code": 95149 + }, + "Could not determine function return type": { + "category": "Message", + "code": 95150 + }, + "Could not convert to arrow function": { + "category": "Message", + "code": 95151 + }, + "Could not convert to named function": { + "category": "Message", + "code": 95152 + }, + "Could not convert to anonymous function": { + "category": "Message", + "code": 95153 + }, + "Can only convert string concatenation": { + "category": "Message", + "code": 95154 + }, + "Selection is not a valid statement or statements": { + "category": "Message", + "code": 95155 + }, "No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": { "category": "Error", diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 551ac9aef18..6355cb15aca 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -3420,6 +3420,12 @@ namespace FourSlash { } } + public verifyRefactorKindsAvailable(kind: string, expected: string[], preferences = ts.emptyOptions) { + const refactors = this.getApplicableRefactorsAtSelection("invoked", kind, preferences); + const availableKinds = ts.flatMap(refactors, refactor => refactor.actions).map(action => action.kind); + assert.deepEqual(availableKinds.sort(), expected.sort(), `Expected kinds to be equal`); + } + public verifyRefactorsAvailable(names: readonly string[]): void { assert.deepEqual(unique(this.getApplicableRefactorsAtSelection(), r => r.name), names); } @@ -3833,14 +3839,14 @@ namespace FourSlash { test(renameKeys(newFileContents, key => pathUpdater(key) || key), "with file moved"); } - private getApplicableRefactorsAtSelection(triggerReason: ts.RefactorTriggerReason = "implicit") { - return this.getApplicableRefactorsWorker(this.getSelection(), this.activeFile.fileName, ts.emptyOptions, triggerReason); + private getApplicableRefactorsAtSelection(triggerReason: ts.RefactorTriggerReason = "implicit", kind?: string, preferences = ts.emptyOptions) { + return this.getApplicableRefactorsWorker(this.getSelection(), this.activeFile.fileName, preferences, triggerReason, kind); } - private getApplicableRefactors(rangeOrMarker: Range | Marker, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason = "implicit"): readonly ts.ApplicableRefactorInfo[] { - return this.getApplicableRefactorsWorker("position" in rangeOrMarker ? rangeOrMarker.position : rangeOrMarker, rangeOrMarker.fileName, preferences, triggerReason); // eslint-disable-line no-in-operator + private getApplicableRefactors(rangeOrMarker: Range | Marker, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason = "implicit", kind?: string): readonly ts.ApplicableRefactorInfo[] { + return this.getApplicableRefactorsWorker("position" in rangeOrMarker ? rangeOrMarker.position : rangeOrMarker, rangeOrMarker.fileName, preferences, triggerReason, kind); // eslint-disable-line no-in-operator } - private getApplicableRefactorsWorker(positionOrRange: number | ts.TextRange, fileName: string, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason): readonly ts.ApplicableRefactorInfo[] { - return this.languageService.getApplicableRefactors(fileName, positionOrRange, preferences, triggerReason) || ts.emptyArray; + private getApplicableRefactorsWorker(positionOrRange: number | ts.TextRange, fileName: string, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason, kind?: string): readonly ts.ApplicableRefactorInfo[] { + return this.languageService.getApplicableRefactors(fileName, positionOrRange, preferences, triggerReason, kind) || ts.emptyArray; } public configurePlugin(pluginName: string, configuration: any): void { diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 6fd2d834039..2e64b2921e8 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -215,6 +215,10 @@ namespace FourSlashInterface { this.state.verifyRefactorAvailable(this.negative, triggerReason, name, actionName); } + public refactorKindAvailable(kind: string, expected: string[], preferences = ts.emptyOptions) { + this.state.verifyRefactorKindsAvailable(kind, expected, preferences); + } + public toggleLineComment(newFileContent: string) { this.state.toggleLineComment(newFileContent); } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index ad8926470a5..9b406411dfd 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -566,7 +566,8 @@ namespace ts.server.protocol { arguments: GetApplicableRefactorsRequestArgs; } export type GetApplicableRefactorsRequestArgs = FileLocationOrRangeRequestArgs & { - triggerReason?: RefactorTriggerReason + triggerReason?: RefactorTriggerReason; + kind?: string; }; export type RefactorTriggerReason = "implicit" | "invoked"; @@ -626,6 +627,11 @@ namespace ts.server.protocol { * the current context. */ notApplicableReason?: string; + + /** + * The hierarchical dotted name of the refactor action. + */ + kind?: string; } export interface GetEditsForRefactorRequest extends Request { diff --git a/src/server/session.ts b/src/server/session.ts index 267b1c44ce3..a3fcdc7787e 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -2129,7 +2129,7 @@ namespace ts.server { private getApplicableRefactors(args: protocol.GetApplicableRefactorsRequestArgs): protocol.ApplicableRefactorInfo[] { const { file, project } = this.getFileAndProject(args); const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; - return project.getLanguageService().getApplicableRefactors(file, this.extractPositionOrRange(args, scriptInfo), this.getPreferences(file), args.triggerReason); + return project.getLanguageService().getApplicableRefactors(file, this.extractPositionOrRange(args, scriptInfo), this.getPreferences(file), args.triggerReason, args.kind); } private getEditsForRefactor(args: protocol.GetEditsForRefactorRequestArgs, simplifiedResult: boolean): RefactorEditInfo | protocol.RefactorEditInfo { diff --git a/src/services/codefixes/generateAccessors.ts b/src/services/codefixes/generateAccessors.ts index f2b397cca3e..99bd53e2934 100644 --- a/src/services/codefixes/generateAccessors.ts +++ b/src/services/codefixes/generateAccessors.ts @@ -4,7 +4,8 @@ namespace ts.codefix { type AcceptedNameType = Identifier | StringLiteral; type ContainerDeclaration = ClassLikeDeclaration | ObjectLiteralExpression; - interface Info { + type Info = AccessorInfo | refactor.RefactorErrorInfo; + interface AccessorInfo { readonly container: ContainerDeclaration; readonly isStatic: boolean; readonly isReadonly: boolean; @@ -16,20 +17,12 @@ namespace ts.codefix { readonly renameAccessor: boolean; } - type InfoOrError = { - info: Info, - error?: never - } | { - info?: never, - error: string - }; - export function generateAccessorFromProperty(file: SourceFile, program: Program, start: number, end: number, context: textChanges.TextChangesContext, _actionName: string): FileTextChanges[] | undefined { const fieldInfo = getAccessorConvertiblePropertyAtPosition(file, program, start, end); - if (!fieldInfo || !fieldInfo.info) return undefined; + if (!fieldInfo || refactor.isRefactorErrorInfo(fieldInfo)) return undefined; const changeTracker = textChanges.ChangeTracker.fromContext(context); - const { isStatic, isReadonly, fieldName, accessorName, originalName, type, container, declaration } = fieldInfo.info; + const { isStatic, isReadonly, fieldName, accessorName, originalName, type, container, declaration } = fieldInfo; suppressLeadingAndTrailingTrivia(fieldName); suppressLeadingAndTrailingTrivia(accessorName); @@ -112,7 +105,7 @@ namespace ts.codefix { return modifierFlags; } - export function getAccessorConvertiblePropertyAtPosition(file: SourceFile, program: Program, start: number, end: number, considerEmptySpans = true): InfoOrError | undefined { + export function getAccessorConvertiblePropertyAtPosition(file: SourceFile, program: Program, start: number, end: number, considerEmptySpans = true): Info | undefined { const node = getTokenAtPosition(file, start); const cursorRequest = start === end && considerEmptySpans; const declaration = findAncestor(node.parent, isAcceptedDeclaration); @@ -142,17 +135,15 @@ namespace ts.codefix { const fieldName = createPropertyName(startWithUnderscore ? name : getUniqueName(`_${name}`, file), declaration.name); const accessorName = createPropertyName(startWithUnderscore ? getUniqueName(name.substring(1), file) : name, declaration.name); return { - info: { - isStatic: hasStaticModifier(declaration), - isReadonly: hasEffectiveReadonlyModifier(declaration), - type: getDeclarationType(declaration, program), - container: declaration.kind === SyntaxKind.Parameter ? declaration.parent.parent : declaration.parent, - originalName: (declaration.name).text, - declaration, - fieldName, - accessorName, - renameAccessor: startWithUnderscore - } + isStatic: hasStaticModifier(declaration), + isReadonly: hasEffectiveReadonlyModifier(declaration), + type: getDeclarationType(declaration, program), + container: declaration.kind === SyntaxKind.Parameter ? declaration.parent.parent : declaration.parent, + originalName: (declaration.name).text, + declaration, + fieldName, + accessorName, + renameAccessor: startWithUnderscore }; } diff --git a/src/services/refactorProvider.ts b/src/services/refactorProvider.ts index 42bda231be6..ccc6f81d292 100644 --- a/src/services/refactorProvider.ts +++ b/src/services/refactorProvider.ts @@ -11,7 +11,9 @@ namespace ts.refactor { export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] { return arrayFrom(flatMapIterator(refactors.values(), refactor => - context.cancellationToken && context.cancellationToken.isCancellationRequested() ? undefined : refactor.getAvailableActions(context))); + context.cancellationToken && context.cancellationToken.isCancellationRequested() || + !refactor.kinds?.some(kind => refactorKindBeginsWith(kind, context.kind)) ? undefined : + refactor.getAvailableActions(context))); } export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined { diff --git a/src/services/refactors/addOrRemoveBracesToArrowFunction.ts b/src/services/refactors/addOrRemoveBracesToArrowFunction.ts index a2a14152da7..3cd8e7dfb34 100644 --- a/src/services/refactors/addOrRemoveBracesToArrowFunction.ts +++ b/src/services/refactors/addOrRemoveBracesToArrowFunction.ts @@ -2,45 +2,40 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction { const refactorName = "Add or remove braces in an arrow function"; const refactorDescription = Diagnostics.Add_or_remove_braces_in_an_arrow_function.message; - const addBracesActionName = "Add braces to arrow function"; - const removeBracesActionName = "Remove braces from arrow function"; - const addBracesActionDescription = Diagnostics.Add_braces_to_arrow_function.message; - const removeBracesActionDescription = Diagnostics.Remove_braces_from_arrow_function.message; - registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); - interface Info { + const addBracesAction = { + name: "Add braces to arrow function", + description: Diagnostics.Add_braces_to_arrow_function.message, + kind: "refactor.rewrite.arrow.braces.add", + }; + const removeBracesAction = { + name: "Remove braces from arrow function", + description: Diagnostics.Remove_braces_from_arrow_function.message, + kind: "refactor.rewrite.arrow.braces.remove" + }; + registerRefactor(refactorName, { + kinds: [removeBracesAction.kind], + getEditsForAction, + getAvailableActions }); + + interface FunctionBracesInfo { func: ArrowFunction; expression: Expression | undefined; returnStatement?: ReturnStatement; addBraces: boolean; } - type InfoOrError = { - info: Info, - error?: never - } | { - info?: never, - error: string - }; - function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { const { file, startPosition, triggerReason } = context; const info = getConvertibleArrowFunctionAtPosition(file, startPosition, triggerReason === "invoked"); if (!info) return emptyArray; - if (info.error === undefined) { + if (!isRefactorErrorInfo(info)) { return [{ name: refactorName, description: refactorDescription, actions: [ - info.info.addBraces ? - { - name: addBracesActionName, - description: addBracesActionDescription - } : { - name: removeBracesActionName, - description: removeBracesActionDescription - } + info.addBraces ? addBracesAction : removeBracesAction ] }]; } @@ -49,15 +44,10 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction { return [{ name: refactorName, description: refactorDescription, - actions: [{ - name: addBracesActionName, - description: addBracesActionDescription, - notApplicableReason: info.error - }, { - name: removeBracesActionName, - description: removeBracesActionDescription, - notApplicableReason: info.error - }] + actions: [ + { ...addBracesAction, notApplicableReason: info.error }, + { ...removeBracesAction, notApplicableReason: info.error }, + ] }]; } @@ -67,19 +57,19 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction { function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { const { file, startPosition } = context; const info = getConvertibleArrowFunctionAtPosition(file, startPosition); - if (!info || !info.info) return undefined; + Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); - const { expression, returnStatement, func } = info.info; + const { expression, returnStatement, func } = info; let body: ConciseBody; - if (actionName === addBracesActionName) { + if (actionName === addBracesAction.name) { const returnStatement = factory.createReturnStatement(expression); body = factory.createBlock([returnStatement], /* multiLine */ true); suppressLeadingAndTrailingTrivia(body); copyLeadingComments(expression!, returnStatement, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ true); } - else if (actionName === removeBracesActionName && returnStatement) { + else if (actionName === removeBracesAction.name && returnStatement) { const actualExpression = expression || factory.createVoidZero(); body = needsParentheses(actualExpression) ? factory.createParenthesizedExpression(actualExpression) : actualExpression; suppressLeadingAndTrailingTrivia(body); @@ -98,7 +88,7 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction { return { renameFilename: undefined, renameLocation: undefined, edits }; } - function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number, considerFunctionBodies = true): InfoOrError | undefined { + function getConvertibleArrowFunctionAtPosition(file: SourceFile, startPosition: number, considerFunctionBodies = true, kind?: string): FunctionBracesInfo | RefactorErrorInfo | undefined { const node = getTokenAtPosition(file, startPosition); const func = getContainingFunction(node); @@ -118,26 +108,13 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction { return undefined; } - if (isExpression(func.body)) { - return { - info: { - func, - addBraces: true, - expression: func.body - } - }; + if (refactorKindBeginsWith(addBracesAction.kind, kind) && isExpression(func.body)) { + return { func, addBraces: true, expression: func.body }; } - else if (func.body.statements.length === 1) { + else if (refactorKindBeginsWith(removeBracesAction.kind, kind) && isBlock(func.body) && func.body.statements.length === 1) { const firstStatement = first(func.body.statements); if (isReturnStatement(firstStatement)) { - return { - info: { - func, - addBraces: false, - expression: firstStatement.expression, - returnStatement: firstStatement - } - }; + return { func, addBraces: false, expression: firstStatement.expression, returnStatement: firstStatement }; } } return undefined; diff --git a/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts b/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts index c9ef923534f..dc5948c8987 100644 --- a/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts +++ b/src/services/refactors/convertArrowFunctionOrFunctionExpression.ts @@ -3,15 +3,30 @@ namespace ts.refactor.convertArrowFunctionOrFunctionExpression { const refactorName = "Convert arrow function or function expression"; const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_arrow_function_or_function_expression); - const toAnonymousFunctionActionName = "Convert to anonymous function"; - const toNamedFunctionActionName = "Convert to named function"; - const toArrowFunctionActionName = "Convert to arrow function"; - - const toAnonymousFunctionActionDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_anonymous_function); - const toNamedFunctionActionDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_named_function); - const toArrowFunctionActionDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_arrow_function); - - registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); + const toAnonymousFunctionAction = { + name: "Convert to anonymous function", + description: getLocaleSpecificMessage(Diagnostics.Convert_to_anonymous_function), + kind: "refactor.rewrite.function.anonymous", + }; + const toNamedFunctionAction = { + name: "Convert to named function", + description: getLocaleSpecificMessage(Diagnostics.Convert_to_named_function), + kind: "refactor.rewrite.function.named", + }; + const toArrowFunctionAction = { + name: "Convert to arrow function", + description: getLocaleSpecificMessage(Diagnostics.Convert_to_arrow_function), + kind: "refactor.rewrite.function.arrow", + }; + registerRefactor(refactorName, { + kinds: [ + toAnonymousFunctionAction.kind, + toNamedFunctionAction.kind, + toArrowFunctionAction.kind + ], + getEditsForAction, + getAvailableActions + }); interface FunctionInfo { readonly selectedVariableDeclaration: boolean; @@ -26,38 +41,50 @@ namespace ts.refactor.convertArrowFunctionOrFunctionExpression { } function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { - const { file, startPosition, program } = context; + const { file, startPosition, program, kind } = context; const info = getFunctionInfo(file, startPosition, program); if (!info) return emptyArray; const { selectedVariableDeclaration, func } = info; const possibleActions: RefactorActionInfo[] = []; - - if (selectedVariableDeclaration || (isArrowFunction(func) && isVariableDeclaration(func.parent))) { - possibleActions.push({ - name: toNamedFunctionActionName, - description: toNamedFunctionActionDescription - }); + const errors: RefactorActionInfo[] = []; + if (refactorKindBeginsWith(toNamedFunctionAction.kind, kind)) { + const error = selectedVariableDeclaration || (isArrowFunction(func) && isVariableDeclaration(func.parent)) ? + undefined : getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_named_function); + if (error) { + errors.push({ ...toNamedFunctionAction, notApplicableReason: error }); + } + else { + possibleActions.push(toNamedFunctionAction); + } } - if (!selectedVariableDeclaration && isArrowFunction(func)) { - possibleActions.push({ - name: toAnonymousFunctionActionName, - description: toAnonymousFunctionActionDescription - }); + if (refactorKindBeginsWith(toAnonymousFunctionAction.kind, kind)) { + const error = !selectedVariableDeclaration && isArrowFunction(func) ? + undefined: getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_anonymous_function); + if (error) { + errors.push({ ...toAnonymousFunctionAction, notApplicableReason: error }); + } + else { + possibleActions.push(toAnonymousFunctionAction); + } } - if (isFunctionExpression(func)) { - possibleActions.push({ - name: toArrowFunctionActionName, - description: toArrowFunctionActionDescription - }); + if (refactorKindBeginsWith(toArrowFunctionAction.kind, kind)) { + const error = isFunctionExpression(func) ? undefined : getLocaleSpecificMessage(Diagnostics.Could_not_convert_to_arrow_function); + if (error) { + errors.push({ ...toArrowFunctionAction, notApplicableReason: error }); + } + else { + possibleActions.push(toArrowFunctionAction); + } } return [{ name: refactorName, description: refactorDescription, - actions: possibleActions + actions: possibleActions.length === 0 && context.preferences.provideRefactorNotApplicableReason ? + errors : possibleActions }]; } @@ -70,18 +97,18 @@ namespace ts.refactor.convertArrowFunctionOrFunctionExpression { const edits: FileTextChanges[] = []; switch (actionName) { - case toAnonymousFunctionActionName: + case toAnonymousFunctionAction.name: edits.push(...getEditInfoForConvertToAnonymousFunction(context, func)); break; - case toNamedFunctionActionName: + case toNamedFunctionAction.name: const variableInfo = getVariableInfo(func); if (!variableInfo) return undefined; edits.push(...getEditInfoForConvertToNamedFunction(context, func, variableInfo)); break; - case toArrowFunctionActionName: + case toArrowFunctionAction.name: if (!isFunctionExpression(func)) return undefined; edits.push(...getEditInfoForConvertToArrowFunction(context, func)); break; diff --git a/src/services/refactors/convertExport.ts b/src/services/refactors/convertExport.ts index 6912979261a..43096a4d081 100644 --- a/src/services/refactors/convertExport.ts +++ b/src/services/refactors/convertExport.ts @@ -1,54 +1,62 @@ /* @internal */ namespace ts.refactor { const refactorName = "Convert export"; - const actionNameDefaultToNamed = "Convert default export to named export"; - const actionNameNamedToDefault = "Convert named export to default export"; + + const defaultToNamedAction = { + name: "Convert default export to named export", + description: Diagnostics.Convert_default_export_to_named_export.message, + kind: "refactor.rewrite.export.named" + }; + const namedToDefaultAction = { + name: "Convert named export to default export", + description: Diagnostics.Convert_named_export_to_default_export.message, + kind: "refactor.rewrite.export.default" + }; registerRefactor(refactorName, { + kinds: [ + defaultToNamedAction.kind, + namedToDefaultAction.kind + ], getAvailableActions(context): readonly ApplicableRefactorInfo[] { const info = getInfo(context, context.triggerReason === "invoked"); if (!info) return emptyArray; - if (info.error === undefined) { - const description = info.info.wasDefault ? Diagnostics.Convert_default_export_to_named_export.message : Diagnostics.Convert_named_export_to_default_export.message; - const actionName = info.info.wasDefault ? actionNameDefaultToNamed : actionNameNamedToDefault; - return [{ name: refactorName, description, actions: [{ name: actionName, description }] }]; + if (!isRefactorErrorInfo(info)) { + const action = info.wasDefault ? defaultToNamedAction : namedToDefaultAction; + return [{ name: refactorName, description: action.description, actions: [action] }]; } if (context.preferences.provideRefactorNotApplicableReason) { return [ - { name: refactorName, description: Diagnostics.Convert_default_export_to_named_export.message, actions: [{ name: actionNameDefaultToNamed, description: Diagnostics.Convert_default_export_to_named_export.message, notApplicableReason: info.error }] }, - { name: refactorName, description: Diagnostics.Convert_named_export_to_default_export.message, actions: [{ name: actionNameNamedToDefault, description: Diagnostics.Convert_named_export_to_default_export.message, notApplicableReason: info.error }] }, + { name: refactorName, description: Diagnostics.Convert_default_export_to_named_export.message, actions: [ + { ...defaultToNamedAction, notApplicableReason: info.error }, + { ...namedToDefaultAction, notApplicableReason: info.error }, + ]} ]; } return emptyArray; }, getEditsForAction(context, actionName): RefactorEditInfo { - Debug.assert(actionName === actionNameDefaultToNamed || actionName === actionNameNamedToDefault, "Unexpected action name"); - const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, Debug.checkDefined(getInfo(context)?.info, "context must have info"), t, context.cancellationToken)); + Debug.assert(actionName === defaultToNamedAction.name || actionName === namedToDefaultAction.name, "Unexpected action name"); + const info = getInfo(context); + Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); + const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, info, t, context.cancellationToken)); return { edits, renameFilename: undefined, renameLocation: undefined }; }, }); // If a VariableStatement, will have exactly one VariableDeclaration, with an Identifier for a name. type ExportToConvert = FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | NamespaceDeclaration | TypeAliasDeclaration | VariableStatement; - interface Info { + interface ExportInfo { readonly exportNode: ExportToConvert; readonly exportName: Identifier; // This is exportNode.name except for VariableStatement_s. readonly wasDefault: boolean; readonly exportingModuleSymbol: Symbol; - } - - type InfoOrError = { - info: Info, - error?: never - } | { - info?: never, - error: string }; - function getInfo(context: RefactorContext, considerPartialSpans = true): InfoOrError | undefined { + function getInfo(context: RefactorContext, considerPartialSpans = true): ExportInfo | RefactorErrorInfo | undefined { const { file } = context; const span = getRefactorContextSpan(context); const token = getTokenAtPosition(file, span.start); @@ -74,7 +82,7 @@ namespace ts.refactor { case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.ModuleDeclaration: { const node = exportNode as FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | TypeAliasDeclaration | NamespaceDeclaration; - return node.name && isIdentifier(node.name) ? { info: { exportNode: node, exportName: node.name, wasDefault, exportingModuleSymbol } } : undefined; + return node.name && isIdentifier(node.name) ? { exportNode: node, exportName: node.name, wasDefault, exportingModuleSymbol } : undefined; } case SyntaxKind.VariableStatement: { const vs = exportNode as VariableStatement; @@ -85,19 +93,19 @@ namespace ts.refactor { const decl = first(vs.declarationList.declarations); if (!decl.initializer) return undefined; Debug.assert(!wasDefault, "Can't have a default flag here"); - return isIdentifier(decl.name) ? { info: { exportNode: vs, exportName: decl.name, wasDefault, exportingModuleSymbol } } : undefined; + return isIdentifier(decl.name) ? { exportNode: vs, exportName: decl.name, wasDefault, exportingModuleSymbol } : undefined; } default: return undefined; } } - function doChange(exportingSourceFile: SourceFile, program: Program, info: Info, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { + function doChange(exportingSourceFile: SourceFile, program: Program, info: ExportInfo, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { changeExport(exportingSourceFile, info, changes, program.getTypeChecker()); changeImports(program, info, changes, cancellationToken); } - function changeExport(exportingSourceFile: SourceFile, { wasDefault, exportNode, exportName }: Info, changes: textChanges.ChangeTracker, checker: TypeChecker): void { + function changeExport(exportingSourceFile: SourceFile, { wasDefault, exportNode, exportName }: ExportInfo, changes: textChanges.ChangeTracker, checker: TypeChecker): void { if (wasDefault) { changes.delete(exportingSourceFile, Debug.checkDefined(findModifier(exportNode, SyntaxKind.DefaultKeyword), "Should find a default keyword in modifier list")); } @@ -131,7 +139,7 @@ namespace ts.refactor { } } - function changeImports(program: Program, { wasDefault, exportName, exportingModuleSymbol }: Info, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { + function changeImports(program: Program, { wasDefault, exportName, exportingModuleSymbol }: ExportInfo, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void { const checker = program.getTypeChecker(); const exportSymbol = Debug.checkDefined(checker.getSymbolAtLocation(exportName), "Export name should resolve to a symbol"); FindAllReferences.Core.eachExportReference(program.getSourceFiles(), checker, cancellationToken, exportSymbol, exportingModuleSymbol, exportName.text, wasDefault, ref => { diff --git a/src/services/refactors/convertImport.ts b/src/services/refactors/convertImport.ts index 5dec7fcf736..e84cb4ef668 100644 --- a/src/services/refactors/convertImport.ts +++ b/src/services/refactors/convertImport.ts @@ -1,46 +1,55 @@ /* @internal */ namespace ts.refactor { const refactorName = "Convert import"; - const actionNameNamespaceToNamed = "Convert namespace import to named imports"; - const actionNameNamedToNamespace = "Convert named imports to namespace import"; - type NamedImportBindingsOrError = { - info: NamedImportBindings, - error?: never - } | { - info?: never, - error: string + const namespaceToNamedAction = { + name: "Convert namespace import to named imports", + description: Diagnostics.Convert_namespace_import_to_named_imports.message, + kind: "refactor.rewrite.import.named", + }; + const namedToNamespaceAction = { + name: "Convert named imports to namespace import", + description: Diagnostics.Convert_named_imports_to_namespace_import.message, + kind: "refactor.rewrite.import.namespace", }; registerRefactor(refactorName, { + kinds: [ + namespaceToNamedAction.kind, + namedToNamespaceAction.kind + ], getAvailableActions(context): readonly ApplicableRefactorInfo[] { - const i = getImportToConvert(context, context.triggerReason === "invoked"); - if (!i) return emptyArray; + const info = getImportToConvert(context, context.triggerReason === "invoked"); + if (!info) return emptyArray; - if (i.error === undefined) { - const description = i.info.kind === SyntaxKind.NamespaceImport ? Diagnostics.Convert_namespace_import_to_named_imports.message : Diagnostics.Convert_named_imports_to_namespace_import.message; - const actionName = i.info.kind === SyntaxKind.NamespaceImport ? actionNameNamespaceToNamed : actionNameNamedToNamespace; - return [{ name: refactorName, description, actions: [{ name: actionName, description }] }]; + if (!isRefactorErrorInfo(info)) { + const namespaceImport = info.kind === SyntaxKind.NamespaceImport; + const action = namespaceImport ? namespaceToNamedAction : namedToNamespaceAction; + return [{ name: refactorName, description: action.description, actions: [action] }]; } if (context.preferences.provideRefactorNotApplicableReason) { return [ - { name: refactorName, description: Diagnostics.Convert_namespace_import_to_named_imports.message, actions: [{ name: actionNameNamespaceToNamed, description: Diagnostics.Convert_namespace_import_to_named_imports.message, notApplicableReason: i.error }] }, - { name: refactorName, description: Diagnostics.Convert_named_imports_to_namespace_import.message, actions: [{ name: actionNameNamedToNamespace, description: Diagnostics.Convert_named_imports_to_namespace_import.message, notApplicableReason: i.error }] } + { name: refactorName, description: namespaceToNamedAction.description, + actions: [{ ...namespaceToNamedAction, notApplicableReason: info.error }] }, + { name: refactorName, description: namedToNamespaceAction.description, + actions: [{ ...namedToNamespaceAction, notApplicableReason: info.error }] } ]; } return emptyArray; }, getEditsForAction(context, actionName): RefactorEditInfo { - Debug.assert(actionName === actionNameNamespaceToNamed || actionName === actionNameNamedToNamespace, "Unexpected action name"); - const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, t, Debug.checkDefined(getImportToConvert(context)?.info, "Context must provide an import to convert"))); + Debug.assert(actionName === namespaceToNamedAction.name || actionName === namedToNamespaceAction.name, "Unexpected action name"); + const info = getImportToConvert(context); + Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); + const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, t, info)); return { edits, renameFilename: undefined, renameLocation: undefined }; } }); // Can convert imports of the form `import * as m from "m";` or `import d, { x, y } from "m";`. - function getImportToConvert(context: RefactorContext, considerPartialSpans = true): NamedImportBindingsOrError | undefined { + function getImportToConvert(context: RefactorContext, considerPartialSpans = true): NamedImportBindings | RefactorErrorInfo | undefined { const { file } = context; const span = getRefactorContextSpan(context); const token = getTokenAtPosition(file, span.start); @@ -57,7 +66,7 @@ namespace ts.refactor { return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_namespace_import_or_named_imports) }; } - return { info: importClause.namedBindings }; + return importClause.namedBindings; } function doChange(sourceFile: SourceFile, program: Program, changes: textChanges.ChangeTracker, toConvert: NamedImportBindings): void { diff --git a/src/services/refactors/convertOverloadListToSingleSignature.ts b/src/services/refactors/convertOverloadListToSingleSignature.ts index fbcb6fece0c..cd0e4d9cd8e 100644 --- a/src/services/refactors/convertOverloadListToSingleSignature.ts +++ b/src/services/refactors/convertOverloadListToSingleSignature.ts @@ -2,8 +2,17 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction { const refactorName = "Convert overload list to single signature"; const refactorDescription = Diagnostics.Convert_overload_list_to_single_signature.message; - registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); + const functionOverloadAction = { + name: refactorName, + description: refactorDescription, + kind: "refactor.rewrite.function.overloadList", + }; + registerRefactor(refactorName, { + kinds: [functionOverloadAction.kind], + getEditsForAction, + getAvailableActions + }); function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { const { file, startPosition, program } = context; @@ -13,10 +22,7 @@ namespace ts.refactor.addOrRemoveBracesToArrowFunction { return [{ name: refactorName, description: refactorDescription, - actions: [{ - name: refactorName, - description: refactorDescription - }] + actions: [functionOverloadAction] }]; } diff --git a/src/services/refactors/convertParamsToDestructuredObject.ts b/src/services/refactors/convertParamsToDestructuredObject.ts index ec2277b5da8..feef5606b11 100644 --- a/src/services/refactors/convertParamsToDestructuredObject.ts +++ b/src/services/refactors/convertParamsToDestructuredObject.ts @@ -2,8 +2,18 @@ namespace ts.refactor.convertParamsToDestructuredObject { const refactorName = "Convert parameters to destructured object"; const minimumParameterLength = 2; - registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); + const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_parameters_to_destructured_object); + const toDestructuredAction = { + name: refactorName, + description: refactorDescription, + kind: "refactor.rewrite.parameters.toDestructured" + }; + registerRefactor(refactorName, { + kinds: [toDestructuredAction.kind], + getEditsForAction, + getAvailableActions + }); function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { const { file, startPosition } = context; @@ -12,14 +22,10 @@ namespace ts.refactor.convertParamsToDestructuredObject { const functionDeclaration = getFunctionDeclarationAtPosition(file, startPosition, context.program.getTypeChecker()); if (!functionDeclaration) return emptyArray; - const description = getLocaleSpecificMessage(Diagnostics.Convert_parameters_to_destructured_object); return [{ name: refactorName, - description, - actions: [{ - name: refactorName, - description - }] + description: refactorDescription, + actions: [toDestructuredAction] }]; } diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index 7641987153d..4238f96e02c 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -3,7 +3,16 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const refactorName = "Convert to template string"; const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_template_string); - registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); + const convertStringAction = { + name: refactorName, + description: refactorDescription, + kind: "refactor.rewrite.string" + }; + registerRefactor(refactorName, { + kinds: [convertStringAction.kind], + getEditsForAction, + getAvailableActions + }); function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { const { file, startPosition } = context; @@ -12,7 +21,13 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const refactorInfo: ApplicableRefactorInfo = { name: refactorName, description: refactorDescription, actions: [] }; if (isBinaryExpression(maybeBinary) && isStringConcatenationValid(maybeBinary)) { - refactorInfo.actions.push({ name: refactorName, description: refactorDescription }); + refactorInfo.actions.push(convertStringAction); + return [refactorInfo]; + } + else if (context.preferences.provideRefactorNotApplicableReason) { + refactorInfo.actions.push({ ...convertStringAction, + notApplicableReason: getLocaleSpecificMessage(Diagnostics.Can_only_convert_string_concatenation) + }); return [refactorInfo]; } return emptyArray; diff --git a/src/services/refactors/convertToOptionalChainExpression.ts b/src/services/refactors/convertToOptionalChainExpression.ts index 52365fc0ba5..8bfb222c8bf 100644 --- a/src/services/refactors/convertToOptionalChainExpression.ts +++ b/src/services/refactors/convertToOptionalChainExpression.ts @@ -3,20 +3,26 @@ namespace ts.refactor.convertToOptionalChainExpression { const refactorName = "Convert to optional chain expression"; const convertToOptionalChainExpressionMessage = getLocaleSpecificMessage(Diagnostics.Convert_to_optional_chain_expression); - registerRefactor(refactorName, { getAvailableActions, getEditsForAction }); + const toOptionalChainAction = { + name: refactorName, + description: convertToOptionalChainExpressionMessage, + kind: "refactor.rewrite.expression.optionalChain", + }; + registerRefactor(refactorName, { + kinds: [toOptionalChainAction.kind], + getAvailableActions, + getEditsForAction + }); function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { const info = getInfo(context, context.triggerReason === "invoked"); if (!info) return emptyArray; - if (!info.error) { + if (!isRefactorErrorInfo(info)) { return [{ name: refactorName, description: convertToOptionalChainExpressionMessage, - actions: [{ - name: refactorName, - description: convertToOptionalChainExpressionMessage - }] + actions: [toOptionalChainAction], }]; } @@ -24,11 +30,7 @@ namespace ts.refactor.convertToOptionalChainExpression { return [{ name: refactorName, description: convertToOptionalChainExpressionMessage, - actions: [{ - name: refactorName, - description: convertToOptionalChainExpressionMessage, - notApplicableReason: info.error - }] + actions: [{ ...toOptionalChainAction, notApplicableReason: info.error }], }]; } return emptyArray; @@ -36,24 +38,16 @@ namespace ts.refactor.convertToOptionalChainExpression { function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { const info = getInfo(context); - if (!info || !info.info) return undefined; + Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); const edits = textChanges.ChangeTracker.with(context, t => - doChange(context.file, context.program.getTypeChecker(), t, Debug.checkDefined(info.info, "context must have info"), actionName) + doChange(context.file, context.program.getTypeChecker(), t, info, actionName) ); return { edits, renameFilename: undefined, renameLocation: undefined }; } - type InfoOrError = { - info: Info, - error?: never; - } | { - info?: never, - error: string; - }; - type Occurrence = PropertyAccessExpression | ElementAccessExpression | Identifier; - interface Info { + interface OptionalChainInfo { finalExpression: PropertyAccessExpression | ElementAccessExpression | CallExpression, occurrences: Occurrence[], expression: ValidExpression, @@ -83,7 +77,7 @@ namespace ts.refactor.convertToOptionalChainExpression { return isValidExpression(node) || isValidStatement(node); } - function getInfo(context: RefactorContext, considerEmptySpans = true): InfoOrError | undefined { + function getInfo(context: RefactorContext, considerEmptySpans = true): OptionalChainInfo | RefactorErrorInfo | undefined { const { file, program } = context; const span = getRefactorContextSpan(context); @@ -103,7 +97,7 @@ namespace ts.refactor.convertToOptionalChainExpression { return isConditionalExpression(expression) ? getConditionalInfo(expression, checker) : getBinaryInfo(expression); } - function getConditionalInfo(expression: ConditionalExpression, checker: TypeChecker): InfoOrError | undefined { + function getConditionalInfo(expression: ConditionalExpression, checker: TypeChecker): OptionalChainInfo | RefactorErrorInfo | undefined { const condition = expression.condition; const finalExpression = getFinalExpressionInChain(expression.whenTrue); @@ -113,16 +107,16 @@ namespace ts.refactor.convertToOptionalChainExpression { if ((isPropertyAccessExpression(condition) || isIdentifier(condition)) && getMatchingStart(condition, finalExpression.expression)) { - return { info: { finalExpression, occurrences: [condition], expression } }; + return { finalExpression, occurrences: [condition], expression }; } else if (isBinaryExpression(condition)) { const occurrences = getOccurrencesInExpression(finalExpression.expression, condition); - return occurrences ? { info: { finalExpression, occurrences, expression } } : + return occurrences ? { finalExpression, occurrences, expression } : { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_matching_access_expressions) }; } } - function getBinaryInfo(expression: BinaryExpression): InfoOrError | undefined { + function getBinaryInfo(expression: BinaryExpression): OptionalChainInfo | RefactorErrorInfo | undefined { if (expression.operatorToken.kind !== SyntaxKind.AmpersandAmpersandToken) { return { error: getLocaleSpecificMessage(Diagnostics.Can_only_convert_logical_AND_access_chains) }; }; @@ -131,7 +125,7 @@ namespace ts.refactor.convertToOptionalChainExpression { if (!finalExpression) return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_convertible_access_expression) }; const occurrences = getOccurrencesInExpression(finalExpression.expression, expression.left); - return occurrences ? { info: { finalExpression, occurrences, expression } } : + return occurrences ? { finalExpression, occurrences, expression } : { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_matching_access_expressions) }; } @@ -288,7 +282,7 @@ namespace ts.refactor.convertToOptionalChainExpression { return toConvert; } - function doChange(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, info: Info, _actionName: string): void { + function doChange(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, info: OptionalChainInfo, _actionName: string): void { const { finalExpression, occurrences, expression } = info; const firstOccurrence = occurrences[occurrences.length - 1]; const convertedChain = convertOccurrences(checker, finalExpression, occurrences); diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 73c8ca01e4c..6478ad24d5b 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -1,39 +1,56 @@ /* @internal */ namespace ts.refactor.extractSymbol { const refactorName = "Extract Symbol"; - registerRefactor(refactorName, { getAvailableActions, getEditsForAction }); + + const extractConstantAction = { + name: "Extract Constant", + description: getLocaleSpecificMessage(Diagnostics.Extract_constant), + kind: "refactor.extract.constant", + }; + const extractFunctionAction = { + name: "Extract Function", + description: getLocaleSpecificMessage(Diagnostics.Extract_function), + kind: "refactor.extract.function", + }; + registerRefactor(refactorName, { + kinds: [ + extractConstantAction.kind, + extractFunctionAction.kind + ], + getAvailableActions, + getEditsForAction + }); /** * Compute the associated code actions * Exported for tests. */ export function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { + const requestedRefactor = context.kind; const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context), context.triggerReason === "invoked"); - const targetRange = rangeToExtract.targetRange; + if (targetRange === undefined) { if (!rangeToExtract.errors || rangeToExtract.errors.length === 0 || !context.preferences.provideRefactorNotApplicableReason) { return emptyArray; } - return [{ - name: refactorName, - description: getLocaleSpecificMessage(Diagnostics.Extract_function), - actions: [{ - description: getLocaleSpecificMessage(Diagnostics.Extract_function), - name: "function_extract_error", - notApplicableReason: getStringError(rangeToExtract.errors) - }] - }, - { - name: refactorName, - description: getLocaleSpecificMessage(Diagnostics.Extract_constant), - actions: [{ - description: getLocaleSpecificMessage(Diagnostics.Extract_constant), - name: "constant_extract_error", - notApplicableReason: getStringError(rangeToExtract.errors) - }] - }]; + const errors = []; + if (refactorKindBeginsWith(extractFunctionAction.kind, requestedRefactor)) { + errors.push({ + name: refactorName, + description: extractFunctionAction.description, + actions: [{ ...extractFunctionAction, notApplicableReason: getStringError(rangeToExtract.errors) }] + }); + } + if (refactorKindBeginsWith(extractConstantAction.kind, requestedRefactor)) { + errors.push({ + name: refactorName, + description: extractConstantAction.description, + actions: [{ ...extractConstantAction, notApplicableReason: getStringError(rangeToExtract.errors) }] + }); + } + return errors; } const extractions = getPossibleExtractions(targetRange, context); @@ -53,46 +70,54 @@ namespace ts.refactor.extractSymbol { let i = 0; for (const { functionExtraction, constantExtraction } of extractions) { const description = functionExtraction.description; - if (functionExtraction.errors.length === 0) { - // Don't issue refactorings with duplicated names. - // Scopes come back in "innermost first" order, so extractions will - // preferentially go into nearer scopes - if (!usedFunctionNames.has(description)) { - usedFunctionNames.set(description, true); - functionActions.push({ + if(refactorKindBeginsWith(extractFunctionAction.kind, requestedRefactor)){ + if (functionExtraction.errors.length === 0) { + // Don't issue refactorings with duplicated names. + // Scopes come back in "innermost first" order, so extractions will + // preferentially go into nearer scopes + if (!usedFunctionNames.has(description)) { + usedFunctionNames.set(description, true); + functionActions.push({ + description, + name: `function_scope_${i}`, + kind: extractFunctionAction.kind + }); + } + } + else if (!innermostErrorFunctionAction) { + innermostErrorFunctionAction = { description, - name: `function_scope_${i}` - }); + name: `function_scope_${i}`, + notApplicableReason: getStringError(functionExtraction.errors), + kind: extractFunctionAction.kind + }; } - } - else if (!innermostErrorFunctionAction) { - innermostErrorFunctionAction = { - description, - name: `function_scope_${i}`, - notApplicableReason: getStringError(functionExtraction.errors) - }; } // Skip these since we don't have a way to report errors yet - if (constantExtraction.errors.length === 0) { - // Don't issue refactorings with duplicated names. - // Scopes come back in "innermost first" order, so extractions will - // preferentially go into nearer scopes - const description = constantExtraction.description; - if (!usedConstantNames.has(description)) { - usedConstantNames.set(description, true); - constantActions.push({ + if(refactorKindBeginsWith(extractConstantAction.kind, requestedRefactor)) { + if (constantExtraction.errors.length === 0) { + // Don't issue refactorings with duplicated names. + // Scopes come back in "innermost first" order, so extractions will + // preferentially go into nearer scopes + const description = constantExtraction.description; + if (!usedConstantNames.has(description)) { + usedConstantNames.set(description, true); + constantActions.push({ + description, + name: `constant_scope_${i}`, + kind: extractConstantAction.kind + }); + } + } + else if (!innermostErrorConstantAction) { + innermostErrorConstantAction = { description, - name: `constant_scope_${i}` - }); + name: `constant_scope_${i}`, + notApplicableReason: getStringError(constantExtraction.errors), + kind: extractConstantAction.kind + }; } - } - else if (!innermostErrorConstantAction) { - innermostErrorConstantAction = { - description, - name: `constant_scope_${i}`, - notApplicableReason: getStringError(constantExtraction.errors) - }; } // *do* increment i anyway because we'll look for the i-th scope @@ -106,7 +131,7 @@ namespace ts.refactor.extractSymbol { infos.push({ name: refactorName, description: getLocaleSpecificMessage(Diagnostics.Extract_function), - actions: functionActions + actions: functionActions, }); } else if (context.preferences.provideRefactorNotApplicableReason && innermostErrorFunctionAction) { diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 7f528ae245d..ab85bb198cc 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -1,25 +1,39 @@ /* @internal */ namespace ts.refactor { const refactorName = "Extract type"; - const extractToTypeAlias = "Extract to type alias"; - const extractToInterface = "Extract to interface"; - const extractToTypeDef = "Extract to typedef"; + + const extractToTypeAliasAction = { + name: "Extract to type alias", + description: getLocaleSpecificMessage(Diagnostics.Extract_to_type_alias), + kind: "refactor.extract.type", + }; + const extractToInterfaceAction = { + name: "Extract to interface", + description: getLocaleSpecificMessage(Diagnostics.Extract_to_interface), + kind: "refactor.extract.interface", + }; + const extractToTypeDefAction = { + name: "Extract to typedef", + description: getLocaleSpecificMessage(Diagnostics.Extract_to_typedef), + kind: "refactor.extract.typedef" + }; + registerRefactor(refactorName, { + kinds: [ + extractToTypeAliasAction.kind, + extractToInterfaceAction.kind, + extractToTypeDefAction.kind + ], getAvailableActions(context): readonly ApplicableRefactorInfo[] { const info = getRangeToExtract(context, context.triggerReason === "invoked"); if (!info) return emptyArray; - if (info.error === undefined) { + if (!isRefactorErrorInfo(info)) { return [{ name: refactorName, description: getLocaleSpecificMessage(Diagnostics.Extract_type), - actions: info.info.isJS ? [{ - name: extractToTypeDef, description: getLocaleSpecificMessage(Diagnostics.Extract_to_typedef) - }] : append([{ - name: extractToTypeAlias, description: getLocaleSpecificMessage(Diagnostics.Extract_to_type_alias) - }], info.info.typeElements && { - name: extractToInterface, description: getLocaleSpecificMessage(Diagnostics.Extract_to_interface) - }) + actions: info.isJS ? + [extractToTypeDefAction] : append([extractToTypeAliasAction], info.typeElements && extractToInterfaceAction) }]; } @@ -28,9 +42,9 @@ namespace ts.refactor { name: refactorName, description: getLocaleSpecificMessage(Diagnostics.Extract_type), actions: [ - { name: extractToTypeDef, description: getLocaleSpecificMessage(Diagnostics.Extract_to_typedef), notApplicableReason: info.error }, - { name: extractToTypeAlias, description: getLocaleSpecificMessage(Diagnostics.Extract_to_type_alias), notApplicableReason: info.error }, - { name: extractToInterface, description: getLocaleSpecificMessage(Diagnostics.Extract_to_interface), notApplicableReason: info.error }, + { ...extractToTypeDefAction, notApplicableReason: info.error }, + { ...extractToTypeAliasAction, notApplicableReason: info.error }, + { ...extractToInterfaceAction, notApplicableReason: info.error }, ] }]; } @@ -39,18 +53,19 @@ namespace ts.refactor { }, getEditsForAction(context, actionName): RefactorEditInfo { const { file, } = context; - const info = Debug.checkDefined(getRangeToExtract(context)?.info, "Expected to find a range to extract"); + const info = getRangeToExtract(context); + Debug.assert(info && !isRefactorErrorInfo(info), "Expected to find a range to extract"); const name = getUniqueName("NewType", file); const edits = textChanges.ChangeTracker.with(context, changes => { switch (actionName) { - case extractToTypeAlias: + case extractToTypeAliasAction.name: Debug.assert(!info.isJS, "Invalid actionName/JS combo"); return doTypeAliasChange(changes, file, name, info); - case extractToTypeDef: + case extractToTypeDefAction.name: Debug.assert(info.isJS, "Invalid actionName/JS combo"); return doTypedefChange(changes, file, name, info); - case extractToInterface: + case extractToInterfaceAction.name: Debug.assert(!info.isJS && !!info.typeElements, "Invalid actionName/JS combo"); return doInterfaceChange(changes, file, name, info as InterfaceInfo); default: @@ -72,16 +87,9 @@ namespace ts.refactor { isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: readonly TypeParameterDeclaration[]; typeElements: readonly TypeElement[]; } - type Info = TypeAliasInfo | InterfaceInfo; - type InfoOrError = { - info: Info, - error?: never - } | { - info?: never, - error: string - }; + type ExtractInfo = TypeAliasInfo | InterfaceInfo; - function getRangeToExtract(context: RefactorContext, considerEmptySpans = true): InfoOrError | undefined { + function getRangeToExtract(context: RefactorContext, considerEmptySpans = true): ExtractInfo | RefactorErrorInfo | undefined { const { file, startPosition } = context; const isJS = isSourceFileJS(file); const current = getTokenAtPosition(file, startPosition); @@ -98,7 +106,7 @@ namespace ts.refactor { if (!typeParameters) return { error: getLocaleSpecificMessage(Diagnostics.No_type_could_be_extracted_from_this_type_node) }; const typeElements = flattenTypeLiteralNodeReference(checker, selection); - return { info: { isJS, selection, firstStatement, typeParameters, typeElements } }; + return { isJS, selection, firstStatement, typeParameters, typeElements }; } function flattenTypeLiteralNodeReference(checker: TypeChecker, node: TypeNode | undefined): readonly TypeElement[] | undefined { @@ -209,7 +217,7 @@ namespace ts.refactor { changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /* typeArguments */ undefined))), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.ExcludeWhitespace }); } - function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: Info) { + function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: ExtractInfo) { const { firstStatement, selection, typeParameters } = info; const node = factory.createJSDocTypedefTag( diff --git a/src/services/refactors/generateGetAccessorAndSetAccessor.ts b/src/services/refactors/generateGetAccessorAndSetAccessor.ts index 160f4f1b5ae..76de5a7f4ea 100644 --- a/src/services/refactors/generateGetAccessorAndSetAccessor.ts +++ b/src/services/refactors/generateGetAccessorAndSetAccessor.ts @@ -2,18 +2,25 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor { const actionName = "Generate 'get' and 'set' accessors"; const actionDescription = Diagnostics.Generate_get_and_set_accessors.message; + + const generateGetSetAction = { + name: actionName, + description: actionDescription, + kind: "refactor.rewrite.property.generateAccessors", + }; registerRefactor(actionName, { + kinds: [generateGetSetAction.kind], getEditsForAction(context, actionName) { if (!context.endPosition) return undefined; const info = codefix.getAccessorConvertiblePropertyAtPosition(context.file, context.program, context.startPosition, context.endPosition); - if (!info || !info.info) return undefined; + Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info"); const edits = codefix.generateAccessorFromProperty(context.file, context.program, context.startPosition, context.endPosition, context, actionName); if (!edits) return undefined; const renameFilename = context.file.fileName; - const nameNeedRename = info.info.renameAccessor ? info.info.accessorName : info.info.fieldName; + const nameNeedRename = info.renameAccessor ? info.accessorName : info.fieldName; const renameLocationOffset = isIdentifier(nameNeedRename) ? 0 : -1; - const renameLocation = renameLocationOffset + getRenameLocation(edits, renameFilename, nameNeedRename.text, /*preferLastLocation*/ isParameter(info.info.declaration)); + const renameLocation = renameLocationOffset + getRenameLocation(edits, renameFilename, nameNeedRename.text, /*preferLastLocation*/ isParameter(info.declaration)); return { renameFilename, renameLocation, edits }; }, @@ -22,16 +29,11 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor { const info = codefix.getAccessorConvertiblePropertyAtPosition(context.file, context.program, context.startPosition, context.endPosition, context.triggerReason === "invoked"); if (!info) return emptyArray; - if (!info.error) { + if (!isRefactorErrorInfo(info)) { return [{ name: actionName, description: actionDescription, - actions: [ - { - name: actionName, - description: actionDescription - } - ] + actions: [generateGetSetAction], }]; } @@ -39,11 +41,7 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor { return [{ name: actionName, description: actionDescription, - actions: [{ - name: actionName, - description: actionDescription, - notApplicableReason: info.error - }] + actions: [{ ...generateGetSetAction, notApplicableReason: info.error }], }]; } diff --git a/src/services/refactors/helpers.ts b/src/services/refactors/helpers.ts new file mode 100644 index 00000000000..59d9c085f2f --- /dev/null +++ b/src/services/refactors/helpers.ts @@ -0,0 +1,25 @@ +/* @internal */ +namespace ts.refactor { + /** + * Returned by refactor funtions when some error message needs to be surfaced to users. + */ + export interface RefactorErrorInfo { + error: string; + }; + + /** + * Checks if some refactor info has refactor error info. + */ + export function isRefactorErrorInfo(info: unknown): info is RefactorErrorInfo { + return (info as RefactorErrorInfo).error !== undefined; + } + + /** + * Checks if string "known" begins with string "requested". + * Used to match requested kinds with a known kind. + */ + export function refactorKindBeginsWith(known: string, requested: string | undefined): boolean { + if(!requested) return true; + return known.substr(0, requested.length) === requested; + } +} \ No newline at end of file diff --git a/src/services/refactors/inferFunctionReturnType.ts b/src/services/refactors/inferFunctionReturnType.ts index 3f46c0edb4c..4fad099cad0 100644 --- a/src/services/refactors/inferFunctionReturnType.ts +++ b/src/services/refactors/inferFunctionReturnType.ts @@ -2,11 +2,21 @@ namespace ts.refactor.inferFunctionReturnType { const refactorName = "Infer function return type"; const refactorDescription = Diagnostics.Infer_function_return_type.message; - registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); + + const inferReturnTypeAction = { + name: refactorName, + description: refactorDescription, + kind: "refactor.rewrite.function.returnType" + }; + registerRefactor(refactorName, { + kinds: [inferReturnTypeAction.kind], + getEditsForAction, + getAvailableActions + }); function getEditsForAction(context: RefactorContext): RefactorEditInfo | undefined { const info = getInfo(context); - if (info) { + if (info && !isRefactorErrorInfo(info)) { const edits = textChanges.ChangeTracker.with(context, t => t.tryInsertTypeAnnotation(context.file, info.declaration, info.returnTypeNode)); return { renameFilename: undefined, renameLocation: undefined, edits }; @@ -16,14 +26,19 @@ namespace ts.refactor.inferFunctionReturnType { function getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[] { const info = getInfo(context); - if (info) { + if (!info) return emptyArray; + if (!isRefactorErrorInfo(info)) { return [{ name: refactorName, description: refactorDescription, - actions: [{ - name: refactorName, - description: refactorDescription - }] + actions: [inferReturnTypeAction] + }]; + } + if (context.preferences.provideRefactorNotApplicableReason) { + return [{ + name: refactorName, + description: refactorDescription, + actions: [{ ...inferReturnTypeAction, notApplicableReason: info.error }] }]; } return emptyArray; @@ -35,21 +50,25 @@ namespace ts.refactor.inferFunctionReturnType { | ArrowFunction | MethodDeclaration; - interface Info { + interface FunctionInfo { declaration: ConvertibleDeclaration; returnTypeNode: TypeNode; } - function getInfo(context: RefactorContext): Info | undefined { - if (isInJSFile(context.file)) return; + function getInfo(context: RefactorContext): FunctionInfo | RefactorErrorInfo | undefined { + if (isInJSFile(context.file) || !refactorKindBeginsWith(inferReturnTypeAction.kind, context.kind)) return; const token = getTokenAtPosition(context.file, context.startPosition); const declaration = findAncestor(token, isConvertibleDeclaration); - if (!declaration || !declaration.body || declaration.type) return; + if (!declaration || !declaration.body || declaration.type) { + return { error: getLocaleSpecificMessage(Diagnostics.Return_type_must_be_inferred_from_a_function) }; + } const typeChecker = context.program.getTypeChecker(); const returnType = tryGetReturnType(typeChecker, declaration); - if (!returnType) return; + if (!returnType) { + return { error: getLocaleSpecificMessage(Diagnostics.Could_not_determine_function_return_type) }; + }; const returnTypeNode = typeChecker.typeToTypeNode(returnType, declaration, NodeBuilderFlags.NoTruncation); if (returnTypeNode) { diff --git a/src/services/refactors/moveToNewFile.ts b/src/services/refactors/moveToNewFile.ts index bf5609620e6..be93b513244 100644 --- a/src/services/refactors/moveToNewFile.ts +++ b/src/services/refactors/moveToNewFile.ts @@ -1,11 +1,26 @@ /* @internal */ namespace ts.refactor { const refactorName = "Move to a new file"; + const description = getLocaleSpecificMessage(Diagnostics.Move_to_a_new_file); + + const moveToNewFileAction = { + name: refactorName, + description, + kind: "refactor.move.newFile", + }; registerRefactor(refactorName, { + kinds: [moveToNewFileAction.kind], getAvailableActions(context): readonly ApplicableRefactorInfo[] { - if (!context.preferences.allowTextChangesInNewFiles || getStatementsToMove(context) === undefined) return emptyArray; - const description = getLocaleSpecificMessage(Diagnostics.Move_to_a_new_file); - return [{ name: refactorName, description, actions: [{ name: refactorName, description }] }]; + const statements = getStatementsToMove(context); + if (context.preferences.allowTextChangesInNewFiles && statements) { + return [{ name: refactorName, description, actions: [moveToNewFileAction] }]; + } + if (context.preferences.provideRefactorNotApplicableReason) { + return [{ name: refactorName, description, actions: + [{ ...moveToNewFileAction, notApplicableReason: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_statement_or_statements) }] + }]; + } + return emptyArray; }, getEditsForAction(context, actionName): RefactorEditInfo { Debug.assert(actionName === refactorName, "Wrong refactor invoked"); diff --git a/src/services/services.ts b/src/services/services.ts index fa0e3770d90..7eedf0a2b85 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2451,7 +2451,7 @@ namespace ts { return Rename.getRenameInfo(program, getValidSourceFile(fileName), position, options); } - function getRefactorContext(file: SourceFile, positionOrRange: number | TextRange, preferences: UserPreferences, formatOptions?: FormatCodeSettings, triggerReason?: RefactorTriggerReason): RefactorContext { + function getRefactorContext(file: SourceFile, positionOrRange: number | TextRange, preferences: UserPreferences, formatOptions?: FormatCodeSettings, triggerReason?: RefactorTriggerReason, kind?: string): RefactorContext { const [startPosition, endPosition] = typeof positionOrRange === "number" ? [positionOrRange, undefined] : [positionOrRange.pos, positionOrRange.end]; return { file, @@ -2463,6 +2463,7 @@ namespace ts { cancellationToken, preferences, triggerReason, + kind }; } @@ -2470,10 +2471,10 @@ namespace ts { return SmartSelectionRange.getSmartSelectionRange(position, syntaxTreeCache.getCurrentSourceFile(fileName)); } - function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions, triggerReason: RefactorTriggerReason): ApplicableRefactorInfo[] { + function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions, triggerReason: RefactorTriggerReason, kind: string): ApplicableRefactorInfo[] { synchronizeHostData(); const file = getValidSourceFile(fileName); - return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences, emptyOptions, triggerReason)); + return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences, emptyOptions, triggerReason, kind)); } function getEditsForRefactor( diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 079b8e6ed4f..01409293403 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -116,6 +116,7 @@ "refactors/extractSymbol.ts", "refactors/extractType.ts", "refactors/generateGetAccessorAndSetAccessor.ts", + "refactors/helpers.ts", "refactors/moveToNewFile.ts", "refactors/addOrRemoveBracesToArrowFunction.ts", "refactors/convertParamsToDestructuredObject.ts", diff --git a/src/services/types.ts b/src/services/types.ts index 35815ade6f5..dd97197a924 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -520,7 +520,7 @@ namespace ts { /** @deprecated `fileName` will be ignored */ applyCodeActionCommand(fileName: string, action: CodeActionCommand | CodeActionCommand[]): Promise; - getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason): ApplicableRefactorInfo[]; + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): ApplicableRefactorInfo[]; getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined; organizeImports(scope: OrganizeImportsScope, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; @@ -792,6 +792,11 @@ namespace ts { * the current context. */ notApplicableReason?: string; + + /** + * The hierarchical dotted name of the refactor action. + */ + kind?: string; } /** @@ -1464,6 +1469,10 @@ namespace ts { /** @internal */ export interface Refactor { + /** List of action kinds a refactor can provide. + * Used to skip unnecessary calculation when specific refactors are requested. */ + kinds?: string[]; + /** Compute the associated code actions */ getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined; @@ -1480,5 +1489,6 @@ namespace ts { cancellationToken?: CancellationToken; preferences: UserPreferences; triggerReason?: RefactorTriggerReason; + kind?: string; } } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 984c51fa854..f2795baa971 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5570,7 +5570,7 @@ declare namespace ts { applyCodeActionCommand(fileName: string, action: CodeActionCommand[]): Promise; /** @deprecated `fileName` will be ignored */ applyCodeActionCommand(fileName: string, action: CodeActionCommand | CodeActionCommand[]): Promise; - getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason): ApplicableRefactorInfo[]; + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): ApplicableRefactorInfo[]; getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined; organizeImports(scope: OrganizeImportsScope, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; @@ -5796,6 +5796,10 @@ declare namespace ts { * the current context. */ notApplicableReason?: string; + /** + * The hierarchical dotted name of the refactor action. + */ + kind?: string; } /** * A set of edits to make in response to a refactor action, plus an optional @@ -6926,6 +6930,7 @@ declare namespace ts.server.protocol { } type GetApplicableRefactorsRequestArgs = FileLocationOrRangeRequestArgs & { triggerReason?: RefactorTriggerReason; + kind?: string; }; type RefactorTriggerReason = "implicit" | "invoked"; /** @@ -6978,6 +6983,10 @@ declare namespace ts.server.protocol { * the current context. */ notApplicableReason?: string; + /** + * The hierarchical dotted name of the refactor action. + */ + kind?: string; } interface GetEditsForRefactorRequest extends Request { command: CommandTypes.GetEditsForRefactor; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 35116f0ddb7..0f9c37e24b4 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5570,7 +5570,7 @@ declare namespace ts { applyCodeActionCommand(fileName: string, action: CodeActionCommand[]): Promise; /** @deprecated `fileName` will be ignored */ applyCodeActionCommand(fileName: string, action: CodeActionCommand | CodeActionCommand[]): Promise; - getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason): ApplicableRefactorInfo[]; + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): ApplicableRefactorInfo[]; getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined; organizeImports(scope: OrganizeImportsScope, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; @@ -5796,6 +5796,10 @@ declare namespace ts { * the current context. */ notApplicableReason?: string; + /** + * The hierarchical dotted name of the refactor action. + */ + kind?: string; } /** * A set of edits to make in response to a refactor action, plus an optional diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index d2ea70036a4..7228fee24b1 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -244,6 +244,7 @@ declare namespace FourSlashInterface { refactorAvailable(name: string, actionName?: string): void; refactorAvailableForTriggerReason(triggerReason: RefactorTriggerReason, name: string, action?: string): void; + refactorKindAvailable(refactorKind: string, expected: string[], preferences?: {}): void; } class verify extends verifyNegatable { assertHasRanges(ranges: Range[]): void; diff --git a/tests/cases/fourslash/refactorKind_extract.ts b/tests/cases/fourslash/refactorKind_extract.ts new file mode 100644 index 00000000000..2162cb8dd0e --- /dev/null +++ b/tests/cases/fourslash/refactorKind_extract.ts @@ -0,0 +1,16 @@ +/// + +//// const foo: /*a*/string/*b*/ = /*c*/1/*d*/; + +goTo.select("a", "b"); +verify.refactorKindAvailable("refactor.extract", +[ + "refactor.extract.type" +]); + +goTo.select("c", "d"); +verify.refactorKindAvailable("refactor.extract", +[ + "refactor.extract.constant", + "refactor.extract.function" +]); diff --git a/tests/cases/fourslash/refactorKind_generateGetAndSetAccessor.ts b/tests/cases/fourslash/refactorKind_generateGetAndSetAccessor.ts new file mode 100644 index 00000000000..52799947c9c --- /dev/null +++ b/tests/cases/fourslash/refactorKind_generateGetAndSetAccessor.ts @@ -0,0 +1,11 @@ +/// + +//// class A { +//// /*a*/public a: string;/*b*/ +//// } + +goTo.select("a", "b"); +verify.refactorKindAvailable("refactor.rewrite.property", +[ + "refactor.rewrite.property.generateAccessors" +]); diff --git a/tests/cases/fourslash/refactorKind_moveToNewFile.ts b/tests/cases/fourslash/refactorKind_moveToNewFile.ts new file mode 100644 index 00000000000..d9b280f5652 --- /dev/null +++ b/tests/cases/fourslash/refactorKind_moveToNewFile.ts @@ -0,0 +1,12 @@ +/// + +//// /*a*/const moveMe = 1;/*b*/ + +goTo.select("a", "b"); +verify.refactorKindAvailable("refactor.move", +[ + "refactor.move.newFile" +], +{ + allowTextChangesInNewFiles: true +}); diff --git a/tests/cases/fourslash/refactorKind_rewriteExport.ts b/tests/cases/fourslash/refactorKind_rewriteExport.ts new file mode 100644 index 00000000000..90c484209a0 --- /dev/null +++ b/tests/cases/fourslash/refactorKind_rewriteExport.ts @@ -0,0 +1,9 @@ +/// + +//// /*a*/export function f() {}/*b*/ + +goTo.select("a", "b"); +verify.refactorKindAvailable("refactor.rewrite.export", +[ + "refactor.rewrite.export.default" +]); diff --git a/tests/cases/fourslash/refactorKind_rewriteFunction.ts b/tests/cases/fourslash/refactorKind_rewriteFunction.ts new file mode 100644 index 00000000000..56fe42cd9a4 --- /dev/null +++ b/tests/cases/fourslash/refactorKind_rewriteFunction.ts @@ -0,0 +1,12 @@ +/// + +//// const arrow = () /*a*/=>/*b*/ 1; + +goTo.select("a", "b"); +verify.refactorKindAvailable("refactor.rewrite", +[ + "refactor.rewrite.arrow.braces.add", + "refactor.rewrite.function.named", + "refactor.rewrite.function.anonymous", + "refactor.rewrite.function.returnType" +]); diff --git a/tests/cases/fourslash/refactorKind_rewriteFunctionOverloadList.ts b/tests/cases/fourslash/refactorKind_rewriteFunctionOverloadList.ts new file mode 100644 index 00000000000..068e9830d9d --- /dev/null +++ b/tests/cases/fourslash/refactorKind_rewriteFunctionOverloadList.ts @@ -0,0 +1,10 @@ +/// + +//// /*a*/declare function foo(): void; +//// declare function foo(a: string): void;/*b*/ + +goTo.select("a", "b"); +verify.refactorKindAvailable("refactor.rewrite", +[ + "refactor.rewrite.function.overloadList" +]); diff --git a/tests/cases/fourslash/refactorKind_rewriteImport.ts b/tests/cases/fourslash/refactorKind_rewriteImport.ts new file mode 100644 index 00000000000..7c78d1c3949 --- /dev/null +++ b/tests/cases/fourslash/refactorKind_rewriteImport.ts @@ -0,0 +1,9 @@ +/// + +//// /*a*/import * as m from "m";/*b*/ + +goTo.select("a", "b"); +verify.refactorKindAvailable("refactor.rewrite", +[ + "refactor.rewrite.import.named" +]); diff --git a/tests/cases/fourslash/refactorKind_rewriteOptionalChain.ts b/tests/cases/fourslash/refactorKind_rewriteOptionalChain.ts new file mode 100644 index 00000000000..5661056447b --- /dev/null +++ b/tests/cases/fourslash/refactorKind_rewriteOptionalChain.ts @@ -0,0 +1,10 @@ +/// + +//// /*a*/foo && foo.bar/*b*/ + +goTo.select("a", "b"); +verify.refactorKindAvailable("refactor.rewrite", +[ + "refactor.rewrite.expression.optionalChain" +]); + diff --git a/tests/cases/fourslash/refactorKind_rewriteParametersToDestructured.ts b/tests/cases/fourslash/refactorKind_rewriteParametersToDestructured.ts new file mode 100644 index 00000000000..86597ce7b2e --- /dev/null +++ b/tests/cases/fourslash/refactorKind_rewriteParametersToDestructured.ts @@ -0,0 +1,9 @@ +/// + +//// function(/*a*/a: number, b: number/*b*/): number {} + +goTo.select("a", "b"); +verify.refactorKindAvailable("refactor.rewrite", +[ + "refactor.rewrite.parameters.toDestructured" +]); diff --git a/tests/cases/fourslash/refactorKind_rewriteString.ts b/tests/cases/fourslash/refactorKind_rewriteString.ts new file mode 100644 index 00000000000..ed9222b0aa1 --- /dev/null +++ b/tests/cases/fourslash/refactorKind_rewriteString.ts @@ -0,0 +1,9 @@ +/// + +//// const foo = /*a*/"a" + bar/*b*/; + +goTo.select("a", "b"); +verify.refactorKindAvailable("refactor.rewrite", +[ + "refactor.rewrite.string" +]); From 3b2c48f3cdfb441ce80eaac77400cdaa7a8d0c11 Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Thu, 24 Dec 2020 06:31:15 +0000 Subject: [PATCH 03/18] Update package-lock.json --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 018e03392b3..f89542e04ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -585,9 +585,9 @@ "dev": true }, "@types/node": { - "version": "14.14.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.14.tgz", - "integrity": "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ==", + "version": "14.14.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.16.tgz", + "integrity": "sha512-naXYePhweTi+BMv11TgioE2/FXU4fSl29HAH1ffxVciNsH3rYXjNP2yM8wqmSm7jS20gM8TIklKiTen+1iVncw==", "dev": true }, "@types/node-fetch": { From e4c4292807f10b093aaffa9dfabdf6fbe22c6ffd Mon Sep 17 00:00:00 2001 From: Baptiste Darthenay Date: Mon, 28 Dec 2020 14:46:47 +0100 Subject: [PATCH 04/18] fix(42128): Stricter type for ES5 Intl, redefine ES2020 intl (#42134) --- src/lib/es2020.intl.d.ts | 252 ++++++++++++++++++++++++++++++++++++--- src/lib/es5.d.ts | 22 ++-- 2 files changed, 247 insertions(+), 27 deletions(-) diff --git a/src/lib/es2020.intl.d.ts b/src/lib/es2020.intl.d.ts index 282b9a4b03d..0db148085d5 100644 --- a/src/lib/es2020.intl.d.ts +++ b/src/lib/es2020.intl.d.ts @@ -54,6 +54,239 @@ declare namespace Intl { */ type RelativeTimeFormatStyle = "long" | "short" | "narrow"; + /** + * Unicode Calendar Identifier + * + * [Reference](http://unicode.org/reports/tr35/#UnicodeCalendarIdentifier) + * + * [Source](https://github.com/unicode-org/cldr/blob/b805d0b/common/bcp47/calendar.xml) + */ + type Calendar = + // Thai Buddhist calendar + | "buddhist" + // Traditional Chinese calendar + | "chinese" + // Coptic calendar + | "coptic" + // Traditional Korean calendar + | "dangi" + // Ethiopic calendar, Amete Alem (epoch approx. 5493 B.C.E) + | "ethiopic-amete-alem" | "ethioaa" + // Ethiopic calendar, Amete Mihret (epoch approx, 8 C.E.) + | "ethiopic" + // Gregorian calendar + | "gregorian" + // Gregorian calendar (alias) + | "gregory" + // Traditional Hebrew calendar + | "hebrew" + // Indian calendar + | "indian" + // Islamic calendar + | "islamic" + // Islamic calendar, Umm al-Qura + | "islamic-umalqura" + // Islamic calendar, tabular (intercalary years [2,5,7,10,13,16,18,21,24,26,29] - astronomical epoch) + | "islamic-tbla" + // Islamic calendar, tabular (intercalary years [2,5,7,10,13,16,18,21,24,26,29] - civil epoch) + | "islamic-civil" + | "islamicc" // Deprecated alias + // Islamic calendar, Saudi Arabia sighting + | "islamic-rgsa" + // ISO calendar (Gregorian calendar using the ISO 8601 calendar week rules) + | "iso8601" + // Japanese Imperial calendar + | "japanese" + // Persian calendar + | "persian" + // Republic of China calendar + | "roc" + ; + + /** + * Unicode Number System Identifier + * + * [Reference](http://unicode.org/reports/tr35/#UnicodeNumberSystemIdentifier) + * + * [Source](https://github.com/unicode-org/cldr/blob/be7a953/common/bcp47/number.xml) + */ + type NumberingSystem = + // Adlam digits + | "adlm" + // Ahom digits + | "ahom" + // Arabic-Indic digits + | "arab" + // Extended Arabic-Indic digits + | "arabext" + // Armenian upper case numerals — algorithmic + | "armn" + // Armenian lower case numerals — algorithmic + | "armnlow" + // Balinese digits + | "bali" + // Bengali digits + | "beng" + // Bhaiksuki digits + | "bhks" + // Brahmi digits + | "brah" + // Chakma digits + | "cakm" + // Cham digits + | "cham" + // Cyrillic numerals — algorithmic + | "cyrl" + // Devanagari digits + | "deva" + // Dives Akuru digits + | "diak" + // Ethiopic numerals — algorithmic + | "ethi" + // Financial numerals — may be algorithmic + | "finance" + // Full width digits + | "fullwide" + // Georgian numerals — algorithmic + | "geor" + // Gunjala Gondi digits + | "gong" + // Masaram Gondi digits + | "gonm" + // Greek upper case numerals — algorithmic + | "grek" + // Greek lower case numerals — algorithmic + | "greklow" + // Gujarati digits + | "gujr" + // Gurmukhi digits + | "guru" + // Han-character day-of-month numbering for lunar/other traditional calendars" + | "hanidays" + // Positional decimal system using Chinese number ideographs as digits + | "hanidec" + // Simplified Chinese numerals — algorithmic + | "hans" + // Simplified Chinese financial numerals — algorithmic + | "hansfin" + // Traditional Chinese numerals — algorithmic + | "hant" + // Traditional Chinese financial numerals — algorithmic + | "hantfin" + // Hebrew numerals — algorithmic + | "hebr" + // Pahawh Hmong digits + | "hmng" + // Nyiakeng Puachue Hmong digits + | "hmnp" + // Javanese digits + | "java" + // Japanese numerals — algorithmic + | "jpan" + // Japanese financial numerals — algorithmic + | "jpanfin" + // Japanese first-year Gannen numbering for Japanese calendar + | "jpanyear" + // Kayah Li digits + | "kali" + // Khmer digits + | "khmr" + // Kannada digits + | "knda" + // Tai Tham Hora (secular) digits + | "lana" + // Tai Tham Tham (ecclesiastical) digits + | "lanatham" + // Lao digits + | "laoo" + // Latin digits + | "latn" + // Lepcha digits + | "lepc" + // Limbu digits + | "limb" + // Mathematical bold digits + | "mathbold" + // Mathematical double-struck digits + | "mathdbl" + // Mathematical monospace digits + | "mathmono" + // Mathematical sans-serif bold digits + | "mathsanb" + // Mathematical sans-serif digits + | "mathsans" + // Malayalam digits + | "mlym" + // Modi digits + | "modi" + // Mongolian digits + | "mong" + // Mro digits + | "mroo" + // Meetei Mayek digits + | "mtei" + // Myanmar digits + | "mymr" + // Myanmar Shan digits + | "mymrshan" + // Myanmar Tai Laing digits + | "mymrtlng" + // Native digits + | "native" + // Newa digits + | "newa" + // N'Ko digits + | "nkoo" + // Ol Chiki digits + | "olck" + // Oriya digits + | "orya" + // Osmanya digits + | "osma" + // Hanifi Rohingya digits + | "rohg" + // Roman upper case numerals — algorithmic + | "roman" + // Roman lowercase numerals — algorithmic + | "romanlow" + // Saurashtra digits + | "saur" + // Sharada digits + | "shrd" + // Khudawadi digits + | "sind" + // Sinhala Lith digits + | "sinh" + // Sora_Sompeng digits + | "sora" + // Sundanese digits + | "sund" + // Takri digits + | "takr" + // New Tai Lue digits + | "talu" + // Tamil numerals — algorithmic + | "taml" + // Modern Tamil decimal digits + | "tamldec" + // Telugu digits + | "telu" + // Thai digits + | "thai" + // Tirhuta digits + | "tirh" + // Tibetan digits + | "tibt" + // Traditional numerals — may be algorithmic + | "traditional" | "traditio" + // Vai digits + | "vaii" + // Warang Citi digits + | "wara" + // Wancho digits + | "wcho" + ; + /** * An object with some or all of properties of `options` parameter * of `Intl.RelativeTimeFormat` constructor. @@ -281,23 +514,10 @@ declare namespace Intl { interface DateTimeFormatOptions { dateStyle?: "full" | "long" | "medium" | "short"; timeStyle?: "full" | "long" | "medium" | "short"; - calendar?: "buddhist" | "chinese" | " coptic" | "ethiopia" | "ethiopic" | "gregory" | " hebrew" | "indian" | "islamic" | "iso8601" | " japanese" | "persian" | "roc"; - dayPeriod?: "narrow" | "short" | " long"; - numberingSystem?: "arab" | "arabext" | " bali" | "beng" | "deva" | "fullwide" | " gujr" | "guru" | "hanidec" | "khmr" | " knda" | "laoo" | "latn" | "limb" | "mlym" | " mong" | "mymr" | "orya" | "tamldec" | " telu" | "thai" | "tibt"; - localeMatcher?: "best fit" | "lookup"; - timeZone?: string; - hour12?: boolean; + calendar?: Calendar; + dayPeriod?: "narrow" | "short" | "long"; + numberingSystem?: NumberingSystem; hourCycle?: "h11" | "h12" | "h23" | "h24"; - formatMatcher?: "best fit" | "basic"; - weekday?: "long" | "short" | "narrow"; - era?: "long" | "short" | "narrow"; - year?: "numeric" | "2-digit"; - month?: "numeric" | "2-digit" | "long" | "short" | "narrow"; - day?: "numeric" | "2-digit"; - hour?: "numeric" | "2-digit"; - minute?: "numeric" | "2-digit"; - second?: "numeric" | "2-digit"; fractionalSecondDigits?: 0 | 1 | 2 | 3; - timeZoneName?: "long" | "short"; } } diff --git a/src/lib/es5.d.ts b/src/lib/es5.d.ts index 422f8cdfb2b..02183314005 100644 --- a/src/lib/es5.d.ts +++ b/src/lib/es5.d.ts @@ -4318,17 +4318,17 @@ declare namespace Intl { }; interface DateTimeFormatOptions { - localeMatcher?: string; - weekday?: string; - era?: string; - year?: string; - month?: string; - day?: string; - hour?: string; - minute?: string; - second?: string; - timeZoneName?: string; - formatMatcher?: string; + localeMatcher?: "best fit" | "lookup"; + weekday?: "long" | "short" | "narrow"; + era?: "long" | "short" | "narrow"; + year?: "numeric" | "2-digit"; + month?: "numeric" | "2-digit" | "long" | "short" | "narrow"; + day?: "numeric" | "2-digit"; + hour?: "numeric" | "2-digit"; + minute?: "numeric" | "2-digit"; + second?: "numeric" | "2-digit"; + timeZoneName?: "long" | "short"; + formatMatcher?: "best fit" | "basic"; hour12?: boolean; timeZone?: string; } From fe297df9df0d5b5a9c8544670e3781bdfb1a9a46 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Mon, 28 Dec 2020 19:34:28 +0200 Subject: [PATCH 05/18] fix(42034): allow convert async function with empty catch (#42123) --- .../codefixes/convertToAsyncFunction.ts | 19 +++++++------------ .../services/convertToAsyncFunction.ts | 12 ++++++++++++ .../convertToAsyncFunction_emptyCatch1.js | 13 +++++++++++++ .../convertToAsyncFunction_emptyCatch1.ts | 13 +++++++++++++ .../convertToAsyncFunction_emptyCatch2.js | 14 ++++++++++++++ .../convertToAsyncFunction_emptyCatch2.ts | 14 ++++++++++++++ 6 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_emptyCatch1.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_emptyCatch1.ts create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_emptyCatch2.js create mode 100644 tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_emptyCatch2.ts diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 4db53ac9af4..fb064d024fc 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -235,7 +235,6 @@ namespace ts.codefix { return transformThen(node, transformer, prevArgName); } if (isPromiseReturningCallExpression(node, transformer.checker, "catch")) { - if (node.arguments.length === 0) return silentFail(); return transformCatch(node, transformer, prevArgName); } if (isPropertyAccessExpression(node)) { @@ -252,15 +251,13 @@ namespace ts.codefix { } function transformCatch(node: CallExpression, transformer: Transformer, prevArgName?: SynthBindingName): readonly Statement[] { - const func = node.arguments[0]; - const argName = getArgBindingName(func, transformer); + const func = singleOrUndefined(node.arguments); + const argName = func ? getArgBindingName(func, transformer) : undefined; let possibleNameForVarDecl: SynthIdentifier | undefined; - /* - If there is another call in the chain after the .catch() we are transforming, we will need to save the result of both paths (try block and catch block) - To do this, we will need to synthesize a variable that we were not aware of while we were adding identifiers to the synthNamesMap - We will use the prevArgName and then update the synthNamesMap with a new variable name for the next transformation step - */ + // If there is another call in the chain after the .catch() we are transforming, we will need to save the result of both paths (try block and catch block) + // To do this, we will need to synthesize a variable that we were not aware of while we were adding identifiers to the synthNamesMap + // We will use the prevArgName and then update the synthNamesMap with a new variable name for the next transformation step if (prevArgName && !shouldReturn(node, transformer)) { if (isSynthIdentifier(prevArgName)) { possibleNameForVarDecl = prevArgName; @@ -282,14 +279,12 @@ namespace ts.codefix { } const tryBlock = factory.createBlock(transformExpression(node.expression, transformer, possibleNameForVarDecl)); - const transformationBody = getTransformationBody(func, possibleNameForVarDecl, argName, node, transformer); + const transformationBody = func ? getTransformationBody(func, possibleNameForVarDecl, argName, node, transformer) : emptyArray; const catchArg = argName ? isSynthIdentifier(argName) ? argName.identifier.text : argName.bindingPattern : "e"; const catchVariableDeclaration = factory.createVariableDeclaration(catchArg); const catchClause = factory.createCatchClause(catchVariableDeclaration, factory.createBlock(transformationBody)); - /* - In order to avoid an implicit any, we will synthesize a type for the declaration using the unions of the types of both paths (try block and catch block) - */ + // In order to avoid an implicit any, we will synthesize a type for the declaration using the unions of the types of both paths (try block and catch block) let varDeclList: VariableStatement | undefined; let varDeclIdentifier: Identifier | undefined; if (possibleNameForVarDecl && !shouldReturn(node, transformer)) { diff --git a/src/testRunner/unittests/services/convertToAsyncFunction.ts b/src/testRunner/unittests/services/convertToAsyncFunction.ts index e57372b3161..5b09aba9549 100644 --- a/src/testRunner/unittests/services/convertToAsyncFunction.ts +++ b/src/testRunner/unittests/services/convertToAsyncFunction.ts @@ -1577,6 +1577,18 @@ const fn = (): Promise<(message: string) => void> => function [#|f|]() { return fn().then(res => res("test")); } +`); + + _testConvertToAsyncFunction("convertToAsyncFunction_emptyCatch1", ` +function [#|f|]() { + return Promise.resolve().catch(); +} +`); + + _testConvertToAsyncFunction("convertToAsyncFunction_emptyCatch2", ` +function [#|f|]() { + return Promise.resolve(0).then(x => x).catch(); +} `); _testConvertToAsyncFunctionWithModule("convertToAsyncFunction_importedFunction", ` diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_emptyCatch1.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_emptyCatch1.js new file mode 100644 index 00000000000..20fa127d990 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_emptyCatch1.js @@ -0,0 +1,13 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return Promise.resolve().catch(); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + try { + return Promise.resolve(); + } catch (e) { } +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_emptyCatch1.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_emptyCatch1.ts new file mode 100644 index 00000000000..20fa127d990 --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_emptyCatch1.ts @@ -0,0 +1,13 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return Promise.resolve().catch(); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + try { + return Promise.resolve(); + } catch (e) { } +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_emptyCatch2.js b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_emptyCatch2.js new file mode 100644 index 00000000000..aa7e9f1bb8d --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_emptyCatch2.js @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return Promise.resolve(0).then(x => x).catch(); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + try { + const x = await Promise.resolve(0); + return x; + } catch (e) { } +} diff --git a/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_emptyCatch2.ts b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_emptyCatch2.ts new file mode 100644 index 00000000000..aa7e9f1bb8d --- /dev/null +++ b/tests/baselines/reference/convertToAsyncFunction/convertToAsyncFunction_emptyCatch2.ts @@ -0,0 +1,14 @@ +// ==ORIGINAL== + +function /*[#|*/f/*|]*/() { + return Promise.resolve(0).then(x => x).catch(); +} + +// ==ASYNC FUNCTION::Convert to async function== + +async function f() { + try { + const x = await Promise.resolve(0); + return x; + } catch (e) { } +} From ea03c2966bcdee0dd7099314b4bf6cc9b7d9e35b Mon Sep 17 00:00:00 2001 From: chenjigeng <178854407@qq.com> Date: Tue, 29 Dec 2020 01:43:43 +0800 Subject: [PATCH 06/18] Fix/jsx syntax quickinfo (#42124) * feat: add jsx type invalid check * feat: update jsxGenericQuickInfo test cases --- src/services/services.ts | 2 +- tests/cases/fourslash/jsxGenericQuickInfo.tsx | 34 +++++++++++++------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 7eedf0a2b85..e2ef969a7b2 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2695,7 +2695,7 @@ namespace ts { return symbol ? [symbol] : emptyArray; } - const discriminatedPropertySymbols = mapDefined(contextualType.types, t => isObjectLiteralExpression(node.parent) && checker.isTypeInvalidDueToUnionDiscriminant(t, node.parent) ? undefined : t.getProperty(name)); + const discriminatedPropertySymbols = mapDefined(contextualType.types, t => (isObjectLiteralExpression(node.parent)|| isJsxAttributes(node.parent)) && checker.isTypeInvalidDueToUnionDiscriminant(t, node.parent) ? undefined : t.getProperty(name)); if (unionSymbolOk && (discriminatedPropertySymbols.length === 0 || discriminatedPropertySymbols.length === contextualType.types.length)) { const symbol = contextualType.getProperty(name); if (symbol) return [symbol]; diff --git a/tests/cases/fourslash/jsxGenericQuickInfo.tsx b/tests/cases/fourslash/jsxGenericQuickInfo.tsx index 359701d5c17..75661937376 100644 --- a/tests/cases/fourslash/jsxGenericQuickInfo.tsx +++ b/tests/cases/fourslash/jsxGenericQuickInfo.tsx @@ -6,17 +6,29 @@ //// } //// interface ElementAttributesProperty { props } //// } -//// interface Props { -//// items: T[]; -//// renderItem: (item: T) => string; +//// interface PropsA { +//// /** comments for A */ +//// name: 'A', +//// items: T[]; +//// renderItem: (item: T) => string; +//// } +//// interface PropsB { +//// /** comments for B */ +//// name: 'B', +//// items: T[]; +//// renderItem: (item: T) => string; //// } //// class Component { -//// constructor(props: Props) {} -//// props: Props; -//// } -//// var b = new Component({items: [0, 1, 2], render/*0*/Item: it/*1*/em => item.toFixed()}); -//// var c = item.toFixed()} -verify.quickInfoAt("0", "(property) Props.renderItem: (item: number) => string"); +//// constructor(props: PropsA | PropsB) {} +//// props: PropsA | PropsB; +//// } +//// var b = new Component({items: [0, 1, 2], render/*0*/Item: it/*1*/em => item.toFixed(), name/*2*/: 'A',}); +//// var c = item.toFixed()} name/*5*/="A" /> + + +verify.quickInfoAt("0", "(property) PropsA.renderItem: (item: number) => string"); verify.quickInfoAt("1", "(parameter) item: number"); -verify.quickInfoAt("2", "(JSX attribute) Props.renderItem: (item: number) => string"); -verify.quickInfoAt("3", "(parameter) item: number"); +verify.quickInfoAt("2", `(property) PropsA.name: "A"`, 'comments for A'); +verify.quickInfoAt("3", "(JSX attribute) PropsA.renderItem: (item: number) => string"); +verify.quickInfoAt("4", "(parameter) item: number"); +verify.quickInfoAt("5", `(JSX attribute) PropsA.name: "A"`, 'comments for A'); From 303ed3a357b3e834d70e1e578c3a194b33813377 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Mon, 28 Dec 2020 23:34:55 +0200 Subject: [PATCH 07/18] fix(42099): fix JSDoc optional properties declaration emit (#42116) --- src/compiler/checker.ts | 2 +- ...jsDeclarationsOptionalTypeLiteralProps1.js | 48 ++++++++++++++++++ ...larationsOptionalTypeLiteralProps1.symbols | 23 +++++++++ ...eclarationsOptionalTypeLiteralProps1.types | 25 ++++++++++ ...jsDeclarationsOptionalTypeLiteralProps2.js | 49 +++++++++++++++++++ ...larationsOptionalTypeLiteralProps2.symbols | 23 +++++++++ ...eclarationsOptionalTypeLiteralProps2.types | 31 ++++++++++++ .../reference/jsdocParamTagTypeLiteral.types | 4 +- ...jsDeclarationsOptionalTypeLiteralProps1.ts | 19 +++++++ ...jsDeclarationsOptionalTypeLiteralProps2.ts | 20 ++++++++ 10 files changed, 241 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps1.js create mode 100644 tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps1.symbols create mode 100644 tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps1.types create mode 100644 tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps2.js create mode 100644 tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps2.symbols create mode 100644 tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps2.types create mode 100644 tests/cases/conformance/jsdoc/declarations/jsDeclarationsOptionalTypeLiteralProps1.ts create mode 100644 tests/cases/conformance/jsdoc/declarations/jsDeclarationsOptionalTypeLiteralProps2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1c3bc622b0c..9001f12ce7a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5931,7 +5931,7 @@ namespace ts { return factory.createPropertySignature( /*modifiers*/ undefined, name, - t.typeExpression && isJSDocOptionalType(t.typeExpression.type) ? factory.createToken(SyntaxKind.QuestionToken) : undefined, + t.isBracketed || t.typeExpression && isJSDocOptionalType(t.typeExpression.type) ? factory.createToken(SyntaxKind.QuestionToken) : undefined, overrideTypeNode || (t.typeExpression && visitNode(t.typeExpression.type, visitExistingNodeTreeSymbols)) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) ); })); diff --git a/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps1.js b/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps1.js new file mode 100644 index 00000000000..962299ec19c --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps1.js @@ -0,0 +1,48 @@ +//// [foo.js] +/** + * foo + * + * @public + * @param {object} opts + * @param {number} opts.a + * @param {number} [opts.b] + * @param {number} [opts.c] + * @returns {number} + */ +function foo({ a, b, c }) { + return a + b + c; +} + + +//// [foo.js] +/** + * foo + * + * @public + * @param {object} opts + * @param {number} opts.a + * @param {number} [opts.b] + * @param {number} [opts.c] + * @returns {number} + */ +function foo({ a, b, c }) { + return a + b + c; +} + + +//// [foo.d.ts] +/** + * foo + * + * @public + * @param {object} opts + * @param {number} opts.a + * @param {number} [opts.b] + * @param {number} [opts.c] + * @returns {number} + */ +declare function foo({ a, b, c }: { + a: number; + b?: number; + c?: number; +}): number; diff --git a/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps1.symbols b/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps1.symbols new file mode 100644 index 00000000000..9d008cb186a --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps1.symbols @@ -0,0 +1,23 @@ +=== tests/cases/conformance/jsdoc/declarations/foo.js === +/** + * foo + * + * @public + * @param {object} opts + * @param {number} opts.a + * @param {number} [opts.b] + * @param {number} [opts.c] + * @returns {number} + */ +function foo({ a, b, c }) { +>foo : Symbol(foo, Decl(foo.js, 0, 0)) +>a : Symbol(a, Decl(foo.js, 10, 14)) +>b : Symbol(b, Decl(foo.js, 10, 17)) +>c : Symbol(c, Decl(foo.js, 10, 20)) + + return a + b + c; +>a : Symbol(a, Decl(foo.js, 10, 14)) +>b : Symbol(b, Decl(foo.js, 10, 17)) +>c : Symbol(c, Decl(foo.js, 10, 20)) +} + diff --git a/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps1.types b/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps1.types new file mode 100644 index 00000000000..f692812bd5a --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps1.types @@ -0,0 +1,25 @@ +=== tests/cases/conformance/jsdoc/declarations/foo.js === +/** + * foo + * + * @public + * @param {object} opts + * @param {number} opts.a + * @param {number} [opts.b] + * @param {number} [opts.c] + * @returns {number} + */ +function foo({ a, b, c }) { +>foo : ({ a, b, c }: { a: number; b?: number; c?: number;}) => number +>a : number +>b : number +>c : number + + return a + b + c; +>a + b + c : number +>a + b : number +>a : number +>b : number +>c : number +} + diff --git a/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps2.js b/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps2.js new file mode 100644 index 00000000000..8157df664ab --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps2.js @@ -0,0 +1,49 @@ +//// [foo.js] +/** + * foo + * + * @public + * @param {object} opts + * @param {number} opts.a + * @param {number} [opts.b] + * @param {number} [opts.c] + * @returns {number} + */ +function foo({ a, b, c }) { + return a + (b ?? 0) + (c ?? 0); +} + + +//// [foo.js] +"use strict"; +/** + * foo + * + * @public + * @param {object} opts + * @param {number} opts.a + * @param {number} [opts.b] + * @param {number} [opts.c] + * @returns {number} + */ +function foo({ a, b, c }) { + return a + (b ?? 0) + (c ?? 0); +} + + +//// [foo.d.ts] +/** + * foo + * + * @public + * @param {object} opts + * @param {number} opts.a + * @param {number} [opts.b] + * @param {number} [opts.c] + * @returns {number} + */ +declare function foo({ a, b, c }: { + a: number; + b?: number | undefined; + c?: number | undefined; +}): number; diff --git a/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps2.symbols b/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps2.symbols new file mode 100644 index 00000000000..9d6df290f62 --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps2.symbols @@ -0,0 +1,23 @@ +=== tests/cases/conformance/jsdoc/declarations/foo.js === +/** + * foo + * + * @public + * @param {object} opts + * @param {number} opts.a + * @param {number} [opts.b] + * @param {number} [opts.c] + * @returns {number} + */ +function foo({ a, b, c }) { +>foo : Symbol(foo, Decl(foo.js, 0, 0)) +>a : Symbol(a, Decl(foo.js, 10, 14)) +>b : Symbol(b, Decl(foo.js, 10, 17)) +>c : Symbol(c, Decl(foo.js, 10, 20)) + + return a + (b ?? 0) + (c ?? 0); +>a : Symbol(a, Decl(foo.js, 10, 14)) +>b : Symbol(b, Decl(foo.js, 10, 17)) +>c : Symbol(c, Decl(foo.js, 10, 20)) +} + diff --git a/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps2.types b/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps2.types new file mode 100644 index 00000000000..b976d6fae6e --- /dev/null +++ b/tests/baselines/reference/jsDeclarationsOptionalTypeLiteralProps2.types @@ -0,0 +1,31 @@ +=== tests/cases/conformance/jsdoc/declarations/foo.js === +/** + * foo + * + * @public + * @param {object} opts + * @param {number} opts.a + * @param {number} [opts.b] + * @param {number} [opts.c] + * @returns {number} + */ +function foo({ a, b, c }) { +>foo : ({ a, b, c }: { a: number; b?: number | undefined; c?: number | undefined;}) => number +>a : number +>b : number | undefined +>c : number | undefined + + return a + (b ?? 0) + (c ?? 0); +>a + (b ?? 0) + (c ?? 0) : number +>a + (b ?? 0) : number +>a : number +>(b ?? 0) : number +>b ?? 0 : number +>b : number | undefined +>0 : 0 +>(c ?? 0) : number +>c ?? 0 : number +>c : number | undefined +>0 : 0 +} + diff --git a/tests/baselines/reference/jsdocParamTagTypeLiteral.types b/tests/baselines/reference/jsdocParamTagTypeLiteral.types index ef926eec222..d38f3fd20cc 100644 --- a/tests/baselines/reference/jsdocParamTagTypeLiteral.types +++ b/tests/baselines/reference/jsdocParamTagTypeLiteral.types @@ -23,7 +23,7 @@ normal(12); * @param {string} [opts1.w="hi"] doc5 */ function foo1(opts1) { ->foo1 : (opts1: { x: string; y?: string | undefined; z: string | undefined; w: string | undefined;}) => void +>foo1 : (opts1: { x: string; y?: string | undefined; z?: string | undefined; w?: string | undefined;}) => void >opts1 : { x: string; y?: string | undefined; z?: string | undefined; w?: string | undefined; } opts1.x; @@ -92,7 +92,7 @@ foo3({x: 'abc'}); * @param {string} [opts4[].w="hi"] */ function foo4(opts4) { ->foo4 : (opts4: { x: string; y?: string | undefined; z: string; w: string;}) => void +>foo4 : (opts4: { x: string; y?: string | undefined; z?: string; w?: string;}) => void >opts4 : { x: string; y?: string | undefined; z?: string | undefined; w?: string | undefined; }[] opts4[0].x; diff --git a/tests/cases/conformance/jsdoc/declarations/jsDeclarationsOptionalTypeLiteralProps1.ts b/tests/cases/conformance/jsdoc/declarations/jsDeclarationsOptionalTypeLiteralProps1.ts new file mode 100644 index 00000000000..d0797b47c4e --- /dev/null +++ b/tests/cases/conformance/jsdoc/declarations/jsDeclarationsOptionalTypeLiteralProps1.ts @@ -0,0 +1,19 @@ +// @allowJs: true +// @checkJs: true +// @target: esnext +// @outDir: ./out +// @declaration: true +// @filename: foo.js +/** + * foo + * + * @public + * @param {object} opts + * @param {number} opts.a + * @param {number} [opts.b] + * @param {number} [opts.c] + * @returns {number} + */ +function foo({ a, b, c }) { + return a + b + c; +} diff --git a/tests/cases/conformance/jsdoc/declarations/jsDeclarationsOptionalTypeLiteralProps2.ts b/tests/cases/conformance/jsdoc/declarations/jsDeclarationsOptionalTypeLiteralProps2.ts new file mode 100644 index 00000000000..860622295c6 --- /dev/null +++ b/tests/cases/conformance/jsdoc/declarations/jsDeclarationsOptionalTypeLiteralProps2.ts @@ -0,0 +1,20 @@ +// @allowJs: true +// @checkJs: true +// @strict: true +// @target: esnext +// @outDir: ./out +// @declaration: true +// @filename: foo.js +/** + * foo + * + * @public + * @param {object} opts + * @param {number} opts.a + * @param {number} [opts.b] + * @param {number} [opts.c] + * @returns {number} + */ +function foo({ a, b, c }) { + return a + (b ?? 0) + (c ?? 0); +} From 3b222fe80ccb218975fcf32f9913100cf0c8a051 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 30 Dec 2020 09:48:07 -0800 Subject: [PATCH 08/18] Shrink error span on @type errors for signatures (#42024) Previously, the error span was too large on @type errors on functions when the type was not a function. The span covered the entire tag. This PR changes the error node just to be the type of the type tag. In other words, the error span was previously this: ``` @type {IncorrectType} ``` But is now just this: ``` IncorrectType ``` Fixes the first error from #41974, but not the other two. Co-authored-by: Ashya Manning Co-authored-by: Nilber Remon Co-authored-by: Ashya Manning Co-authored-by: Nilber Remon --- src/compiler/checker.ts | 2 +- tests/baselines/reference/checkJsdocTypeTag5.errors.txt | 4 ++-- tests/baselines/reference/checkJsdocTypeTag6.errors.txt | 8 ++++---- ...peTagCircularReferenceOnConstructorFunction.errors.txt | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9001f12ce7a..a3b147f25e7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -33382,7 +33382,7 @@ namespace ts { if (isInJSFile(node)) { const typeTag = getJSDocTypeTag(node); if (typeTag && typeTag.typeExpression && !getContextualCallSignature(getTypeFromTypeNode(typeTag.typeExpression), node)) { - error(typeTag, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature); + error(typeTag.typeExpression.type, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature); } } } diff --git a/tests/baselines/reference/checkJsdocTypeTag5.errors.txt b/tests/baselines/reference/checkJsdocTypeTag5.errors.txt index 022817b9997..74bf718c18b 100644 --- a/tests/baselines/reference/checkJsdocTypeTag5.errors.txt +++ b/tests/baselines/reference/checkJsdocTypeTag5.errors.txt @@ -4,7 +4,7 @@ tests/cases/conformance/jsdoc/test.js(7,24): error TS2322: Type 'number' is not tests/cases/conformance/jsdoc/test.js(10,17): error TS2322: Type 'number' is not assignable to type 'string'. tests/cases/conformance/jsdoc/test.js(12,14): error TS2322: Type 'number' is not assignable to type 'string'. tests/cases/conformance/jsdoc/test.js(14,24): error TS2322: Type 'number' is not assignable to type 'string'. -tests/cases/conformance/jsdoc/test.js(28,5): error TS8030: The type of a function declaration must match the function's signature. +tests/cases/conformance/jsdoc/test.js(28,12): error TS8030: The type of a function declaration must match the function's signature. tests/cases/conformance/jsdoc/test.js(34,5): error TS2322: Type '1 | 2' is not assignable to type '2 | 3'. Type '1' is not assignable to type '2 | 3'. @@ -50,7 +50,7 @@ tests/cases/conformance/jsdoc/test.js(34,5): error TS2322: Type '1 | 2' is not a /** @typedef {{(s: string): 0 | 1; (b: boolean): 2 | 3 }} Gioconda */ /** @type {Gioconda} */ - ~~~~~~~~~~~~~~~~ + ~~~~~~~~ !!! error TS8030: The type of a function declaration must match the function's signature. function monaLisa(sb) { return typeof sb === 'string' ? 1 : 2; diff --git a/tests/baselines/reference/checkJsdocTypeTag6.errors.txt b/tests/baselines/reference/checkJsdocTypeTag6.errors.txt index bbc2e8cd6b1..7a7915c0f61 100644 --- a/tests/baselines/reference/checkJsdocTypeTag6.errors.txt +++ b/tests/baselines/reference/checkJsdocTypeTag6.errors.txt @@ -1,11 +1,11 @@ -tests/cases/conformance/jsdoc/test.js(1,5): error TS8030: The type of a function declaration must match the function's signature. +tests/cases/conformance/jsdoc/test.js(1,12): error TS8030: The type of a function declaration must match the function's signature. tests/cases/conformance/jsdoc/test.js(7,5): error TS2741: Property 'prop' is missing in type '(prop: any) => void' but required in type '{ prop: string; }'. -tests/cases/conformance/jsdoc/test.js(10,5): error TS8030: The type of a function declaration must match the function's signature. +tests/cases/conformance/jsdoc/test.js(10,12): error TS8030: The type of a function declaration must match the function's signature. ==== tests/cases/conformance/jsdoc/test.js (3 errors) ==== /** @type {number} */ - ~~~~~~~~~~~~~~ + ~~~~~~ !!! error TS8030: The type of a function declaration must match the function's signature. function f() { return 1 @@ -19,7 +19,7 @@ tests/cases/conformance/jsdoc/test.js(10,5): error TS8030: The type of a functio } /** @type {(a: number) => number} */ - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~ !!! error TS8030: The type of a function declaration must match the function's signature. function add1(a, b) { return a + b; } diff --git a/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.errors.txt b/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.errors.txt index d98cb9297dc..30f73e83d07 100644 --- a/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.errors.txt +++ b/tests/baselines/reference/typeTagCircularReferenceOnConstructorFunction.errors.txt @@ -1,10 +1,10 @@ -tests/cases/conformance/jsdoc/bug27346.js(2,4): error TS8030: The type of a function declaration must match the function's signature. +tests/cases/conformance/jsdoc/bug27346.js(2,11): error TS8030: The type of a function declaration must match the function's signature. ==== tests/cases/conformance/jsdoc/bug27346.js (1 errors) ==== /** * @type {MyClass} - ~~~~~~~~~~~~~~~ + ~~~~~~~ !!! error TS8030: The type of a function declaration must match the function's signature. */ function MyClass() { } From dfe23421baefa8a3309b231118f894ebfda79190 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Wed, 30 Dec 2020 20:42:38 +0200 Subject: [PATCH 09/18] fix(41867): completions - omit the deprecated flag if some declarations are not deprecated (#41941) --- src/services/symbolDisplay.ts | 37 ++++++++++++------- src/services/utilities.ts | 6 ++- ...ag.ts => completionsWithDeprecatedTag1.ts} | 0 .../completionsWithDeprecatedTag2.ts | 15 ++++++++ .../completionsWithDeprecatedTag3.ts | 15 ++++++++ 5 files changed, 58 insertions(+), 15 deletions(-) rename tests/cases/fourslash/{completionsWithDeprecatedTag.ts => completionsWithDeprecatedTag1.ts} (100%) create mode 100644 tests/cases/fourslash/completionsWithDeprecatedTag2.ts create mode 100644 tests/cases/fourslash/completionsWithDeprecatedTag3.ts diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index 4acba52253a..b1f74800635 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -100,31 +100,42 @@ namespace ts.SymbolDisplay { return ScriptElementKind.unknown; } + function isDeprecatedDeclaration(decl: Declaration) { + return !!(getCombinedNodeFlagsAlwaysIncludeJSDoc(decl) & ModifierFlags.Deprecated); + } + + function getNormalizedSymbolModifiers(symbol: Symbol) { + if (symbol.declarations && symbol.declarations.length) { + const [declaration, ...declarations] = symbol.declarations; + // omit deprecated flag if some declarations are not deprecated + const excludeFlags = length(declarations) && isDeprecatedDeclaration(declaration) && some(declarations, d => !isDeprecatedDeclaration(d)) + ? ModifierFlags.Deprecated + : ModifierFlags.None; + const modifiers = getNodeModifiers(declaration, excludeFlags); + if (modifiers) { + return modifiers.split(","); + } + } + return []; + } + export function getSymbolModifiers(typeChecker: TypeChecker, symbol: Symbol): string { if (!symbol) { return ScriptElementKindModifier.none; } - const modifiers = new Set(); - if (symbol.declarations && symbol.declarations.length > 0) { - const kindModifiers = getNodeModifiers(symbol.declarations[0]); - if (kindModifiers !== ScriptElementKindModifier.none) { - kindModifiers.split(",").forEach(m => modifiers.add(m)); - } - } + const modifiers = new Set(getNormalizedSymbolModifiers(symbol)); if (symbol.flags & SymbolFlags.Alias) { const resolvedSymbol = typeChecker.getAliasedSymbol(symbol); - if (resolvedSymbol !== symbol && resolvedSymbol.declarations && resolvedSymbol.declarations.length > 0) { - const kindModifiers = getNodeModifiers(resolvedSymbol.declarations[0]); - if (kindModifiers !== ScriptElementKindModifier.none) { - kindModifiers.split(",").forEach(m => modifiers.add(m)); - } + if (resolvedSymbol !== symbol) { + forEach(getNormalizedSymbolModifiers(resolvedSymbol), modifier => { + modifiers.add(modifier); + }); } } if (symbol.flags & SymbolFlags.Optional) { modifiers.add(ScriptElementKindModifier.optionalModifier); } - return modifiers.size > 0 ? arrayFrom(modifiers.values()).join(",") : ScriptElementKindModifier.none; } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 9cf63087ebd..d456c414bc7 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1588,9 +1588,11 @@ namespace ts { return n.kind === SyntaxKind.EndOfFileToken ? !!(n as EndOfFileToken).jsDoc : n.getWidth(sourceFile) !== 0; } - export function getNodeModifiers(node: Node): string { - const flags = isDeclaration(node) ? getCombinedNodeFlagsAlwaysIncludeJSDoc(node) : ModifierFlags.None; + export function getNodeModifiers(node: Node, excludeFlags = ModifierFlags.None): string { const result: string[] = []; + const flags = isDeclaration(node) + ? getCombinedNodeFlagsAlwaysIncludeJSDoc(node) & ~excludeFlags + : ModifierFlags.None; if (flags & ModifierFlags.Private) result.push(ScriptElementKindModifier.privateMemberModifier); if (flags & ModifierFlags.Protected) result.push(ScriptElementKindModifier.protectedMemberModifier); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag.ts b/tests/cases/fourslash/completionsWithDeprecatedTag1.ts similarity index 100% rename from tests/cases/fourslash/completionsWithDeprecatedTag.ts rename to tests/cases/fourslash/completionsWithDeprecatedTag1.ts diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag2.ts b/tests/cases/fourslash/completionsWithDeprecatedTag2.ts new file mode 100644 index 00000000000..a902c6b30eb --- /dev/null +++ b/tests/cases/fourslash/completionsWithDeprecatedTag2.ts @@ -0,0 +1,15 @@ +/// + +/////** @deprecated foo */ +////declare function foo(); +/////** @deprecated foo */ +////declare function foo(x); +//// +////foo/**/ + +verify.completions({ + marker: "", + includes: [ + { name: "foo", kind: "function", kindModifiers: "deprecated,declare" } + ] +}); diff --git a/tests/cases/fourslash/completionsWithDeprecatedTag3.ts b/tests/cases/fourslash/completionsWithDeprecatedTag3.ts new file mode 100644 index 00000000000..19e9d5c561b --- /dev/null +++ b/tests/cases/fourslash/completionsWithDeprecatedTag3.ts @@ -0,0 +1,15 @@ +/// + +/////** @deprecated foo */ +////declare function foo(); +/////** ok */ +////declare function foo(x); +//// +////foo/**/ + +verify.completions({ + marker: "", + includes: [ + { name: "foo", kind: "function", kindModifiers: "declare" } + ] +}); From e108257fb6cc187d1ffbd06de61835e2dd4e8c12 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Wed, 30 Dec 2020 21:45:18 +0200 Subject: [PATCH 10/18] fix(41965): fix error in definite assignment assertion context (#41989) --- src/compiler/checker.ts | 4 +++- .../definiteAssignmentAssertions.errors.txt | 17 ++++++++++++----- .../reference/definiteAssignmentAssertions.js | 7 +++++++ .../definiteAssignmentAssertions.symbols | 7 +++++++ .../definiteAssignmentAssertions.types | 7 +++++++ .../controlFlow/definiteAssignmentAssertions.ts | 4 ++++ 6 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a3b147f25e7..6c240dce412 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -40134,7 +40134,9 @@ namespace ts { if (node.exclamationToken && (node.parent.parent.kind !== SyntaxKind.VariableStatement || !node.type || node.initializer || node.flags & NodeFlags.Ambient)) { const message = node.initializer ? Diagnostics.Declarations_with_initializers_cannot_also_have_definite_assignment_assertions - : Diagnostics.Declarations_with_definite_assignment_assertions_must_also_have_type_annotations; + : !node.type + ? Diagnostics.Declarations_with_definite_assignment_assertions_must_also_have_type_annotations + : Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context; return grammarErrorOnNode(node.exclamationToken, message); } diff --git a/tests/baselines/reference/definiteAssignmentAssertions.errors.txt b/tests/baselines/reference/definiteAssignmentAssertions.errors.txt index a4f7f80a51b..2c8792a0357 100644 --- a/tests/baselines/reference/definiteAssignmentAssertions.errors.txt +++ b/tests/baselines/reference/definiteAssignmentAssertions.errors.txt @@ -9,11 +9,12 @@ tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(35,15): erro tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(69,10): error TS1264: Declarations with definite assignment assertions must also have type annotations. tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(70,10): error TS1263: Declarations with initializers cannot also have definite assignment assertions. tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(71,10): error TS1263: Declarations with initializers cannot also have definite assignment assertions. -tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(76,15): error TS1264: Declarations with definite assignment assertions must also have type annotations. -tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(77,15): error TS1264: Declarations with definite assignment assertions must also have type annotations. +tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(76,15): error TS1255: A definite assignment assertion '!' is not permitted in this context. +tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(77,15): error TS1255: A definite assignment assertion '!' is not permitted in this context. +tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(80,7): error TS1255: A definite assignment assertion '!' is not permitted in this context. -==== tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts (13 errors) ==== +==== tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts (14 errors) ==== // Suppress strict property initialization check class C1 { @@ -113,8 +114,14 @@ tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts(77,15): erro declare let v1!: number; ~ -!!! error TS1264: Declarations with definite assignment assertions must also have type annotations. +!!! error TS1255: A definite assignment assertion '!' is not permitted in this context. declare var v2!: number; ~ -!!! error TS1264: Declarations with definite assignment assertions must also have type annotations. +!!! error TS1255: A definite assignment assertion '!' is not permitted in this context. + + declare namespace foo { + var v!: number; + ~ +!!! error TS1255: A definite assignment assertion '!' is not permitted in this context. + } \ No newline at end of file diff --git a/tests/baselines/reference/definiteAssignmentAssertions.js b/tests/baselines/reference/definiteAssignmentAssertions.js index 6ee50717ec6..78ea465bdce 100644 --- a/tests/baselines/reference/definiteAssignmentAssertions.js +++ b/tests/baselines/reference/definiteAssignmentAssertions.js @@ -76,6 +76,10 @@ function f4() { declare let v1!: number; declare var v2!: number; + +declare namespace foo { + var v!: number; +} //// [definiteAssignmentAssertions.js] @@ -166,3 +170,6 @@ declare function f3(): void; declare function f4(): void; declare let v1: number; declare var v2: number; +declare namespace foo { + var v: number; +} diff --git a/tests/baselines/reference/definiteAssignmentAssertions.symbols b/tests/baselines/reference/definiteAssignmentAssertions.symbols index 08cc5862f6b..fd63026eb1a 100644 --- a/tests/baselines/reference/definiteAssignmentAssertions.symbols +++ b/tests/baselines/reference/definiteAssignmentAssertions.symbols @@ -147,3 +147,10 @@ declare let v1!: number; declare var v2!: number; >v2 : Symbol(v2, Decl(definiteAssignmentAssertions.ts, 76, 11)) +declare namespace foo { +>foo : Symbol(foo, Decl(definiteAssignmentAssertions.ts, 76, 24)) + + var v!: number; +>v : Symbol(v, Decl(definiteAssignmentAssertions.ts, 79, 4)) +} + diff --git a/tests/baselines/reference/definiteAssignmentAssertions.types b/tests/baselines/reference/definiteAssignmentAssertions.types index b98839d3383..ead57cfce5c 100644 --- a/tests/baselines/reference/definiteAssignmentAssertions.types +++ b/tests/baselines/reference/definiteAssignmentAssertions.types @@ -158,3 +158,10 @@ declare let v1!: number; declare var v2!: number; >v2 : number +declare namespace foo { +>foo : typeof foo + + var v!: number; +>v : number +} + diff --git a/tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts b/tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts index 958f7f4159c..c3a55207653 100644 --- a/tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts +++ b/tests/cases/conformance/controlFlow/definiteAssignmentAssertions.ts @@ -78,3 +78,7 @@ function f4() { declare let v1!: number; declare var v2!: number; + +declare namespace foo { + var v!: number; +} From ea93ee6db9441eb51a1d816e114b0468363dcd9c Mon Sep 17 00:00:00 2001 From: chenjigeng <178854407@qq.com> Date: Thu, 31 Dec 2020 04:13:56 +0800 Subject: [PATCH 11/18] fix: host.isKnownTypesPackageName maybe undefined (#42050) * fix: host.isKnownTypesPackageName maybe undefined * feat: remove GH#18217 comment --- src/services/codefixes/fixCannotFindModule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/codefixes/fixCannotFindModule.ts b/src/services/codefixes/fixCannotFindModule.ts index a5d44c887b9..482480d8420 100644 --- a/src/services/codefixes/fixCannotFindModule.ts +++ b/src/services/codefixes/fixCannotFindModule.ts @@ -52,6 +52,6 @@ namespace ts.codefix { function getTypesPackageNameToInstall(packageName: string, host: LanguageServiceHost, diagCode: number): string | undefined { return diagCode === errorCodeCannotFindModule ? (JsTyping.nodeCoreModules.has(packageName) ? "@types/node" : undefined) - : (host.isKnownTypesPackageName!(packageName) ? getTypesPackageName(packageName) : undefined); // TODO: GH#18217 + : (host.isKnownTypesPackageName?.(packageName) ? getTypesPackageName(packageName) : undefined); } } From f6287c5997b7e05ebb966a1677a366f1cd2ffc4f Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Thu, 31 Dec 2020 06:32:26 +0000 Subject: [PATCH 12/18] Update package-lock.json --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f89542e04ef..49c4f9af68c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -585,9 +585,9 @@ "dev": true }, "@types/node": { - "version": "14.14.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.16.tgz", - "integrity": "sha512-naXYePhweTi+BMv11TgioE2/FXU4fSl29HAH1ffxVciNsH3rYXjNP2yM8wqmSm7jS20gM8TIklKiTen+1iVncw==", + "version": "14.14.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.17.tgz", + "integrity": "sha512-G0lD1/7qD60TJ/mZmhog76k7NcpLWkPVGgzkRy3CTlnFu4LUQh5v2Wa661z6vnXmD8EQrnALUyf0VRtrACYztw==", "dev": true }, "@types/node-fetch": { From 81fa6ae0f377f2a2417320380a1c6f3021259328 Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Fri, 1 Jan 2021 06:30:57 +0000 Subject: [PATCH 13/18] Update package-lock.json --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 49c4f9af68c..ce0b64f507d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7912,9 +7912,9 @@ "dev": true }, "uglify-js": { - "version": "3.12.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.3.tgz", - "integrity": "sha512-feZzR+kIcSVuLi3s/0x0b2Tx4Iokwqt+8PJM7yRHKuldg4MLdam4TCFeICv+lgDtuYiCtdmrtIP+uN9LWvDasw==", + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.4.tgz", + "integrity": "sha512-L5i5jg/SHkEqzN18gQMTWsZk3KelRsfD1wUVNqtq0kzqWQqcJjyL8yc1o8hJgRrWqrAl2mUFbhfznEIoi7zi2A==", "dev": true, "optional": true }, From a412512fa00376b08d925fcaf5e87773dcbceb63 Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Sat, 2 Jan 2021 06:30:32 +0000 Subject: [PATCH 14/18] Update package-lock.json --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ce0b64f507d..5e8e62d31c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -585,9 +585,9 @@ "dev": true }, "@types/node": { - "version": "14.14.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.17.tgz", - "integrity": "sha512-G0lD1/7qD60TJ/mZmhog76k7NcpLWkPVGgzkRy3CTlnFu4LUQh5v2Wa661z6vnXmD8EQrnALUyf0VRtrACYztw==", + "version": "14.14.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.19.tgz", + "integrity": "sha512-4nhBPStMK04rruRVtVc6cDqhu7S9GZai0fpXgPXrFpcPX6Xul8xnrjSdGB4KPBVYG/R5+fXWdCM8qBoiULWGPQ==", "dev": true }, "@types/node-fetch": { From 15c54fb809343b3e599d59bd49331c86b6777055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20H=C3=BCbotter?= Date: Mon, 4 Jan 2021 10:07:49 +0100 Subject: [PATCH 15/18] Add information on gulp diff to the contributing guidelines (#42031) * Add information on gulp diff to the contributing guidelines * Update CONTRIBUTING.md * Update CONTRIBUTING.md Co-authored-by: Daniel Rosenwasser --- CONTRIBUTING.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 459b0793f8c..95437f807ce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -189,16 +189,25 @@ import { f as g } from "file1"; var x = g(); ``` -## Managing the Baselines +## Managing the baselines -Compiler tests generate baselines: one file each for the emitted `.js`, the errors produced by the compiler, the type of each expression, and symbol for each identifier. Additionally, some tests generate baselines for the source map output. +Most tests generate "baselines" to find differences in output. +As an example, compiler tests usually emit one file each for + +- the `.js` and `.d.ts` output (all in the same `.js` output file), +- the errors produced by the compiler (in an `.errors.txt` file), +- the types of each expression (in a `.types` file), +- the symbols for each identifier (in a `.symbols` file), and +- the source map outputs for files if a test opts into them (in a `.js.map` file). When a change in the baselines is detected, the test will fail. To inspect changes vs the expected baselines, use ```Shell -gulp diff +git diff --diff-filter=AM --no-index ./tests/baselines/reference ./tests/baselines/local ``` +Alternatively, you can set the `DIFF` environment variable and run `gulp diff`, or manually run your favorite folder diffing tool between `tests/baselines/reference` and `tests/baselines/local`. Our team largely uses Beyond Compare and WinMerge. + After verifying that the changes in the baselines are correct, run ```Shell From 562237dfda3ab270a9f91d0e4e4d411ff7bbd844 Mon Sep 17 00:00:00 2001 From: ExE Boss <3889017+ExE-Boss@users.noreply.github.com> Date: Mon, 4 Jan 2021 10:50:34 +0100 Subject: [PATCH 16/18] =?UTF-8?q?fix(lib/es2015):=20Fix=C2=A0types=20of?= =?UTF-8?q?=C2=A0`Reflect`=C2=A0methods=20(#41987)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/es2015.reflect.d.ts | 96 ++++++++++++++++++- ...Library_UsingES5LibAndES6FeatureLibs.types | 6 +- 2 files changed, 95 insertions(+), 7 deletions(-) diff --git a/src/lib/es2015.reflect.d.ts b/src/lib/es2015.reflect.d.ts index 61a864b633f..51a8e3ba908 100644 --- a/src/lib/es2015.reflect.d.ts +++ b/src/lib/es2015.reflect.d.ts @@ -1,15 +1,103 @@ declare namespace Reflect { + /** + * Calls the function with the specified object as the this value + * and the elements of specified array as the arguments. + * @param target The function to call. + * @param thisArgument The object to be used as the this object. + * @param argumentsList An array of argument values to be passed to the function. + */ function apply(target: Function, thisArgument: any, argumentsList: ArrayLike): any; - function construct(target: Function, argumentsList: ArrayLike, newTarget?: any): any; + + /** + * Constructs the target with the elements of specified array as the arguments + * and the specified constructor as the `new.target` value. + * @param target The constructor to invoke. + * @param argumentsList An array of argument values to be passed to the constructor. + * @param newTarget The constructor to be used as the `new.target` object. + */ + function construct(target: Function, argumentsList: ArrayLike, newTarget?: Function): any; + + /** + * Adds a property to an object, or modifies attributes of an existing property. + * @param target Object on which to add or modify the property. This can be a native JavaScript object + * (that is, a user-defined object or a built in object) or a DOM object. + * @param propertyKey The property name. + * @param attributes Descriptor for the property. It can be for a data property or an accessor property. + */ function defineProperty(target: object, propertyKey: PropertyKey, attributes: PropertyDescriptor): boolean; + + /** + * Removes a property from an object, equivalent to `delete target[propertyKey]`, + * except it won't throw if `target[propertyKey]` is non-configurable. + * @param target Object from which to remove the own property. + * @param propertyKey The property name. + */ function deleteProperty(target: object, propertyKey: PropertyKey): boolean; + + /** + * Gets the property of target, equivalent to `target[propertyKey]` when `receiver === target`. + * @param target Object that contains the property on itself or in its prototype chain. + * @param propertyKey The property name. + * @param receiver The reference to use as the `this` value in the getter function, + * if `target[propertyKey]` is an accessor property. + */ function get(target: object, propertyKey: PropertyKey, receiver?: any): any; + + /** + * Gets the own property descriptor of the specified object. + * An own property descriptor is one that is defined directly on the object and is not inherited from the object's prototype. + * @param target Object that contains the property. + * @param propertyKey The property name. + */ function getOwnPropertyDescriptor(target: object, propertyKey: PropertyKey): PropertyDescriptor | undefined; - function getPrototypeOf(target: object): object; + + /** + * Returns the prototype of an object. + * @param target The object that references the prototype. + */ + function getPrototypeOf(target: object): object | null; + + /** + * Equivalent to `propertyKey in target`. + * @param target Object that contains the property on itself or in its prototype chain. + * @param propertyKey Name of the property. + */ function has(target: object, propertyKey: PropertyKey): boolean; + + /** + * Returns a value that indicates whether new properties can be added to an object. + * @param target Object to test. + */ function isExtensible(target: object): boolean; - function ownKeys(target: object): PropertyKey[]; + + /** + * Returns the string and symbol keys of the own properties of an object. The own properties of an object + * are those that are defined directly on that object, and are not inherited from the object's prototype. + * @param target Object that contains the own properties. + */ + function ownKeys(target: object): (string | symbol)[]; + + /** + * Prevents the addition of new properties to an object. + * @param target Object to make non-extensible. + * @return Whether the object has been made non-extensible. + */ function preventExtensions(target: object): boolean; + + /** + * Sets the property of target, equivalent to `target[propertyKey] = value` when `receiver === target`. + * @param target Object that contains the property on itself or in its prototype chain. + * @param propertyKey Name of the property. + * @param receiver The reference to use as the `this` value in the setter function, + * if `target[propertyKey]` is an accessor property. + */ function set(target: object, propertyKey: PropertyKey, value: any, receiver?: any): boolean; - function setPrototypeOf(target: object, proto: any): boolean; + + /** + * Sets the prototype of a specified object o to object proto or null. + * @param target The object to change its prototype. + * @param proto The value of the new prototype or null. + * @return Whether setting the prototype was successful. + */ + function setPrototypeOf(target: object, proto: object | null): boolean; } diff --git a/tests/baselines/reference/modularizeLibrary_UsingES5LibAndES6FeatureLibs.types b/tests/baselines/reference/modularizeLibrary_UsingES5LibAndES6FeatureLibs.types index f18184101cf..0cd06a2591e 100644 --- a/tests/baselines/reference/modularizeLibrary_UsingES5LibAndES6FeatureLibs.types +++ b/tests/baselines/reference/modularizeLibrary_UsingES5LibAndES6FeatureLibs.types @@ -16,10 +16,10 @@ var p = new Proxy(t, {}); >{} : {} Reflect.ownKeys({}); ->Reflect.ownKeys({}) : (string | number | symbol)[] ->Reflect.ownKeys : (target: object) => (string | number | symbol)[] +>Reflect.ownKeys({}) : (string | symbol)[] +>Reflect.ownKeys : (target: object) => (string | symbol)[] >Reflect : typeof Reflect ->ownKeys : (target: object) => (string | number | symbol)[] +>ownKeys : (target: object) => (string | symbol)[] >{} : {} function* idGen() { From 822962e7b3c6dda16683848c0af9cea2f607ea41 Mon Sep 17 00:00:00 2001 From: Brandon Pickering Date: Mon, 4 Jan 2021 02:06:01 -0800 Subject: [PATCH 17/18] Improve documentation for basic array methods (#41961) * Improve documentation for basic array methods * Accept baseline changes for new lines from JSDoc changes Co-authored-by: Orta --- src/lib/es5.d.ts | 46 ++++++++++++------- ...tructuringParameterDeclaration4.errors.txt | 2 +- .../reference/destructuringTuple.errors.txt | 4 +- .../reference/promisePermutations.errors.txt | 2 +- .../reference/promisePermutations2.errors.txt | 2 +- .../reference/promisePermutations3.errors.txt | 4 +- .../reference/redefineArray.errors.txt | 2 +- tests/cases/fourslash/commentsUnion.ts | 2 +- .../completionListOfGenericSymbol.ts | 2 +- 9 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/lib/es5.d.ts b/src/lib/es5.d.ts index 02183314005..0a7f8846ca0 100644 --- a/src/lib/es5.d.ts +++ b/src/lib/es5.d.ts @@ -1199,7 +1199,7 @@ interface ConcatArray { interface Array { /** - * Gets or sets the length of the array. This is a number one higher than the highest element defined in an array. + * Gets or sets the length of the array. This is a number one higher than the highest index in the array. */ length: number; /** @@ -1212,44 +1212,54 @@ interface Array { toLocaleString(): string; /** * Removes the last element from an array and returns it. + * If the array is empty, undefined is returned and the array is not modified. */ pop(): T | undefined; /** - * Appends new elements to an array, and returns the new length of the array. - * @param items New elements of the Array. + * Appends new elements to the end of an array, and returns the new length of the array. + * @param items New elements to add to the array. */ push(...items: T[]): number; /** * Combines two or more arrays. - * @param items Additional items to add to the end of array1. + * This method returns a new array without modifying any existing arrays. + * @param items Additional arrays and/or items to add to the end of the array. */ concat(...items: ConcatArray[]): T[]; /** * Combines two or more arrays. - * @param items Additional items to add to the end of array1. + * This method returns a new array without modifying any existing arrays. + * @param items Additional arrays and/or items to add to the end of the array. */ concat(...items: (T | ConcatArray)[]): T[]; /** - * Adds all the elements of an array separated by the specified separator string. - * @param separator A string used to separate one element of an array from the next in the resulting String. If omitted, the array elements are separated with a comma. + * Adds all the elements of an array into a string, separated by the specified separator string. + * @param separator A string used to separate one element of the array from the next in the resulting string. If omitted, the array elements are separated with a comma. */ join(separator?: string): string; /** - * Reverses the elements in an Array. + * Reverses the elements in an array in place. + * This method mutates the array and returns a reference to the same array. */ reverse(): T[]; /** * Removes the first element from an array and returns it. + * If the array is empty, undefined is returned and the array is not modified. */ shift(): T | undefined; /** - * Returns a section of an array. - * @param start The beginning of the specified portion of the array. - * @param end The end of the specified portion of the array. This is exclusive of the element at the index 'end'. + * Returns a copy of a section of an array. + * For both start and end, a negative index can be used to indicate an offset from the end of the array. + * For example, -2 refers to the second to last element of the array. + * @param start The beginning index of the specified portion of the array. + * If start is undefined, then the slice begins at index 0. + * @param end The end index of the specified portion of the array. This is exclusive of the element at the index 'end'. + * If end is undefined, then the slice extends to the end of the array. */ slice(start?: number, end?: number): T[]; /** - * Sorts an array. + * Sorts an array in place. + * This method mutates the array and returns a reference to the same array. * @param compareFn Function used to determine the order of the elements. It is expected to return * a negative value if first argument is less than second argument, zero if they're equal and a positive * value otherwise. If omitted, the elements are sorted in ascending, ASCII character order. @@ -1262,6 +1272,7 @@ interface Array { * Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements. * @param start The zero-based location in the array from which to start removing elements. * @param deleteCount The number of elements to remove. + * @returns An array containing the elements that were deleted. */ splice(start: number, deleteCount?: number): T[]; /** @@ -1269,23 +1280,24 @@ interface Array { * @param start The zero-based location in the array from which to start removing elements. * @param deleteCount The number of elements to remove. * @param items Elements to insert into the array in place of the deleted elements. + * @returns An array containing the elements that were deleted. */ splice(start: number, deleteCount: number, ...items: T[]): T[]; /** - * Inserts new elements at the start of an array. - * @param items Elements to insert at the start of the Array. + * Inserts new elements at the start of an array, and returns the new length of the array. + * @param items Elements to insert at the start of the array. */ unshift(...items: T[]): number; /** - * Returns the index of the first occurrence of a value in an array. + * Returns the index of the first occurrence of a value in an array, or -1 if it is not present. * @param searchElement The value to locate in the array. * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the search starts at index 0. */ indexOf(searchElement: T, fromIndex?: number): number; /** - * Returns the index of the last occurrence of a specified value in an array. + * Returns the index of the last occurrence of a specified value in an array, or -1 if it is not present. * @param searchElement The value to locate in the array. - * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the search starts at the last index in the array. + * @param fromIndex The array index at which to begin searching backward. If fromIndex is omitted, the search starts at the last index in the array. */ lastIndexOf(searchElement: T, fromIndex?: number): number; /** diff --git a/tests/baselines/reference/destructuringParameterDeclaration4.errors.txt b/tests/baselines/reference/destructuringParameterDeclaration4.errors.txt index d3e198c8714..e7e55ca0e1a 100644 --- a/tests/baselines/reference/destructuringParameterDeclaration4.errors.txt +++ b/tests/baselines/reference/destructuringParameterDeclaration4.errors.txt @@ -41,7 +41,7 @@ tests/cases/conformance/es6/destructuring/destructuringParameterDeclaration4.ts( a1(...array2); // Error parameter type is (number|string)[] ~~~~~~ !!! error TS2552: Cannot find name 'array2'. Did you mean 'Array'? -!!! related TS2728 /.ts/lib.es5.d.ts:1403:13: 'Array' is declared here. +!!! related TS2728 /.ts/lib.es5.d.ts:1415:13: 'Array' is declared here. a5([1, 2, "string", false, true]); // Error, parameter type is [any, any, [[any]]] ~~~~~~~~ !!! error TS2322: Type 'string' is not assignable to type '[[any]]'. diff --git a/tests/baselines/reference/destructuringTuple.errors.txt b/tests/baselines/reference/destructuringTuple.errors.txt index 31ffdac6220..7b9785db0f1 100644 --- a/tests/baselines/reference/destructuringTuple.errors.txt +++ b/tests/baselines/reference/destructuringTuple.errors.txt @@ -33,8 +33,8 @@ tests/cases/compiler/destructuringTuple.ts(11,60): error TS2769: No overload mat !!! error TS2769: Overload 2 of 3, '(callbackfn: (previousValue: [], currentValue: number, currentIndex: number, array: number[]) => [], initialValue: []): []', gave the following error. !!! error TS2769: Type 'never[]' is not assignable to type '[]'. !!! error TS2769: Target allows only 0 element(s) but source may have more. -!!! related TS6502 /.ts/lib.es5.d.ts:1368:24: The expected type comes from the return type of this signature. -!!! related TS6502 /.ts/lib.es5.d.ts:1374:27: The expected type comes from the return type of this signature. +!!! related TS6502 /.ts/lib.es5.d.ts:1380:24: The expected type comes from the return type of this signature. +!!! related TS6502 /.ts/lib.es5.d.ts:1386:27: The expected type comes from the return type of this signature. ~~ !!! error TS2769: No overload matches this call. !!! error TS2769: Overload 1 of 2, '(...items: ConcatArray[]): never[]', gave the following error. diff --git a/tests/baselines/reference/promisePermutations.errors.txt b/tests/baselines/reference/promisePermutations.errors.txt index 5554fb9afcf..bcceb3790cc 100644 --- a/tests/baselines/reference/promisePermutations.errors.txt +++ b/tests/baselines/reference/promisePermutations.errors.txt @@ -447,7 +447,7 @@ tests/cases/compiler/promisePermutations.ts(160,21): error TS2769: No overload m !!! error TS2769: The last overload gave the following error. !!! error TS2769: Argument of type '(x: any) => IPromise' is not assignable to parameter of type '(error: any) => Promise'. !!! error TS2769: Property 'catch' is missing in type 'IPromise' but required in type 'Promise'. -!!! related TS2728 /.ts/lib.es5.d.ts:1448:5: 'catch' is declared here. +!!! related TS2728 /.ts/lib.es5.d.ts:1460:5: 'catch' is declared here. !!! related TS2771 tests/cases/compiler/promisePermutations.ts:5:5: The last overload is declared here. var s10g = s10.then(testFunctionP, nIPromise, sIPromise).then(sPromise, sIPromise, sIPromise); // ok diff --git a/tests/baselines/reference/promisePermutations2.errors.txt b/tests/baselines/reference/promisePermutations2.errors.txt index c8b2e09ba0b..053bc3c1a87 100644 --- a/tests/baselines/reference/promisePermutations2.errors.txt +++ b/tests/baselines/reference/promisePermutations2.errors.txt @@ -351,7 +351,7 @@ tests/cases/compiler/promisePermutations2.ts(159,21): error TS2345: Argument of ~~~~~~~~~ !!! error TS2345: Argument of type '(x: any) => IPromise' is not assignable to parameter of type '(error: any) => Promise'. !!! error TS2345: Property 'catch' is missing in type 'IPromise' but required in type 'Promise'. -!!! related TS2728 /.ts/lib.es5.d.ts:1448:5: 'catch' is declared here. +!!! related TS2728 /.ts/lib.es5.d.ts:1460:5: 'catch' is declared here. var s10g = s10.then(testFunctionP, nIPromise, sIPromise).then(sPromise, sIPromise, sIPromise); // ok var r11: IPromise; diff --git a/tests/baselines/reference/promisePermutations3.errors.txt b/tests/baselines/reference/promisePermutations3.errors.txt index f59d1f678b3..3303c1e3a09 100644 --- a/tests/baselines/reference/promisePermutations3.errors.txt +++ b/tests/baselines/reference/promisePermutations3.errors.txt @@ -398,7 +398,7 @@ tests/cases/compiler/promisePermutations3.ts(165,21): error TS2345: Argument of !!! error TS2769: The last overload gave the following error. !!! error TS2769: Argument of type '(x: any) => IPromise' is not assignable to parameter of type '(error: any) => Promise'. !!! error TS2769: Property 'catch' is missing in type 'IPromise' but required in type 'Promise'. -!!! related TS2728 /.ts/lib.es5.d.ts:1448:5: 'catch' is declared here. +!!! related TS2728 /.ts/lib.es5.d.ts:1460:5: 'catch' is declared here. !!! related TS2771 tests/cases/compiler/promisePermutations3.ts:7:5: The last overload is declared here. var s10g = s10.then(testFunctionP, nIPromise, sIPromise).then(sPromise, sIPromise, sIPromise); // ok @@ -445,5 +445,5 @@ tests/cases/compiler/promisePermutations3.ts(165,21): error TS2345: Argument of ~~~~~~~~~~~~~~~ !!! error TS2345: Argument of type '{ (x: T): IPromise; (x: T, y: T): Promise; }' is not assignable to parameter of type '(value: (x: any) => any) => Promise'. !!! error TS2345: Property 'catch' is missing in type 'IPromise' but required in type 'Promise'. -!!! related TS2728 /.ts/lib.es5.d.ts:1448:5: 'catch' is declared here. +!!! related TS2728 /.ts/lib.es5.d.ts:1460:5: 'catch' is declared here. var s12c = s12.then(testFunction12P, testFunction12, testFunction12); // ok \ No newline at end of file diff --git a/tests/baselines/reference/redefineArray.errors.txt b/tests/baselines/reference/redefineArray.errors.txt index 46d6bd02e86..e59c0e9e24a 100644 --- a/tests/baselines/reference/redefineArray.errors.txt +++ b/tests/baselines/reference/redefineArray.errors.txt @@ -5,4 +5,4 @@ tests/cases/compiler/redefineArray.ts(1,1): error TS2741: Property 'isArray' is Array = function (n:number, s:string) {return n;}; ~~~~~ !!! error TS2741: Property 'isArray' is missing in type '(n: number, s: string) => number' but required in type 'ArrayConstructor'. -!!! related TS2728 /.ts/lib.es5.d.ts:1399:5: 'isArray' is declared here. \ No newline at end of file +!!! related TS2728 /.ts/lib.es5.d.ts:1411:5: 'isArray' is declared here. \ No newline at end of file diff --git a/tests/cases/fourslash/commentsUnion.ts b/tests/cases/fourslash/commentsUnion.ts index 616da3ea9d2..cee57c7379d 100644 --- a/tests/cases/fourslash/commentsUnion.ts +++ b/tests/cases/fourslash/commentsUnion.ts @@ -3,4 +3,4 @@ ////var a: Array | Array; ////a./*1*/length -verify.quickInfoAt("1", "(property) Array.length: number", "Gets or sets the length of the array. This is a number one higher than the highest element defined in an array."); \ No newline at end of file +verify.quickInfoAt("1", "(property) Array.length: number", "Gets or sets the length of the array. This is a number one higher than the highest index in the array."); diff --git a/tests/cases/fourslash/completionListOfGenericSymbol.ts b/tests/cases/fourslash/completionListOfGenericSymbol.ts index 380407d2390..6d17cd06d15 100644 --- a/tests/cases/fourslash/completionListOfGenericSymbol.ts +++ b/tests/cases/fourslash/completionListOfGenericSymbol.ts @@ -11,7 +11,7 @@ verify.completions({ { name: "length", text: "(property) Array.length: number", - documentation: "Gets or sets the length of the array. This is a number one higher than the highest element defined in an array.", + documentation: "Gets or sets the length of the array. This is a number one higher than the highest index in the array.", kind: "property", kindModifiers: "declare", }, From f1dca6a61f577bfb83dbc91d2e064057880df209 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Mon, 4 Jan 2021 17:33:26 +0200 Subject: [PATCH 18/18] fix(42019): include the jsdoc tags from the base declaration (#42098) --- src/compiler/utilities.ts | 2 +- src/services/services.ts | 36 ++--- .../reference/quickInfoJsDocTags1.baseline | 110 ++++++++++++++ .../reference/quickInfoJsDocTags3.baseline | 94 ++++++++++++ .../reference/quickInfoJsDocTags4.baseline | 134 +++++++++++++++++ .../reference/quickInfoJsDocTags5.js | 134 +++++++++++++++++ .../reference/quickInfoJsDocTags6.js | 137 ++++++++++++++++++ tests/cases/fourslash/quickInfoJsDocTags.ts | 18 --- tests/cases/fourslash/quickInfoJsDocTags1.ts | 25 +++- tests/cases/fourslash/quickInfoJsDocTags2.ts | 7 + tests/cases/fourslash/quickInfoJsDocTags3.ts | 22 +++ tests/cases/fourslash/quickInfoJsDocTags4.ts | 25 ++++ tests/cases/fourslash/quickInfoJsDocTags5.ts | 28 ++++ tests/cases/fourslash/quickInfoJsDocTags6.ts | 29 ++++ 14 files changed, 758 insertions(+), 43 deletions(-) create mode 100644 tests/baselines/reference/quickInfoJsDocTags1.baseline create mode 100644 tests/baselines/reference/quickInfoJsDocTags3.baseline create mode 100644 tests/baselines/reference/quickInfoJsDocTags4.baseline create mode 100644 tests/baselines/reference/quickInfoJsDocTags5.js create mode 100644 tests/baselines/reference/quickInfoJsDocTags6.js delete mode 100644 tests/cases/fourslash/quickInfoJsDocTags.ts create mode 100644 tests/cases/fourslash/quickInfoJsDocTags2.ts create mode 100644 tests/cases/fourslash/quickInfoJsDocTags3.ts create mode 100644 tests/cases/fourslash/quickInfoJsDocTags4.ts create mode 100644 tests/cases/fourslash/quickInfoJsDocTags5.ts create mode 100644 tests/cases/fourslash/quickInfoJsDocTags6.ts diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 12b815995cf..a50ca22095d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2992,7 +2992,7 @@ namespace ts { } export function getEffectiveImplementsTypeNodes(node: ClassLikeDeclaration): undefined | readonly ExpressionWithTypeArguments[]{ - if(isInJSFile(node)) { + if (isInJSFile(node)) { return getJSDocImplementsTags(node).map(n => n.class); } else { diff --git a/src/services/services.ts b/src/services/services.ts index e2ef969a7b2..3b4ce096834 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -550,9 +550,8 @@ namespace ts { getJsDocTags(): JSDocTagInfo[] { if (this.jsDocTags === undefined) { - this.jsDocTags = this.declaration ? JsDoc.getJsDocTagsFromDeclarations([this.declaration]) : []; + this.jsDocTags = this.declaration ? getJsDocTags([this.declaration], this.checker) : []; } - return this.jsDocTags; } } @@ -566,13 +565,26 @@ namespace ts { return getJSDocTags(node).some(tag => tag.tagName.text === "inheritDoc"); } + function getJsDocTags(declarations: Declaration[], checker: TypeChecker): JSDocTagInfo[] { + let tags = JsDoc.getJsDocTagsFromDeclarations(declarations); + if (tags.length === 0 || declarations.some(hasJSDocInheritDocTag)) { + forEachUnique(declarations, declaration => { + const inheritedTags = findBaseOfDeclaration(checker, declaration, symbol => symbol.getJsDocTags()); + if (inheritedTags) { + tags = [...inheritedTags, ...tags]; + } + }); + } + return tags; + } + function getDocumentationComment(declarations: readonly Declaration[] | undefined, checker: TypeChecker | undefined): SymbolDisplayPart[] { if (!declarations) return emptyArray; let doc = JsDoc.getJsDocCommentsFromDeclarations(declarations); - if (doc.length === 0 || declarations.some(hasJSDocInheritDocTag)) { + if (checker && (doc.length === 0 || declarations.some(hasJSDocInheritDocTag))) { forEachUnique(declarations, declaration => { - const inheritedDocs = findInheritedJSDocComments(declaration, declaration.symbol.name, checker!); // TODO: GH#18217 + const inheritedDocs = findBaseOfDeclaration(checker, declaration, symbol => symbol.getDocumentationComment(checker)); // TODO: GH#16312 Return a ReadonlyArray, avoid copying inheritedDocs if (inheritedDocs) doc = doc.length === 0 ? inheritedDocs.slice() : inheritedDocs.concat(lineBreakPart(), doc); }); @@ -580,20 +592,10 @@ namespace ts { return doc; } - /** - * Attempts to find JSDoc comments for possibly-inherited properties. Checks superclasses then traverses - * implemented interfaces until a symbol is found with the same name and with documentation. - * @param declaration The possibly-inherited declaration to find comments for. - * @param propertyName The name of the possibly-inherited property. - * @param typeChecker A TypeChecker, used to find inherited properties. - * @returns A filled array of documentation comments if any were found, otherwise an empty array. - */ - function findInheritedJSDocComments(declaration: Declaration, propertyName: string, typeChecker: TypeChecker): readonly SymbolDisplayPart[] | undefined { + function findBaseOfDeclaration(checker: TypeChecker, declaration: Declaration, cb: (symbol: Symbol) => T[]): T[] | undefined { return firstDefined(declaration.parent ? getAllSuperTypeNodes(declaration.parent) : emptyArray, superTypeNode => { - const superType = typeChecker.getTypeAtLocation(superTypeNode); - const baseProperty = superType && typeChecker.getPropertyOfType(superType, propertyName); - const inheritedDocs = baseProperty && baseProperty.getDocumentationComment(typeChecker); - return inheritedDocs && inheritedDocs.length ? inheritedDocs : undefined; + const symbol = checker.getPropertyOfType(checker.getTypeAtLocation(superTypeNode), declaration.symbol.name); + return symbol ? cb(symbol) : undefined; }); } diff --git a/tests/baselines/reference/quickInfoJsDocTags1.baseline b/tests/baselines/reference/quickInfoJsDocTags1.baseline new file mode 100644 index 00000000000..dfb5b740e3c --- /dev/null +++ b/tests/baselines/reference/quickInfoJsDocTags1.baseline @@ -0,0 +1,110 @@ +[ + { + "marker": { + "fileName": "/tests/cases/fourslash/quickInfoJsDocTags1.ts", + "position": 298 + }, + "quickInfo": { + "kind": "function", + "kindModifiers": "", + "textSpan": { + "start": 298, + "length": 3 + }, + "displayParts": [ + { + "text": "function", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "foo", + "kind": "functionName" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "x", + "kind": "parameterName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "any", + "kind": "keyword" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "void", + "kind": "keyword" + } + ], + "documentation": [ + { + "text": "Doc", + "kind": "text" + } + ], + "tags": [ + { + "name": "author", + "text": "Me " + }, + { + "name": "augments", + "text": "C Augments it" + }, + { + "name": "template", + "text": "T A template" + }, + { + "name": "type", + "text": "{number | string} A type" + }, + { + "name": "typedef", + "text": "NumOrStr" + }, + { + "name": "property", + "text": "{number} x The prop" + }, + { + "name": "param", + "text": "x The param" + }, + { + "name": "returns", + "text": "The result" + }, + { + "name": "see", + "text": "x (the parameter)" + } + ] + } + } +] \ No newline at end of file diff --git a/tests/baselines/reference/quickInfoJsDocTags3.baseline b/tests/baselines/reference/quickInfoJsDocTags3.baseline new file mode 100644 index 00000000000..454a54169bf --- /dev/null +++ b/tests/baselines/reference/quickInfoJsDocTags3.baseline @@ -0,0 +1,94 @@ +[ + { + "marker": { + "fileName": "/tests/cases/fourslash/quickInfoJsDocTags3.ts", + "position": 290 + }, + "quickInfo": { + "kind": "method", + "kindModifiers": "", + "textSpan": { + "start": 290, + "length": 6 + }, + "displayParts": [ + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "method", + "kind": "text" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "Bar", + "kind": "className" + }, + { + "text": ".", + "kind": "punctuation" + }, + { + "text": "method", + "kind": "methodName" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "void", + "kind": "keyword" + } + ], + "documentation": [ + { + "text": "comment", + "kind": "text" + } + ], + "tags": [ + { + "name": "author", + "text": "Me " + }, + { + "name": "see", + "text": "x (the parameter)" + }, + { + "name": "param", + "text": "x - x comment" + }, + { + "name": "param", + "text": "y - y comment" + }, + { + "name": "throws", + "text": "{Error} comment" + } + ] + } + } +] \ No newline at end of file diff --git a/tests/baselines/reference/quickInfoJsDocTags4.baseline b/tests/baselines/reference/quickInfoJsDocTags4.baseline new file mode 100644 index 00000000000..45d4cf54d33 --- /dev/null +++ b/tests/baselines/reference/quickInfoJsDocTags4.baseline @@ -0,0 +1,134 @@ +[ + { + "marker": { + "fileName": "/tests/cases/fourslash/quickInfoJsDocTags4.ts", + "position": 309 + }, + "quickInfo": { + "kind": "method", + "kindModifiers": "", + "textSpan": { + "start": 309, + "length": 6 + }, + "displayParts": [ + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "method", + "kind": "text" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "Bar", + "kind": "className" + }, + { + "text": ".", + "kind": "punctuation" + }, + { + "text": "method", + "kind": "methodName" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "x", + "kind": "parameterName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "number", + "kind": "keyword" + }, + { + "text": ",", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "y", + "kind": "parameterName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "number", + "kind": "keyword" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "number", + "kind": "keyword" + } + ], + "documentation": [ + { + "text": "comment", + "kind": "text" + } + ], + "tags": [ + { + "name": "author", + "text": "Me " + }, + { + "name": "see", + "text": "x (the parameter)" + }, + { + "name": "param", + "text": "x - x comment" + }, + { + "name": "param", + "text": "y - y comment" + }, + { + "name": "returns", + "text": "The result" + } + ] + } + } +] \ No newline at end of file diff --git a/tests/baselines/reference/quickInfoJsDocTags5.js b/tests/baselines/reference/quickInfoJsDocTags5.js new file mode 100644 index 00000000000..f2bfaa7d4e8 --- /dev/null +++ b/tests/baselines/reference/quickInfoJsDocTags5.js @@ -0,0 +1,134 @@ +[ + { + "marker": { + "fileName": "/tests/cases/fourslash/quickInfoJsDocTags5.js", + "position": 285 + }, + "quickInfo": { + "kind": "method", + "kindModifiers": "", + "textSpan": { + "start": 285, + "length": 6 + }, + "displayParts": [ + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "method", + "kind": "text" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "Bar", + "kind": "className" + }, + { + "text": ".", + "kind": "punctuation" + }, + { + "text": "method", + "kind": "methodName" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "x", + "kind": "parameterName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "any", + "kind": "keyword" + }, + { + "text": ",", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "y", + "kind": "parameterName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "any", + "kind": "keyword" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "number", + "kind": "keyword" + } + ], + "documentation": [ + { + "text": "comment", + "kind": "text" + } + ], + "tags": [ + { + "name": "author", + "text": "Me " + }, + { + "name": "see", + "text": "x (the parameter)" + }, + { + "name": "param", + "text": "x - x comment" + }, + { + "name": "param", + "text": "y - y comment" + }, + { + "name": "returns", + "text": "The result" + } + ] + } + } +] \ No newline at end of file diff --git a/tests/baselines/reference/quickInfoJsDocTags6.js b/tests/baselines/reference/quickInfoJsDocTags6.js new file mode 100644 index 00000000000..01acc30d9bc --- /dev/null +++ b/tests/baselines/reference/quickInfoJsDocTags6.js @@ -0,0 +1,137 @@ +[ + { + "marker": { + "fileName": "/tests/cases/fourslash/quickInfoJsDocTags6.js", + "position": 308 + }, + "quickInfo": { + "kind": "method", + "kindModifiers": "", + "textSpan": { + "start": 308, + "length": 6 + }, + "displayParts": [ + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "method", + "kind": "text" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "Bar", + "kind": "className" + }, + { + "text": ".", + "kind": "punctuation" + }, + { + "text": "method", + "kind": "methodName" + }, + { + "text": "(", + "kind": "punctuation" + }, + { + "text": "x", + "kind": "parameterName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "any", + "kind": "keyword" + }, + { + "text": ",", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "y", + "kind": "parameterName" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "any", + "kind": "keyword" + }, + { + "text": ")", + "kind": "punctuation" + }, + { + "text": ":", + "kind": "punctuation" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "number", + "kind": "keyword" + } + ], + "documentation": [ + { + "text": "comment", + "kind": "text" + } + ], + "tags": [ + { + "name": "author", + "text": "Me " + }, + { + "name": "see", + "text": "x (the parameter)" + }, + { + "name": "param", + "text": "x - x comment" + }, + { + "name": "param", + "text": "y - y comment" + }, + { + "name": "returns", + "text": "The result" + }, + { + "name": "inheritDoc" + } + ] + } + } +] \ No newline at end of file diff --git a/tests/cases/fourslash/quickInfoJsDocTags.ts b/tests/cases/fourslash/quickInfoJsDocTags.ts deleted file mode 100644 index 107277b53a4..00000000000 --- a/tests/cases/fourslash/quickInfoJsDocTags.ts +++ /dev/null @@ -1,18 +0,0 @@ -/// - -// @Filename: quickInfoJsDocTags.ts -/////** -//// * Doc -//// * @author Me -//// * @augments {C} Augments it -//// * @template T A template -//// * @type {number | string} A type -//// * @typedef {number | string} NumOrStr -//// * @property {number} x The prop -//// * @param {number} x The param -//// * @returns The result -//// * @see x (the parameter) -//// */ -////function /**/foo(x) {} - -verify.baselineQuickInfo(); diff --git a/tests/cases/fourslash/quickInfoJsDocTags1.ts b/tests/cases/fourslash/quickInfoJsDocTags1.ts index a4af938ad8a..7e68cf23632 100644 --- a/tests/cases/fourslash/quickInfoJsDocTags1.ts +++ b/tests/cases/fourslash/quickInfoJsDocTags1.ts @@ -1,7 +1,18 @@ -/// - -// @Filename: quickInfoJsDocTags1.ts -/////** Doc */ -////const /**/x = 0; - -verify.quickInfoAt("", "const x: 0", "Doc"); +/// + +// @Filename: quickInfoJsDocTags1.ts +/////** +//// * Doc +//// * @author Me +//// * @augments {C} Augments it +//// * @template T A template +//// * @type {number | string} A type +//// * @typedef {number | string} NumOrStr +//// * @property {number} x The prop +//// * @param {number} x The param +//// * @returns The result +//// * @see x (the parameter) +//// */ +////function /**/foo(x) {} + +verify.baselineQuickInfo(); diff --git a/tests/cases/fourslash/quickInfoJsDocTags2.ts b/tests/cases/fourslash/quickInfoJsDocTags2.ts new file mode 100644 index 00000000000..e2cc88de7d3 --- /dev/null +++ b/tests/cases/fourslash/quickInfoJsDocTags2.ts @@ -0,0 +1,7 @@ +/// + +// @Filename: quickInfoJsDocTags2.ts +/////** Doc */ +////const /**/x = 0; + +verify.quickInfoAt("", "const x: 0", "Doc"); diff --git a/tests/cases/fourslash/quickInfoJsDocTags3.ts b/tests/cases/fourslash/quickInfoJsDocTags3.ts new file mode 100644 index 00000000000..7696d41360f --- /dev/null +++ b/tests/cases/fourslash/quickInfoJsDocTags3.ts @@ -0,0 +1,22 @@ +/// + +// @Filename: quickInfoJsDocTags3.ts +////interface Foo { +//// /** +//// * comment +//// * @author Me +//// * @see x (the parameter) +//// * @param {number} x - x comment +//// * @param {number} y - y comment +//// * @throws {Error} comment +//// */ +//// method(x: number, y: number): void; +////} +//// +////class Bar implements Foo { +//// /**/method(): void { +//// throw new Error("Method not implemented."); +//// } +////} + +verify.baselineQuickInfo(); diff --git a/tests/cases/fourslash/quickInfoJsDocTags4.ts b/tests/cases/fourslash/quickInfoJsDocTags4.ts new file mode 100644 index 00000000000..784555da112 --- /dev/null +++ b/tests/cases/fourslash/quickInfoJsDocTags4.ts @@ -0,0 +1,25 @@ +/// + +// @Filename: quickInfoJsDocTags4.ts +////class Foo { +//// /** +//// * comment +//// * @author Me +//// * @see x (the parameter) +//// * @param {number} x - x comment +//// * @param {number} y - y comment +//// * @returns The result +//// */ +//// method(x: number, y: number): number { +//// return x + y; +//// } +////} +//// +////class Bar extends Foo { +//// /**/method(x: number, y: number): number { +//// const res = super.method(x, y) + 100; +//// return res; +//// } +////} + +verify.baselineQuickInfo(); diff --git a/tests/cases/fourslash/quickInfoJsDocTags5.ts b/tests/cases/fourslash/quickInfoJsDocTags5.ts new file mode 100644 index 00000000000..a0dcaa24e51 --- /dev/null +++ b/tests/cases/fourslash/quickInfoJsDocTags5.ts @@ -0,0 +1,28 @@ +/// + +// @noEmit: true +// @allowJs: true + +// @Filename: quickInfoJsDocTags5.js +////class Foo { +//// /** +//// * comment +//// * @author Me +//// * @see x (the parameter) +//// * @param {number} x - x comment +//// * @param {number} y - y comment +//// * @returns The result +//// */ +//// method(x, y) { +//// return x + y; +//// } +////} +//// +////class Bar extends Foo { +//// /**/method(x, y) { +//// const res = super.method(x, y) + 100; +//// return res; +//// } +////} + +verify.baselineQuickInfo(); diff --git a/tests/cases/fourslash/quickInfoJsDocTags6.ts b/tests/cases/fourslash/quickInfoJsDocTags6.ts new file mode 100644 index 00000000000..ecdcc89bd17 --- /dev/null +++ b/tests/cases/fourslash/quickInfoJsDocTags6.ts @@ -0,0 +1,29 @@ +/// + +// @noEmit: true +// @allowJs: true + +// @Filename: quickInfoJsDocTags6.js +////class Foo { +//// /** +//// * comment +//// * @author Me +//// * @see x (the parameter) +//// * @param {number} x - x comment +//// * @param {number} y - y comment +//// * @returns The result +//// */ +//// method(x, y) { +//// return x + y; +//// } +////} +//// +////class Bar extends Foo { +//// /** @inheritDoc */ +//// /**/method(x, y) { +//// const res = super.method(x, y) + 100; +//// return res; +//// } +////} + +verify.baselineQuickInfo();