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

Add support for transpiling per-file jsx pragmas #21218

Merged
merged 5 commits into from
Feb 27, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
23 changes: 19 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ namespace ts {
resolveName(name, location, meaning, excludeGlobals) {
return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false, excludeGlobals);
},
getJsxNamespace: () => unescapeLeadingUnderscores(getJsxNamespace()),
getJsxNamespace: n => unescapeLeadingUnderscores(getJsxNamespace(n)),
getAccessibleSymbolChain,
getTypePredicateOfSignature,
resolveExternalModuleSymbol,
Expand Down Expand Up @@ -729,7 +729,22 @@ namespace ts {
}
}

function getJsxNamespace(): __String {
function getJsxNamespace(location: Node | undefined): __String {
if (location) {
const file = getSourceFileOfNode(location);
if (file) {
if (file.localJsxNamespace) {
return file.localJsxNamespace;
}
const jsxPragma = file.pragmas.get("jsx");
if (jsxPragma) {
file.localJsxFactory = parseIsolatedEntityName(jsxPragma, languageVersion);
if (file.localJsxFactory) {
return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText;
}
}
}
}
if (!_jsxNamespace) {
_jsxNamespace = "React" as __String;
if (compilerOptions.jsxFactory) {
Expand Down Expand Up @@ -15200,7 +15215,7 @@ namespace ts {
// 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 reactNamespace = getJsxNamespace(node);
const reactLocation = isNodeOpeningLikeElement ? (<JsxOpeningLikeElement>node).tagName : node;
const reactSym = resolveName(reactLocation, reactNamespace, SymbolFlags.Value, reactRefErr, reactNamespace, /*isUse*/ true);
if (reactSym) {
Expand Down Expand Up @@ -25021,7 +25036,7 @@ namespace ts {
return !!(symbol && getCheckFlags(symbol) & CheckFlags.Late);
},
writeLiteralConstValue,
getJsxFactoryEntity: () => _jsxFactoryEntity
getJsxFactoryEntity: location => location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity
};

// defined here to avoid outer scope pollution
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2337,6 +2337,9 @@ namespace ts {
if (node.resolvedTypeReferenceDirectiveNames !== undefined) updated.resolvedTypeReferenceDirectiveNames = node.resolvedTypeReferenceDirectiveNames;
if (node.imports !== undefined) updated.imports = node.imports;
if (node.moduleAugmentations !== undefined) updated.moduleAugmentations = node.moduleAugmentations;
if (node.pragmas !== undefined) updated.pragmas = node.pragmas;
if (node.localJsxFactory !== undefined) updated.localJsxFactory = node.localJsxFactory;
if (node.localJsxNamespace !== undefined) updated.localJsxNamespace = node.localJsxNamespace;
return updateNode(updated, node);
}

Expand Down
25 changes: 25 additions & 0 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6037,6 +6037,7 @@ namespace ts {
const referencedFiles: FileReference[] = [];
const typeReferenceDirectives: FileReference[] = [];
const amdDependencies: { path: string; name: string }[] = [];
let pragmas: { pragma: string, value: string | undefined }[] = [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we gonna add a new concept, then we should consolidate the other ones under pargma.. and we should allow /// as well as multi-line comments.

I would also have helper functions for getting all of these like amdDependecies, amdModuleNAme, checkJs, etc..

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! I wanted to consolidate them, anyway, just didn't think this PR was appropriate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to ensure this works well when doing incremental parsing as well and need test for the same.

let amdModuleName: string;
let checkJsDirective: CheckJsDirective = undefined;

Expand All @@ -6045,6 +6046,9 @@ namespace ts {
// reference comment.
while (true) {
const kind = triviaScanner.scan();
if (kind === SyntaxKind.MultiLineCommentTrivia) {
pragmas = concatenate(pragmas, extractPragmas(sourceText.substring(triviaScanner.getTokenPos(), triviaScanner.getTextPos())));
}
if (kind !== SyntaxKind.SingleLineCommentTrivia) {
if (isTrivia(kind)) {
continue;
Expand Down Expand Up @@ -6110,6 +6114,8 @@ namespace ts {
pos: range.pos
};
}

pragmas = concatenate(pragmas, extractPragmas(comment));
}
}

Expand All @@ -6118,6 +6124,25 @@ namespace ts {
sourceFile.amdDependencies = amdDependencies;
sourceFile.moduleName = amdModuleName;
sourceFile.checkJsDirective = checkJsDirective;
sourceFile.pragmas = createMap();
for (const p of pragmas) {
if (sourceFile.pragmas.has(p.pragma)) {
// First one in semantics matches babel's behavior for the jsx pragma
// TODO: Considering issuing an error/warning on ignored pragma comments? It feels philosophically incorrect to error on a comment, but...
continue;
}
sourceFile.pragmas.set(p.pragma, p.value);
}
}

function extractPragmas(text: string) {
const pragmas: { pragma: string, value: string | undefined }[] = [];
const pragmaRegEx = /@(\w+)(\W+(\w+)(\W+|$))?/gim;
let matchResult: RegExpMatchArray;
while (matchResult = pragmaRegEx.exec(text)) {
pragmas.push({ pragma: matchResult[1], value: matchResult[3] });
}
return pragmas;
}

function setExternalModuleIndicator(sourceFile: SourceFile) {
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/transformers/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ namespace ts {
}

const element = createExpressionForJsxElement(
context.getEmitResolver().getJsxFactoryEntity(),
context.getEmitResolver().getJsxFactoryEntity(currentSourceFile),
compilerOptions.reactNamespace,
tagName,
objectProperties,
Expand All @@ -140,7 +140,7 @@ namespace ts {

function visitJsxOpeningFragment(node: JsxOpeningFragment, children: ReadonlyArray<JsxChild>, isChild: boolean, location: TextRange) {
const element = createExpressionForJsxFragment(
context.getEmitResolver().getJsxFactoryEntity(),
context.getEmitResolver().getJsxFactoryEntity(currentSourceFile),
compilerOptions.reactNamespace,
mapDefined(children, transformJsxChildToExpression),
node,
Expand Down
7 changes: 5 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2541,6 +2541,9 @@ namespace ts {
/* @internal */ ambientModuleNames: ReadonlyArray<string>;
/* @internal */ checkJsDirective: CheckJsDirective | undefined;
/* @internal */ version: string;
/* @internal */ pragmas: Map<string>;
/* @internal */ localJsxNamespace?: __String;
/* @internal */ localJsxFactory?: EntityName;
}

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

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

export const enum SymbolFlags {
Expand Down
2 changes: 1 addition & 1 deletion src/services/codefixes/importFixes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ namespace ts.codefix {
}
else if (isJsxOpeningLikeElement(symbolToken.parent) && symbolToken.parent.tagName === symbolToken) {
// The error wasn't for the symbolAtLocation, it was for the JSX tag itself, which needs access to e.g. `React`.
symbol = checker.getAliasedSymbol(checker.resolveName(checker.getJsxNamespace(), symbolToken.parent.tagName, SymbolFlags.Value, /*excludeGlobals*/ false));
symbol = checker.getAliasedSymbol(checker.resolveName(checker.getJsxNamespace(symbolToken), symbolToken.parent.tagName, SymbolFlags.Value, /*excludeGlobals*/ false));
symbolName = symbol.name;
}
else {
Expand Down
3 changes: 3 additions & 0 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,9 @@ namespace ts {
public ambientModuleNames: string[];
public checkJsDirective: CheckJsDirective | undefined;
public possiblyContainDynamicImport: boolean;
public pragmas: Map<string>;
public localJsxFactory: EntityName;
public localJsxNamespace: __String;

constructor(kind: SyntaxKind, pos: number, end: number) {
super(kind, pos, end);
Expand Down
63 changes: 63 additions & 0 deletions tests/baselines/reference/inlineJsxFactoryDeclarations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//// [tests/cases/conformance/jsx/inline/inlineJsxFactoryDeclarations.tsx] ////

//// [renderer.d.ts]
declare global {
namespace JSX {
interface IntrinsicElements {
[e: string]: any;
}
}
}
export function dom(): void;
export function otherdom(): void;
export { dom as default };
//// [other.tsx]
/** @jsx h */
import { dom as h } from "./renderer"
export const prerendered = <h></h>;
//// [othernoalias.tsx]
/** @jsx otherdom */
import { otherdom } from "./renderer"
export const prerendered2 = <h></h>;
//// [reacty.tsx]
import React from "./renderer"
export const prerendered3 = <h></h>;

//// [index.tsx]
/** @jsx dom */
import { dom } from "./renderer"
<h></h>
export * from "./other";
export * from "./othernoalias";
export * from "./reacty";


//// [other.js]
"use strict";
exports.__esModule = true;
/** @jsx h */
var renderer_1 = require("./renderer");
exports.prerendered = renderer_1.dom("h", null);
//// [othernoalias.js]
"use strict";
exports.__esModule = true;
/** @jsx otherdom */
var renderer_1 = require("./renderer");
exports.prerendered2 = renderer_1.otherdom("h", null);
//// [reacty.js]
"use strict";
exports.__esModule = true;
var renderer_1 = require("./renderer");
exports.prerendered3 = renderer_1["default"].createElement("h", null);
//// [index.js]
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
exports.__esModule = true;
/** @jsx dom */
var renderer_1 = require("./renderer");
renderer_1.dom("h", null);
__export(require("./other"));
__export(require("./othernoalias"));
__export(require("./reacty"));
68 changes: 68 additions & 0 deletions tests/baselines/reference/inlineJsxFactoryDeclarations.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
=== tests/cases/conformance/jsx/inline/renderer.d.ts ===
declare global {
>global : Symbol(global, Decl(renderer.d.ts, 0, 0))

namespace JSX {
>JSX : Symbol(JSX, Decl(renderer.d.ts, 0, 16))

interface IntrinsicElements {
>IntrinsicElements : Symbol(IntrinsicElements, Decl(renderer.d.ts, 1, 19))

[e: string]: any;
>e : Symbol(e, Decl(renderer.d.ts, 3, 13))
}
}
}
export function dom(): void;
>dom : Symbol(dom, Decl(renderer.d.ts, 6, 1))

export function otherdom(): void;
>otherdom : Symbol(otherdom, Decl(renderer.d.ts, 7, 28))

export { dom as default };
>dom : Symbol(default, Decl(renderer.d.ts, 9, 8))
>default : Symbol(default, Decl(renderer.d.ts, 9, 8))

=== tests/cases/conformance/jsx/inline/other.tsx ===
/** @jsx h */
import { dom as h } from "./renderer"
>dom : Symbol(h, Decl(other.tsx, 1, 8))
>h : Symbol(h, Decl(other.tsx, 1, 8))

export const prerendered = <h></h>;
>prerendered : Symbol(prerendered, Decl(other.tsx, 2, 12))
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))

=== tests/cases/conformance/jsx/inline/othernoalias.tsx ===
/** @jsx otherdom */
import { otherdom } from "./renderer"
>otherdom : Symbol(otherdom, Decl(othernoalias.tsx, 1, 8))

export const prerendered2 = <h></h>;
>prerendered2 : Symbol(prerendered2, Decl(othernoalias.tsx, 2, 12))
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))

=== tests/cases/conformance/jsx/inline/reacty.tsx ===
import React from "./renderer"
>React : Symbol(React, Decl(reacty.tsx, 0, 6))

export const prerendered3 = <h></h>;
>prerendered3 : Symbol(prerendered3, Decl(reacty.tsx, 1, 12))
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))

=== tests/cases/conformance/jsx/inline/index.tsx ===
/** @jsx dom */
import { dom } from "./renderer"
>dom : Symbol(dom, Decl(index.tsx, 1, 8))

<h></h>
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))

export * from "./other";
export * from "./othernoalias";
export * from "./reacty";

72 changes: 72 additions & 0 deletions tests/baselines/reference/inlineJsxFactoryDeclarations.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
=== tests/cases/conformance/jsx/inline/renderer.d.ts ===
declare global {
>global : any

namespace JSX {
>JSX : any

interface IntrinsicElements {
>IntrinsicElements : IntrinsicElements

[e: string]: any;
>e : string
}
}
}
export function dom(): void;
>dom : () => void

export function otherdom(): void;
>otherdom : () => void

export { dom as default };
>dom : () => void
>default : () => void

=== tests/cases/conformance/jsx/inline/other.tsx ===
/** @jsx h */
import { dom as h } from "./renderer"
>dom : () => void
>h : () => void

export const prerendered = <h></h>;
>prerendered : any
><h></h> : any
>h : () => void
>h : () => void

=== tests/cases/conformance/jsx/inline/othernoalias.tsx ===
/** @jsx otherdom */
import { otherdom } from "./renderer"
>otherdom : () => void

export const prerendered2 = <h></h>;
>prerendered2 : any
><h></h> : any
>h : any
>h : any

=== tests/cases/conformance/jsx/inline/reacty.tsx ===
import React from "./renderer"
>React : () => void

export const prerendered3 = <h></h>;
>prerendered3 : any
><h></h> : any
>h : any
>h : any

=== tests/cases/conformance/jsx/inline/index.tsx ===
/** @jsx dom */
import { dom } from "./renderer"
>dom : () => void

<h></h>
><h></h> : any
>h : any
>h : any

export * from "./other";
export * from "./othernoalias";
export * from "./reacty";

Loading