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

feature(rslint_parser): TypeScript parameter extensions #2040

Merged
merged 11 commits into from Feb 4, 2022

Conversation

Copy link
Contributor

@MichaReiser MichaReiser commented Feb 2, 2022

Summary

Implements the parsing for TypeScript's function and constructor parameter extensions:

  • optional parameters: function a(b?) {}
  • property members: class A { constructor(private a, readonly b) {} }
  • setters: no optional parameters: set a(b?) {} or set a(b = "test") {}
  • this parameter: 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:

  • optional setter params are disallowed in TS but allowed in JS
  • array and object patterns aren't allowed in property members
  • array and object patterns aren't allowed for optional parameters in implementations (but OK in a function declaration)
  • a parameter can either have a ? or initializer but not both
  • this parameters aren't allowed in constructors and arrow functions

A 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:

  • add the type annotation to binding: Quiet elegant but TypeScript's grammar specifies that ?, ! 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 end
  • Define a JsFormalParameter and JsFormalParameterWithDefault. The JsFormalParameter has an optional ? token but doesn't allow for an initializer. The JsFormalParameterWithDefault doesn'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.
  • Inlining the parameter into 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 a PropertyParameter would 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.

Implements the parsing for TypeScript's function and cosntructor parameter extensions.
@github-actions
Copy link

@github-actions github-actions bot commented Feb 2, 2022

Parser conformance results on ubuntu-latest

TS

Test result main count This PR count Difference
Total 15976 15976 0
Passed 11126 11134 +8
Failed 4848 4840 -8
Panics 2 2 0
Coverage 69.64% 69.69% +0.05%
🔥 Regression (39):
compiler/callbackArgsDifferByOptionality.ts
compiler/contextualTyping24.ts
compiler/duplicateLocalVariable2.ts
compiler/functionCall11.ts
compiler/functionCall12.ts
compiler/functionCall14.ts
compiler/functionCall15.ts
compiler/functionCall16.ts
compiler/functionCall17.ts
compiler/functionCall8.ts
compiler/functionCall9.ts
compiler/functionOverloads.ts
compiler/functionOverloads27.ts
compiler/genericRestArgs.ts
compiler/implicitAnyFunctionInvocationWithAnyArguements.ts
compiler/jsFileCompilationOptionalParameter.ts
compiler/optionalParamTypeComparison.ts
compiler/optionalParameterProperty.ts
compiler/optionalParamterAndVariableDeclaration2.ts
compiler/overloadAssignmentCompat.ts
compiler/propertyParameterWithQuestionMark.ts
compiler/publicGetterProtectedSetterFromThisParameter.ts
compiler/requiredInitializedParameter1.ts
compiler/restParamsWithNonRestParams.ts
compiler/unusedParametersWithUnderscore.ts
conformance/classes/constructorDeclarations/classConstructorParametersAccessibility2.ts
conformance/classes/constructorDeclarations/superCalls/superPropertyInConstructorBeforeSuperCall.ts
conformance/classes/members/inheritanceAndOverriding/derivedClassTransitivity.ts
conformance/classes/members/inheritanceAndOverriding/derivedClassTransitivity2.ts
conformance/classes/members/inheritanceAndOverriding/derivedClassTransitivity4.ts
conformance/controlFlow/controlFlowAliasing.ts
conformance/expressions/functions/contextuallyTypedIifeStrict.ts
conformance/expressions/newOperator/newOperatorErrorCases_noImplicitAny.ts
conformance/expressions/propertyAccess/propertyAccessWidening.ts
conformance/functions/functionImplementationErrors.ts
conformance/parser/ecmascript5/ParameterLists/parserParameterList3.ts
conformance/types/union/unionTypeCallSignatures.ts
conformance/types/union/unionTypeCallSignatures4.ts
conformance/types/union/unionTypeConstructSignatures.ts
🎉 Fixed (47):
compiler/accessorWithInitializer.ts
compiler/argumentsUsedInObjectLiteralProperty.ts
compiler/binaryArithmeticControlFlowGraphNotTooLarge.ts
compiler/commentsArgumentsOfCallExpression2.ts
compiler/commentsCommentParsing.ts
compiler/contextualTypeAppliedToVarArgs.ts
compiler/customAsyncIterator.ts
compiler/declarationEmitDestructuringParameterProperties.ts
compiler/declarationEmitParameterProperty.ts
compiler/declarationEmitPromise.ts
compiler/destructuringAssignmentWithDefault.ts
compiler/emitSkipsThisWithRestParameter.ts
compiler/functionOverloads13.ts
compiler/functionOverloads23.ts
compiler/functionOverloads25.ts
compiler/functionOverloads26.ts
compiler/functionOverloads28.ts
compiler/functionOverloads8.ts
compiler/functionOverloads9.ts
compiler/genericTypeParameterEquivalence2.ts
compiler/inferParameterWithMethodCallInitializer.ts
compiler/inferTypeArgumentsInSignatureWithRestParameters.ts
compiler/optionalConstructorArgInSuper.ts
compiler/optionalParamInOverride.ts
compiler/optionalParamterAndVariableDeclaration.ts
compiler/overloadCallTest.ts
compiler/overloadWithCallbacksWithDifferingOptionalityOnArgs.ts
compiler/recursiveFieldSetting.ts
compiler/restParameterAssignmentCompatibility.ts
compiler/restParameters.ts
compiler/sourceMapValidationFunctions.ts
compiler/strictNullLogicalAndOr.ts
compiler/unionSignaturesWithThisParameter.ts
compiler/unusedParametersThis.ts
conformance/es2021/logicalAssignment/logicalAssignment5.ts
conformance/es6/destructuring/destructuringParameterProperties1.ts
conformance/es6/destructuring/destructuringParameterProperties2.ts
conformance/es6/destructuring/destructuringParameterProperties3.ts
conformance/es6/destructuring/destructuringParameterProperties5.ts
conformance/expressions/functions/contextuallyTypedIife.ts
conformance/parser/ecmascript5/MemberAccessorDeclarations/parserMemberAccessorDeclaration16.ts
conformance/types/spread/objectSpread.ts
conformance/types/thisType/thisTypeInTaggedTemplateCall.ts
conformance/types/thisType/thisTypeOptionalCall.ts
conformance/types/thisType/thisTypeSyntacticContext.ts
conformance/types/union/unionTypeCallSignatures3.ts
fourslash/commentsModulesFourslash.ts

T262

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%

@cloudflare-pages
Copy link

@cloudflare-pages cloudflare-pages bot commented Feb 2, 2022

Deploying with  Cloudflare Pages  Cloudflare Pages

Latest commit: a912415
Status:   Deploy successful!
Preview URL: https://cd062772.tools-8rn.pages.dev

View logs

@MichaReiser MichaReiser requested review from ematipico and xunilrj Feb 3, 2022
@MichaReiser MichaReiser marked this pull request as ready for review Feb 3, 2022
// (this) => {}
// ^^^^
TsThisParameter =
'this'
Copy link
Contributor Author

@MichaReiser MichaReiser Feb 3, 2022

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:

  • JsAnyParameter can implement a binding accessor that returns SyntaxResult. This isn't possible if this is a token
  • this appears in a binding context

Cons:

  • The identifier should ever only be this. Thus, it should be safe to encode it
  • this isn't binding a new variable in the function scope because this is a valid binding in all functions. It may drip analyzers if they introduce a this binding for a function.

Copy link
Collaborator

@ematipico ematipico Feb 3, 2022

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.

Copy link
Contributor Author

@MichaReiser MichaReiser Feb 3, 2022

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.

Copy link
Collaborator

@ematipico ematipico Feb 3, 2022

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.

Copy link
Collaborator

@ematipico ematipico left a comment

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.

xtask/codegen/js.ungram Outdated Show resolved Hide resolved
// (this) => {}
// ^^^^
TsThisParameter =
'this'
Copy link
Collaborator

@ematipico ematipico Feb 3, 2022

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.

crates/rslint_parser/src/syntax/class.rs Show resolved Hide resolved
crates/rslint_parser/src/syntax/class.rs Show resolved Hide resolved
crates/rslint_parser/src/syntax/class.rs Show resolved Hide resolved
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) enum ParameterContext {
Copy link
Collaborator

@ematipico ematipico Feb 3, 2022

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

Copy link
Contributor Author

@MichaReiser MichaReiser Feb 3, 2022

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.

Copy link
Collaborator

@ematipico ematipico Feb 3, 2022

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

@MichaReiser
Copy link
Contributor Author

@MichaReiser MichaReiser commented Feb 3, 2022

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.

Argh, why xD I mean... it does make sense but it adds another invariant.

@MichaReiser
Copy link
Contributor Author

@MichaReiser MichaReiser commented Feb 4, 2022

I explored a few options but decided to not split the arrow and function parameters.

  • Option a) Add this parameter as a field to JsFunctionParameters. The main issue is that the generated code of the SyntaxFactory is no longer able to verify if a node has the right amount of commas or elements. For example for function a(this: string a) where the comma between string and a is missing.
  • Option b): Having separate Parameters, ParametersList, and AnyParameter types for functions/methods, arrow functions, and constructors. The main issue I faced was that it would then be desired to have a JsAnyParameters union type but arrow functions are the weird ones when it comes to functions because they support a single parameter and we don't yet have a way to construct an AstSeparatedList containing a single element (without a backing syntax list).

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 TsThisParameter which is something that was also possible in rome classic.

@MichaReiser MichaReiser requested a review from ematipico Feb 4, 2022
@MichaReiser MichaReiser merged commit a38b515 into main Feb 4, 2022
5 checks passed
@MichaReiser MichaReiser deleted the feature/parameters branch Feb 4, 2022
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.

None yet

2 participants