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

Add jsxFactory compiler option. #11267

Closed
wants to merge 6 commits into from
Closed

Conversation

bradleyayers
Copy link

Fixes #9582

@msftclas
Copy link

Hi @bradleyayers, I'm your friendly neighborhood Microsoft Pull Request Bot (You can call me MSBOT). Thanks for your contribution!
You've already signed the contribution license agreement. Thanks!
We will now validate the agreement and then real humans will evaluate your PR.

TTYL, MSBOT;

@@ -2346,6 +2346,10 @@ namespace ts {
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Invalid_value_for_reactNamespace_0_is_not_a_valid_identifier, options.reactNamespace));
}

if (options.jsxFactory && options.reactNamespace) {
Copy link
Contributor

Choose a reason for hiding this comment

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

should add another check as well that jsxFactory can not be used without jsx: react

@@ -1618,17 +1618,23 @@ namespace ts {
);
}

function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement) {
export function createJsxFactory(jsxFactory: string) {
// No explicit validation of this parameter is required. Users are
Copy link
Contributor

Choose a reason for hiding this comment

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

do not think you can do this. in Program:verifyCompilerOptions, you should check that isIdentifierText(options.jsxFactory, languageVersion)

Copy link
Author

Choose a reason for hiding this comment

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

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

Copy link
Author

Choose a reason for hiding this comment

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

To avoid going around in circles I'll wait for @RyanCavanaugh to chime in.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe we should just restrict this to dotted names for simplicity's sake. Thoughts @mhegazy ?

Copy link
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Author

Choose a reason for hiding this comment

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

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 string a.b.c and using the resulting source file to both assert the structure, and to extract the a identifier to mark it as used
  • …?

Balancing simplicity with correctness is the challenge here, I'm not sure what the preferred bias is.

return createIdentifier(jsxFactory);
}

export function createReactCreateElement(reactNamespace: string, parentElement: JsxOpeningLikeElement) {
Copy link
Contributor

Choose a reason for hiding this comment

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

make reactNamespace optional, or string|undefined.

const jsxFactoryRefErr = compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined;
let jsxFactoryNamespace = "React";
if (compilerOptions.jsxFactory) {
jsxFactoryNamespace = compilerOptions.jsxFactory.split(".")[0];
Copy link
Contributor

Choose a reason for hiding this comment

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

if we say jsxFactory is an identifier, no need to do this here.

Copy link
Author

Choose a reason for hiding this comment

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

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
Copy link
Contributor

Choose a reason for hiding this comment

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

@RyanCavanaugh are dotted names allowed?

Copy link
Contributor

Choose a reason for hiding this comment

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

well.. i suppose if you pass React.createElement it should just work.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, so we need to verify it is a qualified name. we do not want unexpected characters in there.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

const jsxFactoryRefErr = compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined;
let jsxFactoryNamespace = "React";
if (compilerOptions.jsxFactory) {
jsxFactoryNamespace = compilerOptions.jsxFactory.split(".")[0];
Copy link
Member

Choose a reason for hiding this comment

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

This could also be an expression like foo["bar"], should be able to split on . / [

Copy link
Contributor

Choose a reason for hiding this comment

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

I think a qualified name is sufficient here. we could relax that in the future.

@@ -1618,17 +1618,38 @@ namespace ts {
);
}

function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement) {
export function createEntityName(entityName: string) {
Copy link
Author

Choose a reason for hiding this comment

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

I'm skeptical the implementation of this is ideal, open to suggestions.

Copy link
Member

Choose a reason for hiding this comment

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

@ahejlsberg thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps a parseIsolatedEntityName could set a flag in the parser to have it treat the source as synthetic.

@@ -2719,6 +2719,7 @@ namespace ts {
inlineSources?: boolean;
isolatedModules?: boolean;
jsx?: JsxEmit;
jsxFactory?: string;
Copy link
Author

Choose a reason for hiding this comment

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

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?

Copy link
Member

Choose a reason for hiding this comment

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

It'd probably be cleanest to do this once in checker.ts and then only again if needed in emitter.ts.

Copy link
Member

Choose a reason for hiding this comment

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

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);
Copy link
Member

Choose a reason for hiding this comment

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

There is already an isIdentifier node test you can pass to Debug.assertNode.

@@ -584,6 +591,20 @@ namespace ts {
return result;
}

export function parseIsolatedEntityName(content: string, allowReservedWords = false) {
Copy link
Member

Choose a reason for hiding this comment

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

Is allowReservedWords ever used by a caller? If not, I would just remove it.

@@ -463,6 +463,13 @@ namespace ts {
}

/* @internal */
export function parseIsolatedEntityName(content: string) {
const result = Parser.parseIsolatedEntityName(content);
Parser.fixupParentReferences(result.entityName);
Copy link
Member

Choose a reason for hiding this comment

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

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.

@@ -2719,6 +2719,7 @@ namespace ts {
inlineSources?: boolean;
isolatedModules?: boolean;
jsx?: JsxEmit;
jsxFactory?: string;
Copy link
Member

Choose a reason for hiding this comment

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

This is somewhat similar to what we do for externalHelpersModuleName on SourceFile.

@@ -1618,17 +1618,38 @@ namespace ts {
);
}

function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement) {
export function createEntityName(entityName: string) {
Copy link
Member

Choose a reason for hiding this comment

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

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;
Copy link
Member

Choose a reason for hiding this comment

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

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)
Copy link
Member

Choose a reason for hiding this comment

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

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) {
Copy link
Member

Choose a reason for hiding this comment

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

This could be done once during initializeTypeChecker rather than on every call to checkJsxOpeningLikeElement.

stevemao added a commit to stevemao/awesome-pull-requests that referenced this pull request Oct 5, 2016
return createExpressionFromEntityName(entityName);
}

export function createReactCreateElement(reactNamespace: string | undefined, parentElement: JsxOpeningLikeElement) {
// To ensure the emit resolver can properly resolve the namespace, we need to
Copy link
Author

Choose a reason for hiding this comment

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

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.

@wgv-sethlivingston
Copy link

poke. poke-poke. :)

@ashaffer
Copy link

ashaffer commented Nov 9, 2016

Poke. I'd like this too.

@mhegazy
Copy link
Contributor

mhegazy commented Nov 10, 2016

This should be fixed by #12135

@mhegazy mhegazy closed this Nov 10, 2016
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants