Add support for transpiling per-file jsx pragmas (#21218)

* Add support for per-file jsx pragmas

* Add error for using jsx factory pragma with fragments

* More tests, use different regex class for pragma capture

* Unify all pragma parsing machinery
This commit is contained in:
Wesley Wigham
2018-02-26 16:10:00 -08:00
committed by GitHub
parent 0b1e21794d
commit 32c63a2628
23 changed files with 975 additions and 159 deletions
+24 -6
View File
@@ -299,7 +299,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,
@@ -765,7 +765,23 @@ 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) {
const chosenpragma = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma;
file.localJsxFactory = parseIsolatedEntityName(chosenpragma.arguments.factory, languageVersion);
if (file.localJsxFactory) {
return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText;
}
}
}
}
if (!_jsxNamespace) {
_jsxNamespace = "React" as __String;
if (compilerOptions.jsxFactory) {
@@ -15082,8 +15098,10 @@ namespace ts {
function checkJsxFragment(node: JsxFragment, checkMode: CheckMode): Type {
checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment, checkMode);
if (compilerOptions.jsx === JsxEmit.React && compilerOptions.jsxFactory) {
error(node, Diagnostics.JSX_fragment_is_not_supported_when_using_jsxFactory);
if (compilerOptions.jsx === JsxEmit.React && (compilerOptions.jsxFactory || getSourceFileOfNode(node).pragmas.has("jsx"))) {
error(node, compilerOptions.jsxFactory
? Diagnostics.JSX_fragment_is_not_supported_when_using_jsxFactory
: Diagnostics.JSX_fragment_is_not_supported_when_using_an_inline_JSX_factory_pragma);
}
return getJsxGlobalElementType() || anyType;
@@ -15709,7 +15727,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) {
@@ -25556,7 +25574,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
+4
View File
@@ -3788,6 +3788,10 @@
"category": "Error",
"code": 17016
},
"JSX fragment is not supported when using an inline JSX factory pragma": {
"category": "Error",
"code": 17017
},
"Circularity detected while resolving configuration: {0}": {
"category": "Error",
+3
View File
@@ -2375,6 +2375,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);
}
+228 -89
View File
@@ -769,7 +769,9 @@ namespace ts {
// Prime the scanner.
nextToken();
processReferenceComments(sourceFile);
// A member of ReadonlyArray<T> isn't assignable to a member of T[] (and prevents a direct cast) - but this is where we set up those members so they can be readonly in the future
processCommentPragmas(sourceFile as {} as PragmaContext, sourceText);
processPragmasIntoFields(sourceFile as {} as PragmaContext, reportPragmaDiagnostic);
sourceFile.statements = parseList(ParsingContext.SourceElements, parseStatement);
Debug.assert(token() === SyntaxKind.EndOfFileToken);
@@ -787,6 +789,10 @@ namespace ts {
}
return sourceFile;
function reportPragmaDiagnostic(pos: number, end: number, diagnostic: DiagnosticMessage) {
parseDiagnostics.push(createFileDiagnostic(sourceFile, pos, end, diagnostic));
}
}
function addJSDocComment<T extends HasJSDoc>(node: T): T {
@@ -6084,94 +6090,6 @@ namespace ts {
return finishNode(node);
}
function processReferenceComments(sourceFile: SourceFile): void {
const triviaScanner = createScanner(sourceFile.languageVersion, /*skipTrivia*/ false, LanguageVariant.Standard, sourceText);
const referencedFiles: FileReference[] = [];
const typeReferenceDirectives: FileReference[] = [];
const amdDependencies: { path: string; name: string }[] = [];
let amdModuleName: string;
let checkJsDirective: CheckJsDirective = undefined;
// Keep scanning all the leading trivia in the file until we get to something that
// isn't trivia. Any single line comment will be analyzed to see if it is a
// reference comment.
while (true) {
const kind = triviaScanner.scan();
if (kind !== SyntaxKind.SingleLineCommentTrivia) {
if (isTrivia(kind)) {
continue;
}
else {
break;
}
}
const range = {
kind: <SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia>triviaScanner.getToken(),
pos: triviaScanner.getTokenPos(),
end: triviaScanner.getTextPos(),
};
const comment = sourceText.substring(range.pos, range.end);
const referencePathMatchResult = getFileReferenceFromReferencePath(comment, range);
if (referencePathMatchResult) {
const fileReference = referencePathMatchResult.fileReference;
sourceFile.hasNoDefaultLib = referencePathMatchResult.isNoDefaultLib;
const diagnosticMessage = referencePathMatchResult.diagnosticMessage;
if (fileReference) {
if (referencePathMatchResult.isTypeReferenceDirective) {
typeReferenceDirectives.push(fileReference);
}
else {
referencedFiles.push(fileReference);
}
}
if (diagnosticMessage) {
parseDiagnostics.push(createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, diagnosticMessage));
}
}
else {
const amdModuleNameRegEx = /^\/\/\/\s*<amd-module\s+name\s*=\s*('|")(.+?)\1/gim;
const amdModuleNameMatchResult = amdModuleNameRegEx.exec(comment);
if (amdModuleNameMatchResult) {
if (amdModuleName) {
parseDiagnostics.push(createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, Diagnostics.An_AMD_module_cannot_have_multiple_name_assignments));
}
amdModuleName = amdModuleNameMatchResult[2];
}
const amdDependencyRegEx = /^\/\/\/\s*<amd-dependency\s/gim;
const pathRegex = /\spath\s*=\s*('|")(.+?)\1/gim;
const nameRegex = /\sname\s*=\s*('|")(.+?)\1/gim;
const amdDependencyMatchResult = amdDependencyRegEx.exec(comment);
if (amdDependencyMatchResult) {
const pathMatchResult = pathRegex.exec(comment);
const nameMatchResult = nameRegex.exec(comment);
if (pathMatchResult) {
const amdDependency = { path: pathMatchResult[2], name: nameMatchResult ? nameMatchResult[2] : undefined };
amdDependencies.push(amdDependency);
}
}
const checkJsDirectiveRegEx = /^\/\/\/?\s*(@ts-check|@ts-nocheck)\s*$/gim;
const checkJsDirectiveMatchResult = checkJsDirectiveRegEx.exec(comment);
if (checkJsDirectiveMatchResult) {
checkJsDirective = {
enabled: equateStringsCaseInsensitive(checkJsDirectiveMatchResult[1], "@ts-check"),
end: range.end,
pos: range.pos
};
}
}
}
sourceFile.referencedFiles = referencedFiles;
sourceFile.typeReferenceDirectives = typeReferenceDirectives;
sourceFile.amdDependencies = amdDependencies;
sourceFile.moduleName = amdModuleName;
sourceFile.checkJsDirective = checkJsDirective;
}
function setExternalModuleIndicator(sourceFile: SourceFile) {
sourceFile.externalModuleIndicator = forEach(sourceFile.statements, node =>
hasModifier(node, ModifierFlags.Export)
@@ -7552,4 +7470,225 @@ namespace ts {
function isDeclarationFileName(fileName: string): boolean {
return fileExtensionIs(fileName, Extension.Dts);
}
/*@internal*/
export interface PragmaContext {
languageVersion: ScriptTarget;
pragmas?: PragmaMap;
checkJsDirective?: CheckJsDirective;
referencedFiles: FileReference[];
typeReferenceDirectives: FileReference[];
amdDependencies: AmdDependency[];
hasNoDefaultLib?: boolean;
moduleName?: string;
}
/*@internal*/
export function processCommentPragmas(context: PragmaContext, sourceText: string): void {
const triviaScanner = createScanner(context.languageVersion, /*skipTrivia*/ false, LanguageVariant.Standard, sourceText);
const pragmas: PragmaPsuedoMapEntry[] = [];
// Keep scanning all the leading trivia in the file until we get to something that
// isn't trivia. Any single line comment will be analyzed to see if it is a
// reference comment.
while (true) {
const kind = triviaScanner.scan();
if (!isTrivia(kind)) {
break;
}
const range = {
kind: <SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia>triviaScanner.getToken(),
pos: triviaScanner.getTokenPos(),
end: triviaScanner.getTextPos(),
};
const comment = sourceText.substring(range.pos, range.end);
extractPragmas(pragmas, range, comment);
}
context.pragmas = createMap() as PragmaMap;
for (const pragma of pragmas) {
if (context.pragmas.has(pragma.name)) {
const currentValue = context.pragmas.get(pragma.name);
if (currentValue instanceof Array) {
currentValue.push(pragma.args);
}
else {
context.pragmas.set(pragma.name, [currentValue, pragma.args]);
}
continue;
}
context.pragmas.set(pragma.name, pragma.args);
}
}
/*@internal*/
type PragmaDiagnosticReporter = (pos: number, length: number, message: DiagnosticMessage) => void;
/*@internal*/
export function processPragmasIntoFields(context: PragmaContext, reportDiagnostic: PragmaDiagnosticReporter): void {
context.checkJsDirective = undefined;
context.referencedFiles = [];
context.typeReferenceDirectives = [];
context.amdDependencies = [];
context.hasNoDefaultLib = false;
context.pragmas.forEach((entryOrList, key) => {
// TODO: The below should be strongly type-guarded and not need casts/explicit annotations, since entryOrList is related to
// key and key is constrained to a union; but it's not (see GH#21483 for at least partial fix) :(
switch (key) {
case "reference": {
const referencedFiles = context.referencedFiles;
const typeReferenceDirectives = context.typeReferenceDirectives;
forEach(toArray(entryOrList), (arg: PragmaPsuedoMap["reference"]) => {
if (arg.arguments["no-default-lib"]) {
context.hasNoDefaultLib = true;
}
else if (arg.arguments.types) {
typeReferenceDirectives.push({ pos: arg.arguments.types.pos, end: arg.arguments.types.end, fileName: arg.arguments.types.value });
}
else if (arg.arguments.path) {
referencedFiles.push({ pos: arg.arguments.path.pos, end: arg.arguments.path.end, fileName: arg.arguments.path.value });
}
else {
reportDiagnostic(arg.range.pos, arg.range.end - arg.range.pos, Diagnostics.Invalid_reference_directive_syntax);
}
});
break;
}
case "amd-dependency": {
context.amdDependencies = map(
toArray(entryOrList),
({ arguments: { name, path } }: PragmaPsuedoMap["amd-dependency"]) => ({ name, path })
);
break;
}
case "amd-module": {
if (entryOrList instanceof Array) {
for (const entry of entryOrList) {
if (context.moduleName) {
// TODO: It's probably fine to issue this diagnostic on all instances of the pragma
reportDiagnostic(entry.range.pos, entry.range.end - entry.range.pos, Diagnostics.An_AMD_module_cannot_have_multiple_name_assignments);
}
context.moduleName = (entry as PragmaPsuedoMap["amd-module"]).arguments.name;
}
}
else {
context.moduleName = (entryOrList as PragmaPsuedoMap["amd-module"]).arguments.name;
}
break;
}
case "ts-nocheck":
case "ts-check": {
// _last_ of either nocheck or check in a file is the "winner"
forEach(toArray(entryOrList), entry => {
if (!context.checkJsDirective || entry.range.pos > context.checkJsDirective.pos) {
context.checkJsDirective = {
enabled: key === "ts-check",
end: entry.range.end,
pos: entry.range.pos
};
}
});
break;
}
case "jsx": return; // Accessed directly
default: Debug.fail("Unhandled pragma kind"); // Can this be made into an assertNever in the future?
}
});
}
const namedArgRegExCache = createMap<RegExp>();
function getNamedArgRegEx(name: string) {
if (namedArgRegExCache.has(name)) {
return namedArgRegExCache.get(name);
}
const result = new RegExp(`(\\s${name}\\s*=\\s*)('|")(.+?)\\2`, "im");
namedArgRegExCache.set(name, result);
return result;
}
const tripleSlashXMLCommentStartRegEx = /^\/\/\/\s*<(\S+)\s.*?\/>/im;
const singleLinePragmaRegEx = /^\/\/\/?\s*@(\S+)\s*(.*)\s*$/im;
function extractPragmas(pragmas: PragmaPsuedoMapEntry[], range: CommentRange, text: string) {
const tripleSlash = tripleSlashXMLCommentStartRegEx.exec(text);
if (tripleSlash) {
const name = tripleSlash[1].toLowerCase() as keyof PragmaPsuedoMap; // Technically unsafe cast, but we do it so the below check to make it safe typechecks
const pragma = commentPragmas[name] as PragmaDefinition;
if (!pragma || !(pragma.kind & PragmaKindFlags.TripleSlashXML)) {
return;
}
if (pragma.args) {
const argument: {[index: string]: string | {value: string, pos: number, end: number}} = {};
for (const arg of pragma.args) {
const matcher = getNamedArgRegEx(arg.name);
const matchResult = matcher.exec(text);
if (!matchResult && !arg.optional) {
return; // Missing required argument, don't parse
}
else if (matchResult) {
if (arg.captureSpan) {
const startPos = range.pos + matchResult.index + matchResult[1].length + matchResult[2].length;
argument[arg.name] = {
value: matchResult[3],
pos: startPos,
end: startPos + matchResult[3].length
};
}
else {
argument[arg.name] = matchResult[3];
}
}
}
pragmas.push({ name, args: { arguments: argument, range } } as PragmaPsuedoMapEntry);
}
else {
pragmas.push({ name, args: { arguments: {}, range } } as PragmaPsuedoMapEntry);
}
return;
}
const singleLine = singleLinePragmaRegEx.exec(text);
if (singleLine) {
return addPragmaForMatch(pragmas, range, PragmaKindFlags.SingleLine, singleLine);
}
const multiLinePragmaRegEx = /\s*@(\S+)\s*(.*)\s*$/gim; // Defined inline since it uses the "g" flag, which keeps a persistent index (for iterating)
let multiLineMatch: RegExpExecArray;
while (multiLineMatch = multiLinePragmaRegEx.exec(text)) {
addPragmaForMatch(pragmas, range, PragmaKindFlags.MultiLine, multiLineMatch);
}
}
function addPragmaForMatch(pragmas: PragmaPsuedoMapEntry[], range: CommentRange, kind: PragmaKindFlags, match: RegExpExecArray) {
if (!match) return;
const name = match[1].toLowerCase() as keyof PragmaPsuedoMap; // Technically unsafe cast, but we do it so they below check to make it safe typechecks
const pragma = commentPragmas[name] as PragmaDefinition;
if (!pragma || !(pragma.kind & kind)) {
return;
}
const args = match[2]; // Split on spaces and match up positionally with definition
const argument = getNamedPragmaArguments(pragma, args);
if (argument === "fail") return; // Missing required argument, fail to parse it
pragmas.push({ name, args: { arguments: argument, range } } as PragmaPsuedoMapEntry);
return;
}
function getNamedPragmaArguments(pragma: PragmaDefinition, text: string | undefined): {[index: string]: string} | "fail" {
if (!text) return {};
if (!pragma.args) return {};
const args = text.split(/\s+/);
const argMap: {[index: string]: string} = {};
for (let i = 0; i < pragma.args.length; i++) {
const argument = pragma.args[i];
if (!args[i] && !argument.optional) {
return "fail";
}
if (argument.captureSpan) {
return Debug.fail("Capture spans not yet implemented for non-xml pragmas");
}
argMap[argument.name] = args[i];
}
return argMap;
}
}
+2 -2
View File
@@ -122,7 +122,7 @@ namespace ts {
}
const element = createExpressionForJsxElement(
context.getEmitResolver().getJsxFactoryEntity(),
context.getEmitResolver().getJsxFactoryEntity(currentSourceFile),
compilerOptions.reactNamespace,
tagName,
objectProperties,
@@ -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,
+128 -2
View File
@@ -2568,6 +2568,9 @@ namespace ts {
/* @internal */ ambientModuleNames: ReadonlyArray<string>;
/* @internal */ checkJsDirective: CheckJsDirective | undefined;
/* @internal */ version: string;
/* @internal */ pragmas: PragmaMap;
/* @internal */ localJsxNamespace?: __String;
/* @internal */ localJsxFactory?: EntityName;
}
export interface Bundle extends Node {
@@ -2934,7 +2937,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:
@@ -3208,7 +3211,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 {
@@ -5127,4 +5130,127 @@ namespace ts {
Parameters = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis,
IndexSignatureParameters = CommaDelimited | SpaceBetweenSiblings | SingleLine | Indented | SquareBrackets,
}
/* @internal */
export const enum PragmaKindFlags {
None = 0,
/**
* Triple slash comment of the form
* /// <pragma-name argname="value" />
*/
TripleSlashXML = 1 << 0,
/**
* Single line comment of the form
* // @pragma-name argval1 argval2
* or
* /// @pragma-name argval1 argval2
*/
SingleLine = 1 << 1,
/**
* Multiline non-jsdoc pragma of the form
* /* @pragma-name argval1 argval2 * /
*/
MultiLine = 1 << 2,
All = TripleSlashXML | SingleLine | MultiLine,
Default = All,
}
/* @internal */
interface PragmaArgumentSpecification<TName extends string> {
name: TName; // Determines the name of the key in the resulting parsed type, type parameter to cause literal type inference
optional?: boolean;
captureSpan?: boolean;
}
/* @internal */
export interface PragmaDefinition<T1 extends string = string, T2 extends string = string, T3 extends string = string> {
args?: [PragmaArgumentSpecification<T1>] | [PragmaArgumentSpecification<T1>, PragmaArgumentSpecification<T2>] | [PragmaArgumentSpecification<T1>, PragmaArgumentSpecification<T2>, PragmaArgumentSpecification<T3>];
// If not present, defaults to PragmaKindFlags.Default
kind?: PragmaKindFlags;
}
/**
* This function only exists to cause exact types to be inferred for all the literals within `commentPragmas`
*/
/* @internal */
function _contextuallyTypePragmas<T extends {[name: string]: PragmaDefinition<K1, K2, K3>}, K1 extends string, K2 extends string, K3 extends string>(args: T): T {
return args;
}
// While not strictly a type, this is here because `PragmaMap` needs to be here to be used with `SourceFile`, and we don't
// fancy effectively defining it twice, once in value-space and once in type-space
/* @internal */
export const commentPragmas = _contextuallyTypePragmas({
"reference": {
args: [
{ name: "types", optional: true, captureSpan: true },
{ name: "path", optional: true, captureSpan: true },
{ name: "no-default-lib", optional: true }
],
kind: PragmaKindFlags.TripleSlashXML
},
"amd-dependency": {
args: [{ name: "path" }, { name: "name", optional: true }],
kind: PragmaKindFlags.TripleSlashXML
},
"amd-module": {
args: [{ name: "name" }],
kind: PragmaKindFlags.TripleSlashXML
},
"ts-check": {
kind: PragmaKindFlags.SingleLine
},
"ts-nocheck": {
kind: PragmaKindFlags.SingleLine
},
"jsx": {
args: [{ name: "factory" }],
kind: PragmaKindFlags.MultiLine
},
});
/* @internal */
type PragmaArgTypeMaybeCapture<TDesc> = TDesc extends {captureSpan: true} ? {value: string, pos: number, end: number} : string;
/* @internal */
type PragmaArgTypeOptional<TDesc, TName extends string> =
TDesc extends {optional: true}
? {[K in TName]?: PragmaArgTypeMaybeCapture<TDesc>}
: {[K in TName]: PragmaArgTypeMaybeCapture<TDesc>};
/**
* Maps a pragma definition into the desired shape for its arguments object
* Maybe the below is a good argument for types being iterable on struture in some way.
*/
/* @internal */
type PragmaArgumentType<T extends PragmaDefinition> =
T extends { args: [PragmaArgumentSpecification<infer TName1>, PragmaArgumentSpecification<infer TName2>, PragmaArgumentSpecification<infer TName3>] }
? PragmaArgTypeOptional<T["args"][0], TName1> & PragmaArgTypeOptional<T["args"][1], TName2> & PragmaArgTypeOptional<T["args"][2], TName3>
: T extends { args: [PragmaArgumentSpecification<infer TName1>, PragmaArgumentSpecification<infer TName2>] }
? PragmaArgTypeOptional<T["args"][0], TName1> & PragmaArgTypeOptional<T["args"][1], TName2>
: T extends { args: [PragmaArgumentSpecification<infer TName>] }
? PragmaArgTypeOptional<T["args"][0], TName>
: object;
// The above fallback to `object` when there's no args to allow `{}` (as intended), but not the number 2, for example
// TODO: Swap to `undefined` for a cleaner API once strictNullChecks is enabled
type ConcretePragmaSpecs = typeof commentPragmas;
/* @internal */
export type PragmaPsuedoMap = {[K in keyof ConcretePragmaSpecs]?: {arguments: PragmaArgumentType<ConcretePragmaSpecs[K]>, range: CommentRange}};
/* @internal */
export type PragmaPsuedoMapEntry = {[K in keyof PragmaPsuedoMap]: {name: K, args: PragmaPsuedoMap[K]}}[keyof PragmaPsuedoMap];
/**
* A strongly-typed es6 map of pragma entries, the values of which are either a single argument
* value (if only one was found), or an array of multiple argument values if the pragma is present
* in multiple places
*/
/* @internal */
export interface PragmaMap extends Map<PragmaPsuedoMap[keyof PragmaPsuedoMap] | PragmaPsuedoMap[keyof PragmaPsuedoMap][]> {
set<TKey extends keyof PragmaPsuedoMap>(key: TKey, value: PragmaPsuedoMap[TKey] | PragmaPsuedoMap[TKey][]): this;
get<TKey extends keyof PragmaPsuedoMap>(key: TKey): PragmaPsuedoMap[TKey] | PragmaPsuedoMap[TKey][];
forEach(action: <TKey extends keyof PragmaPsuedoMap>(value: PragmaPsuedoMap[TKey] | PragmaPsuedoMap[TKey][], key: TKey) => void): void;
}
}
-34
View File
@@ -1989,40 +1989,6 @@ namespace ts {
return undefined;
}
export function getFileReferenceFromReferencePath(comment: string, commentRange: CommentRange): ReferencePathMatchResult {
const simpleReferenceRegEx = /^\/\/\/\s*<reference\s+/gim;
const isNoDefaultLibRegEx = new RegExp(defaultLibReferenceRegEx.source, "gim");
if (simpleReferenceRegEx.test(comment)) {
if (isNoDefaultLibRegEx.test(comment)) {
return { isNoDefaultLib: true };
}
else {
const refMatchResult = fullTripleSlashReferencePathRegEx.exec(comment);
const refLibResult = !refMatchResult && fullTripleSlashReferenceTypeReferenceDirectiveRegEx.exec(comment);
const match = refMatchResult || refLibResult;
if (match) {
const pos = commentRange.pos + match[1].length + match[2].length;
return {
fileReference: {
pos,
end: pos + match[3].length,
fileName: match[3]
},
isNoDefaultLib: false,
isTypeReferenceDirective: !!refLibResult
};
}
return {
diagnosticMessage: Diagnostics.Invalid_reference_directive_syntax,
isNoDefaultLib: false
};
}
}
return undefined;
}
export function isKeyword(token: SyntaxKind): boolean {
return SyntaxKind.FirstKeyword <= token && token <= SyntaxKind.LastKeyword;
}
+1 -1
View File
@@ -663,7 +663,7 @@ namespace ts.codefix {
const parent = token.parent;
const isNodeOpeningLikeElement = isJsxOpeningLikeElement(parent);
if ((isJsxOpeningLikeElement && (<JsxOpeningLikeElement>parent).tagName === token) || parent.kind === SyntaxKind.JsxOpeningFragment) {
umdSymbol = checker.resolveName(checker.getJsxNamespace(),
umdSymbol = checker.resolveName(checker.getJsxNamespace(parent),
isNodeOpeningLikeElement ? (<JsxOpeningLikeElement>parent).tagName : parent, SymbolFlags.Value, /*excludeGlobals*/ false);
}
}
+14 -25
View File
@@ -1,10 +1,17 @@
namespace ts {
export function preProcessFile(sourceText: string, readImportFiles = true, detectJavaScriptImports = false): PreProcessedFileInfo {
const referencedFiles: FileReference[] = [];
const typeReferenceDirectives: FileReference[] = [];
const pragmaContext: PragmaContext = {
languageVersion: ScriptTarget.ES5, // controls weather the token scanner considers unicode identifiers or not - shouldn't matter, since we're only using it for trivia
pragmas: undefined,
checkJsDirective: undefined,
referencedFiles: [],
typeReferenceDirectives: [],
amdDependencies: [],
hasNoDefaultLib: undefined,
moduleName: undefined
};
const importedFiles: FileReference[] = [];
let ambientExternalModules: { ref: FileReference, depth: number }[];
let isNoDefaultLib = false;
let braceNesting = 0;
// assume that text represent an external module if it contains at least one top level import/export
// ambient modules that are found inside external modules are interpreted as module augmentations
@@ -21,25 +28,6 @@ namespace ts {
return token;
}
function processTripleSlashDirectives(): void {
const commentRanges = getLeadingCommentRanges(sourceText, 0);
forEach(commentRanges, commentRange => {
const comment = sourceText.substring(commentRange.pos, commentRange.end);
const referencePathMatchResult = getFileReferenceFromReferencePath(comment, commentRange);
if (referencePathMatchResult) {
isNoDefaultLib = referencePathMatchResult.isNoDefaultLib;
const fileReference = referencePathMatchResult.fileReference;
if (fileReference) {
const collection = referencePathMatchResult.isTypeReferenceDirective
? typeReferenceDirectives
: referencedFiles;
collection.push(fileReference);
}
}
});
}
function getFileReference() {
const fileName = scanner.getTokenValue();
const pos = scanner.getTokenPos();
@@ -328,7 +316,8 @@ namespace ts {
if (readImportFiles) {
processImports();
}
processTripleSlashDirectives();
processCommentPragmas(pragmaContext, sourceText);
processPragmasIntoFields(pragmaContext, noop);
if (externalModule) {
// for external modules module all nested ambient modules are augmentations
if (ambientExternalModules) {
@@ -337,7 +326,7 @@ namespace ts {
importedFiles.push(decl.ref);
}
}
return { referencedFiles, typeReferenceDirectives, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules: undefined };
return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, importedFiles, isLibFile: pragmaContext.hasNoDefaultLib, ambientExternalModules: undefined };
}
else {
// for global scripts ambient modules still can have augmentations - look for ambient modules with depth > 0
@@ -355,7 +344,7 @@ namespace ts {
}
}
}
return { referencedFiles, typeReferenceDirectives, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules: ambientModuleNames };
return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, importedFiles, isLibFile: pragmaContext.hasNoDefaultLib, ambientExternalModules: ambientModuleNames };
}
}
}
+3
View File
@@ -651,6 +651,9 @@ namespace ts {
public ambientModuleNames: string[];
public checkJsDirective: CheckJsDirective | undefined;
public possiblyContainDynamicImport: boolean;
public pragmas: PragmaMap;
public localJsxFactory: EntityName;
public localJsxNamespace: __String;
constructor(kind: SyntaxKind, pos: number, end: number) {
super(kind, pos, end);
@@ -0,0 +1,74 @@
//// [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 function createElement(): void;
export { dom as default };
//// [otherreacty.tsx]
/** @jsx React.createElement */
import * as React from "./renderer";
<h></h>
//// [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";
//// [otherreacty.js]
"use strict";
exports.__esModule = true;
/** @jsx React.createElement */
var React = require("./renderer");
React.createElement("h", null);
//// [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"));
@@ -0,0 +1,80 @@
=== 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 function createElement(): void;
>createElement : Symbol(createElement, Decl(renderer.d.ts, 8, 33))
export { dom as default };
>dom : Symbol(default, Decl(renderer.d.ts, 10, 8))
>default : Symbol(default, Decl(renderer.d.ts, 10, 8))
=== tests/cases/conformance/jsx/inline/otherreacty.tsx ===
/** @jsx React.createElement */
import * as React from "./renderer";
>React : Symbol(React, Decl(otherreacty.tsx, 1, 6))
<h></h>
>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/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";
@@ -0,0 +1,85 @@
=== 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 function createElement(): void;
>createElement : () => void
export { dom as default };
>dom : () => void
>default : () => void
=== tests/cases/conformance/jsx/inline/otherreacty.tsx ===
/** @jsx React.createElement */
import * as React from "./renderer";
>React : typeof React
<h></h>
><h></h> : any
>h : any
>h : any
=== 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";
@@ -0,0 +1,32 @@
//// [tests/cases/conformance/jsx/inline/inlineJsxFactoryOverridesCompilerOption.tsx] ////
//// [renderer.d.ts]
declare global {
namespace JSX {
interface IntrinsicElements {
[e: string]: any;
}
}
}
export function dom(): void;
export { dom as p };
//// [reacty.tsx]
/** @jsx dom */
import {dom} from "./renderer";
<h></h>
//// [index.tsx]
import { p } from "./renderer";
<h></h>
//// [reacty.js]
"use strict";
exports.__esModule = true;
/** @jsx dom */
var renderer_1 = require("./renderer");
renderer_1.dom("h", null);
//// [index.js]
"use strict";
exports.__esModule = true;
var renderer_1 = require("./renderer");
renderer_1.p("h", null);
@@ -0,0 +1,39 @@
=== 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 { dom as p };
>dom : Symbol(p, Decl(renderer.d.ts, 8, 8))
>p : Symbol(p, Decl(renderer.d.ts, 8, 8))
=== tests/cases/conformance/jsx/inline/reacty.tsx ===
/** @jsx dom */
import {dom} from "./renderer";
>dom : Symbol(dom, Decl(reacty.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))
=== tests/cases/conformance/jsx/inline/index.tsx ===
import { p } from "./renderer";
>p : Symbol(p, Decl(index.tsx, 0, 8))
<h></h>
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))
>h : Symbol(JSX.IntrinsicElements, Decl(renderer.d.ts, 1, 19))
@@ -0,0 +1,41 @@
=== 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 { dom as p };
>dom : () => void
>p : () => void
=== tests/cases/conformance/jsx/inline/reacty.tsx ===
/** @jsx dom */
import {dom} from "./renderer";
>dom : () => void
<h></h>
><h></h> : any
>h : any
>h : any
=== tests/cases/conformance/jsx/inline/index.tsx ===
import { p } from "./renderer";
>p : () => void
<h></h>
><h></h> : any
>h : any
>h : any
@@ -0,0 +1,26 @@
tests/cases/conformance/jsx/inline/index.tsx(3,1): error TS17017: JSX fragment is not supported when using an inline JSX factory pragma
tests/cases/conformance/jsx/inline/reacty.tsx(3,1): error TS17017: JSX fragment is not supported when using an inline JSX factory pragma
==== tests/cases/conformance/jsx/inline/renderer.d.ts (0 errors) ====
declare global {
namespace JSX {
interface IntrinsicElements {
[e: string]: any;
}
}
}
export function dom(): void;
export function createElement(): void;
==== tests/cases/conformance/jsx/inline/reacty.tsx (1 errors) ====
/** @jsx React.createElement */
import * as React from "./renderer";
<><h></h></>
~~~~~~~~~~~~
!!! error TS17017: JSX fragment is not supported when using an inline JSX factory pragma
==== tests/cases/conformance/jsx/inline/index.tsx (1 errors) ====
/** @jsx dom */
import { dom } from "./renderer";
<><h></h></>
~~~~~~~~~~~~
!!! error TS17017: JSX fragment is not supported when using an inline JSX factory pragma
@@ -0,0 +1,35 @@
//// [tests/cases/conformance/jsx/inline/inlineJsxFactoryWithFragmentIsError.tsx] ////
//// [renderer.d.ts]
declare global {
namespace JSX {
interface IntrinsicElements {
[e: string]: any;
}
}
}
export function dom(): void;
export function createElement(): void;
//// [reacty.tsx]
/** @jsx React.createElement */
import * as React from "./renderer";
<><h></h></>
//// [index.tsx]
/** @jsx dom */
import { dom } from "./renderer";
<><h></h></>
//// [reacty.js]
"use strict";
exports.__esModule = true;
/** @jsx React.createElement */
var React = require("./renderer");
React.createElement(React.Fragment, null,
React.createElement("h", null));
//// [index.js]
"use strict";
exports.__esModule = true;
/** @jsx dom */
var renderer_1 = require("./renderer");
renderer_1.dom(React.Fragment, null,
renderer_1.dom("h", null));
@@ -0,0 +1,39 @@
=== 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 createElement(): void;
>createElement : Symbol(createElement, Decl(renderer.d.ts, 7, 28))
=== tests/cases/conformance/jsx/inline/reacty.tsx ===
/** @jsx React.createElement */
import * as React from "./renderer";
>React : Symbol(React, Decl(reacty.tsx, 1, 6))
<><h></h></>
>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))
@@ -0,0 +1,43 @@
=== 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 createElement(): void;
>createElement : () => void
=== tests/cases/conformance/jsx/inline/reacty.tsx ===
/** @jsx React.createElement */
import * as React from "./renderer";
>React : typeof React
<><h></h></>
><><h></h></> : 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></h> : any
>h : any
>h : any
@@ -0,0 +1,36 @@
// @jsx: react
// @filename: renderer.d.ts
declare global {
namespace JSX {
interface IntrinsicElements {
[e: string]: any;
}
}
}
export function dom(): void;
export function otherdom(): void;
export function createElement(): void;
export { dom as default };
// @filename: otherreacty.tsx
/** @jsx React.createElement */
import * as React from "./renderer";
<h></h>
// @filename: other.tsx
/** @jsx h */
import { dom as h } from "./renderer"
export const prerendered = <h></h>;
// @filename: othernoalias.tsx
/** @jsx otherdom */
import { otherdom } from "./renderer"
export const prerendered2 = <h></h>;
// @filename: reacty.tsx
import React from "./renderer"
export const prerendered3 = <h></h>;
// @filename: index.tsx
/** @jsx dom */
import { dom } from "./renderer"
<h></h>
export * from "./other";
export * from "./othernoalias";
export * from "./reacty";
@@ -0,0 +1,19 @@
// @jsx: react
// @jsxFactory: p
// @filename: renderer.d.ts
declare global {
namespace JSX {
interface IntrinsicElements {
[e: string]: any;
}
}
}
export function dom(): void;
export { dom as p };
// @filename: reacty.tsx
/** @jsx dom */
import {dom} from "./renderer";
<h></h>
// @filename: index.tsx
import { p } from "./renderer";
<h></h>
@@ -0,0 +1,19 @@
// @jsx: react
// @filename: renderer.d.ts
declare global {
namespace JSX {
interface IntrinsicElements {
[e: string]: any;
}
}
}
export function dom(): void;
export function createElement(): void;
// @filename: reacty.tsx
/** @jsx React.createElement */
import * as React from "./renderer";
<><h></h></>
// @filename: index.tsx
/** @jsx dom */
import { dom } from "./renderer";
<><h></h></>