-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Changes from all commits
63fed8f
3076add
db9d0ff
71b8f00
ceda48b
49ee64d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10795,13 +10795,24 @@ namespace ts { | |
checkGrammarJsxElement(node); | ||
checkJsxPreconditions(node); | ||
|
||
// The reactNamespace symbol should be marked as 'used' so we don't incorrectly elide its import. And if there | ||
// is no reactNamespace symbol in scope when targeting React emit, we should issue an error. | ||
const reactRefErr = compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined; | ||
const reactNamespace = compilerOptions.reactNamespace ? compilerOptions.reactNamespace : "React"; | ||
const reactSym = resolveName(node.tagName, reactNamespace, SymbolFlags.Value, reactRefErr, reactNamespace); | ||
if (reactSym) { | ||
getSymbolLinks(reactSym).referenced = true; | ||
// The JSX factory namespace symbol should be marked as 'used' so we don't incorrectly elide its import. And if | ||
// 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) { | ||
let { entityName } = parseIsolatedEntityName(compilerOptions.jsxFactory); | ||
while (entityName.kind === SyntaxKind.QualifiedName) { | ||
entityName = (<QualifiedName>entityName).left; | ||
} | ||
Debug.assertNode(entityName, node => node.kind === SyntaxKind.Identifier); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is already an |
||
jsxFactoryNamespace = (<Identifier>entityName).text; | ||
} | ||
else if (compilerOptions.reactNamespace) { | ||
jsxFactoryNamespace = compilerOptions.reactNamespace; | ||
} | ||
const jsxFactorySym = resolveName(node.tagName, jsxFactoryNamespace, SymbolFlags.Value, jsxFactoryRefErr, jsxFactoryNamespace); | ||
if (jsxFactorySym) { | ||
getSymbolLinks(jsxFactorySym).referenced = true; | ||
} | ||
|
||
const targetAttributesType = getJsxElementAttributesType(node); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1618,17 +1618,38 @@ namespace ts { | |
); | ||
} | ||
|
||
function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement) { | ||
export function createEntityName(entityName: string) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm skeptical the implementation of this is ideal, open to suggestions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ahejlsberg thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps a |
||
const { entityName: node } = parseIsolatedEntityName(entityName); | ||
|
||
// Parsing occurred in an isolated context, so nodes need to be synthesized | ||
// and positions erased for correct positioning in emit. | ||
const synthesize = (node: Node) => { | ||
node.pos = -1; | ||
node.end = -1; | ||
node.flags |= NodeFlags.Synthesized; | ||
forEachChild(node, synthesize); | ||
}; | ||
synthesize(node); | ||
|
||
return node; | ||
} | ||
|
||
export function createJsxFactory(jsxFactory: string) { | ||
const entityName = createEntityName(jsxFactory); | ||
return createExpressionFromEntityName(entityName); | ||
} | ||
|
||
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. Choose a reason for hiding this commentThe 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 |
||
// treat this identifier as if it were a source tree node by clearing the `Synthesized` | ||
// flag and setting a parent node. | ||
const react = createIdentifier(reactNamespace || "React"); | ||
react.flags &= ~NodeFlags.Synthesized; | ||
react.parent = parent; | ||
return react; | ||
react.parent = parentElement; | ||
return createPropertyAccess(react, "createElement"); | ||
} | ||
|
||
export function createReactCreateElement(reactNamespace: string, tagName: Expression, props: Expression, children: Expression[], parentElement: JsxOpeningLikeElement, location: TextRange): LeftHandSideExpression { | ||
export function createJsxFactoryCall(jsxFactory: Expression, tagName: Expression, props: Expression, children: Expression[], parentElement: JsxOpeningLikeElement, location: TextRange): LeftHandSideExpression { | ||
const argumentsList = [tagName]; | ||
if (props) { | ||
argumentsList.push(props); | ||
|
@@ -1651,10 +1672,7 @@ namespace ts { | |
} | ||
|
||
return createCall( | ||
createPropertyAccess( | ||
createReactNamespace(reactNamespace, parentElement), | ||
"createElement" | ||
), | ||
jsxFactory, | ||
/*typeArguments*/ undefined, | ||
argumentsList, | ||
location | ||
|
@@ -2909,4 +2927,4 @@ namespace ts { | |
function tryGetModuleNameFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration, host: EmitHost, resolver: EmitResolver, compilerOptions: CompilerOptions) { | ||
return tryGetModuleNameFromFile(resolver.getExternalModuleFileFromDeclaration(declaration), host, compilerOptions); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -462,6 +462,13 @@ namespace ts { | |
return result; | ||
} | ||
|
||
/* @internal */ | ||
export function parseIsolatedEntityName(content: string) { | ||
const result = Parser.parseIsolatedEntityName(content); | ||
Parser.fixupParentReferences(result.entityName); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
return result; | ||
} | ||
|
||
/* @internal */ | ||
// Exposed only for testing. | ||
export function parseJSDocTypeExpressionForTests(content: string, start?: number, length?: number) { | ||
|
@@ -584,6 +591,20 @@ namespace ts { | |
return result; | ||
} | ||
|
||
export function parseIsolatedEntityName(content: string, allowReservedWords = false) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is |
||
initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); | ||
sourceFile = <SourceFile>{ languageVariant: LanguageVariant.Standard, text: content }; | ||
|
||
nextToken(); | ||
const entityName = parseEntityName(allowReservedWords); | ||
parseExpected(SyntaxKind.EndOfFileToken); | ||
|
||
const diagnostics = parseDiagnostics; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
clearState(); | ||
|
||
return { entityName, diagnostics }; | ||
} | ||
|
||
function getLanguageVariant(scriptKind: ScriptKind) { | ||
// .tsx and .jsx files are treated as jsx language variant. | ||
return scriptKind === ScriptKind.TSX || scriptKind === ScriptKind.JSX || scriptKind === ScriptKind.JS ? LanguageVariant.JSX : LanguageVariant.Standard; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -113,8 +113,12 @@ namespace ts { | |
|| createAssignHelper(currentSourceFile.externalHelpersModuleName, segments); | ||
} | ||
|
||
const element = createReactCreateElement( | ||
compilerOptions.reactNamespace, | ||
const jsxFactory = compilerOptions.jsxFactory | ||
? createJsxFactory(compilerOptions.jsxFactory) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is a fully-synthesized entity name, consider caching it on |
||
: createReactCreateElement(compilerOptions.reactNamespace, node); | ||
|
||
const element = createJsxFactoryCall( | ||
jsxFactory, | ||
tagName, | ||
objectProperties, | ||
filter(map(children, transformJsxChildToExpression), isDefined), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2719,6 +2719,7 @@ namespace ts { | |
inlineSources?: boolean; | ||
isolatedModules?: boolean; | ||
jsx?: JsxEmit; | ||
jsxFactory?: string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of parsing the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is somewhat similar to what we do for |
||
lib?: string[]; | ||
/*@internal*/listEmittedFiles?: boolean; | ||
/*@internal*/listFiles?: boolean; | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
tests/cases/conformance/jsx/file.tsx(8,2): error TS2304: Cannot find name 'h'. | ||
|
||
|
||
==== tests/cases/conformance/jsx/file.tsx (1 errors) ==== | ||
declare module JSX { | ||
interface IntrinsicElements { | ||
[s: string]: any; | ||
} | ||
} | ||
|
||
// This should issue an error as 'h' is not declared. | ||
<div />; | ||
~~~ | ||
!!! error TS2304: Cannot find name 'h'. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
//// [file.tsx] | ||
declare module JSX { | ||
interface IntrinsicElements { | ||
[s: string]: any; | ||
} | ||
} | ||
|
||
// This should issue an error as 'h' is not declared. | ||
<div />; | ||
|
||
|
||
//// [file.js] | ||
// This should issue an error as 'h' is not declared. | ||
h("div", null); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
//// [file.tsx] | ||
declare module JSX { | ||
interface IntrinsicElements { | ||
[s: string]: any; | ||
} | ||
} | ||
|
||
declare var h: any; | ||
|
||
<div />; | ||
|
||
|
||
//// [file.js] | ||
h("div", null); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
=== tests/cases/conformance/jsx/file.tsx === | ||
declare module JSX { | ||
>JSX : Symbol(JSX, Decl(file.tsx, 0, 0)) | ||
|
||
interface IntrinsicElements { | ||
>IntrinsicElements : Symbol(IntrinsicElements, Decl(file.tsx, 0, 20)) | ||
|
||
[s: string]: any; | ||
>s : Symbol(s, Decl(file.tsx, 2, 3)) | ||
} | ||
} | ||
|
||
declare var h: any; | ||
>h : Symbol(h, Decl(file.tsx, 6, 11)) | ||
|
||
<div />; | ||
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 0, 20)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
=== tests/cases/conformance/jsx/file.tsx === | ||
declare module JSX { | ||
>JSX : any | ||
|
||
interface IntrinsicElements { | ||
>IntrinsicElements : IntrinsicElements | ||
|
||
[s: string]: any; | ||
>s : string | ||
} | ||
} | ||
|
||
declare var h: any; | ||
>h : any | ||
|
||
<div />; | ||
><div /> : any | ||
>div : any | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
//// [file.tsx] | ||
declare module JSX { | ||
interface IntrinsicElements { | ||
[s: string]: any; | ||
} | ||
} | ||
|
||
declare var React: any; | ||
|
||
<div />; | ||
|
||
|
||
//// [file.js] | ||
React.createElement("div", null); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
=== tests/cases/conformance/jsx/file.tsx === | ||
declare module JSX { | ||
>JSX : Symbol(JSX, Decl(file.tsx, 0, 0)) | ||
|
||
interface IntrinsicElements { | ||
>IntrinsicElements : Symbol(IntrinsicElements, Decl(file.tsx, 0, 20)) | ||
|
||
[s: string]: any; | ||
>s : Symbol(s, Decl(file.tsx, 2, 3)) | ||
} | ||
} | ||
|
||
declare var React: any; | ||
>React : Symbol(React, Decl(file.tsx, 6, 11)) | ||
|
||
<div />; | ||
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 0, 20)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
=== tests/cases/conformance/jsx/file.tsx === | ||
declare module JSX { | ||
>JSX : any | ||
|
||
interface IntrinsicElements { | ||
>IntrinsicElements : IntrinsicElements | ||
|
||
[s: string]: any; | ||
>s : string | ||
} | ||
} | ||
|
||
declare var React: any; | ||
>React : any | ||
|
||
<div />; | ||
><div /> : any | ||
>div : any | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
//// [file.tsx] | ||
declare module JSX { | ||
interface IntrinsicElements { | ||
[s: string]: any; | ||
} | ||
} | ||
|
||
declare var a: any; | ||
|
||
<div />; | ||
|
||
|
||
//// [file.js] | ||
a.b.c("div", null); |
There was a problem hiding this comment.
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 tocheckJsxOpeningLikeElement
.