Add jsxFactory compiler option.#11267
Conversation
|
Hi @bradleyayers, I'm your friendly neighborhood Microsoft Pull Request Bot (You can call me MSBOT). Thanks for your contribution! TTYL, MSBOT; |
src/compiler/program.ts
Outdated
| programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Invalid_value_for_reactNamespace_0_is_not_a_valid_identifier, options.reactNamespace)); | ||
| } | ||
|
|
||
| if (options.jsxFactory && options.reactNamespace) { |
There was a problem hiding this comment.
should add another check as well that jsxFactory can not be used without jsx: react
src/compiler/factory.ts
Outdated
|
|
||
| function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement) { | ||
| export function createJsxFactory(jsxFactory: string) { | ||
| // No explicit validation of this parameter is required. Users are |
There was a problem hiding this comment.
do not think you can do this. in Program:verifyCompilerOptions, you should check that isIdentifierText(options.jsxFactory, languageVersion)
There was a problem hiding this comment.
I went with this approach for simplicity, based on the belief this was acceptable behaviour as per @RyanCavanaugh's comment:
No explicit validation of this parameter occurs; users are assumed to have provided a correct string
There was a problem hiding this comment.
To avoid going around in circles I'll wait for @RyanCavanaugh to chime in.
There was a problem hiding this comment.
Maybe we should just restrict this to dotted names for simplicity's sake. Thoughts @mhegazy ?
There was a problem hiding this comment.
Maybe we should just restrict this to dotted names for simplicity's sake. Thoughts @mhegazy ?
I agree. let's add a check that this is a QualifiedName in Program:verifyCompilerOptions
There was a problem hiding this comment.
I need some help with deciding what strategy is best for this.
To validate a dotted name as a QualifiedName, there’s a few ways I can see to do it:
- basic regex or string manipulation (e.g.
.split(‘.’)) - creating a mini parser using scanner, iterating over the tokens and asserting it’s a sequence of
Identifier (Dot, Identifier)* - using the parser (
createSourceFile) to parse the stringa.b.cand using the resulting source file to both assert the structure, and to extract theaidentifier to mark it as used - …?
Balancing simplicity with correctness is the challenge here, I'm not sure what the preferred bias is.
src/compiler/factory.ts
Outdated
| return createIdentifier(jsxFactory); | ||
| } | ||
|
|
||
| export function createReactCreateElement(reactNamespace: string, parentElement: JsxOpeningLikeElement) { |
There was a problem hiding this comment.
make reactNamespace optional, or string|undefined.
src/compiler/checker.ts
Outdated
| const jsxFactoryRefErr = compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined; | ||
| let jsxFactoryNamespace = "React"; | ||
| if (compilerOptions.jsxFactory) { | ||
| jsxFactoryNamespace = compilerOptions.jsxFactory.split(".")[0]; |
There was a problem hiding this comment.
if we say jsxFactory is an identifier, no need to do this here.
There was a problem hiding this comment.
I was using treating jsxFactory as an Identifier in factory.ts as a convenience in the a.b.c case, but really in that scenario it should be a PropertyAccessExpression.
In that case I only want a out of a.b.c so that I can set referenced = true on that symbol alone.
| @@ -0,0 +1,12 @@ | |||
| //@filename: file.tsx | |||
| //@jsx: react | |||
| //@jsxFactory: a.b.c | |||
There was a problem hiding this comment.
well.. i suppose if you pass React.createElement it should just work.
There was a problem hiding this comment.
OK, so we need to verify it is a qualified name. we do not want unexpected characters in there.
src/compiler/checker.ts
Outdated
| const jsxFactoryRefErr = compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined; | ||
| let jsxFactoryNamespace = "React"; | ||
| if (compilerOptions.jsxFactory) { | ||
| jsxFactoryNamespace = compilerOptions.jsxFactory.split(".")[0]; |
There was a problem hiding this comment.
This could also be an expression like foo["bar"], should be able to split on . / [
There was a problem hiding this comment.
I think a qualified name is sufficient here. we could relax that in the future.
| } | ||
|
|
||
| function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement) { | ||
| export function createEntityName(entityName: string) { |
There was a problem hiding this comment.
I'm skeptical the implementation of this is ideal, open to suggestions.
There was a problem hiding this comment.
Perhaps a parseIsolatedEntityName could set a flag in the parser to have it treat the source as synthetic.
| inlineSources?: boolean; | ||
| isolatedModules?: boolean; | ||
| jsx?: JsxEmit; | ||
| jsxFactory?: string; |
There was a problem hiding this comment.
Instead of parsing the jsxFactory to an EntityName multiple times through-out the program, would it be better to have this be EntityName and parse it once up-front?
There was a problem hiding this comment.
It'd probably be cleanest to do this once in checker.ts and then only again if needed in emitter.ts.
There was a problem hiding this comment.
This is somewhat similar to what we do for externalHelpersModuleName on SourceFile.
| while (entityName.kind === SyntaxKind.QualifiedName) { | ||
| entityName = (<QualifiedName>entityName).left; | ||
| } | ||
| Debug.assertNode(entityName, node => node.kind === SyntaxKind.Identifier); |
There was a problem hiding this comment.
There is already an isIdentifier node test you can pass to Debug.assertNode.
| return result; | ||
| } | ||
|
|
||
| export function parseIsolatedEntityName(content: string, allowReservedWords = false) { |
There was a problem hiding this comment.
Is allowReservedWords ever used by a caller? If not, I would just remove it.
|
|
||
| /* @internal */ | ||
| export function parseIsolatedEntityName(content: string) { | ||
| const result = Parser.parseIsolatedEntityName(content); |
There was a problem hiding this comment.
Parent references aren't used in the emit transforms with respect to synthesized nodes. If this is only used in emit, then fixing up the parent references is unneeded.
| inlineSources?: boolean; | ||
| isolatedModules?: boolean; | ||
| jsx?: JsxEmit; | ||
| jsxFactory?: string; |
There was a problem hiding this comment.
This is somewhat similar to what we do for externalHelpersModuleName on SourceFile.
| } | ||
|
|
||
| function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement) { | ||
| export function createEntityName(entityName: string) { |
There was a problem hiding this comment.
Perhaps a parseIsolatedEntityName could set a flag in the parser to have it treat the source as synthetic.
| const entityName = parseEntityName(allowReservedWords); | ||
| parseExpected(SyntaxKind.EndOfFileToken); | ||
|
|
||
| const diagnostics = parseDiagnostics; |
There was a problem hiding this comment.
Are you using these diagnostics, other than to check that they exist in program.ts:1546? You could return undefined rather than allocating the object literal for every call.
| const element = createReactCreateElement( | ||
| compilerOptions.reactNamespace, | ||
| const jsxFactory = compilerOptions.jsxFactory | ||
| ? createJsxFactory(compilerOptions.jsxFactory) |
There was a problem hiding this comment.
Since this is a fully-synthesized entity name, consider caching it on SourceFile, similar to externalHelpersModuleName rather than reparsing it each time it is hit. You could also create it once at the top of transformJsx.
| // it isn't in scope when targeting React emit, we should issue an error. | ||
| const jsxFactoryRefErr = compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined; | ||
| let jsxFactoryNamespace = "React"; | ||
| if (compilerOptions.jsxFactory) { |
There was a problem hiding this comment.
This could be done once during initializeTypeChecker rather than on every call to checkJsxOpeningLikeElement.
| } | ||
|
|
||
| export function createReactCreateElement(reactNamespace: string | undefined, parentElement: JsxOpeningLikeElement) { | ||
| // To ensure the emit resolver can properly resolve the namespace, we need to |
There was a problem hiding this comment.
I don't really understand this comment. What does "resolving the namespace" by the emit resolver mean, and why does it need to? My guess would have been to ensure that the React symbol is treated as "used" (so that the import is not elided), but this was already done manually in checker.ts:10804, so I must be missing something.
|
poke. poke-poke. :) |
|
Poke. I'd like this too. |
|
This should be fixed by #12135 |
Fixes #9582