Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 53 additions & 14 deletions src/preprocess.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,31 +174,63 @@ export const preprocess = text => {
return [...state.blocks].map(([filename, { transformed_code: text }]) => processor_options.named_blocks ? { text, filename } : text);
};

// https://github.com/sveltejs/svelte-preprocess/blob/main/src/transformers/typescript.ts
// TypeScript transformer for preserving imports correctly when preprocessing TypeScript files
const ts_import_transformer = (context) => {
const ts = processor_options.typescript;
const visit = (node) => {
if (ts.isImportDeclaration(node)) {
if (node.importClause && node.importClause.isTypeOnly) {
return ts.createEmptyStatement();
}

return ts.createImportDeclaration(
node.decorators,
node.modifiers,
node.importClause,
node.moduleSpecifier,
);
}

return ts.visitEachChild(node, (child) => visit(child), context);
};

return (node) => ts.visitNode(node, visit);
};

// How it works for JS:
// 1. compile code
// 2. return ast/vars/warnings
// How it works for TS:
// 1. transpile script contents from TS to JS
// 2. compile result to get Svelte compiler warnings
// 2. compile result to get Svelte compiler warnings and variables
// 3. provide a mapper to 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 and warnings of step 5, vars of step 2
// 5. parse the source to get the AST
// 6. return AST of step 5, warnings and vars of step 2
function compile_code(text, compiler, processor_options) {
let ast;
let warnings;
let vars;

const ts = processor_options.typescript;
let mapper;
let ts_result;
if (processor_options.typescript) {
if (ts) {
const diffs = [];
let accumulated_diff = 0;
const transpiled = text.replace(/<script(\s[^]*?)?>([^]*?)<\/script>/gi, (match, attributes = '', content) => {
const output = processor_options.typescript.transpileModule(
content,
{ reportDiagnostics: false, compilerOptions: { target: processor_options.typescript.ScriptTarget.ESNext, sourceMap: true } }
);
const output = ts.transpileModule(content, {
reportDiagnostics: false,
compilerOptions: {
target: ts.ScriptTarget.ESNext,
importsNotUsedAsValues: ts.ImportsNotUsedAsValues.Preserve,
sourceMap: true
},
transformers: {
before: [ts_import_transformer]
}
});
const original_start = text.indexOf(content);
const generated_start = accumulated_diff + original_start;
accumulated_diff += output.outputText.length - content.length;
Expand All @@ -214,7 +246,14 @@ function compile_code(text, compiler, processor_options) {
return `<script${attributes}>${output.outputText}</script>`;
});
mapper = new DocumentMapper(text, transpiled, diffs);
ts_result = compiler.compile(transpiled, { generate: false, ...processor_options.compiler_options });
try {
ts_result = compiler.compile(transpiled, { generate: false, ...processor_options.compiler_options });
} catch (err) {
// remap the error to be in the correct spot and rethrow it
err.start = mapper.get_original_position(err.start);
err.end = mapper.get_original_position(err.end);
throw err;
}

text = text.replace(/<script(\s[^]*?)?>([^]*?)<\/script>/gi, (match, attributes = '', content) => {
return `<script${attributes}>${content
Expand All @@ -226,12 +265,12 @@ function compile_code(text, compiler, processor_options) {
});
}

const result = compiler.compile(text, { generate: false, ...processor_options.compiler_options });

if (!processor_options.typescript) {
({ ast, warnings, vars } = result);
if (!ts) {
({ ast, warnings, vars } = compiler.compile(text, { generate: false, ...processor_options.compiler_options }));
} else {
ast = result.ast;
// if we do a full recompile Svelte can fail due to the blank script tag not declaring anything
// so instead we just parse for the AST (which is likely faster, anyways)
ast = compiler.parse(text, { ...processor_options.compiler_options });
({ warnings, vars } = ts_result);
}

Expand Down
8 changes: 8 additions & 0 deletions test/samples/typescript-bind-reference/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
parser: '@typescript-eslint/parser',
extends: ['plugin:@typescript-eslint/recommended'],
plugins: ['@typescript-eslint'],
settings: {
'svelte3/typescript': require('typescript'),
},
};
7 changes: 7 additions & 0 deletions test/samples/typescript-bind-reference/Input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
let value = '';
let foo = '';
</script>

<input bind:value />
<input bind:value={foo} />
1 change: 1 addition & 0 deletions test/samples/typescript-bind-reference/expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
10 changes: 10 additions & 0 deletions test/samples/typescript-imports/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
settings: {
'svelte3/typescript': require('typescript'),
},
rules: {
'no-unused-vars': 'error',
},
};
11 changes: 11 additions & 0 deletions test/samples/typescript-imports/Input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script lang='ts'>
import type { Type, UnusedType } from './types';
import Component from './Component.svelte';
import UnusedComponent from './UnusedComponent.svelte';
import { Thing1, Thing2, UnusedThing } from './things';
const a: Type = new Thing1();
a;
</script>

<div on:click={() => new Thing2()}></div>
<Component />
26 changes: 26 additions & 0 deletions test/samples/typescript-imports/expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{
"ruleId": "no-unused-vars",
"severity": 2,
"line": 2,
"column": 22,
"endLine": 2,
"endColumn": 32
},
{
"ruleId": "no-unused-vars",
"severity": 2,
"line": 4,
"column": 9,
"endLine": 4,
"endColumn": 24
},
{
"ruleId": "no-unused-vars",
"severity": 2,
"line": 5,
"column": 27,
"endLine": 5,
"endColumn": 38
}
]