From 192bf8253bcabd5e26fc75371c0224ad9e261a5f Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 16 Feb 2021 00:22:03 +0100 Subject: [PATCH] add TypeScript support (#83) Co-authored-by: Conduitry --- CHANGELOG.md | 4 + README.md | 54 ++++- package.json | 7 +- rollup.config.js | 3 + src/mapping.js | 206 ++++++++++++++++++ src/preprocess.js | 108 +++++++-- src/processor_options.js | 1 + .../typescript-block-filenames/.eslintrc.js | 27 +++ .../typescript-block-filenames/Input.svelte | 10 + .../typescript-block-filenames/expected.json | 12 + .../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 + test/samples/typescript/.eslintrc.js | 12 + test/samples/typescript/Input.svelte | 23 ++ test/samples/typescript/expected.json | 184 ++++++++++++++++ 17 files changed, 732 insertions(+), 16 deletions(-) create mode 100644 src/mapping.js 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 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 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..ae77479 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,52 @@ 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. +### 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 + plugins: [ + 'svelte3', + '@typescript-eslint' // add the TypeScript plugin + ], + overrides: [ // this stays the same + { + files: ['*.svelte'], + processor: 'svelte3/svelte3' + } + ], + rules: { + // ... + }, + settings: { + 'svelte3/typescript': require('typescript'), // pass the TypeScript package to the Svelte plugin + // ... + } +}; +``` + +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 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 +136,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-block-filenames/.eslintrc.js b/test/samples/typescript-block-filenames/.eslintrc.js new file mode 100644 index 0000000..b283cbe --- /dev/null +++ b/test/samples/typescript-block-filenames/.eslintrc.js @@ -0,0 +1,27 @@ +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', + }, +}; 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 + } +] 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": ["**/*"] +} 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..8c05a26 --- /dev/null +++ b/test/samples/typescript/expected.json @@ -0,0 +1,184 @@ +[ + { + "ruleId": "module-script-reactive-declaration", + "severity": 1, + "line": 3, + "column": 2, + "endLine": 3, + "endColumn": 12 + }, + { + "ruleId": "no-undef", + "severity": 2, + "line": 3, + "column": 5, + "endLine": 3, + "endColumn": 7 + }, + { + "ruleId": "unused-export-let", + "severity": 1, + "line": 7, + "column": 13, + "endLine": 7, + "endColumn": 19 + }, + { + "ruleId": "@typescript-eslint/no-explicit-any", + "severity": 1, + "line": 7, + "column": 16, + "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, + "line": 9, + "column": 2, + "endLine": 9, + "endColumn": 3, + "fix": { + "range": [ + 112, + 113 + ], + "text": "" + } + }, + { + "ruleId": "@typescript-eslint/no-explicit-any", + "severity": 1, + "line": 9, + "column": 17, + "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, + "line": 9, + "column": 20, + "endLine": 10, + "endColumn": 2, + "fix": { + "range": [ + 130, + 130 + ], + "text": ";" + } + }, + { + "ruleId": "@typescript-eslint/no-unused-vars", + "severity": 1, + "line": 12, + "column": 8, + "endLine": 12, + "endColumn": 11 + }, + { + "ruleId": "@typescript-eslint/no-empty-interface", + "severity": 2, + "line": 14, + "column": 12, + "endLine": 14, + "endColumn": 13 + }, + { + "ruleId": "@typescript-eslint/no-unused-vars", + "severity": 1, + "line": 14, + "column": 12, + "endLine": 14, + "endColumn": 13 + }, + { + "ruleId": "@typescript-eslint/no-unused-vars", + "severity": 1, + "line": 16, + "column": 13, + "endLine": 16, + "endColumn": 14 + }, + { + "ruleId": "@typescript-eslint/no-empty-function", + "severity": 2, + "line": 16, + "column": 16, + "endLine": 16, + "endColumn": 18 + }, + { + "ruleId": "module-script-reactive-declaration", + "severity": 1, + "line": 19, + "column": 10, + "endLine": 19, + "endColumn": 11 + }, + { + "ruleId": "missing-declaration", + "severity": 1, + "line": 23, + "column": 5, + "endLine": 23, + "endColumn": 6 + }, + { + "ruleId": "no-undef", + "severity": 2, + "line": 23, + "column": 5, + "endLine": 23, + "endColumn": 6 + } +] \ No newline at end of file