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
feature(rslint_parser): TypeScript parameter extensions #2040
Conversation
Implements the parsing for TypeScript's function and cosntructor parameter extensions.
Parser conformance results on ubuntu-latestTS
|
| Test result | main count |
This PR count | Difference |
|---|---|---|---|
| Total | 45250 | 45250 | 0 |
| Passed | 44130 | 44130 | 0 |
| Failed | 1120 | 1120 | 0 |
| Panics | 0 | 0 | 0 |
| Coverage | 97.52% | 97.52% | 0.00% |
Deploying with
|
| Latest commit: |
a912415
|
| Status: | |
| Preview URL: | https://cd062772.tools-8rn.pages.dev |
| // (this) => {} | ||
| // ^^^^ | ||
| TsThisParameter = | ||
| 'this' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm torn if this should be a binding identifier or not
Pros:
JsAnyParametercan implement abindingaccessor that returnsSyntaxResult. This isn't possible ifthisis a tokenthisappears in a binding context
Cons:
- The identifier should ever only be
this. Thus, it should be safe to encode it thisisn't binding a new variable in the function scope becausethisis a valid binding in all functions. It may drip analyzers if they introduce athisbinding for a function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typescript AST marks this as an identifier.
I suspect that they do the binding check during a second pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TypeScript uses a single Parameter node to represent all possible parameters. That kind of forces them to use an Identifier. They also don't have the concept of binding identifiers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I would not make it a binding. This would have lot of implications on analyzers where they try to check if variable is actually used inside the scope.
I suppose that would be fixed when we add a second pass for scoping check.
I noticed that there are error case, I think we should add error cases where we attempt to use this as parameter inside an arrow function. It's not valid in TS.
| // (this) => {} | ||
| // ^^^^ | ||
| TsThisParameter = | ||
| 'this' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typescript AST marks this as an identifier.
I suspect that they do the binding check during a second pass.
| } | ||
|
|
||
| #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| pub(crate) enum ParameterContext { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably we should add ArrowFunction to the context. this as parameter is not allowed there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch. I'm wondering if we should split out a JsAnyArrowFunctionParameter union that doesn't include the TsThisParameter but I'm worried that this is a bit overkill and will lead to a bit of duplication.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we should draw a line for when using a new union. Honestly I don't know
It surely allows us to handle different cases nicely, but if it leads to much duplication, maybe it's not worth it
Argh, why xD I mean... it does make sense but it adds another invariant. |
|
I explored a few options but decided to not split the arrow and function parameters.
That's why I would leave it as is for now. It comes at the cost that a transformer or refactor may convert a function expression to an arrow function without noticing the |
Summary
Implements the parsing for TypeScript's function and constructor parameter extensions:
function a(b?) {}class A { constructor(private a, readonly b) {} }set a(b?) {}orset a(b = "test") {}thisparameter:function a(this: string) {}There are multiple semantic rules that apply to parameters and I haven't found a reasonable approach that covers all (most) of them:
?or initializer but not boththisparameters aren't allowed in constructors and arrow functionsA second design goal is that TypeScript is an extension to the JS grammar so that a JS only analyzer works correctly if it ignores all TypeScript nodes.
The different options that I explored:
?,!and the type annotation appear between the binding and the initializer. Thus, the nodes wouldn't act as an extension that just wrap some Js nodes and add some additional optional children at the endJsFormalParameterandJsFormalParameterWithDefault. TheJsFormalParameterhas an optional?token but doesn't allow for aninitializer. TheJsFormalParameterWithDefaultdoesn't allow the?token but has an initializer. This prevents an AST transform from adding an initializer (or the?token) to a node that already has the?set. However, it felt overly complicated to prevent this minor issue when the AST design doesn't prevent the other invariants. Furthermore, it adds additional complexity when handling parameters.TsPropertyParameter: This would allow to correctly express that property parameter only allow identifier bindings. However, it breaks with the second goal that TypeScript nodes are merely an extension that JS analyzers can safely ignore. Ignoring aPropertyParameterwould mean that they miss an identifier binding and may then report false positives in the body of the constructor.That's why I opted for the "simplest" solution for now, accepting the downside that the AST doesn't encode all invariants.
part of #2046
Test Plan
Verified that all regressions are tests that should fail but are now passing because of fixed syntax errors and the expected errors are type checker errors.
Verified that all fixed tests are indeed intended fixes.
Added new test cases for parameters.