From 60fae7e6b562c2290113bfbaa6effe43e5b81203 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 9 Feb 2021 11:42:15 +0100 Subject: [PATCH 01/13] Add TypeScript support Closes #68 This does not introduce a generic preprocessor-option. Instead it provides a TypeScript-specific implementation. The advantage is that we don't need hacks like deasync because transpilation and mapping can happen synchronously. It also makes post-processing and interaction with ESLint easier. How it works: 1. transpile script contents from TS to JS 2. compile result to get Svelte compiler warnings 3. map those warnings back to its original positions 4. blank script contents 5. compile again to get the AST with original positions in the markdown part 6. use AST of step 5, vars of step 2 and blanked script content to produce the text which is handed to ESLint 7. Let the ESLint TypeScript plugin handle the TS code 8. adjust mappings --- CHANGELOG.md | 4 + README.md | 23 ++- package.json | 8 +- src/mapping.js | 212 ++++++++++++++++++++++++ src/preprocess.js | 99 ++++++++++-- src/processor_options.js | 1 + test/samples/typescript/.eslintrc.js | 12 ++ test/samples/typescript/Input.svelte | 23 +++ test/samples/typescript/expected.json | 221 ++++++++++++++++++++++++++ 9 files changed, 587 insertions(+), 16 deletions(-) create mode 100644 src/mapping.js create mode 100644 test/samples/typescript/.eslintrc.js create mode 100644 test/samples/typescript/Input.svelte create mode 100644 test/samples/typescript/expected.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 49f0af6..787c2e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 3.1.0 (Unreleased) + +- Add TypeScript support + # 3.0.0 - Breaking change: Node 10+ is now required diff --git a/README.md b/README.md index d609238..73d85b4 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,21 @@ module.exports = { By default, this plugin needs to be able to `require('svelte/compiler')`. If ESLint, this plugin, and Svelte are all installed locally in your project, this should not be a problem. +If you want to use TypeScript, you need to adjust your ESLint configuration. In addition to the Svelte plugin, you also need the ESLint-TypeScript plugin. You need to install `typescript`, `@typescript-eslint/parser` and `@typescript-eslint/eslint-plugin` from npm and then adjust your config like this: + +```javascript +module.exports = { + parser: '@typescript-eslint/parser', // add the TypeScript parser + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], // optional - a standard rule set + plugins: ['svelte3', '@typescript-eslint'], // add the TypeScript plugin + overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], // this stays the same + settings: { + 'svelte3/typescript': require('typescript'), // pass the TypeScript package to the Svelte plugin + // ... + } +} +``` + ## Interactions with other plugins Care needs to be taken when using this plugin alongside others. Take a look at [this list of things you need to watch out for](OTHER_PLUGINS.md). @@ -90,12 +105,18 @@ The default is to not ignore any styles. ### `svelte3/named-blocks` -When an [ESLint processor](https://eslint.org/docs/user-guide/configuring#specifying-processor) processes a file, it is able to output named code blocks, which can each have their own linting configuration. When this setting is enabled, the code extracted from ``; + }); + mapper = new DocumentMapper(text, transpiled, diffs); + ts_result = compiler.compile(transpiled, { generate: false, ...processor_options.compiler_options }); + + text = text.replace(/([^]*?)<\/script>/gi, (match, attributes = '', content) => { + return `${content + // blank out the content + .replace(/[^\n]/g, ' ') + // excess blank space can make the svelte parser very slow (sec->min). break it up with comments (works in style/script) + .replace(/[^\n][^\n][^\n][^\n]\n/g, '/**/\n') + }`; + }); + } + + const result = compiler.compile(text, { generate: false, ...processor_options.compiler_options }); + + if (!processor_options.typescript) { + ({ ast, warnings, vars } = result); + } else { + ast = result.ast; + ({ warnings, vars } = ts_result); + } + + return { ast, warnings, vars, mapper }; +} diff --git a/src/processor_options.js b/src/processor_options.js index 83d59a1..c9f6c3d 100644 --- a/src/processor_options.js +++ b/src/processor_options.js @@ -17,6 +17,7 @@ Linter.prototype.verify = function(code, config, options) { processor_options.ignore_styles = settings['svelte3/ignore-styles']; processor_options.compiler_options = settings['svelte3/compiler-options']; processor_options.named_blocks = settings['svelte3/named-blocks']; + processor_options.typescript = settings['svelte3/typescript']; // call original Linter#verify return verify.call(this, code, config, options); }; diff --git a/test/samples/typescript/.eslintrc.js b/test/samples/typescript/.eslintrc.js new file mode 100644 index 0000000..8215883 --- /dev/null +++ b/test/samples/typescript/.eslintrc.js @@ -0,0 +1,12 @@ +module.exports = { + parser: '@typescript-eslint/parser', + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], + plugins: ['@typescript-eslint'], + settings: { + 'svelte3/typescript': require('typescript'), + }, + rules: { + indent: ['error', 'tab'], + semi: 'error', + }, +}; diff --git a/test/samples/typescript/Input.svelte b/test/samples/typescript/Input.svelte new file mode 100644 index 0000000..4ed1764 --- /dev/null +++ b/test/samples/typescript/Input.svelte @@ -0,0 +1,23 @@ + + + + +{b} +

{x}

diff --git a/test/samples/typescript/expected.json b/test/samples/typescript/expected.json new file mode 100644 index 0000000..bac791c --- /dev/null +++ b/test/samples/typescript/expected.json @@ -0,0 +1,221 @@ +[ + { + "ruleId": "module-script-reactive-declaration", + "severity": 1, + "message": "$: has no effect in a module script", + "line": 3, + "column": 2, + "endLine": 3, + "endColumn": 12 + }, + { + "ruleId": "no-undef", + "severity": 2, + "message": "'z2' is not defined.", + "line": 3, + "column": 5, + "nodeType": "Identifier", + "messageId": "undef", + "endLine": 3, + "endColumn": 7 + }, + { + "ruleId": "unused-export-let", + "severity": 1, + "message": "Component has unused export property 'a'. If it is for external reference only, please consider using `export const a`", + "line": 7, + "column": 13, + "endLine": 7, + "endColumn": 19 + }, + { + "ruleId": "@typescript-eslint/no-explicit-any", + "severity": 1, + "message": "Unexpected any. Specify a different type.", + "line": 7, + "column": 16, + "nodeType": "TSAnyKeyword", + "messageId": "unexpectedAny", + "endLine": 7, + "endColumn": 19, + "suggestions": [ + { + "messageId": "suggestUnknown", + "fix": { + "range": [ + 29, + 32 + ], + "text": "unknown" + }, + "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct." + }, + { + "messageId": "suggestNever", + "fix": { + "range": [ + 29, + 32 + ], + "text": "never" + }, + "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of." + } + ] + }, + { + "ruleId": "indent", + "severity": 2, + "message": "Expected indentation of 0 tabs but found 1.", + "line": 9, + "column": 2, + "nodeType": "Keyword", + "messageId": "wrongIndentation", + "endLine": 9, + "endColumn": 3, + "fix": { + "range": [ + 112, + 113 + ], + "text": "" + } + }, + { + "ruleId": "@typescript-eslint/no-explicit-any", + "severity": 1, + "message": "Unexpected any. Specify a different type.", + "line": 9, + "column": 17, + "nodeType": "TSAnyKeyword", + "messageId": "unexpectedAny", + "endLine": 9, + "endColumn": 20, + "suggestions": [ + { + "messageId": "suggestUnknown", + "fix": { + "range": [ + 50, + 53 + ], + "text": "unknown" + }, + "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct." + }, + { + "messageId": "suggestNever", + "fix": { + "range": [ + 50, + 53 + ], + "text": "never" + }, + "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of." + } + ] + }, + { + "ruleId": "semi", + "severity": 2, + "message": "Missing semicolon.", + "line": 9, + "column": 20, + "nodeType": "VariableDeclaration", + "messageId": "missingSemi", + "endLine": 10, + "endColumn": 2, + "fix": { + "range": [ + 130, + 130 + ], + "text": ";" + } + }, + { + "ruleId": "@typescript-eslint/no-unused-vars", + "severity": 1, + "message": "'asd' is assigned a value but never used.", + "line": 12, + "column": 8, + "nodeType": "Identifier", + "messageId": "unusedVar", + "endLine": 12, + "endColumn": 11 + }, + { + "ruleId": "@typescript-eslint/no-empty-interface", + "severity": 2, + "message": "An empty interface is equivalent to `{}`.", + "line": 14, + "column": 12, + "nodeType": "Identifier", + "messageId": "noEmpty", + "endLine": 14, + "endColumn": 13 + }, + { + "ruleId": "@typescript-eslint/no-unused-vars", + "severity": 1, + "message": "'A' is defined but never used.", + "line": 14, + "column": 12, + "nodeType": "Identifier", + "messageId": "unusedVar", + "endLine": 14, + "endColumn": 13 + }, + { + "ruleId": "@typescript-eslint/no-unused-vars", + "severity": 1, + "message": "'x' is defined but never used.", + "line": 16, + "column": 13, + "nodeType": "Identifier", + "messageId": "unusedVar", + "endLine": 16, + "endColumn": 14 + }, + { + "ruleId": "@typescript-eslint/no-empty-function", + "severity": 2, + "message": "Unexpected empty function 'c'.", + "line": 16, + "column": 16, + "nodeType": "FunctionDeclaration", + "messageId": "unexpected", + "endLine": 16, + "endColumn": 18 + }, + { + "ruleId": "module-script-reactive-declaration", + "severity": 1, + "message": "\"z\" is declared in a module script and will not be reactive", + "line": 19, + "column": 10, + "endLine": 19, + "endColumn": 11 + }, + { + "ruleId": "missing-declaration", + "severity": 1, + "message": "'x' is not defined", + "line": 23, + "column": 5, + "endLine": 23, + "endColumn": 6 + }, + { + "ruleId": "no-undef", + "severity": 2, + "message": "'x' is not defined.", + "line": 23, + "column": 5, + "nodeType": "Identifier", + "messageId": "undef", + "endLine": 23, + "endColumn": 6 + } +] \ No newline at end of file From 81ae07db18bfda269431e807825040911d09055c Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 9 Feb 2021 11:59:47 +0100 Subject: [PATCH 02/13] block files test --- .../typescript-block-filenames/.eslintrc.js | 28 +++++++++++++++++++ .../typescript-block-filenames/Input.svelte | 10 +++++++ .../typescript-block-filenames/expected.json | 12 ++++++++ 3 files changed, 50 insertions(+) create mode 100644 test/samples/typescript-block-filenames/.eslintrc.js create mode 100644 test/samples/typescript-block-filenames/Input.svelte create mode 100644 test/samples/typescript-block-filenames/expected.json diff --git a/test/samples/typescript-block-filenames/.eslintrc.js b/test/samples/typescript-block-filenames/.eslintrc.js new file mode 100644 index 0000000..cfd92f6 --- /dev/null +++ b/test/samples/typescript-block-filenames/.eslintrc.js @@ -0,0 +1,28 @@ +module.exports = { + parser: '@typescript-eslint/parser', + extends: ['plugin:@typescript-eslint/recommended'], + plugins: ['@typescript-eslint'], + overrides: [ + { + files: ['**/*.svelte/*_template.ts'], + rules: { + curly: 'off', + }, + }, + { + files: ['**/*.svelte/*_module.ts'], + rules: { + 'no-undef': 'off', + }, + }, + ], + settings: { + 'svelte3/typescript': require('typescript'), + 'svelte3/named-blocks': true, + }, + rules: { + curly: 'error', + 'no-undef': 'error', + }, + }; + \ No newline at end of file diff --git a/test/samples/typescript-block-filenames/Input.svelte b/test/samples/typescript-block-filenames/Input.svelte new file mode 100644 index 0000000..f6b0362 --- /dev/null +++ b/test/samples/typescript-block-filenames/Input.svelte @@ -0,0 +1,10 @@ + + + + +
{ if (foo) bar; }}>blah
diff --git a/test/samples/typescript-block-filenames/expected.json b/test/samples/typescript-block-filenames/expected.json new file mode 100644 index 0000000..f40aa65 --- /dev/null +++ b/test/samples/typescript-block-filenames/expected.json @@ -0,0 +1,12 @@ +[ + { + "ruleId": "curly", + "line": 2, + "column": 2 + }, + { + "ruleId": "curly", + "line": 7, + "column": 2 + } +] From 91e0287dc7e7517d14800f970581f1f670db5042 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 9 Feb 2021 13:46:11 +0100 Subject: [PATCH 03/13] type-aware rules test --- README.md | 20 +++++- .../typescript-type-aware-rules/.eslintrc.js | 17 +++++ .../typescript-type-aware-rules/Input.svelte | 11 ++++ .../typescript-type-aware-rules/expected.json | 66 +++++++++++++++++++ .../typescript-type-aware-rules/tsconfig.json | 3 + 5 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 test/samples/typescript-type-aware-rules/.eslintrc.js create mode 100644 test/samples/typescript-type-aware-rules/Input.svelte create mode 100644 test/samples/typescript-type-aware-rules/expected.json create mode 100644 test/samples/typescript-type-aware-rules/tsconfig.json diff --git a/README.md b/README.md index 73d85b4..0978577 100644 --- a/README.md +++ b/README.md @@ -61,13 +61,29 @@ If you want to use TypeScript, you need to adjust your ESLint configuration. In ```javascript module.exports = { parser: '@typescript-eslint/parser', // add the TypeScript parser - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], // optional - a standard rule set + extends: [ + // optional - a standard rule set + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + // optional - if you want type-aware rules (also see `parserOptions` below). + // Note that this results in slower checks + // because the whole program needs to be compiled and type checked + 'plugin:@typescript-eslint/recommended-requiring-type-checking' + ], plugins: ['svelte3', '@typescript-eslint'], // add the TypeScript plugin overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], // this stays the same settings: { 'svelte3/typescript': require('typescript'), // pass the TypeScript package to the Svelte plugin // ... - } + }, + // The following is only needed if you want to use type-aware rules + // It assumes that your eslint config is at the root next to your tsconfig.json + // More info: https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/TYPED_LINTING.md + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + extraFileExtensions: ['.svelte'], + }, } ``` diff --git a/test/samples/typescript-type-aware-rules/.eslintrc.js b/test/samples/typescript-type-aware-rules/.eslintrc.js new file mode 100644 index 0000000..d66ccce --- /dev/null +++ b/test/samples/typescript-type-aware-rules/.eslintrc.js @@ -0,0 +1,17 @@ +module.exports = { + parser: '@typescript-eslint/parser', + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking',], + plugins: ['@typescript-eslint'], + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + extraFileExtensions: ['.svelte'], + }, + ignorePatterns: ['.eslintrc.js'], + settings: { + 'svelte3/typescript': require('typescript'), + } +}; diff --git a/test/samples/typescript-type-aware-rules/Input.svelte b/test/samples/typescript-type-aware-rules/Input.svelte new file mode 100644 index 0000000..6021a0f --- /dev/null +++ b/test/samples/typescript-type-aware-rules/Input.svelte @@ -0,0 +1,11 @@ + diff --git a/test/samples/typescript-type-aware-rules/expected.json b/test/samples/typescript-type-aware-rules/expected.json new file mode 100644 index 0000000..0ce8f94 --- /dev/null +++ b/test/samples/typescript-type-aware-rules/expected.json @@ -0,0 +1,66 @@ +[ + { + "ruleId": "@typescript-eslint/no-for-in-array", + "severity": 2, + "line": 2, + "column": 2, + "endLine": 4, + "endColumn": 3 + }, + { + "ruleId": "@typescript-eslint/no-unsafe-return", + "severity": 2, + "line": 7, + "column": 3, + "endLine": 7, + "endColumn": 19 + }, + { + "ruleId": "@typescript-eslint/no-explicit-any", + "severity": 1, + "line": 7, + "column": 15, + "endLine": 7, + "endColumn": 18, + "suggestions": [ + { + "messageId": "suggestUnknown", + "fix": { + "range": [ + 61, + 64 + ], + "text": "unknown" + }, + "desc": "Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct." + }, + { + "messageId": "suggestNever", + "fix": { + "range": [ + 61, + 64 + ], + "text": "never" + }, + "desc": "Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of." + } + ] + }, + { + "ruleId": "@typescript-eslint/no-unsafe-member-access", + "severity": 2, + "line": 10, + "column": 2, + "endLine": 10, + "endColumn": 13 + }, + { + "ruleId": "@typescript-eslint/no-unsafe-call", + "severity": 2, + "line": 10, + "column": 2, + "endLine": 10, + "endColumn": 13 + } +] \ No newline at end of file diff --git a/test/samples/typescript-type-aware-rules/tsconfig.json b/test/samples/typescript-type-aware-rules/tsconfig.json new file mode 100644 index 0000000..9aadfdc --- /dev/null +++ b/test/samples/typescript-type-aware-rules/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["**/*"] +} From fead37289ac94f1afd363df492d7bd1bfbaca9e2 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 9 Feb 2021 13:47:15 +0100 Subject: [PATCH 04/13] less bloated expected.json --- test/samples/typescript/expected.json | 37 --------------------------- 1 file changed, 37 deletions(-) diff --git a/test/samples/typescript/expected.json b/test/samples/typescript/expected.json index bac791c..8c05a26 100644 --- a/test/samples/typescript/expected.json +++ b/test/samples/typescript/expected.json @@ -2,7 +2,6 @@ { "ruleId": "module-script-reactive-declaration", "severity": 1, - "message": "$: has no effect in a module script", "line": 3, "column": 2, "endLine": 3, @@ -11,18 +10,14 @@ { "ruleId": "no-undef", "severity": 2, - "message": "'z2' is not defined.", "line": 3, "column": 5, - "nodeType": "Identifier", - "messageId": "undef", "endLine": 3, "endColumn": 7 }, { "ruleId": "unused-export-let", "severity": 1, - "message": "Component has unused export property 'a'. If it is for external reference only, please consider using `export const a`", "line": 7, "column": 13, "endLine": 7, @@ -31,11 +26,8 @@ { "ruleId": "@typescript-eslint/no-explicit-any", "severity": 1, - "message": "Unexpected any. Specify a different type.", "line": 7, "column": 16, - "nodeType": "TSAnyKeyword", - "messageId": "unexpectedAny", "endLine": 7, "endColumn": 19, "suggestions": [ @@ -66,11 +58,8 @@ { "ruleId": "indent", "severity": 2, - "message": "Expected indentation of 0 tabs but found 1.", "line": 9, "column": 2, - "nodeType": "Keyword", - "messageId": "wrongIndentation", "endLine": 9, "endColumn": 3, "fix": { @@ -84,11 +73,8 @@ { "ruleId": "@typescript-eslint/no-explicit-any", "severity": 1, - "message": "Unexpected any. Specify a different type.", "line": 9, "column": 17, - "nodeType": "TSAnyKeyword", - "messageId": "unexpectedAny", "endLine": 9, "endColumn": 20, "suggestions": [ @@ -119,11 +105,8 @@ { "ruleId": "semi", "severity": 2, - "message": "Missing semicolon.", "line": 9, "column": 20, - "nodeType": "VariableDeclaration", - "messageId": "missingSemi", "endLine": 10, "endColumn": 2, "fix": { @@ -137,62 +120,46 @@ { "ruleId": "@typescript-eslint/no-unused-vars", "severity": 1, - "message": "'asd' is assigned a value but never used.", "line": 12, "column": 8, - "nodeType": "Identifier", - "messageId": "unusedVar", "endLine": 12, "endColumn": 11 }, { "ruleId": "@typescript-eslint/no-empty-interface", "severity": 2, - "message": "An empty interface is equivalent to `{}`.", "line": 14, "column": 12, - "nodeType": "Identifier", - "messageId": "noEmpty", "endLine": 14, "endColumn": 13 }, { "ruleId": "@typescript-eslint/no-unused-vars", "severity": 1, - "message": "'A' is defined but never used.", "line": 14, "column": 12, - "nodeType": "Identifier", - "messageId": "unusedVar", "endLine": 14, "endColumn": 13 }, { "ruleId": "@typescript-eslint/no-unused-vars", "severity": 1, - "message": "'x' is defined but never used.", "line": 16, "column": 13, - "nodeType": "Identifier", - "messageId": "unusedVar", "endLine": 16, "endColumn": 14 }, { "ruleId": "@typescript-eslint/no-empty-function", "severity": 2, - "message": "Unexpected empty function 'c'.", "line": 16, "column": 16, - "nodeType": "FunctionDeclaration", - "messageId": "unexpected", "endLine": 16, "endColumn": 18 }, { "ruleId": "module-script-reactive-declaration", "severity": 1, - "message": "\"z\" is declared in a module script and will not be reactive", "line": 19, "column": 10, "endLine": 19, @@ -201,7 +168,6 @@ { "ruleId": "missing-declaration", "severity": 1, - "message": "'x' is not defined", "line": 23, "column": 5, "endLine": 23, @@ -210,11 +176,8 @@ { "ruleId": "no-undef", "severity": 2, - "message": "'x' is not defined.", "line": 23, "column": 5, - "nodeType": "Identifier", - "messageId": "undef", "endLine": 23, "endColumn": 6 } From d3cf4624641a123879b3c538bc371a69d1befd19 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 9 Feb 2021 15:05:00 +0100 Subject: [PATCH 05/13] DEM UNDERSCOREZZ --- src/mapping.js | 162 +++++++++++++++++++++++----------------------- src/preprocess.js | 58 ++++++++++------- 2 files changed, 115 insertions(+), 105 deletions(-) diff --git a/src/mapping.js b/src/mapping.js index aea1051..c6e9168 100644 --- a/src/mapping.js +++ b/src/mapping.js @@ -2,68 +2,68 @@ import { decode } from 'sourcemap-codec'; class GeneratedFragmentMapper { constructor( - generatedCode, - tagInfo, + generated_code, + tag_info, ) { - this.generatedCode = generatedCode; - this.tagInfo = tagInfo; + this.generated_code = generated_code; + this.tag_info = tag_info; } - getPositionRelativeToFragment(positionRelativeToFile) { - const fragmentOffset = this.offsetInFragment(offsetAt(positionRelativeToFile, this.generatedCode)); - return positionAt(fragmentOffset, this.tagInfo.generatedContent); + get_position_relative_to_fragment(positionRelativeToFile) { + const fragment_offset = this.offset_in_fragment(offset_at(positionRelativeToFile, this.generated_code)); + return position_at(fragment_offset, this.tag_info.generated_content); } - offsetInFragment(offset) { - return offset - this.tagInfo.generatedStart + offset_in_fragment(offset) { + return offset - this.tag_info.generated_start } } class OriginalFragmentMapper { constructor( - originalCode, - tagInfo, + original_code, + tag_info, ) { - this.originalCode = originalCode; - this.tagInfo = tagInfo; + this.original_code = original_code; + this.tag_info = tag_info; } - getPositionRelativeToFile(positionRelativeToFragment) { - const parentOffset = this.offsetInParent(offsetAt(positionRelativeToFragment, this.tagInfo.originalContent)); - return positionAt(parentOffset, this.originalCode); + get_position_relative_to_file(positionRelativeToFragment) { + const parent_offset = this.offset_in_parent(offset_at(positionRelativeToFragment, this.tag_info.original_content)); + return position_at(parent_offset, this.original_code); } - offsetInParent(offset) { - return this.tagInfo.originalStart + offset; + offset_in_parent(offset) { + return this.tag_info.original_start + offset; } } class SourceMapper { - constructor(rawSourceMap) { - this.rawSourceMap = rawSourceMap; + constructor(raw_source_map) { + this.raw_source_map = raw_source_map; } - getOriginalPosition(generatedPosition) { - if (generatedPosition.line < 0) { + getOriginalPosition(generated_position) { + if (generated_position.line < 0) { return { line: -1, column: -1 }; } // Lazy-load if (!this.decoded) { - this.decoded = decode(JSON.parse(this.rawSourceMap).mappings); + this.decoded = decode(JSON.parse(this.raw_source_map).mappings); } - let line = generatedPosition.line; - let column = generatedPosition.column; + let line = generated_position.line; + let column = generated_position.column; - let lineMatch = this.decoded[generatedPosition.line]; - while (line >= 0 && (!lineMatch || !lineMatch.length)) { + let line_match = this.decoded[generated_position.line]; + while (line >= 0 && (!line_match || !line_match.length)) { line -= 1; - lineMatch = this.decoded[generatedPosition]; - if (lineMatch && lineMatch.length) { + line_match = this.decoded[generated_position]; + if (line_match && line_match.length) { return { - line: lineMatch[lineMatch.length - 1][2], - column: lineMatch[lineMatch.length - 1][3] + line: line_match[line_match.length - 1][2], + column: line_match[line_match.length - 1][3] }; } } @@ -72,66 +72,66 @@ class SourceMapper { return { line: -1, column: -1 }; } - const columnMatch = lineMatch.find((col, idx) => - idx + 1 === lineMatch.length || - (col[0] <= column && lineMatch[idx + 1][0] > column) + const column_match = line_match.find((col, idx) => + idx + 1 === line_match.length || + (col[0] <= column && line_match[idx + 1][0] > column) ); return { - line: columnMatch[2], - column: columnMatch[3], + line: column_match[2], + column: column_match[3], }; } } export class DocumentMapper { - constructor(originalCode, generatedCode, diffs) { - this.originalCode = originalCode; - this.generatedCode = generatedCode; + constructor(original_code, generated_code, diffs) { + this.original_code = original_code; + this.generated_code = generated_code; this.diffs = diffs; this.mappers = diffs.map(diff => { return { - start: diff.generatedStart, - end: diff.generatedEnd, + start: diff.generated_start, + end: diff.generated_end, diff: diff.diff, - generatedFragmentMapper: new GeneratedFragmentMapper(generatedCode, diff), - sourceMapper: new SourceMapper(diff.map), - originalFragmentMapper: new OriginalFragmentMapper(originalCode, diff) + generated_fragment_mapper: new GeneratedFragmentMapper(generated_code, diff), + source_mapper: new SourceMapper(diff.map), + original_fragment_mapper: new OriginalFragmentMapper(original_code, diff) } }); } - getOriginalPosition(generatedPosition) { - generatedPosition = { line: generatedPosition.line - 1, column: generatedPosition.column }; - const offset = offsetAt(generatedPosition, this.generatedCode); - let originalOffset = offset; + get_original_position(generated_position) { + generated_position = { line: generated_position.line - 1, column: generated_position.column }; + const offset = offset_at(generated_position, this.generated_code); + let original_offset = offset; for (const mapper of this.mappers) { if (offset >= mapper.start && offset <= mapper.end) { - return this.map(mapper, generatedPosition); + return this.map(mapper, generated_position); } if (offset > mapper.end) { - originalOffset -= mapper.diff; + original_offset -= mapper.diff; } } - const originalPosition = positionAt(originalOffset, this.originalCode); - return this.toESLintPosition(originalPosition); + const original_position = position_at(original_offset, this.original_code); + return this.to_ESLint_position(original_position); } map(mapper, generatedPosition) { // Map the position to be relative to the transpiled fragment - const positionInTranspiledFragment = mapper.generatedFragmentMapper.getPositionRelativeToFragment( + const position_in_transpiled_fragment = mapper.generated_fragment_mapper.get_position_relative_to_fragment( generatedPosition ); // Map the position, using the sourcemap, to the original position in the source fragment - const positionInOriginalFragment = mapper.sourceMapper.getOriginalPosition( - positionInTranspiledFragment + const position_in_original_fragment = mapper.source_mapper.getOriginalPosition( + position_in_transpiled_fragment ); // Map the position to be in the original fragment's parent - const originalPosition = mapper.originalFragmentMapper.getPositionRelativeToFile(positionInOriginalFragment); - return this.toESLintPosition(originalPosition); + const original_position = mapper.original_fragment_mapper.get_position_relative_to_file(position_in_original_fragment); + return this.to_ESLint_position(original_position); } - toESLintPosition(position) { + to_ESLint_position(position) { // ESLint line/column is 1-based return { line: position.line + 1, column: position.column + 1 }; } @@ -143,35 +143,35 @@ export class DocumentMapper { * @param position Line and character position * @param text The text for which the offset should be retrived */ -function offsetAt(position, text) { - const lineOffsets = getLineOffsets(text); +function offset_at(position, text) { + const line_offsets = get_line_offsets(text); - if (position.line >= lineOffsets.length) { + if (position.line >= line_offsets.length) { return text.length; } else if (position.line < 0) { return 0; } - const lineOffset = lineOffsets[position.line]; - const nextLineOffset = - position.line + 1 < lineOffsets.length ? lineOffsets[position.line + 1] : text.length; + const line_offset = line_offsets[position.line]; + const next_line_offset = + position.line + 1 < line_offsets.length ? line_offsets[position.line + 1] : text.length; - return clamp(nextLineOffset, lineOffset, lineOffset + position.column); + return clamp(next_line_offset, line_offset, line_offset + position.column); } -function positionAt(offset, text) { +function position_at(offset, text) { offset = clamp(offset, 0, text.length); - const lineOffsets = getLineOffsets(text); + const line_offsets = get_line_offsets(text); let low = 0; - let high = lineOffsets.length; + let high = line_offsets.length; if (high === 0) { - return Position.create(0, offset); + return { line: 0, column: offset }; } while (low < high) { const mid = Math.floor((low + high) / 2); - if (lineOffsets[mid] > offset) { + if (line_offsets[mid] > offset) { high = mid; } else { low = mid + 1; @@ -181,30 +181,30 @@ function positionAt(offset, text) { // low is the least x for which the line offset is larger than the current offset // or array.length if no line offset is larger than the current offset const line = low - 1; - return { line, column: offset - lineOffsets[line] }; + return { line, column: offset - line_offsets[line] }; } -function getLineOffsets(text) { - const lineOffsets = []; - let isLineStart = true; +function get_line_offsets(text) { + const line_offsets = []; + let is_line_start = true; for (let i = 0; i < text.length; i++) { - if (isLineStart) { - lineOffsets.push(i); - isLineStart = false; + if (is_line_start) { + line_offsets.push(i); + is_line_start = false; } const ch = text.charAt(i); - isLineStart = ch === '\r' || ch === '\n'; + is_line_start = ch === '\r' || ch === '\n'; if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') { i++; } } - if (isLineStart && text.length > 0) { - lineOffsets.push(text.length); + if (is_line_start && text.length > 0) { + line_offsets.push(text.length); } - return lineOffsets; + return line_offsets; } function clamp(num, min, max) { diff --git a/src/preprocess.js b/src/preprocess.js index 858c1b6..423a813 100644 --- a/src/preprocess.js +++ b/src/preprocess.js @@ -45,7 +45,7 @@ export const preprocess = text => { // get information about the component let result; try { - result = compileCode(text, compiler, processor_options); + result = compile_code(text, compiler, processor_options); } catch ({ name, message, start, end }) { // convert the error to a linting message, store it, and return state.messages = [ @@ -69,20 +69,20 @@ export const preprocess = text => { // convert warnings to linting messages const filteredWarnings = processor_options.ignore_warnings ? warnings.filter(warning => !processor_options.ignore_warnings(warning)) : warnings; state.messages = filteredWarnings.map(({ code, message, start, end }) => { - const startPos = processor_options.typescript && start ? - mapper.getOriginalPosition(start) : + const start_pos = processor_options.typescript && start ? + mapper.get_original_position(start) : start && { line: start.line, column: start.column + 1 }; - const endPos = processor_options.typescript && end ? - mapper.getOriginalPosition(end) : + const end_pos = processor_options.typescript && end ? + mapper.get_original_position(end) : end && { line: end.line, column: end.column + 1 }; return { ruleId: code, severity: 1, message, - line: startPos && startPos.line, - column: startPos && startPos.column, - endLine: endPos && endPos.line, - endColumn: endPos && endPos.column, + line: start_pos && start_pos.line, + column: start_pos && start_pos.column, + endLine: end_pos && end_pos.line, + endColumn: end_pos && end_pos.column, }; }); @@ -90,12 +90,12 @@ export const preprocess = text => { // Things to think about: // - not all Svelte files may be typescript -> do we need a distinction on a file basis by analyzing the attribute + a config option to tell "treat all as TS"? - const withFileEnding = (fileName) => `${fileName}${processor_options.typescript ? '.ts' : '.js'}`; + const with_file_ending = (filename) => `${filename}${processor_options.typescript ? '.ts' : '.js'}`; if (ast.module) { // block for `; From dd6220a1e6698de694ec581e97e018e7f7574d12 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Thu, 11 Feb 2021 15:43:41 -0500 Subject: [PATCH 06/13] fix a bunch of indentation --- src/mapping.js | 152 ++++++++++++++++++++++++------------------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/src/mapping.js b/src/mapping.js index c6e9168..67c139c 100644 --- a/src/mapping.js +++ b/src/mapping.js @@ -10,8 +10,8 @@ class GeneratedFragmentMapper { } get_position_relative_to_fragment(positionRelativeToFile) { - const fragment_offset = this.offset_in_fragment(offset_at(positionRelativeToFile, this.generated_code)); - return position_at(fragment_offset, this.tag_info.generated_content); + const fragment_offset = this.offset_in_fragment(offset_at(positionRelativeToFile, this.generated_code)); + return position_at(fragment_offset, this.tag_info.generated_content); } offset_in_fragment(offset) { @@ -72,7 +72,7 @@ class SourceMapper { return { line: -1, column: -1 }; } - const column_match = line_match.find((col, idx) => + const column_match = line_match.find((col, idx) => idx + 1 === line_match.length || (col[0] <= column && line_match[idx + 1][0] > column) ); @@ -101,33 +101,33 @@ export class DocumentMapper { }); } - get_original_position(generated_position) { + get_original_position(generated_position) { generated_position = { line: generated_position.line - 1, column: generated_position.column }; - const offset = offset_at(generated_position, this.generated_code); - let original_offset = offset; - for (const mapper of this.mappers) { + const offset = offset_at(generated_position, this.generated_code); + let original_offset = offset; + for (const mapper of this.mappers) { if (offset >= mapper.start && offset <= mapper.end) { return this.map(mapper, generated_position); } - if (offset > mapper.end) { - original_offset -= mapper.diff; - } - } - const original_position = position_at(original_offset, this.original_code); + if (offset > mapper.end) { + original_offset -= mapper.diff; + } + } + const original_position = position_at(original_offset, this.original_code); return this.to_ESLint_position(original_position); - } + } map(mapper, generatedPosition) { - // Map the position to be relative to the transpiled fragment - const position_in_transpiled_fragment = mapper.generated_fragment_mapper.get_position_relative_to_fragment( - generatedPosition - ); - // Map the position, using the sourcemap, to the original position in the source fragment - const position_in_original_fragment = mapper.source_mapper.getOriginalPosition( - position_in_transpiled_fragment - ); - // Map the position to be in the original fragment's parent - const original_position = mapper.original_fragment_mapper.get_position_relative_to_file(position_in_original_fragment); + // Map the position to be relative to the transpiled fragment + const position_in_transpiled_fragment = mapper.generated_fragment_mapper.get_position_relative_to_fragment( + generatedPosition + ); + // Map the position, using the sourcemap, to the original position in the source fragment + const position_in_original_fragment = mapper.source_mapper.getOriginalPosition( + position_in_transpiled_fragment + ); + // Map the position to be in the original fragment's parent + const original_position = mapper.original_fragment_mapper.get_position_relative_to_file(position_in_original_fragment); return this.to_ESLint_position(original_position); } @@ -144,69 +144,69 @@ export class DocumentMapper { * @param text The text for which the offset should be retrived */ function offset_at(position, text) { - const line_offsets = get_line_offsets(text); + const line_offsets = get_line_offsets(text); - if (position.line >= line_offsets.length) { - return text.length; - } else if (position.line < 0) { - return 0; - } + if (position.line >= line_offsets.length) { + return text.length; + } else if (position.line < 0) { + return 0; + } - const line_offset = line_offsets[position.line]; - const next_line_offset = - position.line + 1 < line_offsets.length ? line_offsets[position.line + 1] : text.length; + const line_offset = line_offsets[position.line]; + const next_line_offset = + position.line + 1 < line_offsets.length ? line_offsets[position.line + 1] : text.length; - return clamp(next_line_offset, line_offset, line_offset + position.column); + return clamp(next_line_offset, line_offset, line_offset + position.column); } function position_at(offset, text) { - offset = clamp(offset, 0, text.length); - - const line_offsets = get_line_offsets(text); - let low = 0; - let high = line_offsets.length; - if (high === 0) { - return { line: 0, column: offset }; - } - - while (low < high) { - const mid = Math.floor((low + high) / 2); - if (line_offsets[mid] > offset) { - high = mid; - } else { - low = mid + 1; - } - } - - // low is the least x for which the line offset is larger than the current offset - // or array.length if no line offset is larger than the current offset - const line = low - 1; - return { line, column: offset - line_offsets[line] }; + offset = clamp(offset, 0, text.length); + + const line_offsets = get_line_offsets(text); + let low = 0; + let high = line_offsets.length; + if (high === 0) { + return { line: 0, column: offset }; + } + + while (low < high) { + const mid = Math.floor((low + high) / 2); + if (line_offsets[mid] > offset) { + high = mid; + } else { + low = mid + 1; + } + } + + // low is the least x for which the line offset is larger than the current offset + // or array.length if no line offset is larger than the current offset + const line = low - 1; + return { line, column: offset - line_offsets[line] }; } function get_line_offsets(text) { - const line_offsets = []; - let is_line_start = true; - - for (let i = 0; i < text.length; i++) { - if (is_line_start) { - line_offsets.push(i); - is_line_start = false; - } - const ch = text.charAt(i); - is_line_start = ch === '\r' || ch === '\n'; - if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') { - i++; - } - } - - if (is_line_start && text.length > 0) { - line_offsets.push(text.length); - } - - return line_offsets; + const line_offsets = []; + let is_line_start = true; + + for (let i = 0; i < text.length; i++) { + if (is_line_start) { + line_offsets.push(i); + is_line_start = false; + } + const ch = text.charAt(i); + is_line_start = ch === '\r' || ch === '\n'; + if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') { + i++; + } + } + + if (is_line_start && text.length > 0) { + line_offsets.push(text.length); + } + + return line_offsets; } function clamp(num, min, max) { - return Math.max(min, Math.min(max, num)); + return Math.max(min, Math.min(max, num)); } From e4f9a855d84cde93bca1859c5065f5a1d9f85fc9 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Thu, 11 Feb 2021 15:48:38 -0500 Subject: [PATCH 07/13] more snake case --- src/mapping.js | 16 ++++++++-------- src/preprocess.js | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/mapping.js b/src/mapping.js index 67c139c..844d31d 100644 --- a/src/mapping.js +++ b/src/mapping.js @@ -9,8 +9,8 @@ class GeneratedFragmentMapper { this.tag_info = tag_info; } - get_position_relative_to_fragment(positionRelativeToFile) { - const fragment_offset = this.offset_in_fragment(offset_at(positionRelativeToFile, this.generated_code)); + get_position_relative_to_fragment(position_relative_to_file) { + const fragment_offset = this.offset_in_fragment(offset_at(position_relative_to_file, this.generated_code)); return position_at(fragment_offset, this.tag_info.generated_content); } @@ -28,8 +28,8 @@ class OriginalFragmentMapper { this.tag_info = tag_info; } - get_position_relative_to_file(positionRelativeToFragment) { - const parent_offset = this.offset_in_parent(offset_at(positionRelativeToFragment, this.tag_info.original_content)); + get_position_relative_to_file(position_relative_to_fragment) { + const parent_offset = this.offset_in_parent(offset_at(position_relative_to_fragment, this.tag_info.original_content)); return position_at(parent_offset, this.original_code); } @@ -43,7 +43,7 @@ class SourceMapper { this.raw_source_map = raw_source_map; } - getOriginalPosition(generated_position) { + get_original_position(generated_position) { if (generated_position.line < 0) { return { line: -1, column: -1 }; } @@ -117,13 +117,13 @@ export class DocumentMapper { return this.to_ESLint_position(original_position); } - map(mapper, generatedPosition) { + map(mapper, generated_position) { // Map the position to be relative to the transpiled fragment const position_in_transpiled_fragment = mapper.generated_fragment_mapper.get_position_relative_to_fragment( - generatedPosition + generated_position ); // Map the position, using the sourcemap, to the original position in the source fragment - const position_in_original_fragment = mapper.source_mapper.getOriginalPosition( + const position_in_original_fragment = mapper.source_mapper.get_original_position( position_in_transpiled_fragment ); // Map the position to be in the original fragment's parent diff --git a/src/preprocess.js b/src/preprocess.js index 423a813..8ff9059 100644 --- a/src/preprocess.js +++ b/src/preprocess.js @@ -67,8 +67,8 @@ export const preprocess = text => { state.var_names = new Set(vars.map(v => v.name)); // convert warnings to linting messages - const filteredWarnings = processor_options.ignore_warnings ? warnings.filter(warning => !processor_options.ignore_warnings(warning)) : warnings; - state.messages = filteredWarnings.map(({ code, message, start, end }) => { + const filtered_warnings = processor_options.ignore_warnings ? warnings.filter(warning => !processor_options.ignore_warnings(warning)) : warnings; + state.messages = filtered_warnings.map(({ code, message, start, end }) => { const start_pos = processor_options.typescript && start ? mapper.get_original_position(start) : start && { line: start.line, column: start.column + 1 }; @@ -87,7 +87,7 @@ export const preprocess = text => { }); // build strings that we can send along to ESLint to get the remaining messages - + // Things to think about: // - not all Svelte files may be typescript -> do we need a distinction on a file basis by analyzing the attribute + a config option to tell "treat all as TS"? const with_file_ending = (filename) => `${filename}${processor_options.typescript ? '.ts' : '.js'}`; From a2ff579b2a709416d4cdb8d2178dd0b6fa92e352 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 12 Feb 2021 18:30:40 +0100 Subject: [PATCH 08/13] simplify code --- src/preprocess.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/preprocess.js b/src/preprocess.js index 8ff9059..f3a48dc 100644 --- a/src/preprocess.js +++ b/src/preprocess.js @@ -193,21 +193,20 @@ function compile_code(text, compiler, processor_options) { let ts_result; if (processor_options.typescript) { const diffs = []; + let accumulated_diff = 0; const transpiled = text.replace(/([^]*?)<\/script>/gi, (match, attributes = '', content) => { const output = processor_options.typescript.transpileModule( content, { reportDiagnostics: false, compilerOptions: { target: processor_options.typescript.ScriptTarget.ESNext, sourceMap: true } } ); - const prev_diff = diffs.length ? diffs[diffs.length - 1].accumulated_diff : 0; const original_start = text.indexOf(content); - const generated_start = prev_diff + original_start; + const generated_start = accumulated_diff + original_start; + accumulated_diff += output.outputText.length - content.length; diffs.push({ original_start: original_start, - original_end: original_start + content.length, generated_start: generated_start, generated_end: generated_start + output.outputText.length, diff: output.outputText.length - content.length, - accumulated_diff: prev_diff + output.outputText.length - content.length, original_content: content, generated_content: output.outputText, map: output.sourceMapText From 996824ddf7e50bf1876b2c68e51e67cc0b242491 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 12 Feb 2021 18:33:59 +0100 Subject: [PATCH 09/13] variable name, code style --- src/mapping.js | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/mapping.js b/src/mapping.js index 844d31d..c1e785d 100644 --- a/src/mapping.js +++ b/src/mapping.js @@ -1,40 +1,34 @@ import { decode } from 'sourcemap-codec'; class GeneratedFragmentMapper { - constructor( - generated_code, - tag_info, - ) { + constructor(generated_code, diff) { this.generated_code = generated_code; - this.tag_info = tag_info; + this.diff = diff; } get_position_relative_to_fragment(position_relative_to_file) { const fragment_offset = this.offset_in_fragment(offset_at(position_relative_to_file, this.generated_code)); - return position_at(fragment_offset, this.tag_info.generated_content); + return position_at(fragment_offset, this.diff.generated_content); } offset_in_fragment(offset) { - return offset - this.tag_info.generated_start + return offset - this.diff.generated_start } } class OriginalFragmentMapper { - constructor( - original_code, - tag_info, - ) { + constructor(original_code, diff) { this.original_code = original_code; - this.tag_info = tag_info; + this.diff = diff; } get_position_relative_to_file(position_relative_to_fragment) { - const parent_offset = this.offset_in_parent(offset_at(position_relative_to_fragment, this.tag_info.original_content)); + const parent_offset = this.offset_in_parent(offset_at(position_relative_to_fragment, this.diff.original_content)); return position_at(parent_offset, this.original_code); } offset_in_parent(offset) { - return this.tag_info.original_start + offset; + return this.diff.original_start + offset; } } From d5b80207696833421943184285eba9b00cf33935 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 15 Feb 2021 14:41:34 +0100 Subject: [PATCH 10/13] line match bugfix --- src/mapping.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mapping.js b/src/mapping.js index c1e785d..a3d2080 100644 --- a/src/mapping.js +++ b/src/mapping.js @@ -50,10 +50,10 @@ class SourceMapper { let line = generated_position.line; let column = generated_position.column; - let line_match = this.decoded[generated_position.line]; + let line_match = this.decoded[line]; while (line >= 0 && (!line_match || !line_match.length)) { line -= 1; - line_match = this.decoded[generated_position]; + line_match = this.decoded[line]; if (line_match && line_match.length) { return { line: line_match[line_match.length - 1][2], From b3b1d61ce513c91919625d382c1bed96ddb74cf6 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 15 Feb 2021 18:18:01 -0500 Subject: [PATCH 11/13] adjust readme --- README.md | 53 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 0978577..ae77479 100644 --- a/README.md +++ b/README.md @@ -56,35 +56,50 @@ module.exports = { By default, this plugin needs to be able to `require('svelte/compiler')`. If ESLint, this plugin, and Svelte are all installed locally in your project, this should not be a problem. -If you want to use TypeScript, you need to adjust your ESLint configuration. In addition to the Svelte plugin, you also need the ESLint-TypeScript plugin. You need to install `typescript`, `@typescript-eslint/parser` and `@typescript-eslint/eslint-plugin` from npm and then adjust your config like this: +### Installation with TypeScript + +If you want to use TypeScript, you'll need a different ESLint configuration. In addition to the Svelte plugin, you also need the ESLint TypeScript parser and plugin. Install `typescript`, `@typescript-eslint/parser` and `@typescript-eslint/eslint-plugin` from npm and then adjust your config like this: ```javascript module.exports = { parser: '@typescript-eslint/parser', // add the TypeScript parser - extends: [ - // optional - a standard rule set - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - // optional - if you want type-aware rules (also see `parserOptions` below). - // Note that this results in slower checks - // because the whole program needs to be compiled and type checked - 'plugin:@typescript-eslint/recommended-requiring-type-checking' + plugins: [ + 'svelte3', + '@typescript-eslint' // add the TypeScript plugin + ], + overrides: [ // this stays the same + { + files: ['*.svelte'], + processor: 'svelte3/svelte3' + } ], - plugins: ['svelte3', '@typescript-eslint'], // add the TypeScript plugin - overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], // this stays the same + rules: { + // ... + }, settings: { 'svelte3/typescript': require('typescript'), // pass the TypeScript package to the Svelte plugin // ... - }, - // The following is only needed if you want to use type-aware rules - // It assumes that your eslint config is at the root next to your tsconfig.json - // More info: https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/TYPED_LINTING.md - parserOptions: { + } +}; +``` + +If you also want to be able to use type-aware linting rules (which will result in slower linting, because the whole program needs to be compiled and type-checked), then you also need to add some `parserOptions` configuration. The values below assume that your ESLint config is at the root of your project next to your `tsconfig.json`. For more information, see [here](https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/TYPED_LINTING.md). + +```javascript +module.exports = { + // ... + parserOptions: { // add these parser options tsconfigRootDir: __dirname, project: ['./tsconfig.json'], extraFileExtensions: ['.svelte'], }, -} + extends: [ // then, enable whichever type-aware rules you want to use + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking' + ], + // ... +}; ``` ## Interactions with other plugins @@ -129,9 +144,9 @@ The default is to not use named code blocks. ### `svelte3/typescript` -If you use TypeScript inside your Svelte components and want ESLint support, you need to set this option. It expects the TypeScript package. +If you use TypeScript inside your Svelte components and want ESLint support, you need to set this option. It expects an instance of the TypeScript package. This probably means doing `'svelte3/typescript': require('typescript')`. -Example: `"svelte3/typescript": require("typescript")` +The default is to not enable TypeScript support. ### `svelte3/compiler` From d86cd2434d183f688eedba65cf149dc04c013f18 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 15 Feb 2021 18:18:34 -0500 Subject: [PATCH 12/13] bundle sourcemap-codec --- package.json | 5 ++--- rollup.config.js | 3 +++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index a050b26..f4b9bec 100644 --- a/package.json +++ b/package.json @@ -34,14 +34,13 @@ "test": "npm run build && node test" }, "devDependencies": { + "@rollup/plugin-node-resolve": "^11.2.0", "@typescript-eslint/eslint-plugin": "^4.14.2", "@typescript-eslint/parser": "^4.14.2", "eslint": ">=6.0.0", "rollup": "^2", + "sourcemap-codec": "1.4.8", "svelte": "^3.2.0", "typescript": "^4.0.0" - }, - "dependencies": { - "sourcemap-codec": "^1.4.8" } } diff --git a/rollup.config.js b/rollup.config.js index 78de19d..ee052fd 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,4 +1,7 @@ +import node_resolve from '@rollup/plugin-node-resolve'; + export default { input: 'src/index.js', output: { file: 'index.js', format: 'cjs' }, + plugins: [ node_resolve() ], }; From f2857067e23b749a694cccf353dd34be3d0c2b98 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 15 Feb 2021 18:18:48 -0500 Subject: [PATCH 13/13] tweak/tidy --- src/mapping.js | 2 +- src/preprocess.js | 2 +- .../typescript-block-filenames/.eslintrc.js | 31 +++++++++---------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/mapping.js b/src/mapping.js index a3d2080..f8bf778 100644 --- a/src/mapping.js +++ b/src/mapping.js @@ -135,7 +135,7 @@ export class DocumentMapper { /** * Get the offset of the line and character position * @param position Line and character position - * @param text The text for which the offset should be retrived + * @param text The text for which the offset should be retrieved */ function offset_at(position, text) { const line_offsets = get_line_offsets(text); diff --git a/src/preprocess.js b/src/preprocess.js index f3a48dc..bd2c933 100644 --- a/src/preprocess.js +++ b/src/preprocess.js @@ -1,7 +1,7 @@ import { new_block, get_translation } from './block.js'; import { processor_options } from './processor_options.js'; import { state } from './state.js'; -import { DocumentMapper } from './mapping'; +import { DocumentMapper } from './mapping.js'; let default_compiler; diff --git a/test/samples/typescript-block-filenames/.eslintrc.js b/test/samples/typescript-block-filenames/.eslintrc.js index cfd92f6..b283cbe 100644 --- a/test/samples/typescript-block-filenames/.eslintrc.js +++ b/test/samples/typescript-block-filenames/.eslintrc.js @@ -3,26 +3,25 @@ module.exports = { extends: ['plugin:@typescript-eslint/recommended'], plugins: ['@typescript-eslint'], overrides: [ - { - files: ['**/*.svelte/*_template.ts'], - rules: { - curly: 'off', + { + files: ['**/*.svelte/*_template.ts'], + rules: { + curly: 'off', + }, }, - }, - { - files: ['**/*.svelte/*_module.ts'], - rules: { - 'no-undef': 'off', + { + files: ['**/*.svelte/*_module.ts'], + rules: { + 'no-undef': 'off', + }, }, - }, ], settings: { - 'svelte3/typescript': require('typescript'), - 'svelte3/named-blocks': true, + 'svelte3/typescript': require('typescript'), + 'svelte3/named-blocks': true, }, rules: { - curly: 'error', - 'no-undef': 'error', + curly: 'error', + 'no-undef': 'error', }, - }; - \ No newline at end of file +};