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

[tsx] raise error on single arrow type argument without comma #14135

Merged
merged 12 commits into from Feb 27, 2022
Merged

[tsx] raise error on single arrow type argument without comma #14135

merged 12 commits into from Feb 27, 2022

Conversation

ozanhonamlioglu
Copy link
Contributor

@ozanhonamlioglu ozanhonamlioglu commented Jan 11, 2022

Q                       A
Fixed Issues?
Patch: Bug Fix? Yes
Major: Breaking Change?
Minor: New Feature?
Tests Added + Pass? Yes
Documentation PR Link
Any Dependency Changes?
License MIT

In this issue #13778, we solved trailing comma end of single type usage in typescript plugin. But we didn't raised any error yet to notice developer about their wrong usage. Now with this PR, we are informing developer via raising an error.

Here is demo code

// INPUT
<T>(a: T): T => a; 

// ERROR
"Single type definition T should have a trailing comma end of it. Example usage: <T,>."

@nicolo-ribaudo you mentioned the bug in the discussion.
#14113 (comment)

@babel-bot
Copy link
Collaborator

babel-bot commented Jan 11, 2022

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/51363/

@nicolo-ribaudo nicolo-ribaudo added area: typescript pkg: parser PR: Spec Compliance 👓 A type of pull request used for our changelog categories labels Jan 11, 2022
) {
const typeName = arrow.node.typeParameters.params[0].name;
const start = arrow.node.start;
this.raise(start, TSErrors.SingleTypeWithoutTrailingComma, typeName);
Copy link
Member

Choose a reason for hiding this comment

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

The Babel 8 tests (you can run them with BABEL_8_BREAKING=true yarn jest parser) are failing, I think it's becaus typeName isn't a string anymore but a node: #12829

@@ -2930,6 +2932,17 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return expr;
}, state);

// report error if single type parameter used without trailing comma.
if (
arrow.node?.type === "ArrowFunctionExpression" &&
Copy link
Member

Choose a reason for hiding this comment

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

We should only report the error if this.hasPlugin("jsx")

// report error if single type parameter used without trailing comma.
if (
arrow.node?.type === "ArrowFunctionExpression" &&
arrow.node?.typeParameters?.params.length === 1 &&
Copy link
Member

Choose a reason for hiding this comment

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

After the previous check we know that node is defined, so we don't need the first ?.. Also, we don't need it after .typeParameters in the next check.

@ozanhonamlioglu
Copy link
Contributor Author

ozanhonamlioglu commented Jan 12, 2022

I have updated the PR to adapt to babel 8 features.

I have a question but it can maybe out of topic @nicolo-ribaudo

What do you suggest me to be good on Babel library? I started to educate myself on compilers and interpreters with a book named "Crafting Interpreters". The book is so good, it teach me so much stuff about compilers. But do you have any suggestion specificly on Babel.

Thanks

@nicolo-ribaudo
Copy link
Member

nicolo-ribaudo commented Jan 13, 2022

We have some videos about how Babel works at https://babeljs.io/videos, and https://github.com/jamiebuilds/babel-handbook shows different methods that are used in Babel plugins. Unfortunately we don't have much more 😅

I started to educate myself on compilers and interpreters with a book named "Crafting Interpreters". The book is so good, it teach me so much stuff about compilers.

I will probably buy it too one day 👀

@nicolo-ribaudo
Copy link
Member

CI is now failing on this test: https://github.com/microsoft/TypeScript/blob/main/tests/cases/compiler/jsxChildrenGenericContextualTypes.tsx

I think it's because of <T extends string>(p: LitProps<T>) => <div></div>: apparently in that case the comma is not required, so we shouldn't throw an error if there is extends.

@ozanhonamlioglu
Copy link
Contributor Author

CI is now failing on this test: https://github.com/microsoft/TypeScript/blob/main/tests/cases/compiler/jsxChildrenGenericContextualTypes.tsx

I think it's because of <T extends string>(p: LitProps<T>) => <div></div>: apparently in that case the comma is not required, so we shouldn't throw an error if there is extends.

Oh you right

@nicolo-ribaudo
Copy link
Member

Btw, you can run that external TS tests with make test-typescript (you first need to run make bootstrap-typescript the first time, to clone the TS repo in the correct folder).

@@ -2930,6 +2932,33 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return expr;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you move the check before this line? It should simplify arrow.node checks and we have typeParameters ready. We can leak isSingleTypeWithoutTrailingComma out of tryParse and throw afterwards.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

of course

Comment on lines 2948 to 2951
const node = parameter.name;
let identifierName = "";
if (typeof node === "object") identifierName = node.name;
else identifierName = node;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const node = parameter.name;
let identifierName = "";
if (typeof node === "object") identifierName = node.name;
else identifierName = node;
const identifierName = process.env.BABEL_8_BREAKING ? parameter.name.name : parameter.name;

So we can remove Babel 7 AST support when we release Babel 8.

@@ -148,6 +148,8 @@ const TSErrors = makeErrorTemplates(
"A 'set' accessor cannot have rest parameter.",
SetAccesorCannotHaveReturnType:
"A 'set' accessor cannot have a return type annotation.",
SingleTypeWithoutTrailingComma:
"Single type definition %0 should have a trailing comma end of it. Example usage: <%0,>.",
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"Single type definition %0 should have a trailing comma end of it. Example usage: <%0,>.",
"Single type parameter %0 should have a trailing comma. Example usage: <%0,>.",

) {
const parameter = arrow.node.typeParameters.params[0];
if (parameter.constraint) {
// If Type has any constraints, means that it is not alone their.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// If Type has any constraints, means that it is not alone their.
// If parameter has any constraints, it must contain multiple tokens

@@ -2908,6 +2910,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// Either way, we're looking at a '<': tt.jsxTagStart or relational.

let typeParameters: ?N.TsTypeParameterDeclaration;
let hasSingleTypeError = { status: false, start: 0, identifierName: "" };
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@JLHwung I needed to raise error outside const arrow = .... method because it should return expr end of it instead an error.
This just a quick type which is defined in here but if you suggest me to move the type to anywhere else, that is okay too.

Copy link
Member

Choose a reason for hiding this comment

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

Can we do let invalidSingleType: ?N.TSTypeParameter, and then just do invalidSingleType = parameter when it's invalid? It's similar to what we already do with the typeParameters variable, and we avoid allocationg a few objects unnecessary.

@@ -148,6 +148,8 @@ const TSErrors = makeErrorTemplates(
"A 'set' accessor cannot have rest parameter.",
SetAccesorCannotHaveReturnType:
"A 'set' accessor cannot have a return type annotation.",
SingleTypeWithoutTrailingComma:
Copy link
Member

Choose a reason for hiding this comment

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

Nit: I'd prefer to name this SingleTypeParameterWithoutTrailingComma

@@ -2908,6 +2910,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// Either way, we're looking at a '<': tt.jsxTagStart or relational.

let typeParameters: ?N.TsTypeParameterDeclaration;
let hasSingleTypeError = { status: false, start: 0, identifierName: "" };
Copy link
Member

Choose a reason for hiding this comment

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

Can we do let invalidSingleType: ?N.TSTypeParameter, and then just do invalidSingleType = parameter when it's invalid? It's similar to what we already do with the typeParameters variable, and we avoid allocationg a few objects unnecessary.

return expr;
}, state);

if (hasSingleTypeError.status) {
this.raise(
hasSingleTypeError.start,
Copy link
Member

Choose a reason for hiding this comment

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

Rather than at .start, we could report the error at parameter.end + 1 which is where we expect the trailing comma.

Copy link
Member

@nicolo-ribaudo nicolo-ribaudo left a comment

Choose a reason for hiding this comment

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

Thanks!

Comment on lines 2952 to 2967
this.raise(
invalidSingleType.end + 1,
Copy link
Member

Choose a reason for hiding this comment

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

In 478a970 we changed the signature of .raise; could you rebase this PR? 🙏 (otherwise I can do it)

As the new location, you can probably use { at: this.state.startLoc } (which is the beginning of the token after the type parameter).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @nicolo-ribaudo , of course I can do it. I Will do it today night. But if you have any urgency, you can do it for me as well.

@@ -1,6 +1,9 @@
{
"type": "File",
"start":0,"end":79,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":18}},
"errors": [
"SyntaxError: Single type parameter T should have a trailing comma. Example usage: <T,>. (2:17)"
Copy link
Member

Choose a reason for hiding this comment

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

Uh, this made me realize that this.state.startLoc doesn't work because we didn't just finish parsing the type parameters, but the whole arrow. I guess we can go back to { at: invalidSingleType.end } 🙁

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You right, I actually realized that something wrong with that. Taking it back so and will update the PR

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nicolo-ribaudo but now it says 2:2 actually it should be 2:3 right? { at: invalidSingleType.loc.end }

Copy link
Member

Choose a reason for hiding this comment

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

2:2 is way better than 2:17 🤷

If we still want to report 2:3 (which is slightly better than 2:2, since it's consistent with the "unexpected token, expected ," errors) we could store this.state.startLoc after the typeParameters = this.tsParseTypeParameters(); call a few lines above.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did something different, lets see if it passes from tests, and if a valid usage for you also?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nicolo-ribaudo do you think it is okay?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @JLHwung can you give a last review for the PR? thanks

{
at: {
...invalidSingleType.loc.end,
column: invalidSingleType.loc.end.column + 1,
Copy link
Contributor

Choose a reason for hiding this comment

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

There is createPositionWithColumnOffset in ./util/location serving the same purpose.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh I didn't know that. So you want me to use that method for the position and update the PR right?
@JLHwung

Copy link
Member

Choose a reason for hiding this comment

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

Yes, please!

@ozanhonamlioglu
Copy link
Contributor Author

@nicolo-ribaudo @JLHwung Hey guys
Sorry for delay but I had soo much busy days over the last weeks. I wan't to keep moving from where I left and want to solve more issues...
Can you review my PR and give some comments or merge please... I just updated it
Thanks

@nicolo-ribaudo nicolo-ribaudo changed the title raise error on single type usage without comma [tsx] raise error on single arrow type argument without comma Feb 27, 2022
@nicolo-ribaudo nicolo-ribaudo merged commit 5749c16 into babel:main Feb 27, 2022

// report error if single type parameter used without trailing comma.
if (
this.hasPlugin("jsx") &&
Copy link

Choose a reason for hiding this comment

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

Is it possible to check it only in tsx file? @ozanhonamlioglu
(#14361 )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nicolo-ribaudo Sounds logical to me, shall I change it with tsx? What is your opinion?

Copy link
Member

Choose a reason for hiding this comment

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

TSX is "jsx in TypeScript". This check is only executed when TypeScript is enabled, so it's already only checking for tsx.

Copy link

Choose a reason for hiding this comment

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

TSX is "jsx in TypeScript". This check is only executed when TypeScript is enabled, so it's already only checking for tsx.

In our project, we used the same presets (@babel/preset-env + @babel/preset-react + @babel/preset-typescript) to transform all the files, which caused the .ts files to be processed by the jsx plugin, so the generics in the .ts files also triggered the SingleTypeParameterWithoutTrailingComma error.

This is a wrong usage, we will improve it. Thank you! @ozanhonamlioglu @nicolo-ribaudo

@github-actions github-actions bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Jun 16, 2022
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 16, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: typescript outdated A closed issue/PR that is archived due to age. Recommended to make a new issue pkg: parser PR: Spec Compliance 👓 A type of pull request used for our changelog categories
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants