Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) rewrite svelte2tsx #1237

Merged
merged 104 commits into from
Jan 27, 2022
Merged
Show file tree
Hide file tree
Changes from 84 commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
09205d0
(feat) rewrite svelte2tsx
Nov 9, 2021
12f6ca2
debug update
Nov 9, 2021
37e7a96
fix: don't hand Style/Script attributes
Nov 9, 2021
0d5526a
if/else support
Nov 9, 2021
2ab4748
add missing shims
Nov 9, 2021
f86523e
handle each
Nov 9, 2021
260df70
handle mustache tags in markup
Nov 9, 2021
0489f90
make tests generate new versions
Nov 9, 2021
9900099
generate ts/js, not jsx/tsx
Nov 9, 2021
38d65f0
add test outputs that are correct already
Nov 9, 2021
840e5d0
await support
Nov 10, 2021
4a849f6
create svelte namespace alias for less confusion when errors or hover…
Nov 10, 2021
ee3f1dd
refine API a little - new option for mode instead of new setting, typ…
Nov 11, 2021
8804d9e
support action/animation/transition directives
Nov 11, 2021
ff31a8c
fix action/animation/transitions: add shims, handle no params case di…
Nov 11, 2021
65d444a
support key,debug,html
Nov 11, 2021
e451db5
adjust transform util: replace overwritten content with one whitespac…
Nov 11, 2021
51b0bcb
support class, binding
Nov 11, 2021
9cf937f
transform shorthands to property shorthands for better source mapping…
Nov 15, 2021
85bf360
options/window/head/body svelte element support
Nov 16, 2021
51e4fef
support slot tag
Nov 16, 2021
1408f8c
support slot let
Nov 17, 2021
8c80637
Merge branch 'master' into svelte2tsx-rewrite
Nov 17, 2021
732662f
fix mappings
Nov 17, 2021
7449447
support svelteself/sveltecomponent
Nov 18, 2021
66add32
better svelte:component/self handling, tests
Nov 19, 2021
4214e0c
fix animation/transition params case
Nov 19, 2021
2077646
quotes fix
Nov 19, 2021
704ff92
css custom properties, multiple/empty attr fix
Nov 20, 2021
6e63f16
fix "used before defined" edge case error when let:x {x} used on same…
Nov 20, 2021
1869f3b
fix auto-closing
Nov 24, 2021
7144ef0
fix EachBlock startEnd
Nov 24, 2021
e9b9fd7
document limitation of each/await transformation
Nov 24, 2021
7766e86
handle empty script/style tags
Nov 24, 2021
0130e4d
handle namespaced attributes
Nov 24, 2021
c86788f
move test
Nov 24, 2021
fee80d1
handle event modifiers
Nov 24, 2021
6182956
fix default slot positioning + sveltefragment
Nov 24, 2021
be56c9c
module script processing
Nov 27, 2021
d0a0b23
text finetuning
Nov 27, 2021
18d3cd7
fix autocompletion for last attr of node
Nov 27, 2021
0f22a7f
Merge branch 'master' into svelte2tsx-rewrite
Nov 29, 2021
ee17d6b
several fixes: wrap html with async lambda for await, adjust class,ea…
Dec 1, 2021
465c5aa
fix each block edge case, dont add unused consts, adjust node-utils f…
Dec 1, 2021
f0b8892
more fixes
Dec 3, 2021
508cdc7
tests
Dec 10, 2021
a511009
tests
Dec 10, 2021
6028c30
Merge branch 'master' into svelte2tsx-rewrite
Dec 10, 2021
b724c47
ignore samples
Dec 10, 2021
7cc11bc
test
Dec 10, 2021
d0bc000
fix attribute error + helpers + tests
Dec 10, 2021
d0aff56
Merge branch 'master' into svelte2tsx-rewrite
Dec 17, 2021
d7e7a68
Merge branch 'master' into svelte2tsx-rewrite
Dec 18, 2021
d996715
Merge branch 'master' into svelte2tsx-rewrite
Dec 18, 2021
b53ad68
add config to enable/disable new transformation, disabled by default
Dec 19, 2021
7eebe2c
better mapping for on:xx on html elements
Dec 22, 2021
5c8cf93
tests
Dec 22, 2021
d8cc465
fix
Dec 22, 2021
e0a0148
fix
Dec 22, 2021
f6cb638
Merge branch 'master' into svelte2tsx-rewrite
Jan 10, 2022
de9e546
tests
Jan 10, 2022
c20d273
better event name mapping
Jan 10, 2022
c28296e
update diagnostic tests so that they can run both new and old transfo…
Jan 10, 2022
007d0f1
update test
Jan 10, 2022
541f286
Fix await block transformation to deal with cases mentioned in its fu…
Jan 10, 2022
195da86
handle each block shadowing and ensure it's an arraylike
Jan 10, 2022
54567b2
fix typing to any else TS will create a weird type
Jan 10, 2022
aee8fd0
remap "missing prop" error
Jan 10, 2022
5183973
Merge branch 'master' into svelte2tsx-rewrite
Jan 13, 2022
9361888
Merge branch 'master' into svelte2tsx-rewrite
Jan 13, 2022
bcb991d
Add support for StyleDirective and ConstTag, fix some bugs
Jan 14, 2022
dea6cb7
ensure test doesn't mess with other tests
Jan 14, 2022
627584d
adjust renameprovider
Jan 17, 2022
f8f4863
ensure test is not flaky
Jan 17, 2022
d4f3c03
fix html attribute util
Jan 17, 2022
74fa41d
fix jsx
Jan 17, 2022
6d50025
lint
Jan 17, 2022
efeb165
cleanup
Jan 17, 2022
3533b3c
ensure no shims appear in definitions
Jan 17, 2022
8813a0f
code actions tests for new and old
Jan 17, 2022
5bee32d
completion tests for old and new
Jan 17, 2022
fb32450
find references tests for old and new
Jan 17, 2022
81aa288
hover provider tests for old and new
Jan 17, 2022
dbf1187
lint
Jan 17, 2022
3e81db8
handle spread
Jan 19, 2022
90512c4
better event name mapping
Jan 19, 2022
fc680aa
fix typo
Jan 19, 2022
2ba6c59
fix regex
Jan 19, 2022
1c0ee88
simplify selfclosing logic
Jan 19, 2022
6faa3f8
add missing expectedv2 files
Jan 19, 2022
aae863b
map component end tag
Jan 19, 2022
51106ac
add support for namespacing createElement, default namespace is svelt…
Jan 20, 2022
5a7d824
better document symbols for new transformation
Jan 20, 2022
32ec15d
fix tests
Jan 21, 2022
c4a7e8a
update typescript plugin to use new version, bump svelte2tsx version
Jan 21, 2022
d620ab6
lint
Jan 21, 2022
61bfc07
lint, try fix tests
Jan 21, 2022
fc57843
comment out part of test
Jan 22, 2022
46db627
no semantic highlighting for on:event
Jan 22, 2022
b984540
ensure whitespace is mapped to props/attrs for better completion
Jan 27, 2022
ffd2c8f
tests
Jan 27, 2022
cefd1ca
better slot name mapping
Jan 27, 2022
f442854
fix dev script
Jan 27, 2022
3063337
deduplicate html tag completions
Jan 27, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ packages/svelte2tsx/test/**/*
packages/svelte2tsx/index.*
declare module '*.svelte'
packages/svelte2tsx/svelte-shims.d.ts
packages/svelte2tsx/svelte-jsx.d.ts
4 changes: 2 additions & 2 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
packages/svelte2tsx/*.d.ts
packages/svelte2tsx/test/*/samples/*/*sx
packages/svelte2tsx/test/*/samples/*/*.svelte
packages/svelte2tsx/repl/*
packages/svelte2tsx/test/*/samples/**/*
packages/svelte2tsx/test/sourcemaps/samples/*
packages/svelte2tsx/test/emitDts/samples/*/expected/**
packages/language-server/test/**/*.svelte
Expand Down
13 changes: 13 additions & 0 deletions packages/language-server/src/lib/documents/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,19 @@ export function getNodeIfIsInStartTag(html: HTMLDocument, offset: number): Node
}
}

/**
* Returns `true` if `offset` is a html tag and within the name of the start tag or end tag
*/
export function isInHTMLTagRange(html: HTMLDocument, offset: number): boolean {
const node = html.findNodeAt(offset);
return (
!!node.tag &&
node.tag[0] === node.tag[0].toLowerCase() &&
(node.start + node.tag.length + 1 >= offset ||
(!!node.endTagStart && node.endTagStart <= offset))
);
}

/**
* Gets word range at position.
* Delimiter is by default a whitespace, but can be adjusted.
Expand Down
2 changes: 2 additions & 0 deletions packages/language-server/src/ls-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const defaultLSConfig: LSConfig = {
},
svelte: {
enable: true,
useNewTransformation: false,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used to determine whether or not to use the new transformation. My idea is to go into a month-long beta phase where this is false by default and we encourage people to switch it on to try it out. After that we toggle it to true by default and then see what the feedback is. If everything is good, we can remove it ~2 months afterwards and remove all the old code, simplifying the code base.

This boolean is used in a few places in some Providers to do something differently.

compilerWarnings: {},
diagnostics: { enable: true },
rename: { enable: true },
Expand Down Expand Up @@ -176,6 +177,7 @@ export type CompilerWarningsSettings = Record<string, 'ignore' | 'error'>;

export interface LSSvelteConfig {
enable: boolean;
useNewTransformation: boolean;
compilerWarnings: CompilerWarningsSettings;
diagnostics: {
enable: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export interface SnapshotFragment extends DocumentMapper {
*/
export interface SvelteSnapshotOptions {
transformOnTemplateError: boolean;
useNewTransformation: boolean;
}

export namespace DocumentSnapshot {
Expand Down Expand Up @@ -159,13 +160,20 @@ function preprocessSvelteFile(document: Document, options: SvelteSnapshotOptions
getScriptKindFromAttributes(document.scriptInfo?.attributes ?? {}),
getScriptKindFromAttributes(document.moduleScriptInfo?.attributes ?? {})
].includes(ts.ScriptKind.TSX)
? ts.ScriptKind.TSX
? options.useNewTransformation
? ts.ScriptKind.TS
: ts.ScriptKind.TSX
: options.useNewTransformation
? ts.ScriptKind.JS
: ts.ScriptKind.JSX;

try {
const tsx = svelte2tsx(text, {
filename: document.getFilePath() ?? undefined,
isTsFile: scriptKind === ts.ScriptKind.TSX,
isTsFile: options.useNewTransformation
? scriptKind === ts.ScriptKind.TS
: scriptKind === ts.ScriptKind.TSX,
mode: options.useNewTransformation ? 'ts' : 'tsx',
emitOnTemplateError: options.transformOnTemplateError,
namespace: document.config?.compilerOptions?.namespace,
accessors:
Expand Down Expand Up @@ -388,7 +396,7 @@ export class SvelteSnapshotFragment implements SnapshotFragment {
constructor(
private readonly mapper: DocumentMapper,
public readonly text: string,
private readonly parent: Document,
public readonly parent: Document,
private readonly url: string
) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export class LSAndTSDocResolver {
return {
ambientTypesSource: this.isSvelteCheck ? 'svelte-check' : 'svelte2tsx',
createDocument: this.createDocument,
useNewTransformation: this.configManager.getConfig().svelte.useNewTransformation,
transformOnTemplateError: !this.isSvelteCheck,
globalSnapshotsManager: this.globalSnapshotsManager,
notifyExceedSizeLimit: this.notifyExceedSizeLimit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,11 @@ export class TypeScriptPlugin
configManager
);
this.updateImportsProvider = new UpdateImportsProviderImpl(this.lsAndTsDocResolver);
this.diagnosticsProvider = new DiagnosticsProviderImpl(this.lsAndTsDocResolver);
this.renameProvider = new RenameProviderImpl(this.lsAndTsDocResolver);
this.diagnosticsProvider = new DiagnosticsProviderImpl(
this.lsAndTsDocResolver,
configManager
);
this.renameProvider = new RenameProviderImpl(this.lsAndTsDocResolver, configManager);
this.hoverProvider = new HoverProviderImpl(this.lsAndTsDocResolver);
this.findReferencesProvider = new FindReferencesProviderImpl(this.lsAndTsDocResolver);
this.selectionRangeProvider = new SelectionRangeProviderImpl(this.lsAndTsDocResolver);
Expand Down Expand Up @@ -300,7 +303,10 @@ export class TypeScriptPlugin
defs.definitions.map(async (def) => {
const { fragment, snapshot } = await docs.retrieve(def.fileName);

if (isNoTextSpanInGeneratedCode(snapshot.getFullText(), def.textSpan)) {
if (
!def.fileName.endsWith('svelte-shims.d.ts') &&
isNoTextSpanInGeneratedCode(snapshot.getFullText(), def.textSpan)
) {
return LocationLink.create(
pathToUrl(def.fileName),
convertToLocationRange(fragment, def.textSpan),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,25 +324,28 @@ export class CodeActionsProviderImpl implements CodeActionsProvider {
start,
length: end - start
},
(node): node is ts.JsxOpeningLikeElement | ts.JsxClosingElement =>
ts.isJsxClosingElement(node) || ts.isJsxOpeningLikeElement(node)
(node): node is ts.JsxOpeningLikeElement | ts.JsxClosingElement | ts.Identifier =>
this.configManager.getConfig().svelte.useNewTransformation
? ts.isNewExpression(node.parent) && ts.isIdentifier(node)
: ts.isJsxClosingElement(node) || ts.isJsxOpeningLikeElement(node)
);

if (!node) {
return;
}

const tagName = ts.isIdentifier(node) ? node : node.tagName;
const completion = lang.getCompletionsAtPosition(
filePath,
node.tagName.getEnd(),
tagName.getEnd(),
userPreferences
);

if (!completion) {
return;
}

const name = node.tagName.getText();
const name = tagName.getText();
const suffixedName = name + '__SvelteComponent_';
const errorPreventingUserPreferences =
this.completionProvider.fixUserPreferencesForSvelteComponentImport(userPreferences);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,10 @@ function isValidCompletion(
if (!isCompletionInHTMLStartTag) {
return isNoSvelte2tsxCompletion;
}
// TODO with the new transformation this is ts.ScriptElementKind.memberVariableElement
// which is also true for all properties of any other object -> how reliably filter this out?
// ---> another /*ignore*/ pragma?
// ---> OR: make these lower priority if we find out they are inside a html start tag
return (value) =>
// Remove jsx attributes on html tags because they are doubled by the HTML
// attribute suggestions, and for events they are wrong (onX instead of on:X).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import ts from 'typescript';
import { CancellationToken, Diagnostic, DiagnosticSeverity } from 'vscode-languageserver';
import { Document, getTextInRange, isRangeInTag, mapRangeToOriginal } from '../../../lib/documents';
import {
Document,
getNodeIfIsInStartTag,
getTextInRange,
isRangeInTag,
mapRangeToOriginal
} from '../../../lib/documents';
import { DiagnosticsProvider } from '../../interfaces';
import { LSAndTSDocResolver } from '../LSAndTSDocResolver';
import { convertRange, getDiagnosticTag, mapSeverity } from '../utils';
import { convertRange, getDiagnosticTag, hasNonZeroRange, mapSeverity } from '../utils';
import { SvelteDocumentSnapshot, SvelteSnapshotFragment } from '../DocumentSnapshot';
import {
isInGeneratedCode,
isAfterSvelte2TsxPropsReturn,
findNodeAtSpan,
isReactiveStatement,
isInReactiveStatement,
gatherIdentifiers
gatherIdentifiers,
isHTMLAttributeName
} from './utils';
import { not, flatten, passMap, regexIndexOf, swapRangeStartEndIfNecessary } from '../../../utils';
import { LSConfigManager } from '../../../ls-config';

enum DiagnosticCode {
MODIFIERS_CANNOT_APPEAR_HERE = 1184, // "Modifiers cannot appear here."
Expand All @@ -24,11 +32,19 @@ enum DiagnosticCode {
NEVER_READ = 6133, // "'{0}' is declared but its value is never read."
ALL_IMPORTS_UNUSED = 6192, // "All imports in import declaration are unused."
UNUSED_LABEL = 7028, // "Unused label."
DUPLICATED_JSX_ATTRIBUTES = 17001 // "JSX elements cannot have multiple attributes with the same name."
DUPLICATED_JSX_ATTRIBUTES = 17001, // "JSX elements cannot have multiple attributes with the same name."
DUPLICATE_IDENTIFIER = 2300, // "Duplicate identifier 'xxx'"
MULTIPLE_PROPS_SAME_NAME = 1117, // "An object literal cannot have multiple properties with the same name in strict mode."
TYPE_X_NOT_ASSIGNABLE_TO_TYPE_Y = 2345, // "Argument of type '..' is not assignable to parameter of type '..'."
MISSING_PROPS = 2739, // "Type '...' is missing the following properties from type '..': ..."
MISSING_PROP = 2741 // "Property '..' is missing in type '..' but required in type '..'."
}

export class DiagnosticsProviderImpl implements DiagnosticsProvider {
constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {}
constructor(
private readonly lsAndTsDocResolver: LSAndTSDocResolver,
private configManager: LSConfigManager
) {}

async getDiagnostics(
document: Document,
Expand All @@ -43,7 +59,8 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider {
return [];
}

const isTypescript = tsDoc.scriptKind === ts.ScriptKind.TSX;
const isTypescript =
tsDoc.scriptKind === ts.ScriptKind.TSX || tsDoc.scriptKind === ts.ScriptKind.TS;

// Document preprocessing failed, show parser error instead
if (tsDoc.parserError) {
Expand All @@ -67,7 +84,8 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider {
];
diagnostics = diagnostics
.filter(isNotGenerated(tsDoc.getText(0, tsDoc.getLength())))
.filter(not(isUnusedReactiveStatementLabel));
.filter(not(isUnusedReactiveStatementLabel))
.filter(isNoFalsePositive1(this.configManager.getConfig().svelte.useNewTransformation));
diagnostics = resolveNoopsInReactiveStatements(lang, diagnostics);

return diagnostics
Expand All @@ -79,9 +97,15 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider {
code: diagnostic.code,
tags: getDiagnosticTag(diagnostic)
}))
.map(mapRange(fragment, document))
.map(
mapRange(
fragment,
document,
this.configManager.getConfig().svelte.useNewTransformation
)
)
.filter(hasNoNegativeLines)
.filter(isNoFalsePositive(document, tsDoc))
.filter(isNoFalsePositive2(document, tsDoc))
.map(enhanceIfNecessary)
.map(swapDiagRangeStartEndIfNecessary);
}
Expand All @@ -93,7 +117,8 @@ export class DiagnosticsProviderImpl implements DiagnosticsProvider {

function mapRange(
fragment: SvelteSnapshotFragment,
document: Document
document: Document,
useNewTransformation: boolean
): (value: Diagnostic) => Diagnostic {
return (diagnostic) => {
let range = mapRangeToOriginal(fragment, diagnostic.range);
Expand Down Expand Up @@ -123,6 +148,21 @@ function mapRange(
}
}

if (
useNewTransformation &&
[DiagnosticCode.MISSING_PROP, DiagnosticCode.MISSING_PROPS].includes(
diagnostic.code as number
) &&
!hasNonZeroRange({ range })
) {
const node = getNodeIfIsInStartTag(document.html, document.offsetAt(range.start));
if (node) {
// This is a "some prop missing" error on a component -> remap
range.start = document.positionAt(node.start + 1);
range.end = document.positionAt(node.start + 1 + (node.tag?.length || 1));
}
}

return { ...diagnostic, range };
};
}
Expand All @@ -144,6 +184,26 @@ function copyDiagnosticAndChangeNode(diagnostic: ts.Diagnostic) {
});
}

function isNoFalsePositive1(useNewTransformation: boolean) {
if (!useNewTransformation) {
return () => true;
}

return (diagnostic: ts.Diagnostic) => {
if (
![
DiagnosticCode.MULTIPLE_PROPS_SAME_NAME,
DiagnosticCode.DUPLICATE_IDENTIFIER
].includes(diagnostic.code)
) {
return true;
}

const node = findDiagnosticNode(diagnostic);
return !node || !isHTMLAttributeName(node);
};
}

/**
* In some rare cases mapping of diagnostics does not work and produces negative lines.
* We filter out these diagnostics with negative lines because else the LSP
Expand All @@ -153,7 +213,7 @@ function hasNoNegativeLines(diagnostic: Diagnostic): boolean {
return diagnostic.range.start.line >= 0 && diagnostic.range.end.line >= 0;
}

function isNoFalsePositive(document: Document, tsDoc: SvelteDocumentSnapshot) {
function isNoFalsePositive2(document: Document, tsDoc: SvelteDocumentSnapshot) {
const text = document.getText();
const usesPug = document.getLanguageAttribute('template') === 'pug';

Expand Down
Loading