Skip to content
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

Porting #19249 and #19968 to release-2.6 #20002

Merged
merged 13 commits into from
Nov 14, 2017
3 changes: 3 additions & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3296,6 +3296,9 @@ namespace ts {
case SyntaxKind.JsxOpeningElement:
case SyntaxKind.JsxText:
case SyntaxKind.JsxClosingElement:
case SyntaxKind.JsxFragment:
case SyntaxKind.JsxOpeningFragment:
case SyntaxKind.JsxClosingFragment:
case SyntaxKind.JsxAttribute:
case SyntaxKind.JsxAttributes:
case SyntaxKind.JsxSpreadAttribute:
Expand Down
73 changes: 53 additions & 20 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13481,7 +13481,13 @@ namespace ts {
// JSX expression can appear in two position : JSX Element's children or JSX attribute
const jsxAttributes = isJsxAttributeLike(node.parent) ?
node.parent.parent :
node.parent.openingElement.attributes; // node.parent is JsxElement
isJsxElement(node.parent) ?
node.parent.openingElement.attributes :
undefined; // node.parent is JsxFragment with no attributes

if (!jsxAttributes) {
return undefined; // don't check children of a fragment
}

// When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type
// which is a type of the parameter of the signature we are trying out.
Expand Down Expand Up @@ -14061,13 +14067,13 @@ namespace ts {
}

function checkJsxSelfClosingElement(node: JsxSelfClosingElement): Type {
checkJsxOpeningLikeElement(node);
checkJsxOpeningLikeElementOrOpeningFragment(node);
return getJsxGlobalElementType() || anyType;
}

function checkJsxElement(node: JsxElement): Type {
// Check attributes
checkJsxOpeningLikeElement(node.openingElement);
checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement);

// Perform resolution on the closing tag so that rename/go to definition/etc work
if (isJsxIntrinsicIdentifier(node.closingElement.tagName)) {
Expand All @@ -14080,6 +14086,16 @@ namespace ts {
return getJsxGlobalElementType() || anyType;
}

function checkJsxFragment(node: JsxFragment): Type {
checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment);

if (compilerOptions.jsx === JsxEmit.React && compilerOptions.jsxFactory) {
error(node, Diagnostics.JSX_fragment_is_not_supported_when_using_jsxFactory);
}

return getJsxGlobalElementType() || anyType;
}

/**
* Returns true iff the JSX element name would be a valid JS identifier, ignoring restrictions about keywords not being identifiers
*/
Expand Down Expand Up @@ -14184,19 +14200,7 @@ namespace ts {
const parent = openingLikeElement.parent.kind === SyntaxKind.JsxElement ? openingLikeElement.parent as JsxElement : undefined;
// We have to check that openingElement of the parent is the one we are visiting as this may not be true for selfClosingElement
if (parent && parent.openingElement === openingLikeElement && parent.children.length > 0) {
const childrenTypes: Type[] = [];
for (const child of (parent as JsxElement).children) {
// In React, JSX text that contains only whitespaces will be ignored so we don't want to type-check that
// because then type of children property will have constituent of string type.
if (child.kind === SyntaxKind.JsxText) {
if (!child.containsOnlyWhiteSpaces) {
childrenTypes.push(stringType);
}
}
else {
childrenTypes.push(checkExpression(child, checkMode));
}
}
const childrenTypes: Type[] = checkJsxChildren(parent as JsxElement, checkMode);

if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") {
// Error if there is a attribute named "children" explicitly specified and children element.
Expand Down Expand Up @@ -14236,6 +14240,23 @@ namespace ts {
}
}

function checkJsxChildren(node: JsxElement | JsxFragment, checkMode?: CheckMode) {
const childrenTypes: Type[] = [];
for (const child of node.children) {
// In React, JSX text that contains only whitespaces will be ignored so we don't want to type-check that
// because then type of children property will have constituent of string type.
if (child.kind === SyntaxKind.JsxText) {
if (!child.containsOnlyWhiteSpaces) {
childrenTypes.push(stringType);
}
}
else {
childrenTypes.push(checkExpression(child, checkMode));
}
}
return childrenTypes;
}

/**
* Check attributes property of opening-like element. This function is called during chooseOverload to get call signature of a JSX opening-like element.
* (See "checkApplicableSignatureForJsxOpeningLikeElement" for how the function is used)
Expand Down Expand Up @@ -14743,14 +14764,19 @@ namespace ts {
}
}

function checkJsxOpeningLikeElement(node: JsxOpeningLikeElement) {
checkGrammarJsxElement(node);
function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) {
const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node);

if (isNodeOpeningLikeElement) {
checkGrammarJsxElement(<JsxOpeningLikeElement>node);
}
checkJsxPreconditions(node);
// 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 = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined;
const reactNamespace = getJsxNamespace();
const reactSym = resolveName(node.tagName, reactNamespace, SymbolFlags.Value, reactRefErr, reactNamespace, /*isUse*/ true);
const reactLocation = isNodeOpeningLikeElement ? (<JsxOpeningLikeElement>node).tagName : node;
const reactSym = resolveName(reactLocation, reactNamespace, SymbolFlags.Value, reactRefErr, reactNamespace, /*isUse*/ true);
if (reactSym) {
// Mark local symbol as referenced here because it might not have been marked
// if jsx emit was not react as there wont be error being emitted
Expand All @@ -14762,7 +14788,12 @@ namespace ts {
}
}

checkJsxAttributesAssignableToTagNameAttributes(node);
if (isNodeOpeningLikeElement) {
checkJsxAttributesAssignableToTagNameAttributes(<JsxOpeningLikeElement>node);
}
else {
checkJsxChildren((node as JsxOpeningFragment).parent);
}
}

/**
Expand Down Expand Up @@ -18526,6 +18557,8 @@ namespace ts {
return checkJsxElement(<JsxElement>node);
case SyntaxKind.JsxSelfClosingElement:
return checkJsxSelfClosingElement(<JsxSelfClosingElement>node);
case SyntaxKind.JsxFragment:
return checkJsxFragment(<JsxFragment>node);
case SyntaxKind.JsxAttributes:
return checkJsxAttributes(<JsxAttributes>node, checkMode);
case SyntaxKind.JsxOpeningElement:
Expand Down
12 changes: 12 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3603,6 +3603,18 @@
"category": "Error",
"code": 17013
},
"JSX fragment has no corresponding closing tag.": {
"category": "Error",
"code": 17014
},
"Expected corresponding closing tag for JSX fragment.": {
"category": "Error",
"code": 17015
},
"JSX fragment is not supported when using --jsxFactory": {
"category": "Error",
"code":17016
},

"Circularity detected while resolving configuration: {0}": {
"category": "Error",
Expand Down
46 changes: 28 additions & 18 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -699,9 +699,11 @@ namespace ts {
case SyntaxKind.JsxText:
return emitJsxText(<JsxText>node);
case SyntaxKind.JsxOpeningElement:
return emitJsxOpeningElement(<JsxOpeningElement>node);
case SyntaxKind.JsxOpeningFragment:
return emitJsxOpeningElementOrFragment(<JsxOpeningElement>node);
case SyntaxKind.JsxClosingElement:
return emitJsxClosingElement(<JsxClosingElement>node);
case SyntaxKind.JsxClosingFragment:
return emitJsxClosingElementOrFragment(<JsxClosingElement>node);
case SyntaxKind.JsxAttribute:
return emitJsxAttribute(<JsxAttribute>node);
case SyntaxKind.JsxAttributes:
Expand Down Expand Up @@ -836,6 +838,8 @@ namespace ts {
return emitJsxElement(<JsxElement>node);
case SyntaxKind.JsxSelfClosingElement:
return emitJsxSelfClosingElement(<JsxSelfClosingElement>node);
case SyntaxKind.JsxFragment:
return emitJsxFragment(<JsxFragment>node);

// Transformation nodes
case SyntaxKind.PartiallyEmittedExpression:
Expand Down Expand Up @@ -2060,7 +2064,7 @@ namespace ts {

function emitJsxElement(node: JsxElement) {
emit(node.openingElement);
emitList(node, node.children, ListFormat.JsxElementChildren);
emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren);
emit(node.closingElement);
}

Expand All @@ -2075,24 +2079,36 @@ namespace ts {
write("/>");
}

function emitJsxOpeningElement(node: JsxOpeningElement) {
function emitJsxFragment(node: JsxFragment) {
emit(node.openingFragment);
emitList(node, node.children, ListFormat.JsxElementOrFragmentChildren);
emit(node.closingFragment);
}

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

if (isJsxOpeningElement(node)) {
emitJsxTagName(node.tagName);
// We are checking here so we won't re-enter the emitting pipeline and emit extra sourcemap
if (node.attributes.properties && node.attributes.properties.length > 0) {
write(" ");
emit(node.attributes);
}
}

write(">");
}

function emitJsxText(node: JsxText) {
writer.writeLiteral(getTextOfNode(node, /*includeTrivia*/ true));
}

function emitJsxClosingElement(node: JsxClosingElement) {
function emitJsxClosingElementOrFragment(node: JsxClosingElement | JsxClosingFragment) {
write("</");
emitJsxTagName(node.tagName);
if (isJsxClosingElement(node)) {
emitJsxTagName(node.tagName);
}
write(">");
}

Expand Down Expand Up @@ -2611,12 +2627,6 @@ namespace ts {
writer.decreaseIndent();
}

function writeIfAny(nodes: NodeArray<Node>, text: string) {
if (some(nodes)) {
write(text);
}
}

function writeToken(token: SyntaxKind, pos: number, contextNode?: Node) {
return onEmitSourceMapOfToken
? onEmitSourceMapOfToken(contextNode, token, pos, writeTokenText)
Expand Down Expand Up @@ -3176,7 +3186,7 @@ namespace ts {
EnumMembers = CommaDelimited | Indented | MultiLine,
CaseBlockClauses = Indented | MultiLine,
NamedImportsOrExportsElements = CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | SingleLine | SpaceBetweenBraces,
JsxElementChildren = SingleLine | NoInterveningComments,
JsxElementOrFragmentChildren = SingleLine | NoInterveningComments,
JsxElementAttributes = SingleLine | SpaceBetweenSiblings | NoInterveningComments,
CaseOrDefaultClauseStatements = Indented | MultiLine | NoTrailingNewLine | OptionalIfEmpty,
HeritageClauseTypes = CommaDelimited | SpaceBetweenSiblings | SingleLine,
Expand Down
53 changes: 50 additions & 3 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2115,6 +2115,22 @@ namespace ts {
: node;
}

export function createJsxFragment(openingFragment: JsxOpeningFragment, children: ReadonlyArray<JsxChild>, closingFragment: JsxClosingFragment) {
const node = <JsxFragment>createSynthesizedNode(SyntaxKind.JsxFragment);
node.openingFragment = openingFragment;
node.children = createNodeArray(children);
node.closingFragment = closingFragment;
return node;
}

export function updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: ReadonlyArray<JsxChild>, closingFragment: JsxClosingFragment) {
return node.openingFragment !== openingFragment
|| node.children !== children
|| node.closingFragment !== closingFragment
? updateNode(createJsxFragment(openingFragment, children, closingFragment), node)
: node;
}

export function createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression) {
const node = <JsxAttribute>createSynthesizedNode(SyntaxKind.JsxAttribute);
node.name = name;
Expand Down Expand Up @@ -2951,7 +2967,7 @@ namespace ts {
);
}

function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement) {
function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment) {
// To ensure the emit resolver can properly resolve the namespace, we need to
// treat this identifier as if it were a source tree node by clearing the `Synthesized`
// flag and setting a parent node.
Expand All @@ -2963,7 +2979,7 @@ namespace ts {
return react;
}

function createJsxFactoryExpressionFromEntityName(jsxFactory: EntityName, parent: JsxOpeningLikeElement): Expression {
function createJsxFactoryExpressionFromEntityName(jsxFactory: EntityName, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression {
if (isQualifiedName(jsxFactory)) {
const left = createJsxFactoryExpressionFromEntityName(jsxFactory.left, parent);
const right = createIdentifier(idText(jsxFactory.right));
Expand All @@ -2975,7 +2991,7 @@ namespace ts {
}
}

function createJsxFactoryExpression(jsxFactoryEntity: EntityName, reactNamespace: string, parent: JsxOpeningLikeElement): Expression {
function createJsxFactoryExpression(jsxFactoryEntity: EntityName, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression {
return jsxFactoryEntity ?
createJsxFactoryExpressionFromEntityName(jsxFactoryEntity, parent) :
createPropertyAccess(
Expand Down Expand Up @@ -3016,6 +3032,37 @@ namespace ts {
);
}

export function createExpressionForJsxFragment(jsxFactoryEntity: EntityName, reactNamespace: string, children: Expression[], parentElement: JsxOpeningFragment, location: TextRange): LeftHandSideExpression {
const tagName = createPropertyAccess(
createReactNamespace(reactNamespace, parentElement),
"Fragment"
);

const argumentsList = [<Expression>tagName];
argumentsList.push(createNull());

if (children && children.length > 0) {
if (children.length > 1) {
for (const child of children) {
child.startsOnNewLine = true;
argumentsList.push(child);
}
}
else {
argumentsList.push(children[0]);
}
}

return setTextRange(
createCall(
createJsxFactoryExpression(jsxFactoryEntity, reactNamespace, parentElement),
/*typeArguments*/ undefined,
argumentsList
),
location
);
}

// Helpers

export function getHelperName(name: string) {
Expand Down
Loading