Skip to content

Commit 8e01a86

Browse files
authored
Consistently ignore attributes with hyphenated names in JSX (#44873)
* Consistently skip attributes with hyphenated names in JSX * Add regression test * Accept new baselines * Fix tests * Accept new baselines
1 parent 1da18c6 commit 8e01a86

10 files changed

+227
-24
lines changed

src/compiler/checker.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16794,7 +16794,7 @@ namespace ts {
1679416794
function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator {
1679516795
if (!length(node.properties)) return;
1679616796
for (const prop of node.properties) {
16797-
if (isJsxSpreadAttribute(prop)) continue;
16797+
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(idText(prop.name))) continue;
1679816798
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(idText(prop.name)) };
1679916799
}
1680016800
}
@@ -17355,7 +17355,7 @@ namespace ts {
1735517355
}
1735617356

1735717357
function isIgnoredJsxProperty(source: Type, sourceProp: Symbol) {
17358-
return getObjectFlags(source) & ObjectFlags.JsxAttributes && !isUnhyphenatedJsxName(sourceProp.escapedName);
17358+
return getObjectFlags(source) & ObjectFlags.JsxAttributes && isHyphenatedJsxName(sourceProp.escapedName);
1735917359
}
1736017360

1736117361
function getNormalizedType(type: Type, writing: boolean): Type {
@@ -26585,8 +26585,8 @@ namespace ts {
2658526585
return getJsxElementTypeAt(node) || anyType;
2658626586
}
2658726587

26588-
function isUnhyphenatedJsxName(name: string | __String) {
26589-
return !stringContains(name as string, "-");
26588+
function isHyphenatedJsxName(name: string | __String) {
26589+
return stringContains(name as string, "-");
2659026590
}
2659126591

2659226592
/**
@@ -27127,7 +27127,7 @@ namespace ts {
2712727127
if (getPropertyOfObjectType(targetType, name) ||
2712827128
getApplicableIndexInfoForName(targetType, name) ||
2712927129
isLateBoundName(name) && getIndexInfoOfType(targetType, stringType) ||
27130-
isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) {
27130+
isComparingJsxAttributes && isHyphenatedJsxName(name)) {
2713127131
// For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known.
2713227132
return true;
2713327133
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
tests/cases/compiler/ignoredJsxAttributes.tsx(16,5): error TS2322: Type 'number' is not assignable to type 'string'.
2+
tests/cases/compiler/ignoredJsxAttributes.tsx(20,11): error TS2741: Property 'foo' is missing in type '{ bar: string; "data-yadda": number; }' but required in type 'Props'.
3+
4+
5+
==== tests/cases/compiler/ignoredJsxAttributes.tsx (2 errors) ====
6+
/// <reference path="/.lib/react16.d.ts" />
7+
8+
// Repro from #44797
9+
10+
import * as React from "react";
11+
12+
interface Props {
13+
foo: string;
14+
[dataProp: string]: string;
15+
}
16+
17+
declare function Yadda(props: Props): JSX.Element;
18+
19+
let props: Props = {
20+
foo: "",
21+
"data-yadda": 42, // Error
22+
~~~~~~~~~~~~
23+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
24+
!!! related TS6501 tests/cases/compiler/ignoredJsxAttributes.tsx:9:5: The expected type comes from this index signature.
25+
};
26+
27+
let x1 = <Yadda foo="hello" data-yadda={42}/>;
28+
let x2 = <Yadda bar="hello" data-yadda={42}/>; // Error
29+
~~~~~
30+
!!! error TS2741: Property 'foo' is missing in type '{ bar: string; "data-yadda": number; }' but required in type 'Props'.
31+
!!! related TS2728 tests/cases/compiler/ignoredJsxAttributes.tsx:8:5: 'foo' is declared here.
32+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//// [ignoredJsxAttributes.tsx]
2+
/// <reference path="/.lib/react16.d.ts" />
3+
4+
// Repro from #44797
5+
6+
import * as React from "react";
7+
8+
interface Props {
9+
foo: string;
10+
[dataProp: string]: string;
11+
}
12+
13+
declare function Yadda(props: Props): JSX.Element;
14+
15+
let props: Props = {
16+
foo: "",
17+
"data-yadda": 42, // Error
18+
};
19+
20+
let x1 = <Yadda foo="hello" data-yadda={42}/>;
21+
let x2 = <Yadda bar="hello" data-yadda={42}/>; // Error
22+
23+
24+
//// [ignoredJsxAttributes.js]
25+
"use strict";
26+
/// <reference path="react16.d.ts" />
27+
exports.__esModule = true;
28+
// Repro from #44797
29+
var React = require("react");
30+
var props = {
31+
foo: "",
32+
"data-yadda": 42
33+
};
34+
var x1 = React.createElement(Yadda, { foo: "hello", "data-yadda": 42 });
35+
var x2 = React.createElement(Yadda, { bar: "hello", "data-yadda": 42 }); // Error
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
=== tests/cases/compiler/ignoredJsxAttributes.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
4+
// Repro from #44797
5+
6+
import * as React from "react";
7+
>React : Symbol(React, Decl(ignoredJsxAttributes.tsx, 4, 6))
8+
9+
interface Props {
10+
>Props : Symbol(Props, Decl(ignoredJsxAttributes.tsx, 4, 31))
11+
12+
foo: string;
13+
>foo : Symbol(Props.foo, Decl(ignoredJsxAttributes.tsx, 6, 17))
14+
15+
[dataProp: string]: string;
16+
>dataProp : Symbol(dataProp, Decl(ignoredJsxAttributes.tsx, 8, 5))
17+
}
18+
19+
declare function Yadda(props: Props): JSX.Element;
20+
>Yadda : Symbol(Yadda, Decl(ignoredJsxAttributes.tsx, 9, 1))
21+
>props : Symbol(props, Decl(ignoredJsxAttributes.tsx, 11, 23))
22+
>Props : Symbol(Props, Decl(ignoredJsxAttributes.tsx, 4, 31))
23+
>JSX : Symbol(JSX, Decl(react16.d.ts, 2493, 12))
24+
>Element : Symbol(JSX.Element, Decl(react16.d.ts, 2494, 23))
25+
26+
let props: Props = {
27+
>props : Symbol(props, Decl(ignoredJsxAttributes.tsx, 13, 3))
28+
>Props : Symbol(Props, Decl(ignoredJsxAttributes.tsx, 4, 31))
29+
30+
foo: "",
31+
>foo : Symbol(foo, Decl(ignoredJsxAttributes.tsx, 13, 20))
32+
33+
"data-yadda": 42, // Error
34+
>"data-yadda" : Symbol("data-yadda", Decl(ignoredJsxAttributes.tsx, 14, 12))
35+
36+
};
37+
38+
let x1 = <Yadda foo="hello" data-yadda={42}/>;
39+
>x1 : Symbol(x1, Decl(ignoredJsxAttributes.tsx, 18, 3))
40+
>Yadda : Symbol(Yadda, Decl(ignoredJsxAttributes.tsx, 9, 1))
41+
>foo : Symbol(foo, Decl(ignoredJsxAttributes.tsx, 18, 15))
42+
>data-yadda : Symbol(data-yadda, Decl(ignoredJsxAttributes.tsx, 18, 27))
43+
44+
let x2 = <Yadda bar="hello" data-yadda={42}/>; // Error
45+
>x2 : Symbol(x2, Decl(ignoredJsxAttributes.tsx, 19, 3))
46+
>Yadda : Symbol(Yadda, Decl(ignoredJsxAttributes.tsx, 9, 1))
47+
>bar : Symbol(bar, Decl(ignoredJsxAttributes.tsx, 19, 15))
48+
>data-yadda : Symbol(data-yadda, Decl(ignoredJsxAttributes.tsx, 19, 27))
49+
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
=== tests/cases/compiler/ignoredJsxAttributes.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
4+
// Repro from #44797
5+
6+
import * as React from "react";
7+
>React : typeof React
8+
9+
interface Props {
10+
foo: string;
11+
>foo : string
12+
13+
[dataProp: string]: string;
14+
>dataProp : string
15+
}
16+
17+
declare function Yadda(props: Props): JSX.Element;
18+
>Yadda : (props: Props) => JSX.Element
19+
>props : Props
20+
>JSX : any
21+
22+
let props: Props = {
23+
>props : Props
24+
>{ foo: "", "data-yadda": 42, // Error} : { foo: string; "data-yadda": number; }
25+
26+
foo: "",
27+
>foo : string
28+
>"" : ""
29+
30+
"data-yadda": 42, // Error
31+
>"data-yadda" : number
32+
>42 : 42
33+
34+
};
35+
36+
let x1 = <Yadda foo="hello" data-yadda={42}/>;
37+
>x1 : JSX.Element
38+
><Yadda foo="hello" data-yadda={42}/> : JSX.Element
39+
>Yadda : (props: Props) => JSX.Element
40+
>foo : string
41+
>data-yadda : number
42+
>42 : 42
43+
44+
let x2 = <Yadda bar="hello" data-yadda={42}/>; // Error
45+
>x2 : JSX.Element
46+
><Yadda bar="hello" data-yadda={42}/> : JSX.Element
47+
>Yadda : (props: Props) => JSX.Element
48+
>bar : string
49+
>data-yadda : number
50+
>42 : 42
51+

tests/baselines/reference/tsxAttributeResolution7.errors.txt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
tests/cases/conformance/jsx/file.tsx(9,8): error TS2322: Type 'number' is not assignable to type 'string'.
1+
tests/cases/conformance/jsx/file.tsx(9,2): error TS2322: Type '{ "data-foo": number; }' is not assignable to type '{ "data-foo"?: string; }'.
2+
Types of property '"data-foo"' are incompatible.
3+
Type 'number' is not assignable to type 'string'.
24

35

46
==== tests/cases/conformance/jsx/file.tsx (1 errors) ====
@@ -11,9 +13,10 @@ tests/cases/conformance/jsx/file.tsx(9,8): error TS2322: Type 'number' is not as
1113

1214
// Error
1315
<test1 data-foo={32} />;
14-
~~~~~~~~
15-
!!! error TS2322: Type 'number' is not assignable to type 'string'.
16-
!!! related TS6500 tests/cases/conformance/jsx/file.tsx:4:12: The expected type comes from property 'data-foo' which is declared here on type '{ "data-foo"?: string; }'
16+
~~~~~
17+
!!! error TS2322: Type '{ "data-foo": number; }' is not assignable to type '{ "data-foo"?: string; }'.
18+
!!! error TS2322: Types of property '"data-foo"' are incompatible.
19+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
1720

1821
// OK
1922
<test1 data-foo={'32'} />;

tests/baselines/reference/tsxStatelessFunctionComponentOverload4.errors.txt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ tests/cases/conformance/jsx/file.tsx(17,13): error TS2769: No overload matches t
3131
Type '{ yy: boolean; yy1: string; }' is not assignable to type '{ yy: number; yy1: string; }'.
3232
Types of property 'yy' are incompatible.
3333
Type 'boolean' is not assignable to type 'number'.
34-
tests/cases/conformance/jsx/file.tsx(25,12): error TS2769: No overload matches this call.
34+
tests/cases/conformance/jsx/file.tsx(25,13): error TS2769: No overload matches this call.
3535
Overload 1 of 2, '(j: { "extra-data": string; }): Element', gave the following error.
36-
Type 'boolean' is not assignable to type 'string'.
36+
Type '{ "extra-data": true; }' is not assignable to type '{ "extra-data": string; }'.
37+
Types of property '"extra-data"' are incompatible.
38+
Type 'boolean' is not assignable to type 'string'.
3739
Overload 2 of 2, '(n: { yy: string; direction?: number; }): Element', gave the following error.
3840
Property 'yy' is missing in type '{ "extra-data": true; }' but required in type '{ yy: string; direction?: number; }'.
3941
tests/cases/conformance/jsx/file.tsx(26,12): error TS2769: No overload matches this call.
@@ -142,13 +144,14 @@ tests/cases/conformance/jsx/file.tsx(36,12): error TS2769: No overload matches t
142144

143145
// Error
144146
const d1 = <TestingOneThing extra-data />
145-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
147+
~~~~~~~~~~~~~~~
146148
!!! error TS2769: No overload matches this call.
147149
!!! error TS2769: Overload 1 of 2, '(j: { "extra-data": string; }): Element', gave the following error.
148-
!!! error TS2769: Type 'boolean' is not assignable to type 'string'.
150+
!!! error TS2769: Type '{ "extra-data": true; }' is not assignable to type '{ "extra-data": string; }'.
151+
!!! error TS2769: Types of property '"extra-data"' are incompatible.
152+
!!! error TS2769: Type 'boolean' is not assignable to type 'string'.
149153
!!! error TS2769: Overload 2 of 2, '(n: { yy: string; direction?: number; }): Element', gave the following error.
150154
!!! error TS2769: Property 'yy' is missing in type '{ "extra-data": true; }' but required in type '{ yy: string; direction?: number; }'.
151-
!!! related TS6500 tests/cases/conformance/jsx/file.tsx:21:38: The expected type comes from property 'extra-data' which is declared here on type 'IntrinsicAttributes & { "extra-data": string; }'
152155
!!! related TS2728 tests/cases/conformance/jsx/file.tsx:22:38: 'yy' is declared here.
153156
const d2 = <TestingOneThing yy="hello" direction="left" />
154157
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tests/baselines/reference/tsxStatelessFunctionComponentOverload5.errors.txt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ tests/cases/conformance/jsx/file.tsx(55,68): error TS2769: No overload matches t
2222
Type 'boolean' is not assignable to type 'string'.
2323
Overload 3 of 3, '(hyphenProps: HyphenProps): Element', gave the following error.
2424
Type 'boolean' is not assignable to type 'string'.
25-
tests/cases/conformance/jsx/file.tsx(56,12): error TS2769: No overload matches this call.
25+
tests/cases/conformance/jsx/file.tsx(56,13): error TS2769: No overload matches this call.
2626
Overload 1 of 3, '(buttonProps: ButtonProps): Element', gave the following error.
2727
Property 'onClick' is missing in type '{ "data-format": true; }' but required in type 'ButtonProps'.
2828
Overload 2 of 3, '(linkProps: LinkProps): Element', gave the following error.
2929
Property 'to' is missing in type '{ "data-format": true; }' but required in type 'LinkProps'.
3030
Overload 3 of 3, '(hyphenProps: HyphenProps): Element', gave the following error.
31-
Type 'boolean' is not assignable to type 'string'.
31+
Type '{ "data-format": true; }' is not assignable to type 'HyphenProps'.
32+
Types of property '"data-format"' are incompatible.
33+
Type 'boolean' is not assignable to type 'string'.
3234

3335

3436
==== tests/cases/conformance/jsx/file.tsx (4 errors) ====
@@ -122,14 +124,15 @@ tests/cases/conformance/jsx/file.tsx(56,12): error TS2769: No overload matches t
122124
!!! related TS6500 tests/cases/conformance/jsx/file.tsx:5:5: The expected type comes from property 'className' which is declared here on type 'IntrinsicAttributes & LinkProps'
123125
!!! related TS6500 tests/cases/conformance/jsx/file.tsx:5:5: The expected type comes from property 'className' which is declared here on type 'IntrinsicAttributes & HyphenProps'
124126
const b8 = <MainButton data-format />; // incorrect type for specified hyphanated name
125-
~~~~~~~~~~~~~~~~~~~~~~~~~~
127+
~~~~~~~~~~
126128
!!! error TS2769: No overload matches this call.
127129
!!! error TS2769: Overload 1 of 3, '(buttonProps: ButtonProps): Element', gave the following error.
128130
!!! error TS2769: Property 'onClick' is missing in type '{ "data-format": true; }' but required in type 'ButtonProps'.
129131
!!! error TS2769: Overload 2 of 3, '(linkProps: LinkProps): Element', gave the following error.
130132
!!! error TS2769: Property 'to' is missing in type '{ "data-format": true; }' but required in type 'LinkProps'.
131133
!!! error TS2769: Overload 3 of 3, '(hyphenProps: HyphenProps): Element', gave the following error.
132-
!!! error TS2769: Type 'boolean' is not assignable to type 'string'.
134+
!!! error TS2769: Type '{ "data-format": true; }' is not assignable to type 'HyphenProps'.
135+
!!! error TS2769: Types of property '"data-format"' are incompatible.
136+
!!! error TS2769: Type 'boolean' is not assignable to type 'string'.
133137
!!! related TS2728 tests/cases/conformance/jsx/file.tsx:9:5: 'onClick' is declared here.
134-
!!! related TS2728 tests/cases/conformance/jsx/file.tsx:13:5: 'to' is declared here.
135-
!!! related TS6500 tests/cases/conformance/jsx/file.tsx:17:5: The expected type comes from property 'data-format' which is declared here on type 'IntrinsicAttributes & HyphenProps'
138+
!!! related TS2728 tests/cases/conformance/jsx/file.tsx:13:5: 'to' is declared here.

tests/baselines/reference/tsxStatelessFunctionComponentsWithTypeArguments2.errors.txt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
tests/cases/conformance/jsx/file.tsx(8,43): error TS2322: Type 'number' is not assignable to type 'string'.
1+
tests/cases/conformance/jsx/file.tsx(8,15): error TS2322: Type 'T & { "ignore-prop": number; }' is not assignable to type 'IntrinsicAttributes & { prop: number; "ignore-prop": string; }'.
2+
Type 'T & { "ignore-prop": number; }' is not assignable to type '{ prop: number; "ignore-prop": string; }'.
3+
Types of property '"ignore-prop"' are incompatible.
4+
Type 'number' is not assignable to type 'string'.
25
tests/cases/conformance/jsx/file.tsx(13,15): error TS2322: Type 'T' is not assignable to type 'IntrinsicAttributes & { prop: unknown; "ignore-prop": string; }'.
36
Type 'T' is not assignable to type '{ prop: unknown; "ignore-prop": string; }'.
47
tests/cases/conformance/jsx/file.tsx(20,19): error TS2322: Type '(a: number, b: string) => void' is not assignable to type '(arg: number) => void'.
@@ -16,9 +19,11 @@ tests/cases/conformance/jsx/file.tsx(31,52): error TS2322: Type '(val: string) =
1619
// Error
1720
function Bar<T extends {prop: number}>(arg: T) {
1821
let a1 = <ComponentSpecific1 {...arg} ignore-prop={10} />;
19-
~~~~~~~~~~~
20-
!!! error TS2322: Type 'number' is not assignable to type 'string'.
21-
!!! related TS6500 tests/cases/conformance/jsx/file.tsx:3:53: The expected type comes from property 'ignore-prop' which is declared here on type 'IntrinsicAttributes & { prop: number; "ignore-prop": string; }'
22+
~~~~~~~~~~~~~~~~~~
23+
!!! error TS2322: Type 'T & { "ignore-prop": number; }' is not assignable to type 'IntrinsicAttributes & { prop: number; "ignore-prop": string; }'.
24+
!!! error TS2322: Type 'T & { "ignore-prop": number; }' is not assignable to type '{ prop: number; "ignore-prop": string; }'.
25+
!!! error TS2322: Types of property '"ignore-prop"' are incompatible.
26+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
2227
}
2328

2429
// Error
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// @strict: true
2+
// @jsx: react
3+
/// <reference path="/.lib/react16.d.ts" />
4+
5+
// Repro from #44797
6+
7+
import * as React from "react";
8+
9+
interface Props {
10+
foo: string;
11+
[dataProp: string]: string;
12+
}
13+
14+
declare function Yadda(props: Props): JSX.Element;
15+
16+
let props: Props = {
17+
foo: "",
18+
"data-yadda": 42, // Error
19+
};
20+
21+
let x1 = <Yadda foo="hello" data-yadda={42}/>;
22+
let x2 = <Yadda bar="hello" data-yadda={42}/>; // Error

0 commit comments

Comments
 (0)