Skip to content

[WIP] Clean up version of using overload for JSX stateless function component #12107

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

Closed
wants to merge 25 commits into from
Closed
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7d02df8
Parse JSX attributes as its own unique AST node and bind the node
Nov 8, 2016
2eeabeb
Use chooseOverload logic to pick JSX stateless function
Nov 8, 2016
e0a5d63
Update emitter to use JSXAttributes node instead of JSXAttribute node…
Nov 8, 2016
e5c9409
Add language service support for JSXAttributes
Nov 8, 2016
fdc6d74
Update existed conformance tests
Nov 8, 2016
b71f142
Add tests for using spread attributes resolution in JSX stateful comp…
Nov 8, 2016
4dfe8e8
Add tests for using default attributes in JSX stateful component
Nov 8, 2016
4f221d9
Add tests for overload stateless function component
Nov 8, 2016
3bd435f
Add tests for generic stateless function component
Nov 8, 2016
d73d2b8
Add a test for contextual type in JSXAttributes
Nov 8, 2016
c86290c
Update react.d.ts used during tests
Nov 8, 2016
e7ae8c0
Cache react.d.ts library when running tests
Nov 8, 2016
bb4227f
Update conformance tests baselines
Nov 8, 2016
03a57b2
Add language service tests
Nov 8, 2016
d3aaf30
Fix linting error
Nov 8, 2016
13ca860
Address comment: call getContextualType instead of accessing contextu…
Nov 14, 2016
7a95f39
Address comment: stop widen type when checking for spread any;
Nov 14, 2016
eff392e
Refactor getJsxAttributeSymbolsFromJsxOpeningLikeElement to createJsx…
Nov 15, 2016
83992ea
Address comment: fix spelling mistakes
Nov 15, 2016
b9482f8
Address comment: calling `checkApplicableSignatureForJsxOpeningLikeEl…
Nov 15, 2016
075a8ce
Address comment: fix spelling, rename function to be more consistent
Nov 15, 2016
c1c1e07
Address comment: minor fix indentation, fix function name isObjectLit…
Nov 15, 2016
2a62f49
Address PR: gotoDefinition return the last signature when there is an…
Nov 16, 2016
46b8f32
Address PR: convert Foreach to for...of
Nov 16, 2016
6e91aee
Address comment: fix type, inline code, clarify name of variables
Dec 6, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
@@ -1299,6 +1299,7 @@ namespace ts {
case SyntaxKind.TypeLiteral:
case SyntaxKind.JSDocTypeLiteral:
case SyntaxKind.JSDocRecordType:
case SyntaxKind.JsxAttributes:
return ContainerFlags.IsContainer;

case SyntaxKind.InterfaceDeclaration:
@@ -1404,6 +1405,7 @@ namespace ts {
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.JSDocRecordType:
case SyntaxKind.JSDocTypeLiteral:
case SyntaxKind.JsxAttributes:
// Interface/Object-types always have their children added to the 'members' of
// their container. They are only accessible through an instance of their
// container, and are never in scope otherwise (even inside the body of the
@@ -1587,6 +1589,14 @@ namespace ts {
return bindAnonymousDeclaration(node, SymbolFlags.ObjectLiteral, "__object");
}

function bindJsxAttributes(node: JsxAttributes) {
return bindAnonymousDeclaration(node, SymbolFlags.ObjectLiteral, "__jsxAttributes");
}

function bindJsxAttribute(node: JsxAttribute, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) {
return declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes);
}

function bindAnonymousDeclaration(node: Declaration, symbolFlags: SymbolFlags, name: string) {
const symbol = createSymbol(symbolFlags, name);
addDeclarationToSymbol(symbol, node, symbolFlags);
@@ -1996,6 +2006,12 @@ namespace ts {
case SyntaxKind.ModuleDeclaration:
return bindModuleDeclaration(<ModuleDeclaration>node);

// Jsx-attributes
case SyntaxKind.JsxAttributes:
return bindJsxAttributes(<JsxAttributes>node);
case SyntaxKind.JsxAttribute:
return bindJsxAttribute(<JsxAttribute>node, SymbolFlags.Property, SymbolFlags.PropertyExcludes);

// Imports and exports
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.NamespaceImport:
@@ -3052,6 +3068,7 @@ namespace ts {
case SyntaxKind.JsxText:
case SyntaxKind.JsxClosingElement:
case SyntaxKind.JsxAttribute:
case SyntaxKind.JsxAttributes:
case SyntaxKind.JsxSpreadAttribute:
case SyntaxKind.JsxExpression:
// These nodes are Jsx syntax.
673 changes: 492 additions & 181 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

18 changes: 15 additions & 3 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
@@ -712,6 +712,8 @@ const _super = (function (geti, seti) {
return emitJsxClosingElement(<JsxClosingElement>node);
case SyntaxKind.JsxAttribute:
return emitJsxAttribute(<JsxAttribute>node);
case SyntaxKind.JsxAttributes:
return emitJsxAttributes(<JsxAttributes>node);
case SyntaxKind.JsxSpreadAttribute:
return emitJsxSpreadAttribute(<JsxSpreadAttribute>node);
case SyntaxKind.JsxExpression:
@@ -1969,15 +1971,21 @@ const _super = (function (geti, seti) {
write("<");
emitJsxTagName(node.tagName);
write(" ");
emitList(node, node.attributes, ListFormat.JsxElementAttributes);
// We are checking here so we won't re-enter the emiting pipeline and emit extra sourcemap
if (node.attributes.properties && node.attributes.properties.length > 0) {
emit(node.attributes);
}
write("/>");
}

function emitJsxOpeningElement(node: JsxOpeningElement) {
write("<");
emitJsxTagName(node.tagName);
writeIfAny(node.attributes, " ");
emitList(node, node.attributes, ListFormat.JsxElementAttributes);
writeIfAny(node.attributes.properties, " ");
// We are checking here so we won't re-enter the emiting pipeline and emit extra sourcemap
if (node.attributes.properties && node.attributes.properties.length > 0) {
emit(node.attributes);
}
write(">");
}

@@ -1991,6 +1999,10 @@ const _super = (function (geti, seti) {
write(">");
}

function emitJsxAttributes(node: JsxAttributes) {
emitList(node, node.properties, ListFormat.JsxElementAttributes);
}

function emitJsxAttribute(node: JsxAttribute) {
emit(node.name);
emitWithPrefix("=", node.initializer);
26 changes: 20 additions & 6 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
@@ -1237,28 +1237,28 @@ namespace ts {
return node;
}

export function createJsxSelfClosingElement(tagName: JsxTagNameExpression, attributes: JsxAttributeLike[], location?: TextRange) {
export function createJsxSelfClosingElement(tagName: JsxTagNameExpression, attributes: JsxAttributes, location?: TextRange) {
const node = <JsxSelfClosingElement>createNode(SyntaxKind.JsxSelfClosingElement, location);
node.tagName = tagName;
node.attributes = createNodeArray(attributes);
node.attributes = attributes;
return node;
}

export function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, attributes: JsxAttributeLike[]) {
export function updateJsxSelfClosingElement(node: JsxSelfClosingElement, tagName: JsxTagNameExpression, attributes: JsxAttributes) {
if (node.tagName !== tagName || node.attributes !== attributes) {
return updateNode(createJsxSelfClosingElement(tagName, attributes, node), node);
}
return node;
}

export function createJsxOpeningElement(tagName: JsxTagNameExpression, attributes: JsxAttributeLike[], location?: TextRange) {
export function createJsxOpeningElement(tagName: JsxTagNameExpression, attributes: JsxAttributes, location?: TextRange) {
const node = <JsxOpeningElement>createNode(SyntaxKind.JsxOpeningElement, location);
node.tagName = tagName;
node.attributes = createNodeArray(attributes);
node.attributes = attributes;
return node;
}

export function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, attributes: JsxAttributeLike[]) {
export function updateJsxOpeningElement(node: JsxOpeningElement, tagName: JsxTagNameExpression, attributes: JsxAttributes) {
if (node.tagName !== tagName || node.attributes !== attributes) {
return updateNode(createJsxOpeningElement(tagName, attributes, node), node);
}
@@ -1278,6 +1278,20 @@ namespace ts {
return node;
}

export function createJsxAttributes(properties: JsxAttributeLike[], location?: TextRange) {
const jsxAttributes = <JsxAttributes>createNode(SyntaxKind.JsxAttributes, location);
setEmitFlags(jsxAttributes, EmitFlags.NoSourceMap);
jsxAttributes.properties = createNodeArray(properties);
return jsxAttributes;
}

export function updateJsxAttributes(jsxAttributes: JsxAttributes, properties: JsxAttributeLike[]) {
if (jsxAttributes.properties !== properties) {
return updateNode(createJsxAttributes(properties, jsxAttributes), jsxAttributes);
}
return jsxAttributes;
}

export function createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression, location?: TextRange) {
const node = <JsxAttribute>createNode(SyntaxKind.JsxAttribute, location);
node.name = name;
12 changes: 10 additions & 2 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
@@ -361,7 +361,9 @@ namespace ts {
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxOpeningElement:
return visitNode(cbNode, (<JsxOpeningLikeElement>node).tagName) ||
visitNodes(cbNodes, (<JsxOpeningLikeElement>node).attributes);
visitNode(cbNode, (<JsxOpeningLikeElement>node).attributes);
case SyntaxKind.JsxAttributes:
return visitNodes(cbNodes, (<JsxAttributes>node).properties);
case SyntaxKind.JsxAttribute:
return visitNode(cbNode, (<JsxAttribute>node).name) ||
visitNode(cbNode, (<JsxAttribute>node).initializer);
@@ -3795,14 +3797,20 @@ namespace ts {
return result;
}

function parseJsxAttributes(): JsxAttributes {
const jsxAttributes = <JsxAttributes>createNode(SyntaxKind.JsxAttributes);
jsxAttributes.properties = parseList(ParsingContext.JsxAttributes, parseJsxAttribute);
return finishNode(jsxAttributes);
}

function parseJsxOpeningOrSelfClosingElement(inExpressionContext: boolean): JsxOpeningElement | JsxSelfClosingElement {
const fullStart = scanner.getStartPos();

parseExpected(SyntaxKind.LessThanToken);

const tagName = parseJsxElementName();
const attributes = parseJsxAttributes();

const attributes = parseList(ParsingContext.JsxAttributes, parseJsxAttribute);
let node: JsxOpeningLikeElement;

if (token() === SyntaxKind.GreaterThanToken) {
2 changes: 1 addition & 1 deletion src/compiler/transformers/jsx.ts
Original file line number Diff line number Diff line change
@@ -86,7 +86,7 @@ namespace ts {
function visitJsxOpeningLikeElement(node: JsxOpeningLikeElement, children: JsxChild[], isChild: boolean, location: TextRange) {
const tagName = getTagName(node);
let objectProperties: Expression;
const attrs = node.attributes;
const attrs = node.attributes.properties;
if (attrs.length === 0) {
// When there are no attributes, React wants "null"
objectProperties = createNull();
34 changes: 21 additions & 13 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
@@ -308,6 +308,7 @@ namespace ts {
JsxOpeningElement,
JsxClosingElement,
JsxAttribute,
JsxAttributes,
JsxSpreadAttribute,
JsxExpression,

@@ -704,6 +705,7 @@ namespace ts {
// SyntaxKind.BindingElement
// SyntaxKind.Property
// SyntaxKind.PropertyAssignment
// SyntaxKind.JsxAttribute
// SyntaxKind.ShorthandPropertyAssignment
// SyntaxKind.EnumMember
// SyntaxKind.JSDocPropertyTag
@@ -1372,7 +1374,7 @@ namespace ts {
template: TemplateLiteral;
}

export type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator;
export type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement;

export interface AsExpression extends Expression {
kind: SyntaxKind.AsExpression;
@@ -1401,35 +1403,38 @@ namespace ts {
closingElement: JsxClosingElement;
}

/// Either the opening tag in a <Tag>...</Tag> pair, or the lone <Tag /> in a self-closing form
export type JsxOpeningLikeElement = JsxSelfClosingElement | JsxOpeningElement;

export type JsxAttributeLike = JsxAttribute | JsxSpreadAttribute;

export type JsxTagNameExpression = PrimaryExpression | PropertyAccessExpression;

export interface JsxAttributes extends ObjectLiteralExpressionBase<JsxAttributeLike> {
}

/// The opening element of a <Tag>...</Tag> JsxElement
export interface JsxOpeningElement extends Expression {
kind: SyntaxKind.JsxOpeningElement;
tagName: JsxTagNameExpression;
attributes: NodeArray<JsxAttribute | JsxSpreadAttribute>;
attributes: JsxAttributes;
}

/// A JSX expression of the form <TagName attrs />
export interface JsxSelfClosingElement extends PrimaryExpression {
kind: SyntaxKind.JsxSelfClosingElement;
tagName: JsxTagNameExpression;
attributes: NodeArray<JsxAttribute | JsxSpreadAttribute>;
attributes: JsxAttributes;
}

/// Either the opening tag in a <Tag>...</Tag> pair, or the lone <Tag /> in a self-closing form
export type JsxOpeningLikeElement = JsxSelfClosingElement | JsxOpeningElement;

export type JsxAttributeLike = JsxAttribute | JsxSpreadAttribute;

export interface JsxAttribute extends Node {
export interface JsxAttribute extends ObjectLiteralElement {
kind: SyntaxKind.JsxAttribute;
name: Identifier;
/// JSX attribute initializers are optional; <X y /> is sugar for <X y={true} />
initializer?: StringLiteral | JsxExpression;
}

export interface JsxSpreadAttribute extends Node {
export interface JsxSpreadAttribute extends ObjectLiteralElement {
kind: SyntaxKind.JsxSpreadAttribute;
expression: Expression;
}
@@ -2297,7 +2302,7 @@ namespace ts {
getAliasedSymbol(symbol: Symbol): Symbol;
getExportsOfModule(moduleSymbol: Symbol): Symbol[];

getJsxElementAttributesType(elementNode: JsxOpeningLikeElement): Type;
getAllAttributesTypeFromJsxOpeningLikeElement(elementNode: JsxOpeningLikeElement): Type;
getJsxIntrinsicTagNames(): Symbol[];
isOptionalParameter(node: ParameterDeclaration): boolean;
getAmbientModules(): Symbol[];
@@ -2590,6 +2595,7 @@ namespace ts {
/* @internal */ isReferenced?: boolean; // True if the symbol is referenced elsewhere
/* @internal */ isReplaceableByMethod?: boolean; // Can this Javascript class property be replaced by a method symbol?
/* @internal */ isAssigned?: boolean; // True if the symbol is a parameter with assignments
/* @internal */ syntheticKind?: SyntheticSymbolKind; // Synthetic symbols are either spread or union/intersection
}

/* @internal */
@@ -2669,7 +2675,7 @@ namespace ts {
isVisible?: boolean; // Is this node visible
hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context
jsxFlags?: JsxFlags; // flags for knowing what kind of element/attributes we're dealing with
resolvedJsxType?: Type; // resolved element attributes type of a JSX openinglike element
resolvedJsxElementAttributesType?: Type; // resolved element attributes type of a JSX openinglike element
hasSuperCall?: boolean; // recorded result when we try to find super-call. We only try to find one if this flag is undefined, indicating that we haven't made an attempt.
superCall?: ExpressionStatement; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing
switchTypes?: Type[]; // Cached array of switch case expression types
@@ -2704,6 +2710,8 @@ namespace ts {
ContainsObjectLiteral = 1 << 22, // Type is or contains object literal type
/* @internal */
ContainsAnyFunctionType = 1 << 23, // Type is or contains object literal type
/* @internal */
JsxAttributes = 1 << 24, // Jsx attributes type

/* @internal */
Nullable = Undefined | Null,
@@ -2866,7 +2874,7 @@ namespace ts {

/* @internal */
// Object literals are initially marked fresh. Freshness disappears following an assignment,
// before a type assertion, or when when an object literal's type is widened. The regular
// before a type assertion, or when an object literal's type is widened. The regular
// version of a fresh type is identical except for the TypeFlags.FreshObjectLiteral flag.
export interface FreshObjectLiteralType extends ResolvedType {
regularType: ResolvedType; // Regular version of fresh type
31 changes: 31 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
@@ -1073,6 +1073,8 @@ namespace ts {

export function isCallLikeExpression(node: Node): node is CallLikeExpression {
switch (node.kind) {
case SyntaxKind.JsxOpeningElement:
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.CallExpression:
case SyntaxKind.NewExpression:
case SyntaxKind.TaggedTemplateExpression:
@@ -1087,6 +1089,9 @@ namespace ts {
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
return (<TaggedTemplateExpression>node).tag;
}
else if (isJsxOpeningLikeElement(node)) {
return node.tagName;
}

// Will either be a CallExpression, NewExpression, or Decorator.
return (<CallExpression | Decorator>node).expression;
@@ -4132,6 +4137,7 @@ namespace ts {
|| kind === SyntaxKind.ImportEqualsDeclaration
|| kind === SyntaxKind.ImportSpecifier
|| kind === SyntaxKind.InterfaceDeclaration
|| kind === SyntaxKind.JsxAttribute
|| kind === SyntaxKind.MethodDeclaration
|| kind === SyntaxKind.MethodSignature
|| kind === SyntaxKind.ModuleDeclaration
@@ -4244,6 +4250,11 @@ namespace ts {
|| kind === SyntaxKind.JsxText;
}

export function isJsxAttributes(node: Node): node is JsxAttributes {
const kind = node.kind;
return kind === SyntaxKind.JsxAttributes;
}

export function isJsxAttributeLike(node: Node): node is JsxAttributeLike {
const kind = node.kind;
return kind === SyntaxKind.JsxAttribute
@@ -4258,6 +4269,10 @@ namespace ts {
return node.kind === SyntaxKind.JsxAttribute;
}

export function isJsxOpeningLikeElement(node: Node): node is JsxOpeningLikeElement {
return node.kind === SyntaxKind.JsxOpeningElement || node.kind === SyntaxKind.JsxSelfClosingElement;
}

export function isStringLiteralOrJsxExpression(node: Node): node is StringLiteral | JsxExpression {
const kind = node.kind;
return kind === SyntaxKind.StringLiteral
@@ -4608,4 +4623,20 @@ namespace ts {

return flags;
}

export function getTextOfPropertyName(name: PropertyName): string {
switch (name.kind) {
case SyntaxKind.Identifier:
return (<Identifier>name).text;
case SyntaxKind.StringLiteral:
case SyntaxKind.NumericLiteral:
return (<LiteralExpression>name).text;
case SyntaxKind.ComputedPropertyName:
if (isStringOrNumericLiteral((<ComputedPropertyName>name).expression.kind)) {
return (<LiteralExpression>(<ComputedPropertyName>name).expression).text;
}
}

return undefined;
}
}
Loading