Skip to content

Commit f4d3143

Browse files
committed
Add support for per-file jsx pragmas
1 parent 639b278 commit f4d3143

File tree

11 files changed

+292
-9
lines changed

11 files changed

+292
-9
lines changed

src/compiler/checker.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ namespace ts {
291291
resolveName(name, location, meaning, excludeGlobals) {
292292
return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false, excludeGlobals);
293293
},
294-
getJsxNamespace: () => unescapeLeadingUnderscores(getJsxNamespace()),
294+
getJsxNamespace: n => unescapeLeadingUnderscores(getJsxNamespace(n)),
295295
getAccessibleSymbolChain,
296296
getTypePredicateOfSignature,
297297
resolveExternalModuleSymbol,
@@ -729,7 +729,22 @@ namespace ts {
729729
}
730730
}
731731

732-
function getJsxNamespace(): __String {
732+
function getJsxNamespace(location: Node | undefined): __String {
733+
if (location) {
734+
const file = getSourceFileOfNode(location);
735+
if (file) {
736+
if (file.localJsxNamespace) {
737+
return file.localJsxNamespace;
738+
}
739+
const jsxPragma = file.pragmas.get("jsx");
740+
if (jsxPragma) {
741+
file.localJsxFactory = parseIsolatedEntityName(jsxPragma, languageVersion);
742+
if (file.localJsxFactory) {
743+
return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText;
744+
}
745+
}
746+
}
747+
}
733748
if (!_jsxNamespace) {
734749
_jsxNamespace = "React" as __String;
735750
if (compilerOptions.jsxFactory) {
@@ -15200,7 +15215,7 @@ namespace ts {
1520015215
// The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import.
1520115216
// And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error.
1520215217
const reactRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined;
15203-
const reactNamespace = getJsxNamespace();
15218+
const reactNamespace = getJsxNamespace(node);
1520415219
const reactLocation = isNodeOpeningLikeElement ? (<JsxOpeningLikeElement>node).tagName : node;
1520515220
const reactSym = resolveName(reactLocation, reactNamespace, SymbolFlags.Value, reactRefErr, reactNamespace, /*isUse*/ true);
1520615221
if (reactSym) {
@@ -25021,7 +25036,7 @@ namespace ts {
2502125036
return !!(symbol && getCheckFlags(symbol) & CheckFlags.Late);
2502225037
},
2502325038
writeLiteralConstValue,
25024-
getJsxFactoryEntity: () => _jsxFactoryEntity
25039+
getJsxFactoryEntity: location => location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity
2502525040
};
2502625041

2502725042
// defined here to avoid outer scope pollution

src/compiler/factory.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2337,6 +2337,9 @@ namespace ts {
23372337
if (node.resolvedTypeReferenceDirectiveNames !== undefined) updated.resolvedTypeReferenceDirectiveNames = node.resolvedTypeReferenceDirectiveNames;
23382338
if (node.imports !== undefined) updated.imports = node.imports;
23392339
if (node.moduleAugmentations !== undefined) updated.moduleAugmentations = node.moduleAugmentations;
2340+
if (node.pragmas !== undefined) updated.pragmas = node.pragmas;
2341+
if (node.localJsxFactory !== undefined) updated.localJsxFactory = node.localJsxFactory;
2342+
if (node.localJsxNamespace !== undefined) updated.localJsxNamespace = node.localJsxNamespace;
23402343
return updateNode(updated, node);
23412344
}
23422345

src/compiler/parser.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6037,6 +6037,7 @@ namespace ts {
60376037
const referencedFiles: FileReference[] = [];
60386038
const typeReferenceDirectives: FileReference[] = [];
60396039
const amdDependencies: { path: string; name: string }[] = [];
6040+
let pragmas: { pragma: string, value: string | undefined }[] = [];
60406041
let amdModuleName: string;
60416042
let checkJsDirective: CheckJsDirective = undefined;
60426043

@@ -6045,6 +6046,9 @@ namespace ts {
60456046
// reference comment.
60466047
while (true) {
60476048
const kind = triviaScanner.scan();
6049+
if (kind === SyntaxKind.MultiLineCommentTrivia) {
6050+
pragmas = concatenate(pragmas, extractPragmas(sourceText.substring(triviaScanner.getTokenPos(), triviaScanner.getTextPos())));
6051+
}
60486052
if (kind !== SyntaxKind.SingleLineCommentTrivia) {
60496053
if (isTrivia(kind)) {
60506054
continue;
@@ -6110,6 +6114,8 @@ namespace ts {
61106114
pos: range.pos
61116115
};
61126116
}
6117+
6118+
pragmas = concatenate(pragmas, extractPragmas(comment));
61136119
}
61146120
}
61156121

@@ -6118,6 +6124,25 @@ namespace ts {
61186124
sourceFile.amdDependencies = amdDependencies;
61196125
sourceFile.moduleName = amdModuleName;
61206126
sourceFile.checkJsDirective = checkJsDirective;
6127+
sourceFile.pragmas = createMap();
6128+
for (const p of pragmas) {
6129+
if (sourceFile.pragmas.has(p.pragma)) {
6130+
// First one in semantics matches babel's behavior for the jsx pragma
6131+
// TODO: Considering issuing an error/warning on ignored pragma comments? It feels philosophically incorrect to error on a comment, but...
6132+
continue;
6133+
}
6134+
sourceFile.pragmas.set(p.pragma, p.value);
6135+
}
6136+
}
6137+
6138+
function extractPragmas(text: string) {
6139+
const pragmas: { pragma: string, value: string | undefined }[] = [];
6140+
const pragmaRegEx = /@(\w+)(\W+(\w+)(\W+|$))?/gim;
6141+
let matchResult: RegExpMatchArray;
6142+
while (matchResult = pragmaRegEx.exec(text)) {
6143+
pragmas.push({ pragma: matchResult[1], value: matchResult[3] });
6144+
}
6145+
return pragmas;
61216146
}
61226147

61236148
function setExternalModuleIndicator(sourceFile: SourceFile) {

src/compiler/transformers/jsx.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ namespace ts {
122122
}
123123

124124
const element = createExpressionForJsxElement(
125-
context.getEmitResolver().getJsxFactoryEntity(),
125+
context.getEmitResolver().getJsxFactoryEntity(currentSourceFile),
126126
compilerOptions.reactNamespace,
127127
tagName,
128128
objectProperties,
@@ -140,7 +140,7 @@ namespace ts {
140140

141141
function visitJsxOpeningFragment(node: JsxOpeningFragment, children: ReadonlyArray<JsxChild>, isChild: boolean, location: TextRange) {
142142
const element = createExpressionForJsxFragment(
143-
context.getEmitResolver().getJsxFactoryEntity(),
143+
context.getEmitResolver().getJsxFactoryEntity(currentSourceFile),
144144
compilerOptions.reactNamespace,
145145
mapDefined(children, transformJsxChildToExpression),
146146
node,

src/compiler/types.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2541,6 +2541,9 @@ namespace ts {
25412541
/* @internal */ ambientModuleNames: ReadonlyArray<string>;
25422542
/* @internal */ checkJsDirective: CheckJsDirective | undefined;
25432543
/* @internal */ version: string;
2544+
/* @internal */ pragmas: Map<string>;
2545+
/* @internal */ localJsxNamespace?: __String;
2546+
/* @internal */ localJsxFactory?: EntityName;
25442547
}
25452548

25462549
export interface Bundle extends Node {
@@ -2905,7 +2908,7 @@ namespace ts {
29052908
/* @internal */ isArrayLikeType(type: Type): boolean;
29062909
/* @internal */ getAllPossiblePropertiesOfTypes(type: ReadonlyArray<Type>): Symbol[];
29072910
/* @internal */ resolveName(name: string, location: Node, meaning: SymbolFlags, excludeGlobals: boolean): Symbol | undefined;
2908-
/* @internal */ getJsxNamespace(): string;
2911+
/* @internal */ getJsxNamespace(location?: Node): string;
29092912

29102913
/**
29112914
* Note that this will return undefined in the following case:
@@ -3169,7 +3172,7 @@ namespace ts {
31693172
getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[];
31703173
isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean;
31713174
writeLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, writer: EmitTextWriter): void;
3172-
getJsxFactoryEntity(): EntityName;
3175+
getJsxFactoryEntity(location?: Node): EntityName;
31733176
}
31743177

31753178
export const enum SymbolFlags {

src/services/codefixes/importFixes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,7 @@ namespace ts.codefix {
746746
}
747747
else if (isJsxOpeningLikeElement(symbolToken.parent) && symbolToken.parent.tagName === symbolToken) {
748748
// The error wasn't for the symbolAtLocation, it was for the JSX tag itself, which needs access to e.g. `React`.
749-
symbol = checker.getAliasedSymbol(checker.resolveName(checker.getJsxNamespace(), symbolToken.parent.tagName, SymbolFlags.Value, /*excludeGlobals*/ false));
749+
symbol = checker.getAliasedSymbol(checker.resolveName(checker.getJsxNamespace(symbolToken), symbolToken.parent.tagName, SymbolFlags.Value, /*excludeGlobals*/ false));
750750
symbolName = symbol.name;
751751
}
752752
else {

src/services/services.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,9 @@ namespace ts {
647647
public ambientModuleNames: string[];
648648
public checkJsDirective: CheckJsDirective | undefined;
649649
public possiblyContainDynamicImport: boolean;
650+
public pragmas: Map<string>;
651+
public localJsxFactory: EntityName;
652+
public localJsxNamespace: __String;
650653

651654
constructor(kind: SyntaxKind, pos: number, end: number) {
652655
super(kind, pos, end);
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//// [tests/cases/conformance/jsx/inline/inlineJsxFactoryDeclarations.tsx] ////
2+
3+
//// [renderer.d.ts]
4+
declare global {
5+
namespace JSX {
6+
interface IntrinsicElements {
7+
[e: string]: any;
8+
}
9+
}
10+
}
11+
export function dom(): void;
12+
export function otherdom(): void;
13+
export { dom as default };
14+
//// [other.tsx]
15+
/** @jsx h */
16+
import { dom as h } from "./renderer"
17+
export const prerendered = <h></h>;
18+
//// [othernoalias.tsx]
19+
/** @jsx otherdom */
20+
import { otherdom } from "./renderer"
21+
export const prerendered2 = <h></h>;
22+
//// [reacty.tsx]
23+
import React from "./renderer"
24+
export const prerendered3 = <h></h>;
25+
26+
//// [index.tsx]
27+
/** @jsx dom */
28+
import { dom } from "./renderer"
29+
<h></h>
30+
export * from "./other";
31+
export * from "./othernoalias";
32+
export * from "./reacty";
33+
34+
35+
//// [other.js]
36+
"use strict";
37+
exports.__esModule = true;
38+
/** @jsx h */
39+
var renderer_1 = require("./renderer");
40+
exports.prerendered = renderer_1.dom("h", null);
41+
//// [othernoalias.js]
42+
"use strict";
43+
exports.__esModule = true;
44+
/** @jsx otherdom */
45+
var renderer_1 = require("./renderer");
46+
exports.prerendered2 = renderer_1.otherdom("h", null);
47+
//// [reacty.js]
48+
"use strict";
49+
exports.__esModule = true;
50+
var renderer_1 = require("./renderer");
51+
exports.prerendered3 = renderer_1["default"].createElement("h", null);
52+
//// [index.js]
53+
"use strict";
54+
function __export(m) {
55+
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
56+
}
57+
exports.__esModule = true;
58+
/** @jsx dom */
59+
var renderer_1 = require("./renderer");
60+
renderer_1.dom("h", null);
61+
__export(require("./other"));
62+
__export(require("./othernoalias"));
63+
__export(require("./reacty"));
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
=== tests/cases/conformance/jsx/inline/renderer.d.ts ===
2+
declare global {
3+
>global : Symbol(global, Decl(renderer.d.ts, 0, 0))
4+
5+
namespace JSX {
6+
>JSX : Symbol(JSX, Decl(renderer.d.ts, 0, 16))
7+
8+
interface IntrinsicElements {
9+
>IntrinsicElements : Symbol(IntrinsicElements, Decl(renderer.d.ts, 1, 19))
10+
11+
[e: string]: any;
12+
>e : Symbol(e, Decl(renderer.d.ts, 3, 13))
13+
}
14+
}
15+
}
16+
export function dom(): void;
17+
>dom : Symbol(dom, Decl(renderer.d.ts, 6, 1))
18+
19+
export function otherdom(): void;
20+
>otherdom : Symbol(otherdom, Decl(renderer.d.ts, 7, 28))
21+
22+
export { dom as default };
23+
>dom : Symbol(default, Decl(renderer.d.ts, 9, 8))
24+
>default : Symbol(default, Decl(renderer.d.ts, 9, 8))
25+
26+
=== tests/cases/conformance/jsx/inline/other.tsx ===
27+
/** @jsx h */
28+
import { dom as h } from "./renderer"
29+
>dom : Symbol(h, Decl(other.tsx, 1, 8))
30+
>h : Symbol(h, Decl(other.tsx, 1, 8))
31+
32+
export const prerendered = <h></h>;
33+
>prerendered : Symbol(prerendered, Decl(other.tsx, 2, 12))
34+
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))
35+
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))
36+
37+
=== tests/cases/conformance/jsx/inline/othernoalias.tsx ===
38+
/** @jsx otherdom */
39+
import { otherdom } from "./renderer"
40+
>otherdom : Symbol(otherdom, Decl(othernoalias.tsx, 1, 8))
41+
42+
export const prerendered2 = <h></h>;
43+
>prerendered2 : Symbol(prerendered2, Decl(othernoalias.tsx, 2, 12))
44+
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))
45+
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))
46+
47+
=== tests/cases/conformance/jsx/inline/reacty.tsx ===
48+
import React from "./renderer"
49+
>React : Symbol(React, Decl(reacty.tsx, 0, 6))
50+
51+
export const prerendered3 = <h></h>;
52+
>prerendered3 : Symbol(prerendered3, Decl(reacty.tsx, 1, 12))
53+
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))
54+
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))
55+
56+
=== tests/cases/conformance/jsx/inline/index.tsx ===
57+
/** @jsx dom */
58+
import { dom } from "./renderer"
59+
>dom : Symbol(dom, Decl(index.tsx, 1, 8))
60+
61+
<h></h>
62+
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))
63+
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))
64+
65+
export * from "./other";
66+
export * from "./othernoalias";
67+
export * from "./reacty";
68+
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
=== tests/cases/conformance/jsx/inline/renderer.d.ts ===
2+
declare global {
3+
>global : any
4+
5+
namespace JSX {
6+
>JSX : any
7+
8+
interface IntrinsicElements {
9+
>IntrinsicElements : IntrinsicElements
10+
11+
[e: string]: any;
12+
>e : string
13+
}
14+
}
15+
}
16+
export function dom(): void;
17+
>dom : () => void
18+
19+
export function otherdom(): void;
20+
>otherdom : () => void
21+
22+
export { dom as default };
23+
>dom : () => void
24+
>default : () => void
25+
26+
=== tests/cases/conformance/jsx/inline/other.tsx ===
27+
/** @jsx h */
28+
import { dom as h } from "./renderer"
29+
>dom : () => void
30+
>h : () => void
31+
32+
export const prerendered = <h></h>;
33+
>prerendered : any
34+
><h></h> : any
35+
>h : () => void
36+
>h : () => void
37+
38+
=== tests/cases/conformance/jsx/inline/othernoalias.tsx ===
39+
/** @jsx otherdom */
40+
import { otherdom } from "./renderer"
41+
>otherdom : () => void
42+
43+
export const prerendered2 = <h></h>;
44+
>prerendered2 : any
45+
><h></h> : any
46+
>h : any
47+
>h : any
48+
49+
=== tests/cases/conformance/jsx/inline/reacty.tsx ===
50+
import React from "./renderer"
51+
>React : () => void
52+
53+
export const prerendered3 = <h></h>;
54+
>prerendered3 : any
55+
><h></h> : any
56+
>h : any
57+
>h : any
58+
59+
=== tests/cases/conformance/jsx/inline/index.tsx ===
60+
/** @jsx dom */
61+
import { dom } from "./renderer"
62+
>dom : () => void
63+
64+
<h></h>
65+
><h></h> : any
66+
>h : any
67+
>h : any
68+
69+
export * from "./other";
70+
export * from "./othernoalias";
71+
export * from "./reacty";
72+

0 commit comments

Comments
 (0)