Skip to content

Commit f7bac98

Browse files
committed
Verify that jsxFactory is either identifier or qualified name
1 parent 4b8a557 commit f7bac98

16 files changed

+792
-5
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2381,6 +2381,10 @@
23812381
"category": "Error",
23822382
"code": 5066
23832383
},
2384+
"Invalid value for 'jsxFactory'. '{0}' is not a valid identifier or qualified-name.": {
2385+
"category": "Error",
2386+
"code": 5067
2387+
},
23842388
"Concatenate and emit output to single file.": {
23852389
"category": "Message",
23862390
"code": 6001

src/compiler/parser.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,10 @@ namespace ts {
438438
return result;
439439
}
440440

441+
export function parseIsolatedEntityName(text: string, languageVersion: ScriptTarget): EntityName {
442+
return Parser.parseIsolatedEntityName(text, languageVersion);
443+
}
444+
441445
export function isExternalModule(file: SourceFile): boolean {
442446
return file.externalModuleIndicator !== undefined;
443447
}
@@ -589,6 +593,16 @@ namespace ts {
589593
return result;
590594
}
591595

596+
export function parseIsolatedEntityName(content: string, languageVersion: ScriptTarget): EntityName {
597+
initializeState(content, languageVersion, /*syntaxCursor*/ undefined, ScriptKind.JS);
598+
// Prime the scanner.
599+
nextToken();
600+
const entityName = parseEntityName(/*allowReservedWords*/ true);
601+
const isInvalid = token() === SyntaxKind.EndOfFileToken && !parseDiagnostics.length;
602+
clearState();
603+
return isInvalid ? entityName : undefined;
604+
}
605+
592606
function getLanguageVariant(scriptKind: ScriptKind) {
593607
// .tsx and .jsx files are treated as jsx language variant.
594608
return scriptKind === ScriptKind.TSX || scriptKind === ScriptKind.JSX || scriptKind === ScriptKind.JS ? LanguageVariant.JSX : LanguageVariant.Standard;

src/compiler/program.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,6 +1674,9 @@ namespace ts {
16741674
if (options.reactNamespace) {
16751675
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "reactNamespace", "jsxFactory"));
16761676
}
1677+
if (!parseIsolatedEntityName(options.jsxFactory, languageVersion)) {
1678+
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Invalid_value_for_jsxFactory_0_is_not_a_valid_identifier_or_qualified_name, options.jsxFactory));
1679+
}
16771680
}
16781681
else if (options.reactNamespace && !isIdentifierText(options.reactNamespace, languageVersion)) {
16791682
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Invalid_value_for_reactNamespace_0_is_not_a_valid_identifier, options.reactNamespace));

src/harness/harness.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,7 +1768,7 @@ namespace Harness {
17681768
}
17691769

17701770
// Regex for parsing options in the format "@Alpha: Value of any sort"
1771-
const optionRegex = /^[\/]{2}\s*@(\w+)\s*:\s*(\S*)/gm; // multiple matches on multiple lines
1771+
const optionRegex = /^[\/]{2}\s*@(\w+)\s*:\s*([^\r\n]*)/gm; // multiple matches on multiple lines
17721772

17731773
function extractCompilerSettings(content: string): CompilerSettings {
17741774
const opts: CompilerSettings = {};
@@ -1777,7 +1777,7 @@ namespace Harness {
17771777
/* tslint:disable:no-null-keyword */
17781778
while ((match = optionRegex.exec(content)) !== null) {
17791779
/* tslint:enable:no-null-keyword */
1780-
opts[match[1]] = match[2];
1780+
opts[match[1]] = match[2].trim();
17811781
}
17821782

17831783
return opts;
@@ -1805,7 +1805,7 @@ namespace Harness {
18051805
// Comment line, check for global/file @options and record them
18061806
optionRegex.lastIndex = 0;
18071807
const metaDataName = testMetaData[1].toLowerCase();
1808-
currentFileOptions[testMetaData[1]] = testMetaData[2];
1808+
currentFileOptions[testMetaData[1]] = testMetaData[2].trim();
18091809
if (metaDataName !== "filename") {
18101810
continue;
18111811
}
@@ -1825,12 +1825,12 @@ namespace Harness {
18251825
// Reset local data
18261826
currentFileContent = undefined;
18271827
currentFileOptions = {};
1828-
currentFileName = testMetaData[2];
1828+
currentFileName = testMetaData[2].trim();
18291829
refs = [];
18301830
}
18311831
else {
18321832
// First metadata marker in the file
1833-
currentFileName = testMetaData[2];
1833+
currentFileName = testMetaData[2].trim();
18341834
}
18351835
}
18361836
else {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
tests/cases/compiler/test.tsx(12,5): error TS2304: Cannot find name 'React'.
2+
tests/cases/compiler/test.tsx(13,5): error TS2304: Cannot find name 'React'.
3+
4+
5+
==== tests/cases/compiler/Element.ts (0 errors) ====
6+
7+
declare namespace JSX {
8+
interface Element {
9+
name: string;
10+
isIntrinsic: boolean;
11+
isCustomElement: boolean;
12+
toString(renderId?: number): string;
13+
bindDOM(renderId?: number): number;
14+
resetComponent(): void;
15+
instantiateComponents(renderId?: number): number;
16+
props: any;
17+
}
18+
}
19+
export namespace Element {
20+
export function isElement(el: any): el is JSX.Element {
21+
return el.markAsChildOfRootElement !== undefined;
22+
}
23+
24+
export function createElement(args: any[]) {
25+
26+
return {
27+
}
28+
}
29+
}
30+
31+
export let createElement = Element.createElement;
32+
33+
function toCamelCase(text: string): string {
34+
return text[0].toLowerCase() + text.substring(1);
35+
}
36+
37+
==== tests/cases/compiler/test.tsx (2 errors) ====
38+
import { Element} from './Element';
39+
let createElement = Element.createElement;
40+
let c: {
41+
a?: {
42+
b: string
43+
}
44+
};
45+
46+
class A {
47+
view() {
48+
return [
49+
<meta content="helloworld"></meta>,
50+
~~~~
51+
!!! error TS2304: Cannot find name 'React'.
52+
<meta content={c.a!.b}></meta>
53+
~~~~
54+
!!! error TS2304: Cannot find name 'React'.
55+
];
56+
}
57+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//// [tests/cases/compiler/jsxFactoryIdentifier.ts] ////
2+
3+
//// [Element.ts]
4+
5+
declare namespace JSX {
6+
interface Element {
7+
name: string;
8+
isIntrinsic: boolean;
9+
isCustomElement: boolean;
10+
toString(renderId?: number): string;
11+
bindDOM(renderId?: number): number;
12+
resetComponent(): void;
13+
instantiateComponents(renderId?: number): number;
14+
props: any;
15+
}
16+
}
17+
export namespace Element {
18+
export function isElement(el: any): el is JSX.Element {
19+
return el.markAsChildOfRootElement !== undefined;
20+
}
21+
22+
export function createElement(args: any[]) {
23+
24+
return {
25+
}
26+
}
27+
}
28+
29+
export let createElement = Element.createElement;
30+
31+
function toCamelCase(text: string): string {
32+
return text[0].toLowerCase() + text.substring(1);
33+
}
34+
35+
//// [test.tsx]
36+
import { Element} from './Element';
37+
let createElement = Element.createElement;
38+
let c: {
39+
a?: {
40+
b: string
41+
}
42+
};
43+
44+
class A {
45+
view() {
46+
return [
47+
<meta content="helloworld"></meta>,
48+
<meta content={c.a!.b}></meta>
49+
];
50+
}
51+
}
52+
53+
//// [Element.js]
54+
"use strict";
55+
var Element;
56+
(function (Element) {
57+
function isElement(el) {
58+
return el.markAsChildOfRootElement !== undefined;
59+
}
60+
Element.isElement = isElement;
61+
function createElement(args) {
62+
return {};
63+
}
64+
Element.createElement = createElement;
65+
})(Element = exports.Element || (exports.Element = {}));
66+
exports.createElement = Element.createElement;
67+
function toCamelCase(text) {
68+
return text[0].toLowerCase() + text.substring(1);
69+
}
70+
//// [test.js]
71+
"use strict";
72+
const Element_1 = require("./Element");
73+
let createElement = Element_1.Element.createElement;
74+
let c;
75+
class A {
76+
view() {
77+
return [
78+
React.createElement("meta", { content: "helloworld" }),
79+
React.createElement("meta", { content: c.a.b })
80+
];
81+
}
82+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
error TS5067: Invalid value for 'jsxFactory'. 'Element.createElement=' is not a valid identifier or qualified-name.
2+
tests/cases/compiler/test.tsx(12,5): error TS2304: Cannot find name 'React'.
3+
tests/cases/compiler/test.tsx(13,5): error TS2304: Cannot find name 'React'.
4+
5+
6+
!!! error TS5067: Invalid value for 'jsxFactory'. 'Element.createElement=' is not a valid identifier or qualified-name.
7+
==== tests/cases/compiler/Element.ts (0 errors) ====
8+
9+
declare namespace JSX {
10+
interface Element {
11+
name: string;
12+
isIntrinsic: boolean;
13+
isCustomElement: boolean;
14+
toString(renderId?: number): string;
15+
bindDOM(renderId?: number): number;
16+
resetComponent(): void;
17+
instantiateComponents(renderId?: number): number;
18+
props: any;
19+
}
20+
}
21+
export namespace Element {
22+
export function isElement(el: any): el is JSX.Element {
23+
return el.markAsChildOfRootElement !== undefined;
24+
}
25+
26+
export function createElement(args: any[]) {
27+
28+
return {
29+
}
30+
}
31+
}
32+
33+
export let createElement = Element.createElement;
34+
35+
function toCamelCase(text: string): string {
36+
return text[0].toLowerCase() + text.substring(1);
37+
}
38+
39+
==== tests/cases/compiler/test.tsx (2 errors) ====
40+
import { Element} from './Element';
41+
42+
let c: {
43+
a?: {
44+
b: string
45+
}
46+
};
47+
48+
class A {
49+
view() {
50+
return [
51+
<meta content="helloworld"></meta>,
52+
~~~~
53+
!!! error TS2304: Cannot find name 'React'.
54+
<meta content={c.a!.b}></meta>
55+
~~~~
56+
!!! error TS2304: Cannot find name 'React'.
57+
];
58+
}
59+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//// [tests/cases/compiler/jsxFactoryNotIdentifierOrQualifiedName.ts] ////
2+
3+
//// [Element.ts]
4+
5+
declare namespace JSX {
6+
interface Element {
7+
name: string;
8+
isIntrinsic: boolean;
9+
isCustomElement: boolean;
10+
toString(renderId?: number): string;
11+
bindDOM(renderId?: number): number;
12+
resetComponent(): void;
13+
instantiateComponents(renderId?: number): number;
14+
props: any;
15+
}
16+
}
17+
export namespace Element {
18+
export function isElement(el: any): el is JSX.Element {
19+
return el.markAsChildOfRootElement !== undefined;
20+
}
21+
22+
export function createElement(args: any[]) {
23+
24+
return {
25+
}
26+
}
27+
}
28+
29+
export let createElement = Element.createElement;
30+
31+
function toCamelCase(text: string): string {
32+
return text[0].toLowerCase() + text.substring(1);
33+
}
34+
35+
//// [test.tsx]
36+
import { Element} from './Element';
37+
38+
let c: {
39+
a?: {
40+
b: string
41+
}
42+
};
43+
44+
class A {
45+
view() {
46+
return [
47+
<meta content="helloworld"></meta>,
48+
<meta content={c.a!.b}></meta>
49+
];
50+
}
51+
}
52+
53+
//// [Element.js]
54+
"use strict";
55+
var Element;
56+
(function (Element) {
57+
function isElement(el) {
58+
return el.markAsChildOfRootElement !== undefined;
59+
}
60+
Element.isElement = isElement;
61+
function createElement(args) {
62+
return {};
63+
}
64+
Element.createElement = createElement;
65+
})(Element = exports.Element || (exports.Element = {}));
66+
exports.createElement = Element.createElement;
67+
function toCamelCase(text) {
68+
return text[0].toLowerCase() + text.substring(1);
69+
}
70+
//// [test.js]
71+
"use strict";
72+
let c;
73+
class A {
74+
view() {
75+
return [
76+
React.createElement("meta", { content: "helloworld" }),
77+
React.createElement("meta", { content: c.a.b })
78+
];
79+
}
80+
}

0 commit comments

Comments
 (0)