Fixes #668
Details on the syntax are described here:
https://devblogs.microsoft.com/typescript/announcing-typescript-4-5/#type-on-import-names
This was implemented in Babel here: babel/babel#13802
In terms of behavior, the type syntax is *almost* a no-op in valid code; we
already scan the file to see which identifiers have been used in a value or type
context and remove type imports automatically, and this PR doesn't change that.
However, there are a few subtle differences:
* When targeting commonjs, if an imported name has a `type` modifier and is used
in a value context, we do *not* transform the syntax to access the module like
we did before. For example, this comes up if accessing a global value with the
same name as an imported type.
* In both CJS and ESM targets, code that imports and then re-exports a type
needs to have that export elided. Previously, this was a situation where TS
doesn't know whether the name is a type or not, so it included the export as a
value.
Right now, there is no equivalent to `--preserveValueImports`, but this could be
added in the future. It *almost* would turn the TS transform into a purely
syntax-level transform without the need for scope analysis and variable name
tracking (thus simplifying and speeding up Sucrase if it became the standard
approach), except that even with that flag set, type-only exports are still
elided.
In terms of implementation details, the Babel parser details were a bit
AST-focused and didn't need to deal with `IdentifierRole`s, so I re-implemented
parsing using a simpler approach, which was also useful in the transform step.
It turns out that in both Flow and TS, you can parse an import or export
specifier by accepting anywhere from 1 to 4 identifiers, and the number of
identifiers is enough to exactly determine the syntax. This is more reliable
than matching on the names `type` (or `typeof`) and `as` because those are
contextual keywords that might be valid identifiers.
In the transform step, there were 5 places where we were manually walking
import/export specifiers, so it seemed best to unify that parsing into a shared
utility. This potentially has some performance cost due to the object
allocations, but it's likely to be very minor and could potentially be improved
later.