From 0507978a86880e2ca88bc9fbb9b4b8e6daff4a44 Mon Sep 17 00:00:00 2001 From: "Lyu, Wei Da" Date: Tue, 6 Sep 2022 13:30:58 +0800 Subject: [PATCH 1/2] (fix) add JSDoc before the export keyword in quick-fix --- .../features/CodeActionsProvider.ts | 49 +++++++++++ .../features/CodeActionsProvider.test.ts | 88 +++++++++++++++++++ .../code-actions/codeaction-add-jsdoc.svelte | 14 +++ 3 files changed, 151 insertions(+) create mode 100644 packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-add-jsdoc.svelte diff --git a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts index ca7c7f276..b07049d69 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts @@ -15,6 +15,7 @@ import { Document, getLineAtPosition, isAtEndOfLine, + isInTag, isRangeInTag, mapRangeToOriginal } from '../../../lib/documents'; @@ -355,6 +356,14 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { ); } + if (fix.fixName === 'inferFromUsage') { + originalRange = this.checkAddJsDocCodeActionRange( + snapshot, + originalRange, + document + ); + } + if (originalRange.start.line < 0 || originalRange.end.line < 0) { return undefined; } @@ -690,6 +699,46 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { return TextEdit.insert(position, linesOfNewText.join('\n')); } + private checkAddJsDocCodeActionRange( + snapshot: DocumentSnapshot, + originalRange: Range, + document: Document + ): Range { + if ( + snapshot.scriptKind !== ts.ScriptKind.JS && + snapshot.scriptKind !== ts.ScriptKind.JSX && + !isInTag(originalRange.start, document.scriptInfo) + ) { + return originalRange; + } + + const documentText = document.getText(); + const offset = document.offsetAt(originalRange.start); + const exportKeywordOffset = documentText.lastIndexOf('export', offset); + + // export let a; + if ( + exportKeywordOffset < 0 || + documentText.slice(exportKeywordOffset + 'export'.length, offset).trim() + ) { + return originalRange; + } + + const charBeforeExport = documentText[exportKeywordOffset - 1]; + if ( + (charBeforeExport !== undefined && !charBeforeExport.trim()) || + charBeforeExport === ';' + ) { + const position = document.positionAt(exportKeywordOffset); + return { + start: position, + end: position + }; + } + + return originalRange; + } + private async getLSAndTSDoc(document: Document) { return this.lsAndTsDocResolver.getLSAndTSDoc(document); } diff --git a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts index d3a7de7c2..e6c53bf62 100644 --- a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts @@ -350,6 +350,94 @@ function test(useNewTransformation: boolean) { ]); }); + it('provide quickfix for adding jsDoc type to props', async () => { + const { provider, document } = setup('codeaction-add-jsdoc.svelte'); + const errorRange = Range.create(Position.create(7, 8), Position.create(7, 11)); + + const codeActions = await provider.getCodeActions(document, errorRange, { + diagnostics: [ + { + code: 7034, + message: + "Variable 'abc' implicitly has type 'any' in some locations where its type cannot be determined.", + range: errorRange + } + ] + }); + + const addJsDoc = codeActions.find( + (fix) => fix.title === "Infer type of 'abc' from usage" + ); + + (addJsDoc?.edit?.documentChanges?.[0])?.edits.forEach( + (edit) => (edit.newText = harmonizeNewLines(edit.newText)) + ); + + assert.deepStrictEqual(addJsDoc?.edit, { + documentChanges: [ + { + edits: [ + { + newText: `/**\n${indent} * @type {any}\n${indent} */\n${indent}`, + range: { + start: { character: 4, line: 3 }, + end: { character: 4, line: 3 } + } + } + ], + textDocument: { + uri: getUri('codeaction-add-jsdoc.svelte'), + version: null + } + } + ] + }); + }); + + it('provide quickfix for adding jsDoc type to non props when props exist', async () => { + const { provider, document } = setup('codeaction-add-jsdoc.svelte'); + const errorRange = Range.create(Position.create(9, 8), Position.create(9, 10)); + + const codeActions = await provider.getCodeActions(document, errorRange, { + diagnostics: [ + { + code: 7034, + message: + "Variable 'ab' implicitly has type 'any' in some locations where its type cannot be determined.", + range: errorRange + } + ] + }); + + const addJsDoc = codeActions.find( + (fix) => fix.title === "Infer type of 'ab' from usage" + ); + + (addJsDoc?.edit?.documentChanges?.[0])?.edits.forEach( + (edit) => (edit.newText = harmonizeNewLines(edit.newText)) + ); + + assert.deepStrictEqual(addJsDoc?.edit, { + documentChanges: [ + { + edits: [ + { + newText: `/**\n${indent} * @type {any}\n${indent} */\n${indent}`, + range: { + start: { character: 4, line: 9 }, + end: { character: 4, line: 9 } + } + } + ], + textDocument: { + uri: getUri('codeaction-add-jsdoc.svelte'), + version: null + } + } + ] + }); + }); + it('provides quickfix for component import', async () => { const { provider, document } = setup('codeactions.svelte'); diff --git a/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-add-jsdoc.svelte b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-add-jsdoc.svelte new file mode 100644 index 000000000..468ed4eac --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/code-actions/codeaction-add-jsdoc.svelte @@ -0,0 +1,14 @@ + + +{abc} +{ab} From 2a921392c2ca8c916d48f1b69c0b7770f75c721c Mon Sep 17 00:00:00 2001 From: "Lyu, Wei Da" Date: Mon, 19 Sep 2022 13:37:40 +0800 Subject: [PATCH 2/2] DRY --- .../features/CodeActionsProvider.ts | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts index b07049d69..bfeb4d8f4 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts @@ -6,6 +6,7 @@ import { CodeActionKind, Diagnostic, OptionalVersionedTextDocumentIdentifier, + Position, Range, TextDocumentEdit, TextEdit, @@ -675,18 +676,13 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { originalRange: Range, document: Document, edit: ts.TextChange - ): TextEdit { - const startOffset = document.offsetAt(originalRange.start); - const text = document.getText(); - - // svetlte2tsx removes export in instance script - const insertedAfterExport = text.slice(0, startOffset).trim().endsWith('export'); - - if (!insertedAfterExport) { - return TextEdit.replace(originalRange, edit.newText); + ): TextEdit | null { + if (!isInTag(originalRange.start, document.scriptInfo)) { + return null; } - const position = document.positionAt(text.lastIndexOf('export', startOffset)); + const position = + this.fixPropsCodeActionRange(originalRange.start, document) ?? originalRange.start; // fix the length of trailing indent const linesOfNewText = edit.newText.split('\n'); @@ -699,21 +695,12 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { return TextEdit.insert(position, linesOfNewText.join('\n')); } - private checkAddJsDocCodeActionRange( - snapshot: DocumentSnapshot, - originalRange: Range, - document: Document - ): Range { - if ( - snapshot.scriptKind !== ts.ScriptKind.JS && - snapshot.scriptKind !== ts.ScriptKind.JSX && - !isInTag(originalRange.start, document.scriptInfo) - ) { - return originalRange; - } - + /** + * svelte2tsx removes export in instance script + */ + private fixPropsCodeActionRange(start: Position, document: Document): Position | undefined { const documentText = document.getText(); - const offset = document.offsetAt(originalRange.start); + const offset = document.offsetAt(start); const exportKeywordOffset = documentText.lastIndexOf('export', offset); // export let a; @@ -721,7 +708,7 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { exportKeywordOffset < 0 || documentText.slice(exportKeywordOffset + 'export'.length, offset).trim() ) { - return originalRange; + return; } const charBeforeExport = documentText[exportKeywordOffset - 1]; @@ -729,7 +716,26 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { (charBeforeExport !== undefined && !charBeforeExport.trim()) || charBeforeExport === ';' ) { - const position = document.positionAt(exportKeywordOffset); + return document.positionAt(exportKeywordOffset); + } + } + + private checkAddJsDocCodeActionRange( + snapshot: DocumentSnapshot, + originalRange: Range, + document: Document + ): Range { + if ( + snapshot.scriptKind !== ts.ScriptKind.JS && + snapshot.scriptKind !== ts.ScriptKind.JSX && + !isInTag(originalRange.start, document.scriptInfo) + ) { + return originalRange; + } + + const position = this.fixPropsCodeActionRange(originalRange.start, document); + + if (position) { return { start: position, end: position