From 0b8c038d4a6293613c36427b1334a8d3ea16863c Mon Sep 17 00:00:00 2001 From: Lyu Jason Date: Thu, 28 Jan 2021 17:46:11 +0800 Subject: [PATCH 1/9] blank possibly operator or property access near end of moustache --- packages/svelte2tsx/src/utils/htmlxparser.ts | 44 ++++++++++++++++++- .../samples/editing-mustache/expected.jsx | 3 ++ .../samples/editing-mustache/input.svelte | 3 ++ .../uses-$store-with-increments/expected.tsx | 3 +- .../uses-$store-with-increments/input.svelte | 3 +- 5 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/expected.jsx create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/input.svelte diff --git a/packages/svelte2tsx/src/utils/htmlxparser.ts b/packages/svelte2tsx/src/utils/htmlxparser.ts index 38ae98fbe..1c3ec4a40 100644 --- a/packages/svelte2tsx/src/utils/htmlxparser.ts +++ b/packages/svelte2tsx/src/utils/htmlxparser.ts @@ -103,7 +103,8 @@ export function parseHtmlx(htmlx: string): Node { const deconstructed = blankVerbatimContent(htmlx, verbatimElements); //extract the html content parsed as htmlx this excludes our script and style tags - const svelteHtmlxAst = compiler.parse(deconstructed).html; + const svelteHtmlxAst = compiler.parse(blankPossiblyErrorOperatorOrPropertyAccess(deconstructed)) + .html; //restore our script and style tags as nodes to maintain validity with HTMLx for (const s of verbatimElements) { @@ -113,3 +114,44 @@ export function parseHtmlx(htmlx: string): Node { } return svelteHtmlxAst; } + +const possibleOperatorOrPropertyAccess = [ + '.', + '?', + '*', + '/', + '~', + '=', + '<', + '>', + '!', + '&', + '^', + '|', + ',' +]; + +function blankPossiblyErrorOperatorOrPropertyAccess(htmlx: string) { + let index = htmlx.indexOf('}'); + let lastIndex = 0; + const { length } = htmlx; + + while (index < length && index >= 0) { + let backwardIndex = index - 1; + while (backwardIndex > lastIndex) { + const char = htmlx.charAt(backwardIndex); + if (possibleOperatorOrPropertyAccess.includes(char)) { + htmlx = + htmlx.substring(0, backwardIndex) + ' ' + htmlx.substring(backwardIndex + 1); + } else if (!/\s/.test(char)) { + break; + } + backwardIndex--; + } + + lastIndex = index; + index = htmlx.indexOf('}', index + 1); + } + + return htmlx; +} diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/expected.jsx b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/expected.jsx new file mode 100644 index 000000000..093b38abc --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/expected.jsx @@ -0,0 +1,3 @@ +<>{abc. } +{abc?. } +{abc ?} diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/input.svelte b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/input.svelte new file mode 100644 index 000000000..ea3253ba9 --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/input.svelte @@ -0,0 +1,3 @@ +{abc. } +{abc?. } +{abc ?} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-increments/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-increments/expected.tsx index 71a9f63c4..623d32999 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-increments/expected.tsx +++ b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-increments/expected.tsx @@ -11,7 +11,8 @@ function render() { () => (<> -); + +); return { props: {}, slots: {}, getters: {}, events: {} }} export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-increments/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-increments/input.svelte index c9dbd392a..3e426ad3e 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-increments/input.svelte +++ b/packages/svelte2tsx/test/svelte2tsx/samples/uses-$store-with-increments/input.svelte @@ -6,4 +6,5 @@ - \ No newline at end of file + + From 099a1eb66556e49ea899b26d3591e7ef8a7bbabb Mon Sep 17 00:00:00 2001 From: Lyu Jason Date: Thu, 28 Jan 2021 20:34:16 +0800 Subject: [PATCH 2/9] plus and minus --- packages/svelte2tsx/src/utils/htmlxparser.ts | 19 ++++++++++++++++--- .../samples/editing-mustache/expected.jsx | 3 ++- .../samples/editing-mustache/input.svelte | 1 + 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/svelte2tsx/src/utils/htmlxparser.ts b/packages/svelte2tsx/src/utils/htmlxparser.ts index 1c3ec4a40..3f0be254b 100644 --- a/packages/svelte2tsx/src/utils/htmlxparser.ts +++ b/packages/svelte2tsx/src/utils/htmlxparser.ts @@ -128,7 +128,9 @@ const possibleOperatorOrPropertyAccess = [ '&', '^', '|', - ',' + ',', + '+', + '-' ]; function blankPossiblyErrorOperatorOrPropertyAccess(htmlx: string) { @@ -141,8 +143,19 @@ function blankPossiblyErrorOperatorOrPropertyAccess(htmlx: string) { while (backwardIndex > lastIndex) { const char = htmlx.charAt(backwardIndex); if (possibleOperatorOrPropertyAccess.includes(char)) { - htmlx = - htmlx.substring(0, backwardIndex) + ' ' + htmlx.substring(backwardIndex + 1); + const isPlusOrMinus = char === '+' || char === '-'; + const isIncrementOrDecrement = + isPlusOrMinus && htmlx.charAt(backwardIndex - 1) === char; + + if (isIncrementOrDecrement) { + backwardIndex -= 2; + continue; + } else { + htmlx = + htmlx.substring(0, backwardIndex) + + ' ' + + htmlx.substring(backwardIndex + 1); + } } else if (!/\s/.test(char)) { break; } diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/expected.jsx b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/expected.jsx index 093b38abc..80a57fb03 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/expected.jsx +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/expected.jsx @@ -1,3 +1,4 @@ <>{abc. } {abc?. } -{abc ?} +{abc ?} +{a+} diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/input.svelte b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/input.svelte index ea3253ba9..5571bc9e1 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/input.svelte +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/input.svelte @@ -1,3 +1,4 @@ {abc. } {abc?. } {abc ?} +{a+} From 191b91adea7ca0cce1fc3d1e62e5381f29954bb6 Mon Sep 17 00:00:00 2001 From: Lyu Jason Date: Thu, 28 Jan 2021 20:53:49 +0800 Subject: [PATCH 3/9] use set --- packages/svelte2tsx/src/utils/htmlxparser.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/svelte2tsx/src/utils/htmlxparser.ts b/packages/svelte2tsx/src/utils/htmlxparser.ts index 3f0be254b..a0d969a15 100644 --- a/packages/svelte2tsx/src/utils/htmlxparser.ts +++ b/packages/svelte2tsx/src/utils/htmlxparser.ts @@ -115,7 +115,7 @@ export function parseHtmlx(htmlx: string): Node { return svelteHtmlxAst; } -const possibleOperatorOrPropertyAccess = [ +const possibleOperatorOrPropertyAccess = new Set([ '.', '?', '*', @@ -131,7 +131,7 @@ const possibleOperatorOrPropertyAccess = [ ',', '+', '-' -]; +]); function blankPossiblyErrorOperatorOrPropertyAccess(htmlx: string) { let index = htmlx.indexOf('}'); @@ -142,7 +142,7 @@ function blankPossiblyErrorOperatorOrPropertyAccess(htmlx: string) { let backwardIndex = index - 1; while (backwardIndex > lastIndex) { const char = htmlx.charAt(backwardIndex); - if (possibleOperatorOrPropertyAccess.includes(char)) { + if (possibleOperatorOrPropertyAccess.has(char)) { const isPlusOrMinus = char === '+' || char === '-'; const isIncrementOrDecrement = isPlusOrMinus && htmlx.charAt(backwardIndex - 1) === char; From fcbb5f36b5cb5337f1750c93a7a8e48cd701b9d4 Mon Sep 17 00:00:00 2001 From: Lyu Jason Date: Fri, 29 Jan 2021 14:30:35 +0800 Subject: [PATCH 4/9] no slash, it might be regexr --- packages/svelte2tsx/src/utils/htmlxparser.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/svelte2tsx/src/utils/htmlxparser.ts b/packages/svelte2tsx/src/utils/htmlxparser.ts index a0d969a15..b29c6b2fb 100644 --- a/packages/svelte2tsx/src/utils/htmlxparser.ts +++ b/packages/svelte2tsx/src/utils/htmlxparser.ts @@ -119,7 +119,6 @@ const possibleOperatorOrPropertyAccess = new Set([ '.', '?', '*', - '/', '~', '=', '<', From 12d3dcc8900e12eb0ce191c44e5e427ed2c26c18 Mon Sep 17 00:00:00 2001 From: Lyu Jason Date: Fri, 29 Jan 2021 14:54:26 +0800 Subject: [PATCH 5/9] test for regex --- .../test/htmlx2jsx/samples/editing-mustache/expected.jsx | 3 ++- .../test/htmlx2jsx/samples/editing-mustache/input.svelte | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/expected.jsx b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/expected.jsx index 80a57fb03..469498d7e 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/expected.jsx +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/expected.jsx @@ -1,4 +1,5 @@ <>{abc. } {abc?. } {abc ?} -{a+} +{a+} + diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/input.svelte b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/input.svelte index 5571bc9e1..596ed855d 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/input.svelte +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/input.svelte @@ -2,3 +2,4 @@ {abc?. } {abc ?} {a+} + From a082816148edbc7126693470f8121a0cb2cc5260 Mon Sep 17 00:00:00 2001 From: Lyu Jason Date: Fri, 29 Jan 2021 15:01:11 +0800 Subject: [PATCH 6/9] code style --- packages/svelte2tsx/src/utils/htmlxparser.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/svelte2tsx/src/utils/htmlxparser.ts b/packages/svelte2tsx/src/utils/htmlxparser.ts index b29c6b2fb..2b241c5d5 100644 --- a/packages/svelte2tsx/src/utils/htmlxparser.ts +++ b/packages/svelte2tsx/src/utils/htmlxparser.ts @@ -149,12 +149,9 @@ function blankPossiblyErrorOperatorOrPropertyAccess(htmlx: string) { if (isIncrementOrDecrement) { backwardIndex -= 2; continue; - } else { - htmlx = - htmlx.substring(0, backwardIndex) + - ' ' + - htmlx.substring(backwardIndex + 1); } + htmlx = + htmlx.substring(0, backwardIndex) + ' ' + htmlx.substring(backwardIndex + 1); } else if (!/\s/.test(char)) { break; } From 9b7f089a42a200bf2f18c690cfbc40b6ff6d12a1 Mon Sep 17 00:00:00 2001 From: Lyu Jason Date: Tue, 2 Feb 2021 13:24:32 +0800 Subject: [PATCH 7/9] new config emitOnTemplateError --- packages/svelte2tsx/src/htmlxtojsx/index.ts | 4 +-- packages/svelte2tsx/src/svelte2tsx/index.ts | 16 +++++++++--- packages/svelte2tsx/src/utils/htmlxparser.ts | 8 +++--- packages/svelte2tsx/test/helpers.js | 25 +++++++++++++++--- .../samples/editing-mustache/test.js | 23 ++++++++++++++++ .../samples/editing-mustache/expected.tsx | 7 +++++ .../samples/editing-mustache/input.svelte | 1 + .../samples/editing-mustache/test.js | 26 +++++++++++++++++++ 8 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/test.js create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/editing-mustache/expected.tsx create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/editing-mustache/input.svelte create mode 100644 packages/svelte2tsx/test/svelte2tsx/samples/editing-mustache/test.js diff --git a/packages/svelte2tsx/src/htmlxtojsx/index.ts b/packages/svelte2tsx/src/htmlxtojsx/index.ts index aa1c7b007..2e2171bab 100644 --- a/packages/svelte2tsx/src/htmlxtojsx/index.ts +++ b/packages/svelte2tsx/src/htmlxtojsx/index.ts @@ -138,8 +138,8 @@ export function convertHtmlxToJsx( /** * @internal For testing only */ -export function htmlx2jsx(htmlx: string) { - const ast = parseHtmlx(htmlx); +export function htmlx2jsx(htmlx: string, options?: { emitOnTemplateError?: boolean }) { + const ast = parseHtmlx(htmlx, options); const str = new MagicString(htmlx); convertHtmlxToJsx(str, ast); diff --git a/packages/svelte2tsx/src/svelte2tsx/index.ts b/packages/svelte2tsx/src/svelte2tsx/index.ts index 4c9c15ec6..fdf8a0182 100644 --- a/packages/svelte2tsx/src/svelte2tsx/index.ts +++ b/packages/svelte2tsx/src/svelte2tsx/index.ts @@ -66,8 +66,11 @@ type TemplateProcessResult = { */ const COMPONENT_SUFFIX = '__SvelteComponent_'; -function processSvelteTemplate(str: MagicString): TemplateProcessResult { - const htmlxAst = parseHtmlx(str.original); +function processSvelteTemplate( + str: MagicString, + options?: { emitOnTemplateError?: boolean } +): TemplateProcessResult { + const htmlxAst = parseHtmlx(str.original, options); let uses$$props = false; let uses$$restProps = false; @@ -407,7 +410,12 @@ function createRenderFunction({ export function svelte2tsx( svelte: string, - options?: { filename?: string; strictMode?: boolean; isTsFile?: boolean } + options?: { + filename?: string; + strictMode?: boolean; + isTsFile?: boolean; + emitOnTemplateError?: boolean; + } ) { const str = new MagicString(svelte); // process the htmlx as a svelte template @@ -420,7 +428,7 @@ export function svelte2tsx( uses$$restProps, events, componentDocumentation - } = processSvelteTemplate(str); + } = processSvelteTemplate(str, options); /* Rearrange the script tags so that module is first, and instance second followed finally by the template * This is a bit convoluted due to some trouble I had with magic string. A simple str.move(start,end,0) for each script wasn't enough diff --git a/packages/svelte2tsx/src/utils/htmlxparser.ts b/packages/svelte2tsx/src/utils/htmlxparser.ts index 2b241c5d5..721b0461e 100644 --- a/packages/svelte2tsx/src/utils/htmlxparser.ts +++ b/packages/svelte2tsx/src/utils/htmlxparser.ts @@ -96,15 +96,17 @@ function blankVerbatimContent(htmlx: string, verbatimElements: Node[]) { return output; } -export function parseHtmlx(htmlx: string): Node { +export function parseHtmlx(htmlx: string, options?: { emitOnTemplateError?: boolean }): Node { //Svelte tries to parse style and script tags which doesn't play well with typescript, so we blank them out. //HTMLx spec says they should just be retained after processing as is, so this is fine const verbatimElements = findVerbatimElements(htmlx); const deconstructed = blankVerbatimContent(htmlx, verbatimElements); //extract the html content parsed as htmlx this excludes our script and style tags - const svelteHtmlxAst = compiler.parse(blankPossiblyErrorOperatorOrPropertyAccess(deconstructed)) - .html; + const parsingCode = options?.emitOnTemplateError ? + blankPossiblyErrorOperatorOrPropertyAccess(deconstructed) : + deconstructed; + const svelteHtmlxAst = compiler.parse(parsingCode).html; //restore our script and style tags as nodes to maintain validity with HTMLx for (const s of verbatimElements) { diff --git a/packages/svelte2tsx/test/helpers.js b/packages/svelte2tsx/test/helpers.js index a905bedc6..14cf0a441 100644 --- a/packages/svelte2tsx/test/helpers.js +++ b/packages/svelte2tsx/test/helpers.js @@ -56,11 +56,18 @@ function test_samples(dir, transform, tsx) { const skip = testName.startsWith('.'); check_dir(path, { required: ['*.svelte'], - allowed: ['expected.js', `expected.${tsx}`] + allowed: ['expected.js', `expected.${tsx}`, 'test.js'] }); (skip ? it.skip : solo ? it.only : it)(testName, function () { - const fileName = fs.readdirSync(path).find((f) => f.endsWith('.svelte')); - const output = transform(readFileSync(`${path}/${fileName}`), testName, fileName); + const testJsPath = `${path}/test.js`; + if (fs.existsSync(testJsPath)) { + const test = require(testJsPath); + test(); + return; + } + + const { filename, content } = get_input_content(path); + const output = transform(content, testName, filename); if (!has_expected) { after(() => { fs.writeFileSync(expected_path, output.code); @@ -78,4 +85,14 @@ function test_samples(dir, transform, tsx) { } } -module.exports = { benchmark, test_samples }; +/** + * + * @param {string} dirPath + */ +function get_input_content(dirPath) { + const filename = fs.readdirSync(dirPath).find((f) => f.endsWith('.svelte')); + const content = readFileSync(`${dirPath}/${filename}`) + return { filename, content } +} + +module.exports = { benchmark, test_samples, get_input_content, readFileSync }; diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/test.js b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/test.js new file mode 100644 index 000000000..f3c99ae43 --- /dev/null +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/test.js @@ -0,0 +1,23 @@ +const assert = require('assert'); +const { htmlx2jsx } = require('../../../build/htmlxtojsx'); +const { get_input_content, readFileSync } = require('../../../helpers'); + +module.exports = function() { + const input = get_input_content(__dirname); + + assert.throws(() => { + htmlx2jsx(input.content) + }, { + name: 'ParseError', + code: 'parse-error', + start: { line: 1, column: 8, character: 8 }, + end: { line: 1, column: 8, character: 8 }, + frame: "1: {abc. }\n ^\n2: {abc?. }\n3: {abc ?}" + }) + + const expected_path = `${__dirname}/expected.jsx` + assert.strictEqual( + htmlx2jsx(input.content, { emitOnTemplateError: true }).code, + readFileSync(expected_path).toString() + ) +} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/editing-mustache/expected.tsx b/packages/svelte2tsx/test/svelte2tsx/samples/editing-mustache/expected.tsx new file mode 100644 index 000000000..d3b14b5ec --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/editing-mustache/expected.tsx @@ -0,0 +1,7 @@ +/// +<>;function render() { +<>{a?.} +return { props: {}, slots: {}, getters: {}, events: {} }} + +export default class Input__SvelteComponent_ extends createSvelte2TsxComponent(__sveltets_partial(__sveltets_with_any_event(render))) { +} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/editing-mustache/input.svelte b/packages/svelte2tsx/test/svelte2tsx/samples/editing-mustache/input.svelte new file mode 100644 index 000000000..9b55a4b31 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/editing-mustache/input.svelte @@ -0,0 +1 @@ +{a?.} diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/editing-mustache/test.js b/packages/svelte2tsx/test/svelte2tsx/samples/editing-mustache/test.js new file mode 100644 index 000000000..83fb6bf32 --- /dev/null +++ b/packages/svelte2tsx/test/svelte2tsx/samples/editing-mustache/test.js @@ -0,0 +1,26 @@ +const assert = require('assert'); +const svelte2tsx = require('../../../build/index'); +const { get_input_content, readFileSync } = require('../../../helpers'); + +module.exports = function() { + const input = get_input_content(__dirname); + + assert.throws(() => { + svelte2tsx(input.content) + }, { + name: 'ParseError', + code: 'parse-error', + start: { line: 1, column: 4, character: 4 }, + end: { line: 1, column: 4, character: 4 }, + frame: "1: {a?.}\n ^" + }) + + const expected_path = `${__dirname}/expected.tsx` + assert.strictEqual( + svelte2tsx(input.content, { + emitOnTemplateError: true, + filename: input.filename + }).code, + readFileSync(expected_path).toString() + ) +} From 8b0365cf5252d3e06192826fd89c8416dd7e20be Mon Sep 17 00:00:00 2001 From: Lyu Jason Date: Tue, 2 Feb 2021 14:56:06 +0800 Subject: [PATCH 8/9] use differenct config on svelte-check and editor --- .../plugins/typescript/DocumentSnapshot.ts | 4 +- .../plugins/typescript/LSAndTSDocResolver.ts | 24 ++++++++--- .../plugins/typescript/TypeScriptPlugin.ts | 15 +++++-- .../src/plugins/typescript/service.ts | 40 ++++++++++++------- packages/language-server/src/svelte-check.ts | 9 +++-- .../features/CompletionProvider.test.ts | 31 ++++++++++++-- .../testfiles/completions/mustache.svelte | 6 +++ packages/svelte2tsx/index.d.ts | 4 ++ 8 files changed, 102 insertions(+), 31 deletions(-) create mode 100644 packages/language-server/test/plugins/typescript/testfiles/completions/mustache.svelte diff --git a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts index 7038e987a..7214b2613 100644 --- a/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts +++ b/packages/language-server/src/plugins/typescript/DocumentSnapshot.ts @@ -73,6 +73,7 @@ export interface SnapshotFragment extends DocumentMapper { */ export interface SvelteSnapshotOptions { strictMode: boolean; + transformOnTemplateError: boolean; } export namespace DocumentSnapshot { @@ -142,7 +143,8 @@ function preprocessSvelteFile(document: Document, options: SvelteSnapshotOptions const tsx = svelte2tsx(text, { strictMode: options.strictMode, filename: document.getFilePath() ?? undefined, - isTsFile: scriptKind === ts.ScriptKind.TSX + isTsFile: scriptKind === ts.ScriptKind.TSX, + emitOnTemplateError: options.transformOnTemplateError }); text = tsx.code; tsxMap = tsx.map; diff --git a/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts b/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts index 87d15b7d5..a12e7f932 100644 --- a/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts +++ b/packages/language-server/src/plugins/typescript/LSAndTSDocResolver.ts @@ -7,7 +7,8 @@ import { getLanguageServiceForDocument, getLanguageServiceForPath, getService, - LanguageServiceContainer + LanguageServiceContainer, + LanguageServiceDocumentContext } from './service'; import { SnapshotManager } from './SnapshotManager'; @@ -15,7 +16,8 @@ export class LSAndTSDocResolver { constructor( private readonly docManager: DocumentManager, private readonly workspaceUris: string[], - private readonly configManager: LSConfigManager + private readonly configManager: LSConfigManager, + private readonly transformOnTemplateError = true ) { docManager.on( 'documentChange', @@ -43,8 +45,15 @@ export class LSAndTSDocResolver { return document; }; + private get lsDocumentContext(): LanguageServiceDocumentContext { + return { + createDocument: this.createDocument, + transformOnTemplateError: this.transformOnTemplateError + }; + } + getLSForPath(path: string) { - return getLanguageServiceForPath(path, this.workspaceUris, this.createDocument); + return getLanguageServiceForPath(path, this.workspaceUris, this.lsDocumentContext); } getLSAndTSDoc( @@ -57,7 +66,7 @@ export class LSAndTSDocResolver { const lang = getLanguageServiceForDocument( document, this.workspaceUris, - this.createDocument + this.lsDocumentContext ); const filePath = document.getFilePath()!; const tsDoc = this.getSnapshot(filePath, document); @@ -74,7 +83,10 @@ export class LSAndTSDocResolver { let tsDoc = snapshotManager.get(filePath); if (!tsDoc) { - const options = { strictMode: !!tsService.compilerOptions.strict }; + const options = { + strictMode: !!tsService.compilerOptions.strict, + transformOnTemplateError: this.transformOnTemplateError + }; tsDoc = document ? DocumentSnapshot.fromDocument(document, options) : DocumentSnapshot.fromFilePath(filePath, options); @@ -99,7 +111,7 @@ export class LSAndTSDocResolver { } private getTSService(filePath: string): LanguageServiceContainer { - return getService(filePath, this.workspaceUris, this.createDocument); + return getService(filePath, this.workspaceUris, this.lsDocumentContext); } private getUserPreferences(scriptKind: ts.ScriptKind): ts.UserPreferences { diff --git a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts index b72033b9e..a132da24b 100644 --- a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts +++ b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts @@ -100,10 +100,16 @@ export class TypeScriptPlugin constructor( docManager: DocumentManager, configManager: LSConfigManager, - workspaceUris: string[] + workspaceUris: string[], + isEditor = true ) { this.configManager = configManager; - this.lsAndTsDocResolver = new LSAndTSDocResolver(docManager, workspaceUris, configManager); + this.lsAndTsDocResolver = new LSAndTSDocResolver( + docManager, + workspaceUris, + configManager, + /**transformOnTemplateError */isEditor + ); this.completionProvider = new CompletionsProviderImpl(this.lsAndTsDocResolver); this.codeActionsProvider = new CodeActionsProviderImpl( this.lsAndTsDocResolver, @@ -381,7 +387,10 @@ export class TypeScriptPlugin // Since the options parameter only applies to svelte snapshots, and this is not // a svelte file, we can just set it to false without having any effect. - snapshotManager.updateByFileName(fileName, { strictMode: false }); + snapshotManager.updateByFileName(fileName, { + strictMode: false, + transformOnTemplateError: false + }); } } diff --git a/packages/language-server/src/plugins/typescript/service.ts b/packages/language-server/src/plugins/typescript/service.ts index 933a7f8ad..f2f70e418 100644 --- a/packages/language-server/src/plugins/typescript/service.ts +++ b/packages/language-server/src/plugins/typescript/service.ts @@ -19,27 +19,34 @@ export interface LanguageServiceContainer { const services = new Map(); -export type CreateDocument = (fileName: string, content: string) => Document; +export interface LanguageServiceDocumentContext { + transformOnTemplateError: boolean; + createDocument: (fileName: string, content: string) => Document; +} export function getLanguageServiceForPath( path: string, workspaceUris: string[], - createDocument: CreateDocument + docContext: LanguageServiceDocumentContext ): ts.LanguageService { - return getService(path, workspaceUris, createDocument).getService(); + return getService(path, workspaceUris, docContext).getService(); } export function getLanguageServiceForDocument( document: Document, workspaceUris: string[], - createDocument: CreateDocument + docContext: LanguageServiceDocumentContext ): ts.LanguageService { - return getService(document.getFilePath() || '', workspaceUris, createDocument).updateDocument( + return getService(document.getFilePath() || '', workspaceUris, docContext).updateDocument( document ); } -export function getService(path: string, workspaceUris: string[], createDocument: CreateDocument) { +export function getService( + path: string, + workspaceUris: string[], + docContext: LanguageServiceDocumentContext +) { const tsconfigPath = findTsConfigPath(path, workspaceUris); let service: LanguageServiceContainer; @@ -47,7 +54,7 @@ export function getService(path: string, workspaceUris: string[], createDocument service = services.get(tsconfigPath)!; } else { Logger.log('Initialize new ts service at ', tsconfigPath); - service = createLanguageService(tsconfigPath, createDocument); + service = createLanguageService(tsconfigPath, docContext); services.set(tsconfigPath, service); } @@ -56,7 +63,7 @@ export function getService(path: string, workspaceUris: string[], createDocument export function createLanguageService( tsconfigPath: string, - createDocument: CreateDocument + docContext: LanguageServiceDocumentContext ): LanguageServiceContainer { const workspacePath = tsconfigPath ? dirname(tsconfigPath) : ''; @@ -105,6 +112,10 @@ export function createLanguageService( getScriptKind: (fileName: string) => getSnapshot(fileName).scriptKind }; let languageService = ts.createLanguageService(host); + const transformationConfig = { + strictMode: !!compilerOptions.strict, + transformOnTemplateError: docContext.transformOnTemplateError + }; return { tsconfigPath, @@ -128,9 +139,7 @@ export function createLanguageService( return languageService; } - const newSnapshot = DocumentSnapshot.fromDocument(document, { - strictMode: !!compilerOptions.strict - }); + const newSnapshot = DocumentSnapshot.fromDocument(document, transformationConfig); if (preSnapshot && preSnapshot.scriptKind !== newSnapshot.scriptKind) { // Restart language service as it doesn't handle script kind changes. languageService.dispose(); @@ -151,11 +160,12 @@ export function createLanguageService( if (isSvelteFilePath(fileName)) { const file = ts.sys.readFile(fileName) || ''; - doc = DocumentSnapshot.fromDocument(createDocument(fileName, file), { - strictMode: !!compilerOptions.strict - }); + doc = DocumentSnapshot.fromDocument( + docContext.createDocument(fileName, file), + transformationConfig + ); } else { - doc = DocumentSnapshot.fromFilePath(fileName, { strictMode: !!compilerOptions.strict }); + doc = DocumentSnapshot.fromFilePath(fileName, transformationConfig); } snapshotManager.set(fileName, doc); diff --git a/packages/language-server/src/svelte-check.ts b/packages/language-server/src/svelte-check.ts index 90c0e3bd9..e7b00d812 100644 --- a/packages/language-server/src/svelte-check.ts +++ b/packages/language-server/src/svelte-check.ts @@ -43,9 +43,12 @@ export class SvelteCheck { } if (shouldRegister('js')) { this.pluginHost.register( - new TypeScriptPlugin(this.docManager, this.configManager, [ - pathToUrl(workspacePath) - ]) + new TypeScriptPlugin( + this.docManager, + this.configManager, + [pathToUrl(workspacePath)], + /**isEditor */ false + ) ); } diff --git a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts index 3c9997bd9..9fdda750d 100644 --- a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts @@ -67,10 +67,10 @@ describe('CompletionProviderImpl', () => { ); assert.ok(completions!.items.length > 0, 'Expected completions to have length'); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { data, ...withoutData } = completions!.items[0]; + const first = completions!.items[0]; + delete first.data; - assert.deepStrictEqual(withoutData, { + assert.deepStrictEqual(first, { label: 'b', insertText: undefined, kind: CompletionItemKind.Method, @@ -80,6 +80,31 @@ describe('CompletionProviderImpl', () => { }); }); + it('provides completions on simple property access in mustache', async () => { + const { completionProvider, document } = setup('mustache.svelte'); + + const completions = await completionProvider.getCompletions( + document, + Position.create(5, 3), + { + triggerKind: CompletionTriggerKind.TriggerCharacter, + triggerCharacter: '.' + } + ); + + const first = completions!.items[0]; + delete first.data; + + assert.deepStrictEqual(first, { + label: 'b', + insertText: undefined, + kind: CompletionItemKind.Field, + sortText: '1', + commitCharacters: ['.', ',', '('], + preselect: undefined + }); + }); + it('provides event completions', async () => { const { completionProvider, document } = setup('component-events-completion.svelte'); diff --git a/packages/language-server/test/plugins/typescript/testfiles/completions/mustache.svelte b/packages/language-server/test/plugins/typescript/testfiles/completions/mustache.svelte new file mode 100644 index 000000000..38c574bb0 --- /dev/null +++ b/packages/language-server/test/plugins/typescript/testfiles/completions/mustache.svelte @@ -0,0 +1,6 @@ + +{a?.} +{b.} diff --git a/packages/svelte2tsx/index.d.ts b/packages/svelte2tsx/index.d.ts index b2a577671..e0a277835 100644 --- a/packages/svelte2tsx/index.d.ts +++ b/packages/svelte2tsx/index.d.ts @@ -32,5 +32,9 @@ export default function svelte2tsx( * `svelte-preprocess`. */ isTsFile?: boolean; + /** + * Whether to try emitting result when there's a syntax error in the template + */ + emitOnTemplateError?: boolean; } ): SvelteCompiledToTsx From a70f325dc8645e5002396a27b5ed4f0129926b50 Mon Sep 17 00:00:00 2001 From: Lyu Jason Date: Tue, 2 Feb 2021 15:00:11 +0800 Subject: [PATCH 9/9] format --- .../plugins/typescript/TypeScriptPlugin.ts | 2 +- packages/svelte2tsx/src/utils/htmlxparser.ts | 6 ++-- packages/svelte2tsx/test/helpers.js | 4 +-- .../samples/editing-mustache/test.js | 29 ++++++++++--------- .../samples/editing-mustache/test.js | 29 ++++++++++--------- 5 files changed, 38 insertions(+), 32 deletions(-) diff --git a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts index a132da24b..5831e91e4 100644 --- a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts +++ b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts @@ -108,7 +108,7 @@ export class TypeScriptPlugin docManager, workspaceUris, configManager, - /**transformOnTemplateError */isEditor + /**transformOnTemplateError */ isEditor ); this.completionProvider = new CompletionsProviderImpl(this.lsAndTsDocResolver); this.codeActionsProvider = new CodeActionsProviderImpl( diff --git a/packages/svelte2tsx/src/utils/htmlxparser.ts b/packages/svelte2tsx/src/utils/htmlxparser.ts index 721b0461e..d5fa342d9 100644 --- a/packages/svelte2tsx/src/utils/htmlxparser.ts +++ b/packages/svelte2tsx/src/utils/htmlxparser.ts @@ -103,9 +103,9 @@ export function parseHtmlx(htmlx: string, options?: { emitOnTemplateError?: bool const deconstructed = blankVerbatimContent(htmlx, verbatimElements); //extract the html content parsed as htmlx this excludes our script and style tags - const parsingCode = options?.emitOnTemplateError ? - blankPossiblyErrorOperatorOrPropertyAccess(deconstructed) : - deconstructed; + const parsingCode = options?.emitOnTemplateError + ? blankPossiblyErrorOperatorOrPropertyAccess(deconstructed) + : deconstructed; const svelteHtmlxAst = compiler.parse(parsingCode).html; //restore our script and style tags as nodes to maintain validity with HTMLx diff --git a/packages/svelte2tsx/test/helpers.js b/packages/svelte2tsx/test/helpers.js index 14cf0a441..9d5b81b72 100644 --- a/packages/svelte2tsx/test/helpers.js +++ b/packages/svelte2tsx/test/helpers.js @@ -91,8 +91,8 @@ function test_samples(dir, transform, tsx) { */ function get_input_content(dirPath) { const filename = fs.readdirSync(dirPath).find((f) => f.endsWith('.svelte')); - const content = readFileSync(`${dirPath}/${filename}`) - return { filename, content } + const content = readFileSync(`${dirPath}/${filename}`); + return { filename, content }; } module.exports = { benchmark, test_samples, get_input_content, readFileSync }; diff --git a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/test.js b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/test.js index f3c99ae43..970b8e422 100644 --- a/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/test.js +++ b/packages/svelte2tsx/test/htmlx2jsx/samples/editing-mustache/test.js @@ -2,22 +2,25 @@ const assert = require('assert'); const { htmlx2jsx } = require('../../../build/htmlxtojsx'); const { get_input_content, readFileSync } = require('../../../helpers'); -module.exports = function() { +module.exports = function () { const input = get_input_content(__dirname); - assert.throws(() => { - htmlx2jsx(input.content) - }, { - name: 'ParseError', - code: 'parse-error', - start: { line: 1, column: 8, character: 8 }, - end: { line: 1, column: 8, character: 8 }, - frame: "1: {abc. }\n ^\n2: {abc?. }\n3: {abc ?}" - }) + assert.throws( + () => { + htmlx2jsx(input.content); + }, + { + name: 'ParseError', + code: 'parse-error', + start: { line: 1, column: 8, character: 8 }, + end: { line: 1, column: 8, character: 8 }, + frame: '1: {abc. }\n ^\n2: {abc?. }\n3: {abc ?}' + } + ); - const expected_path = `${__dirname}/expected.jsx` + const expected_path = `${__dirname}/expected.jsx`; assert.strictEqual( htmlx2jsx(input.content, { emitOnTemplateError: true }).code, readFileSync(expected_path).toString() - ) -} + ); +}; diff --git a/packages/svelte2tsx/test/svelte2tsx/samples/editing-mustache/test.js b/packages/svelte2tsx/test/svelte2tsx/samples/editing-mustache/test.js index 83fb6bf32..429136706 100644 --- a/packages/svelte2tsx/test/svelte2tsx/samples/editing-mustache/test.js +++ b/packages/svelte2tsx/test/svelte2tsx/samples/editing-mustache/test.js @@ -2,25 +2,28 @@ const assert = require('assert'); const svelte2tsx = require('../../../build/index'); const { get_input_content, readFileSync } = require('../../../helpers'); -module.exports = function() { +module.exports = function () { const input = get_input_content(__dirname); - assert.throws(() => { - svelte2tsx(input.content) - }, { - name: 'ParseError', - code: 'parse-error', - start: { line: 1, column: 4, character: 4 }, - end: { line: 1, column: 4, character: 4 }, - frame: "1: {a?.}\n ^" - }) + assert.throws( + () => { + svelte2tsx(input.content); + }, + { + name: 'ParseError', + code: 'parse-error', + start: { line: 1, column: 4, character: 4 }, + end: { line: 1, column: 4, character: 4 }, + frame: '1: {a?.}\n ^' + } + ); - const expected_path = `${__dirname}/expected.tsx` + const expected_path = `${__dirname}/expected.tsx`; assert.strictEqual( svelte2tsx(input.content, { emitOnTemplateError: true, filename: input.filename }).code, readFileSync(expected_path).toString() - ) -} + ); +};