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) add svelte2tsx for better ts support #57

Merged
merged 24 commits into from
May 12, 2020

Conversation

dummdidumm
Copy link
Member

@dummdidumm dummdidumm commented May 4, 2020

This PR adds svelte2tsx to get better typescript support.

Still TODO:

  • There is one failing test for code actions. I think there is an edge case of source mapping there, the end of the replace range cannot be mapped. Not really sure how to deal with this. Change test to see if non-edge-cases work and then just leave it at that? see (feat) add svelte2tsx for better ts support #57 (comment)

To discuss:

  • Right now it is differentiated between JSX and TSX based on whether or not the user uses lang="typescript" or not. Advantage: User does not get type errors he does not want because he uses JS. Disadvantage: You don't get component name/prop typechecking. What to do? Maybe add a settings option to make this configurable by the user? the @ts-check comment can be used for that
  • I think the @ts-check comment at the top of script contents will not work anymore. Is this bad? will work now

@halfnelson could you also have a short look and see I'm not doing anything funky there?

Related #11

@jasonlyu123
Copy link
Member

jasonlyu123 commented May 5, 2020

I think @ts-check should be moved to the top of jsx in svelte2tsx. Typescript would then check js if it exists. As for per project check, user should use the checkJS option in jsconfig.

The lang/attribute setting is kind of weird to me though. Because it would change the entire file to ts, not just the script it annotated. I understand it's very hard to support multiple-languages in the same component. Maybe we should warn user that this is not supported. Or we could invent a new special comment/tag/detective that should be placed at the top of the file to specify ts-check and the language type of that file.

tsxMap.sources = [uri];
}
} catch (e) {
text = '';
Copy link
Contributor

Choose a reason for hiding this comment

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

I had this approach and found that it gave too man angry red squiggles when the document is in a state where it won't parse.

https://github.com/halfnelson/svelte-type-checker-vscode/blob/svelte-native-support/server/src/DocumentSnapshot.ts#L50
In my plugin I stash the parse error so that when I get a call to get the diagnostics, I can check for the existence of the error and return the parse error (instead of treating the file as blank). Is the intention to let the svelte plugin point out the location of an parse errors?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point. At the moment there is no error at all shown if there are parsing errors. With your strategy this could be pointing to parser errors instead, as you said. I will look into it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

@halfnelson
Copy link
Contributor

Looks good to me 👍 Regarding the failing test for code actions. I had a bunch of trouble with off by one errors, especially due to the fact that TS's lines start at 0 and characters at 1, and sourcemap starts at 1,1 (from memory), So there might still be a mapping conversion error in there.

@dummdidumm
Copy link
Member Author

The lang/attribute setting is kind of weird to me though. Because it would change the entire file to ts, not just the script it annotated. I understand it's very hard to support multiple-languages in the same component. Maybe we should warn user that this is not supported. Or we could invent a new special comment/tag/detective that should be placed at the top of the file to specify ts-check and the language type of that file.

Sorry, I don't fully understand what you mean. Do you mean the current implementation is confusing? Or that it would be confusing if we would add a config setting?

I also wonder what's going on under the hood with if the user does not specifiy to use typescript, so the ending is jsx; but still svelte2tsx will create a tsx file. Guess I have to dig further into this.

@jasonlyu123
Copy link
Member

jasonlyu123 commented May 5, 2020

@dummdidumm what I think is weird is that the lang/type attribute on one script would change the entire file's script kind.

<script context="module">
let b = false;
b = 1;  // get type error here
</script>
<script lang="typescript">
let a = 1;
</script>
<button tabindex="-1" /> <!--get type error here -->

So I thought if it would better be if we have a new way to set the script kind at the top of the file. for example:

<!--lang=typscript-->
<script>
</script>

<ComponentA />

@dummdidumm
Copy link
Member Author

Ah I understand. Yeah I think this new way would clear up confusion, but at the same time it could introduce new ones. As of now, all preprocessors look at lang or type attributes inside style / script to know what to do. So someone could ask "why do I need to add this comment at the top additionally?". Also, most users will only ever have one script block inside a component, and they would expect to have everything checked if they add lang="typescript" to one of the script tags - at least that is what I think.

@jasonlyu123
Copy link
Member

jasonlyu123 commented May 5, 2020

Fair point.
What about the user explicitly use two languages in the context and the instance script. which one gets priority? Also, should we warn them this is not supported?

<script context="module" lang="javascript">
</script>
<script lang="typescript">
</script>
<button tabindex="-1" />

@dummdidumm
Copy link
Member Author

I would give typescript the priority since javascript is valid typescript but not the other way round. A warning is a good idea.

@dummdidumm
Copy link
Member Author

Ok, I did test what happens if we use a script without typescript but with checkJs enabled -> it reports errors. So I would leave the logic as is for now: If the user does have lang="typescript" or the likes set on his script, the file will be treated as tsx, else jsx.

What will not work is adding //@ts-check to the top of the file. @halfnelson would you consider adding something like "if //@ts-check on top of the script, add this comment to the top of the tsx-file" to svelte2tsx? If not, we have to either say "ok this is not supported anymore" or deal with it ourselves.

@dummdidumm
Copy link
Member Author

dummdidumm commented May 5, 2020

Ok I found the bug that produces the code actions error.

This script:

<script>let a = true</script>

Gets transformed to this:

<></>;function render() {
let a = true; /// <<---- additional ;
<></>
return { props: {}, slots: {} }}

export default class {
    $$prop_def = __sveltets_partial(render().props)
    $$slot_def = render().slots
}

The code has added a ; at the end of the line. The code fix wants to delete the whole line, which means it slips into the next line, which does not exist in the original, and boom. It seems like svelte2tsx adds a ; to the end of the script. And if the script closes within the same line as the last code AND that code has no ; at the end, then svelte2tsx does not add the ; in the next line but in the same line. The fix would be to always add that ; in a new line. This is a huge enough edge case for me that I'm ok with adjusting the test.

Simon Holthausen added 2 commits May 5, 2020 19:03
There is an edge case where it fails, but that edge case is edgy enough to adjust the test. See sveltejs#57 (comment)
- adjust completions (some workarounds for wrong mappings needed)
- add more completions tests
@dummdidumm
Copy link
Member Author

dummdidumm commented May 8, 2020

Quick update: I merged master into the branch and am now working hard on getting imports right for svelte components. Turns out that there are a lot of edge cases. Also I came across a major drawback of the svelte2tsx approach: Because the svelte compiler is used, intermediate states inside the template will almost always give parsing errors, so there is no code generated.

For example this:

<script>
</script>
<SomeComp

is not recognized as valid by the compiler, so I have to place the import at the right position "by hand".

EDIT: just had an idea how to simplify this. Doing this would mean to alway do the import below the script opening tag, not below the last previous import. But I think that's okay.

@dummdidumm
Copy link
Member Author

Svelte imports are working now, without compromising the other imports.

So there is only one thing left to discuss: Ditch //@ts-check support and maybe add it again later or add it as part of this PR? What do you think?

@jasonlyu123
Copy link
Member

jasonlyu123 commented May 11, 2020

I would like to retain support for @ts-check and @ts-nocheck. If someone used checkJs in the compiler option, they might expect to be able to whitelist some file. And svelte2tsx doesn't move either of them to the top.

Is it possible that we add it to the top of tsx if we detect it for now? This detection can be removed later if svelte2tsx decide to support it.

@orta do you know if is there any public typescript API that can detect @ts-check and @ts-nocheck? I only found some internal. if not, is this regex ok?

/^\s*(\/\/[ \t\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]*@ts-(no)?check$)/
  • the first characher set is just \s without linebreak and vertical tab

@dummdidumm dummdidumm marked this pull request as ready for review May 11, 2020 09:20
@dummdidumm
Copy link
Member Author

I added //@ts-(no)check support, it's now ready for review and merge if accepted.

Some things could be refactored. For example I now need style tag info inside ts aswell, so we could move this extraction to the parent document. But I would like to keep these refactorings out of this PR because I fear it will get too big and will touch too many files.

@jasonlyu123
Copy link
Member

jasonlyu123 commented May 11, 2020

Find one issue, the template type check diagnoses needed to update if a dependent component is updated. But this seems needed quite some code to fix, I think, as you said this PR is getting too big, it could be done in another PR.

Overall this is working quite nicely!

@dummdidumm dummdidumm requested a review from orta May 12, 2020 08:28
@orta
Copy link
Contributor

orta commented May 12, 2020

Perfect, I was about to recommend // @ts-check as the solution to your PR question. Reading PR now.

@orta
Copy link
Contributor

orta commented May 12, 2020

@jasonlyu123 - no, no public API for that (it's a step in the parser for us)

Copy link
Contributor

@orta orta left a comment

Choose a reason for hiding this comment

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

Looks great - a minor regex issue, but everything feels to be in place. Great work.

result.line += this.nrPrependesLines;
return result;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

await new SourceMapConsumer(this.tsxMap),
uri,
this.nrPrependedLines,
);
Copy link
Contributor

Choose a reason for hiding this comment

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

this is a lot happening on a single express, might be worth a refactor later

Copy link
Member Author

Choose a reason for hiding this comment

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

Absolutely true, one of the first things I'm gonna look into after this is merged.

}
}

// eslint-disable-next-line
const tsCheckRegex = /^\s*(\/\/[ \t\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]*(@ts-(no)?check)($|\n|\r\n))/;
Copy link
Contributor

Choose a reason for hiding this comment

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

This regex might needs a tweak, here's some examples: https://regex101.com/r/ZXpJNc/2as I think TypeScript is more liberal (any type of comment, and any whitespace after ) in accepting ts-check

Copy link
Member Author

Choose a reason for hiding this comment

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

You are right. I played around a little. Seems like it accepts the comment if

  • it is before the first line of code (so other lines with comments before it are ok)
  • must be @ts-(no)check
  • the comment which has @ts-(no)check can have any type of whitespace before it, but not other characters
  • what's coming after @ts-(no)check is irrelevant as long there is any kind of whitespace or line break, so this would be picked up, too: // @ts-check asdasd

.map((diagnostic) => mapDiagnosticToParent(fragment, diagnostic))
.filter(
(diagnostic) => diagnostic.range.start.line >= 0 && diagnostic.range.end.line >= 0,
);
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if this could accidentally remove diagnostics which come from setup around the specific file? It seems unlikely, but at least worth bringing up

Copy link
Member Author

Choose a reason for hiding this comment

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

I added this because in some rare cases the diagnostic could not be mapped correctly. But VSCode or the LSP (don't know which one) then seems to swallow all other errors aswell as soon as it encounters one diagnostic with negative lines. I will add a comment.

commitCharacters: getCommitCharactersForScriptElement(comp.kind),
preselect: comp.isRecommended,
// Make sure svelte component takes precedence
sortText: isSvelteComp ? '-1' : comp.sortText,
Copy link
Contributor

Choose a reason for hiding this comment

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

nice work

};
}

private getCompletionLableAndInsert(comp: ts.CompletionEntry) {
const { kind, kindModifiers, name } = comp;
private getCompletionLableAndInsert(fragment: SnapshotFragment, comp: ts.CompletionEntry) {
Copy link
Contributor

Choose a reason for hiding this comment

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

irrelevant: getCompletionLableAndInsert has a typo

Copy link
Member

Choose a reason for hiding this comment

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

that is my fault lol

Copy link
Member Author

Choose a reason for hiding this comment

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

Np, I will change it 😄

@dummdidumm dummdidumm merged commit 6cb8d6e into sveltejs:master May 12, 2020
@dummdidumm dummdidumm deleted the svelte2tsx branch May 12, 2020 15:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants