diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 878323a41b8..6d5cee0362e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -335,6 +335,7 @@ namespace ts { }); let jsxElementType: Type; + let _jsxNamespace: string; /** Things we lazy load from the JSX namespace */ const jsxTypes = createMap(); const JsxNames = { @@ -372,6 +373,22 @@ namespace ts { return checker; + function getJsxNamespace(): string { + if (_jsxNamespace === undefined) { + _jsxNamespace = "React"; + if (compilerOptions.jsxFactory) { + const jsxEntity = host.getJsxFactoryEntity(); + if (jsxEntity) { + _jsxNamespace = getFirstIdentifier(jsxEntity).text; + } + } + else if (compilerOptions.reactNamespace) { + _jsxNamespace = compilerOptions.reactNamespace; + } + } + return _jsxNamespace; + } + function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) { // Ensure we have all the type information in place for this file so that all the // emitter questions of this resolver will return the right information. @@ -11337,10 +11354,10 @@ namespace ts { function checkJsxOpeningLikeElement(node: JsxOpeningLikeElement) { 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. + // The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import. + // And if there is no reactNamespace/jsxFactory's 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 reactNamespace = getJsxNamespace(); const reactSym = resolveName(node.tagName, reactNamespace, SymbolFlags.Value, reactRefErr, reactNamespace); if (reactSym) { // Mark local symbol as referenced here because it might not have been marked diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 213e6a95ab1..a733d93a3dd 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -329,6 +329,9 @@ namespace ts { // Map storing if there is emit blocking diagnostics for given input const hasEmitBlockingDiagnostics = createFileMap(getCanonicalFileName); + // ReactNamespace and jsxFactory information + let jsxFactoryEntity: EntityName; + let resolveModuleNamesWorker: (moduleNames: string[], containingFile: string) => ResolvedModuleFull[]; if (host.resolveModuleNames) { resolveModuleNamesWorker = (moduleNames, containingFile) => host.resolveModuleNames(moduleNames, containingFile).map(resolved => { @@ -421,7 +424,8 @@ namespace ts { getFileProcessingDiagnostics: () => fileProcessingDiagnostics, getResolvedTypeReferenceDirectives: () => resolvedTypeReferenceDirectives, isSourceFileFromExternalLibrary, - dropDiagnosticsProducingTypeChecker + dropDiagnosticsProducingTypeChecker, + getJsxFactoryEntity: () => jsxFactoryEntity }; verifyCompilerOptions(); @@ -1674,7 +1678,8 @@ namespace ts { if (options.reactNamespace) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "reactNamespace", "jsxFactory")); } - if (!parseIsolatedEntityName(options.jsxFactory, languageVersion)) { + jsxFactoryEntity = parseIsolatedEntityName(options.jsxFactory, languageVersion); + if (!jsxFactoryEntity) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Invalid_value_for_jsxFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFactory)); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5ca542cfe11..09d32ad817e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2177,6 +2177,7 @@ namespace ts { getTypeChecker(): TypeChecker; /* @internal */ getCommonSourceDirectory(): string; + /* @internal */ getJsxFactoryEntity(): EntityName; // For testing purposes only. Should not be used by any other consumers (including the // language service). @@ -2250,6 +2251,7 @@ namespace ts { /* @internal */ export interface TypeCheckerHost { getCompilerOptions(): CompilerOptions; + getJsxFactoryEntity(): EntityName; getSourceFiles(): SourceFile[]; getSourceFile(fileName: string): SourceFile; diff --git a/tests/baselines/reference/jsxFactoryIdentifier.errors.txt b/tests/baselines/reference/jsxFactoryIdentifier.errors.txt deleted file mode 100644 index e0cb485cf7f..00000000000 --- a/tests/baselines/reference/jsxFactoryIdentifier.errors.txt +++ /dev/null @@ -1,57 +0,0 @@ -tests/cases/compiler/test.tsx(12,5): error TS2304: Cannot find name 'React'. -tests/cases/compiler/test.tsx(13,5): error TS2304: Cannot find name 'React'. - - -==== tests/cases/compiler/Element.ts (0 errors) ==== - - declare namespace JSX { - interface Element { - name: string; - isIntrinsic: boolean; - isCustomElement: boolean; - toString(renderId?: number): string; - bindDOM(renderId?: number): number; - resetComponent(): void; - instantiateComponents(renderId?: number): number; - props: any; - } - } - export namespace Element { - export function isElement(el: any): el is JSX.Element { - return el.markAsChildOfRootElement !== undefined; - } - - export function createElement(args: any[]) { - - return { - } - } - } - - export let createElement = Element.createElement; - - function toCamelCase(text: string): string { - return text[0].toLowerCase() + text.substring(1); - } - -==== tests/cases/compiler/test.tsx (2 errors) ==== - import { Element} from './Element'; - let createElement = Element.createElement; - let c: { - a?: { - b: string - } - }; - - class A { - view() { - return [ - , - ~~~~ -!!! error TS2304: Cannot find name 'React'. - - ~~~~ -!!! error TS2304: Cannot find name 'React'. - ]; - } - } \ No newline at end of file diff --git a/tests/baselines/reference/jsxFactoryIdentifier.symbols b/tests/baselines/reference/jsxFactoryIdentifier.symbols new file mode 100644 index 00000000000..0d0f1e80490 --- /dev/null +++ b/tests/baselines/reference/jsxFactoryIdentifier.symbols @@ -0,0 +1,125 @@ +=== tests/cases/compiler/Element.ts === + +declare namespace JSX { +>JSX : Symbol(JSX, Decl(Element.ts, 0, 0)) + + interface Element { +>Element : Symbol(Element, Decl(Element.ts, 1, 23)) + + name: string; +>name : Symbol(Element.name, Decl(Element.ts, 2, 23)) + + isIntrinsic: boolean; +>isIntrinsic : Symbol(Element.isIntrinsic, Decl(Element.ts, 3, 21)) + + isCustomElement: boolean; +>isCustomElement : Symbol(Element.isCustomElement, Decl(Element.ts, 4, 29)) + + toString(renderId?: number): string; +>toString : Symbol(Element.toString, Decl(Element.ts, 5, 33)) +>renderId : Symbol(renderId, Decl(Element.ts, 6, 17)) + + bindDOM(renderId?: number): number; +>bindDOM : Symbol(Element.bindDOM, Decl(Element.ts, 6, 44)) +>renderId : Symbol(renderId, Decl(Element.ts, 7, 16)) + + resetComponent(): void; +>resetComponent : Symbol(Element.resetComponent, Decl(Element.ts, 7, 43)) + + instantiateComponents(renderId?: number): number; +>instantiateComponents : Symbol(Element.instantiateComponents, Decl(Element.ts, 8, 31)) +>renderId : Symbol(renderId, Decl(Element.ts, 9, 30)) + + props: any; +>props : Symbol(Element.props, Decl(Element.ts, 9, 57)) + } +} +export namespace Element { +>Element : Symbol(Element, Decl(Element.ts, 12, 1)) + + export function isElement(el: any): el is JSX.Element { +>isElement : Symbol(isElement, Decl(Element.ts, 13, 26)) +>el : Symbol(el, Decl(Element.ts, 14, 30)) +>el : Symbol(el, Decl(Element.ts, 14, 30)) +>JSX : Symbol(JSX, Decl(Element.ts, 0, 0)) +>Element : Symbol(JSX.Element, Decl(Element.ts, 1, 23)) + + return el.markAsChildOfRootElement !== undefined; +>el : Symbol(el, Decl(Element.ts, 14, 30)) +>undefined : Symbol(undefined) + } + + export function createElement(args: any[]) { +>createElement : Symbol(createElement, Decl(Element.ts, 16, 5)) +>args : Symbol(args, Decl(Element.ts, 18, 34)) + + return { + } + } +} + +export let createElement = Element.createElement; +>createElement : Symbol(createElement, Decl(Element.ts, 25, 10)) +>Element.createElement : Symbol(Element.createElement, Decl(Element.ts, 16, 5)) +>Element : Symbol(Element, Decl(Element.ts, 12, 1)) +>createElement : Symbol(Element.createElement, Decl(Element.ts, 16, 5)) + +function toCamelCase(text: string): string { +>toCamelCase : Symbol(toCamelCase, Decl(Element.ts, 25, 49)) +>text : Symbol(text, Decl(Element.ts, 27, 21)) + + return text[0].toLowerCase() + text.substring(1); +>text[0].toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) +>text : Symbol(text, Decl(Element.ts, 27, 21)) +>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) +>text.substring : Symbol(String.substring, Decl(lib.es5.d.ts, --, --)) +>text : Symbol(text, Decl(Element.ts, 27, 21)) +>substring : Symbol(String.substring, Decl(lib.es5.d.ts, --, --)) +} + +=== tests/cases/compiler/test.tsx === +import { Element} from './Element'; +>Element : Symbol(Element, Decl(test.tsx, 0, 8)) + +let createElement = Element.createElement; +>createElement : Symbol(createElement, Decl(test.tsx, 1, 3)) +>Element.createElement : Symbol(Element.createElement, Decl(Element.ts, 16, 5)) +>Element : Symbol(Element, Decl(test.tsx, 0, 8)) +>createElement : Symbol(Element.createElement, Decl(Element.ts, 16, 5)) + +let c: { +>c : Symbol(c, Decl(test.tsx, 2, 3)) + + a?: { +>a : Symbol(a, Decl(test.tsx, 2, 8)) + + b: string +>b : Symbol(b, Decl(test.tsx, 3, 6)) + } +}; + +class A { +>A : Symbol(A, Decl(test.tsx, 6, 2)) + + view() { +>view : Symbol(A.view, Decl(test.tsx, 8, 9)) + + return [ + , +>meta : Symbol(unknown) +>content : Symbol(unknown) +>meta : Symbol(unknown) + + +>meta : Symbol(unknown) +>content : Symbol(unknown) +>c.a!.b : Symbol(b, Decl(test.tsx, 3, 6)) +>c.a : Symbol(a, Decl(test.tsx, 2, 8)) +>c : Symbol(c, Decl(test.tsx, 2, 3)) +>a : Symbol(a, Decl(test.tsx, 2, 8)) +>b : Symbol(b, Decl(test.tsx, 3, 6)) +>meta : Symbol(unknown) + + ]; + } +} diff --git a/tests/baselines/reference/jsxFactoryIdentifier.types b/tests/baselines/reference/jsxFactoryIdentifier.types new file mode 100644 index 00000000000..c946c90559d --- /dev/null +++ b/tests/baselines/reference/jsxFactoryIdentifier.types @@ -0,0 +1,140 @@ +=== tests/cases/compiler/Element.ts === + +declare namespace JSX { +>JSX : any + + interface Element { +>Element : Element + + name: string; +>name : string + + isIntrinsic: boolean; +>isIntrinsic : boolean + + isCustomElement: boolean; +>isCustomElement : boolean + + toString(renderId?: number): string; +>toString : (renderId?: number) => string +>renderId : number + + bindDOM(renderId?: number): number; +>bindDOM : (renderId?: number) => number +>renderId : number + + resetComponent(): void; +>resetComponent : () => void + + instantiateComponents(renderId?: number): number; +>instantiateComponents : (renderId?: number) => number +>renderId : number + + props: any; +>props : any + } +} +export namespace Element { +>Element : typeof Element + + export function isElement(el: any): el is JSX.Element { +>isElement : (el: any) => el is JSX.Element +>el : any +>el : any +>JSX : any +>Element : JSX.Element + + return el.markAsChildOfRootElement !== undefined; +>el.markAsChildOfRootElement !== undefined : boolean +>el.markAsChildOfRootElement : any +>el : any +>markAsChildOfRootElement : any +>undefined : undefined + } + + export function createElement(args: any[]) { +>createElement : (args: any[]) => {} +>args : any[] + + return { +>{ } : {} + } + } +} + +export let createElement = Element.createElement; +>createElement : (args: any[]) => {} +>Element.createElement : (args: any[]) => {} +>Element : typeof Element +>createElement : (args: any[]) => {} + +function toCamelCase(text: string): string { +>toCamelCase : (text: string) => string +>text : string + + return text[0].toLowerCase() + text.substring(1); +>text[0].toLowerCase() + text.substring(1) : string +>text[0].toLowerCase() : string +>text[0].toLowerCase : () => string +>text[0] : string +>text : string +>0 : 0 +>toLowerCase : () => string +>text.substring(1) : string +>text.substring : (start: number, end?: number) => string +>text : string +>substring : (start: number, end?: number) => string +>1 : 1 +} + +=== tests/cases/compiler/test.tsx === +import { Element} from './Element'; +>Element : typeof Element + +let createElement = Element.createElement; +>createElement : (args: any[]) => {} +>Element.createElement : (args: any[]) => {} +>Element : typeof Element +>createElement : (args: any[]) => {} + +let c: { +>c : { a?: { b: string; }; } + + a?: { +>a : { b: string; } + + b: string +>b : string + } +}; + +class A { +>A : A + + view() { +>view : () => any[] + + return [ +>[ , ] : any[] + + , +> : any +>meta : any +>content : any +>meta : any + + +> : any +>meta : any +>content : any +>c.a!.b : string +>c.a! : { b: string; } +>c.a : { b: string; } +>c : { a?: { b: string; }; } +>a : { b: string; } +>b : string +>meta : any + + ]; + } +} diff --git a/tests/baselines/reference/jsxFactoryQualifiedName.errors.txt b/tests/baselines/reference/jsxFactoryQualifiedName.errors.txt deleted file mode 100644 index 32c8500ea3b..00000000000 --- a/tests/baselines/reference/jsxFactoryQualifiedName.errors.txt +++ /dev/null @@ -1,57 +0,0 @@ -tests/cases/compiler/test.tsx(12,5): error TS2304: Cannot find name 'React'. -tests/cases/compiler/test.tsx(13,5): error TS2304: Cannot find name 'React'. - - -==== tests/cases/compiler/Element.ts (0 errors) ==== - - declare namespace JSX { - interface Element { - name: string; - isIntrinsic: boolean; - isCustomElement: boolean; - toString(renderId?: number): string; - bindDOM(renderId?: number): number; - resetComponent(): void; - instantiateComponents(renderId?: number): number; - props: any; - } - } - export namespace Element { - export function isElement(el: any): el is JSX.Element { - return el.markAsChildOfRootElement !== undefined; - } - - export function createElement(args: any[]) { - - return { - } - } - } - - export let createElement = Element.createElement; - - function toCamelCase(text: string): string { - return text[0].toLowerCase() + text.substring(1); - } - -==== tests/cases/compiler/test.tsx (2 errors) ==== - import { Element} from './Element'; - - let c: { - a?: { - b: string - } - }; - - class A { - view() { - return [ - , - ~~~~ -!!! error TS2304: Cannot find name 'React'. - - ~~~~ -!!! error TS2304: Cannot find name 'React'. - ]; - } - } \ No newline at end of file diff --git a/tests/baselines/reference/jsxFactoryQualifiedName.js b/tests/baselines/reference/jsxFactoryQualifiedName.js index aec6614bf01..b5da232a3d4 100644 --- a/tests/baselines/reference/jsxFactoryQualifiedName.js +++ b/tests/baselines/reference/jsxFactoryQualifiedName.js @@ -69,6 +69,7 @@ function toCamelCase(text) { } //// [test.js] "use strict"; +const Element_1 = require("./Element"); let c; class A { view() { diff --git a/tests/baselines/reference/jsxFactoryQualifiedName.symbols b/tests/baselines/reference/jsxFactoryQualifiedName.symbols new file mode 100644 index 00000000000..16cd35d1ca3 --- /dev/null +++ b/tests/baselines/reference/jsxFactoryQualifiedName.symbols @@ -0,0 +1,119 @@ +=== tests/cases/compiler/Element.ts === + +declare namespace JSX { +>JSX : Symbol(JSX, Decl(Element.ts, 0, 0)) + + interface Element { +>Element : Symbol(Element, Decl(Element.ts, 1, 23)) + + name: string; +>name : Symbol(Element.name, Decl(Element.ts, 2, 23)) + + isIntrinsic: boolean; +>isIntrinsic : Symbol(Element.isIntrinsic, Decl(Element.ts, 3, 21)) + + isCustomElement: boolean; +>isCustomElement : Symbol(Element.isCustomElement, Decl(Element.ts, 4, 29)) + + toString(renderId?: number): string; +>toString : Symbol(Element.toString, Decl(Element.ts, 5, 33)) +>renderId : Symbol(renderId, Decl(Element.ts, 6, 17)) + + bindDOM(renderId?: number): number; +>bindDOM : Symbol(Element.bindDOM, Decl(Element.ts, 6, 44)) +>renderId : Symbol(renderId, Decl(Element.ts, 7, 16)) + + resetComponent(): void; +>resetComponent : Symbol(Element.resetComponent, Decl(Element.ts, 7, 43)) + + instantiateComponents(renderId?: number): number; +>instantiateComponents : Symbol(Element.instantiateComponents, Decl(Element.ts, 8, 31)) +>renderId : Symbol(renderId, Decl(Element.ts, 9, 30)) + + props: any; +>props : Symbol(Element.props, Decl(Element.ts, 9, 57)) + } +} +export namespace Element { +>Element : Symbol(Element, Decl(Element.ts, 12, 1)) + + export function isElement(el: any): el is JSX.Element { +>isElement : Symbol(isElement, Decl(Element.ts, 13, 26)) +>el : Symbol(el, Decl(Element.ts, 14, 30)) +>el : Symbol(el, Decl(Element.ts, 14, 30)) +>JSX : Symbol(JSX, Decl(Element.ts, 0, 0)) +>Element : Symbol(JSX.Element, Decl(Element.ts, 1, 23)) + + return el.markAsChildOfRootElement !== undefined; +>el : Symbol(el, Decl(Element.ts, 14, 30)) +>undefined : Symbol(undefined) + } + + export function createElement(args: any[]) { +>createElement : Symbol(createElement, Decl(Element.ts, 16, 5)) +>args : Symbol(args, Decl(Element.ts, 18, 34)) + + return { + } + } +} + +export let createElement = Element.createElement; +>createElement : Symbol(createElement, Decl(Element.ts, 25, 10)) +>Element.createElement : Symbol(Element.createElement, Decl(Element.ts, 16, 5)) +>Element : Symbol(Element, Decl(Element.ts, 12, 1)) +>createElement : Symbol(Element.createElement, Decl(Element.ts, 16, 5)) + +function toCamelCase(text: string): string { +>toCamelCase : Symbol(toCamelCase, Decl(Element.ts, 25, 49)) +>text : Symbol(text, Decl(Element.ts, 27, 21)) + + return text[0].toLowerCase() + text.substring(1); +>text[0].toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) +>text : Symbol(text, Decl(Element.ts, 27, 21)) +>toLowerCase : Symbol(String.toLowerCase, Decl(lib.es5.d.ts, --, --)) +>text.substring : Symbol(String.substring, Decl(lib.es5.d.ts, --, --)) +>text : Symbol(text, Decl(Element.ts, 27, 21)) +>substring : Symbol(String.substring, Decl(lib.es5.d.ts, --, --)) +} + +=== tests/cases/compiler/test.tsx === +import { Element} from './Element'; +>Element : Symbol(Element, Decl(test.tsx, 0, 8)) + +let c: { +>c : Symbol(c, Decl(test.tsx, 2, 3)) + + a?: { +>a : Symbol(a, Decl(test.tsx, 2, 8)) + + b: string +>b : Symbol(b, Decl(test.tsx, 3, 6)) + } +}; + +class A { +>A : Symbol(A, Decl(test.tsx, 6, 2)) + + view() { +>view : Symbol(A.view, Decl(test.tsx, 8, 9)) + + return [ + , +>meta : Symbol(unknown) +>content : Symbol(unknown) +>meta : Symbol(unknown) + + +>meta : Symbol(unknown) +>content : Symbol(unknown) +>c.a!.b : Symbol(b, Decl(test.tsx, 3, 6)) +>c.a : Symbol(a, Decl(test.tsx, 2, 8)) +>c : Symbol(c, Decl(test.tsx, 2, 3)) +>a : Symbol(a, Decl(test.tsx, 2, 8)) +>b : Symbol(b, Decl(test.tsx, 3, 6)) +>meta : Symbol(unknown) + + ]; + } +} diff --git a/tests/baselines/reference/jsxFactoryQualifiedName.types b/tests/baselines/reference/jsxFactoryQualifiedName.types new file mode 100644 index 00000000000..663ba4b5bfc --- /dev/null +++ b/tests/baselines/reference/jsxFactoryQualifiedName.types @@ -0,0 +1,134 @@ +=== tests/cases/compiler/Element.ts === + +declare namespace JSX { +>JSX : any + + interface Element { +>Element : Element + + name: string; +>name : string + + isIntrinsic: boolean; +>isIntrinsic : boolean + + isCustomElement: boolean; +>isCustomElement : boolean + + toString(renderId?: number): string; +>toString : (renderId?: number) => string +>renderId : number + + bindDOM(renderId?: number): number; +>bindDOM : (renderId?: number) => number +>renderId : number + + resetComponent(): void; +>resetComponent : () => void + + instantiateComponents(renderId?: number): number; +>instantiateComponents : (renderId?: number) => number +>renderId : number + + props: any; +>props : any + } +} +export namespace Element { +>Element : typeof Element + + export function isElement(el: any): el is JSX.Element { +>isElement : (el: any) => el is JSX.Element +>el : any +>el : any +>JSX : any +>Element : JSX.Element + + return el.markAsChildOfRootElement !== undefined; +>el.markAsChildOfRootElement !== undefined : boolean +>el.markAsChildOfRootElement : any +>el : any +>markAsChildOfRootElement : any +>undefined : undefined + } + + export function createElement(args: any[]) { +>createElement : (args: any[]) => {} +>args : any[] + + return { +>{ } : {} + } + } +} + +export let createElement = Element.createElement; +>createElement : (args: any[]) => {} +>Element.createElement : (args: any[]) => {} +>Element : typeof Element +>createElement : (args: any[]) => {} + +function toCamelCase(text: string): string { +>toCamelCase : (text: string) => string +>text : string + + return text[0].toLowerCase() + text.substring(1); +>text[0].toLowerCase() + text.substring(1) : string +>text[0].toLowerCase() : string +>text[0].toLowerCase : () => string +>text[0] : string +>text : string +>0 : 0 +>toLowerCase : () => string +>text.substring(1) : string +>text.substring : (start: number, end?: number) => string +>text : string +>substring : (start: number, end?: number) => string +>1 : 1 +} + +=== tests/cases/compiler/test.tsx === +import { Element} from './Element'; +>Element : typeof Element + +let c: { +>c : { a?: { b: string; }; } + + a?: { +>a : { b: string; } + + b: string +>b : string + } +}; + +class A { +>A : A + + view() { +>view : () => any[] + + return [ +>[ , ] : any[] + + , +> : any +>meta : any +>content : any +>meta : any + + +> : any +>meta : any +>content : any +>c.a!.b : string +>c.a! : { b: string; } +>c.a : { b: string; } +>c : { a?: { b: string; }; } +>a : { b: string; } +>b : string +>meta : any + + ]; + } +}