Skip to content

Conversation

@rtsao
Copy link
Contributor

@rtsao rtsao commented Mar 2, 2023

Fixes #234

The following should parse correctly (TS playground) (Flow playground):

import type from './User.js';

Before:

(program
  (import_statement
    (ERROR (identifier)) (string (string_fragment)))

After:

(program
  (import_statement
    (import_clause (identifier)) (string (string_fragment)))

Checklist:

  • All tests pass in CI.
  • There are sufficient tests for the new fix/feature.
  • Grammar rules have not been renamed unless absolutely necessary.
  • The conflicts section hasn't grown too much.
  • The parser size hasn't grown too much (check the value of STATE_COUNT in src/parser.c).

Copy link
Contributor

@hendrikvanantwerpen hendrikvanantwerpen left a comment

Choose a reason for hiding this comment

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

Thank you for willing contributing to this grammar!

I played a bit with the TS playground to get a feel for the different cases. Some interesting variants I found. The ones that parsed in Tree-sitter:

  • import A from './User.js'; // import identifier 'A'
  • import type A from './User.js'; // import type 'A'
  • import type { A } from './User.js'; // import type 'A'
  • import { type A } from './User.js'; // import type 'A'
  • import A, { type type } from './User.js'; // import identifier 'A' and type 'type'
  • import type type from './User.js'; // import type 'type'

And those that didn't:

  • import type from './User.js'; // import identifier 'type'
  • import { type, A } from './User.js'; // import identifiers 'type' and 'A'
  • import type, { type A } from './User.js'; // import identifier 'type' and type 'A'
  • import { type A } from './User.js'; // import type 'A'
  • import type, A from './User.js'; // syntax error
  • import { A, type } from './User.js'; // import identifiers 'type' and 'A'

The comments indicate the behavior in TSC.

I am surprised the type is not allowed for the $.identifier in import_clause and import_specifier. Especially since it works in other places, such as let type = 42;.

I think the fix worked, but fixing it like this will require doing it on more places (at least import_specifier). It would be better if we understand why type is not accepted in those positions and see if we can fix that, so it works everywhere where $.identifier is used.

@rtsao
Copy link
Contributor Author

rtsao commented Mar 8, 2023

I am surprised the type is not allowed for the $.identifier in import_clause and import_specifier. Especially since it works in other places, such as let type = 42;.

Yeah, I was puzzled by this too. I thought it might have to do with it being a reserved identifier

but removing that didn't seem affect how import statements were parsed.

After some time trying to figure it out, there didn't seem to be anything obvious so I went this workaround.

I've just pushed a fix for the additional edge case with import specifiers and added more test cases.

If someone with more knowledge of tree-sitter and this grammar can point out what is preventing type from being parsed as a normal identifier in these instances, I'd be happy to refactor this. But in the meantime, I think this approach is still a workable fix for the parse errors.

Copy link
Contributor

@hendrikvanantwerpen hendrikvanantwerpen left a comment

Choose a reason for hiding this comment

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

Thanks for adding more tests! It seems that this is the best solution for now. One last nit: I think we should create a separate production for the new choice, instead of replicating it several times. Something like:

_import_identifier: $ => choice($.identifier, alias('type', $.identifier))

and then use $._import_identifier in those places. That way we keep this localized, and if we find a more principled solution later, it will be easier to fix.

@rtsao
Copy link
Contributor Author

rtsao commented Mar 9, 2023

Good idea, thanks!

Copy link
Contributor

@hendrikvanantwerpen hendrikvanantwerpen left a comment

Choose a reason for hiding this comment

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

That's much cleaner. There's one place where the change wasn't applied, but otherwise this looks good.

choice(
field('name', $._import_identifier),
seq(
field('name', choice($._module_export_name, alias('type', $.identifier))),
Copy link
Contributor

Choose a reason for hiding this comment

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

One more place where we can use $._import_identifier.

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 think replacing choice($._module_export_name, alias('type', $.identifier)) with $._import_identifier would change the semantics of the grammar because $._module_export_name is equivalent to choice($.string, $.identifier) not merely $.identifier. See tree-sitter/tree-sitter-javascript#234 for more context.

If you'd like, I could factor out alias('type', $.identifier) from choice($.identifier, alias('type', $.identifier)) into its own rule and use it here.

Copy link
Contributor

Choose a reason for hiding this comment

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

You're absolutely right, this is not the same case!

@hendrikvanantwerpen hendrikvanantwerpen merged commit b66d19b into tree-sitter:master Mar 12, 2023
@hendrikvanantwerpen
Copy link
Contributor

Thanks for the contribution, @rtsao!

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.

Support for import type in TypeScript

2 participants