mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-10 06:41:59 -06:00
Merge pull request #8939 from Microsoft/pattern_ambient_modules
Allow wildcard ("*") patterns in ambient module declarations
This commit is contained in:
commit
9ffd00d6ff
@ -1183,9 +1183,9 @@ namespace ts {
|
||||
lastContainer = next;
|
||||
}
|
||||
|
||||
function declareSymbolAndAddToSymbolTable(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): void {
|
||||
function declareSymbolAndAddToSymbolTable(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol {
|
||||
// Just call this directly so that the return type of this function stays "void".
|
||||
declareSymbolAndAddToSymbolTableWorker(node, symbolFlags, symbolExcludes);
|
||||
return declareSymbolAndAddToSymbolTableWorker(node, symbolFlags, symbolExcludes);
|
||||
}
|
||||
|
||||
function declareSymbolAndAddToSymbolTableWorker(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol {
|
||||
@ -1289,7 +1289,22 @@ namespace ts {
|
||||
declareSymbolAndAddToSymbolTable(node, SymbolFlags.NamespaceModule, SymbolFlags.NamespaceModuleExcludes);
|
||||
}
|
||||
else {
|
||||
declareSymbolAndAddToSymbolTable(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes);
|
||||
let pattern: Pattern | undefined;
|
||||
if (node.name.kind === SyntaxKind.StringLiteral) {
|
||||
const text = (<StringLiteral>node.name).text;
|
||||
if (hasZeroOrOneAsteriskCharacter(text)) {
|
||||
pattern = tryParsePattern(text);
|
||||
}
|
||||
else {
|
||||
errorOnFirstToken(node.name, Diagnostics.Pattern_0_can_have_at_most_one_Asterisk_character, text);
|
||||
}
|
||||
}
|
||||
|
||||
const symbol = declareSymbolAndAddToSymbolTable(node, SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes);
|
||||
|
||||
if (pattern) {
|
||||
(file.patternAmbientModules || (file.patternAmbientModules = [])).push({ pattern, symbol });
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -2067,10 +2082,10 @@ namespace ts {
|
||||
checkStrictModeFunctionName(<FunctionDeclaration>node);
|
||||
if (inStrictMode) {
|
||||
checkStrictModeFunctionDeclaration(node);
|
||||
return bindBlockScopedDeclaration(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes);
|
||||
bindBlockScopedDeclaration(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes);
|
||||
}
|
||||
else {
|
||||
return declareSymbolAndAddToSymbolTable(<Declaration>node, SymbolFlags.Function, SymbolFlags.FunctionExcludes);
|
||||
declareSymbolAndAddToSymbolTable(<Declaration>node, SymbolFlags.Function, SymbolFlags.FunctionExcludes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -140,6 +140,12 @@ namespace ts {
|
||||
const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true);
|
||||
|
||||
const globals: SymbolTable = {};
|
||||
/**
|
||||
* List of every ambient module with a "*" wildcard.
|
||||
* Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches.
|
||||
* This is only used if there is no exact match.
|
||||
*/
|
||||
let patternAmbientModules: PatternAmbientModule[];
|
||||
|
||||
let getGlobalESSymbolConstructorSymbol: () => Symbol;
|
||||
|
||||
@ -1285,6 +1291,14 @@ namespace ts {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (patternAmbientModules) {
|
||||
const pattern = findBestPatternMatch(patternAmbientModules, _ => _.pattern, moduleName);
|
||||
if (pattern) {
|
||||
return getMergedSymbol(pattern.symbol);
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleNotFoundError) {
|
||||
// report errors only if it was requested
|
||||
error(moduleReferenceLiteral, moduleNotFoundError, moduleName);
|
||||
@ -17646,6 +17660,10 @@ namespace ts {
|
||||
if (!isExternalOrCommonJsModule(file)) {
|
||||
mergeSymbolTable(globals, file.locals);
|
||||
}
|
||||
if (file.patternAmbientModules && file.patternAmbientModules.length) {
|
||||
patternAmbientModules = concatenate(patternAmbientModules, file.patternAmbientModules);
|
||||
}
|
||||
|
||||
if (file.moduleAugmentations.length) {
|
||||
(augmentations || (augmentations = [])).push(file.moduleAugmentations);
|
||||
}
|
||||
|
||||
@ -95,7 +95,8 @@ namespace ts {
|
||||
return compilerOptions.traceResolution && host.trace !== undefined;
|
||||
}
|
||||
|
||||
function hasZeroOrOneAsteriskCharacter(str: string): boolean {
|
||||
/* @internal */
|
||||
export function hasZeroOrOneAsteriskCharacter(str: string): boolean {
|
||||
let seenAsterisk = false;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (str.charCodeAt(i) === CharacterCodes.asterisk) {
|
||||
@ -496,48 +497,23 @@ namespace ts {
|
||||
trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, state.compilerOptions.baseUrl, moduleName);
|
||||
}
|
||||
|
||||
let longestMatchPrefixLength = -1;
|
||||
let matchedPattern: string;
|
||||
let matchedStar: string;
|
||||
|
||||
// string is for exact match
|
||||
let matchedPattern: Pattern | string | undefined = undefined;
|
||||
if (state.compilerOptions.paths) {
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName);
|
||||
}
|
||||
|
||||
for (const key in state.compilerOptions.paths) {
|
||||
const pattern: string = key;
|
||||
const indexOfStar = pattern.indexOf("*");
|
||||
if (indexOfStar !== -1) {
|
||||
const prefix = pattern.substr(0, indexOfStar);
|
||||
const suffix = pattern.substr(indexOfStar + 1);
|
||||
if (moduleName.length >= prefix.length + suffix.length &&
|
||||
startsWith(moduleName, prefix) &&
|
||||
endsWith(moduleName, suffix)) {
|
||||
|
||||
// use length of prefix as betterness criteria
|
||||
if (prefix.length > longestMatchPrefixLength) {
|
||||
longestMatchPrefixLength = prefix.length;
|
||||
matchedPattern = pattern;
|
||||
matchedStar = moduleName.substr(prefix.length, moduleName.length - suffix.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (pattern === moduleName) {
|
||||
// pattern was matched as is - no need to search further
|
||||
matchedPattern = pattern;
|
||||
matchedStar = undefined;
|
||||
break;
|
||||
}
|
||||
}
|
||||
matchedPattern = matchPatternOrExact(getKeys(state.compilerOptions.paths), moduleName);
|
||||
}
|
||||
|
||||
if (matchedPattern) {
|
||||
const matchedStar = typeof matchedPattern === "string" ? undefined : matchedText(matchedPattern, moduleName);
|
||||
const matchedPatternText = typeof matchedPattern === "string" ? matchedPattern : patternText(matchedPattern);
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPattern);
|
||||
trace(state.host, Diagnostics.Module_name_0_matched_pattern_1, moduleName, matchedPatternText);
|
||||
}
|
||||
for (const subst of state.compilerOptions.paths[matchedPattern]) {
|
||||
const path = matchedStar ? subst.replace("\*", matchedStar) : subst;
|
||||
for (const subst of state.compilerOptions.paths[matchedPatternText]) {
|
||||
const path = matchedStar ? subst.replace("*", matchedStar) : subst;
|
||||
const candidate = normalizePath(combinePaths(state.compilerOptions.baseUrl, path));
|
||||
if (state.traceEnabled) {
|
||||
trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path);
|
||||
@ -560,6 +536,75 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* patternStrings contains both pattern strings (containing "*") and regular strings.
|
||||
* Return an exact match if possible, or a pattern match, or undefined.
|
||||
* (These are verified by verifyCompilerOptions to have 0 or 1 "*" characters.)
|
||||
*/
|
||||
function matchPatternOrExact(patternStrings: string[], candidate: string): string | Pattern | undefined {
|
||||
const patterns: Pattern[] = [];
|
||||
for (const patternString of patternStrings) {
|
||||
const pattern = tryParsePattern(patternString);
|
||||
if (pattern) {
|
||||
patterns.push(pattern);
|
||||
}
|
||||
else if (patternString === candidate) {
|
||||
// pattern was matched as is - no need to search further
|
||||
return patternString;
|
||||
}
|
||||
}
|
||||
|
||||
return findBestPatternMatch(patterns, _ => _, candidate);
|
||||
}
|
||||
|
||||
function patternText({prefix, suffix}: Pattern): string {
|
||||
return `${prefix}*${suffix}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given that candidate matches pattern, returns the text matching the '*'.
|
||||
* E.g.: matchedText(tryParsePattern("foo*baz"), "foobarbaz") === "bar"
|
||||
*/
|
||||
function matchedText(pattern: Pattern, candidate: string): string {
|
||||
Debug.assert(isPatternMatch(pattern, candidate));
|
||||
return candidate.substr(pattern.prefix.length, candidate.length - pattern.suffix.length);
|
||||
}
|
||||
|
||||
/** Return the object corresponding to the best pattern to match `candidate`. */
|
||||
/* @internal */
|
||||
export function findBestPatternMatch<T>(values: T[], getPattern: (value: T) => Pattern, candidate: string): T | undefined {
|
||||
let matchedValue: T | undefined = undefined;
|
||||
// use length of prefix as betterness criteria
|
||||
let longestMatchPrefixLength = -1;
|
||||
|
||||
for (const v of values) {
|
||||
const pattern = getPattern(v);
|
||||
if (isPatternMatch(pattern, candidate) && pattern.prefix.length > longestMatchPrefixLength) {
|
||||
longestMatchPrefixLength = pattern.prefix.length;
|
||||
matchedValue = v;
|
||||
}
|
||||
}
|
||||
|
||||
return matchedValue;
|
||||
}
|
||||
|
||||
function isPatternMatch({prefix, suffix}: Pattern, candidate: string) {
|
||||
return candidate.length >= prefix.length + suffix.length &&
|
||||
startsWith(candidate, prefix) &&
|
||||
endsWith(candidate, suffix);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function tryParsePattern(pattern: string): Pattern | undefined {
|
||||
// This should be verified outside of here and a proper error thrown.
|
||||
Debug.assert(hasZeroOrOneAsteriskCharacter(pattern));
|
||||
const indexOfStar = pattern.indexOf("*");
|
||||
return indexOfStar === -1 ? undefined : {
|
||||
prefix: pattern.substr(0, indexOfStar),
|
||||
suffix: pattern.substr(indexOfStar + 1)
|
||||
};
|
||||
}
|
||||
|
||||
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations {
|
||||
const containingDirectory = getDirectoryPath(containingFile);
|
||||
const supportedExtensions = getSupportedExtensions(compilerOptions);
|
||||
|
||||
@ -1658,6 +1658,7 @@ namespace ts {
|
||||
/* @internal */ resolvedTypeReferenceDirectiveNames: Map<ResolvedTypeReferenceDirective>;
|
||||
/* @internal */ imports: LiteralExpression[];
|
||||
/* @internal */ moduleAugmentations: LiteralExpression[];
|
||||
/* @internal */ patternAmbientModules?: PatternAmbientModule[];
|
||||
}
|
||||
|
||||
export interface ScriptReferenceHost {
|
||||
@ -2135,6 +2136,20 @@ namespace ts {
|
||||
[index: string]: Symbol;
|
||||
}
|
||||
|
||||
/** Represents a "prefix*suffix" pattern. */
|
||||
/* @internal */
|
||||
export interface Pattern {
|
||||
prefix: string;
|
||||
suffix: string;
|
||||
}
|
||||
|
||||
/** Used to track a `declare module "foo*"`-like declaration. */
|
||||
/* @internal */
|
||||
export interface PatternAmbientModule {
|
||||
pattern: Pattern;
|
||||
symbol: Symbol;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export const enum NodeCheckFlags {
|
||||
TypeChecked = 0x00000001, // Node has been type checked
|
||||
|
||||
44
tests/baselines/reference/ambientDeclarationsPatterns.js
Normal file
44
tests/baselines/reference/ambientDeclarationsPatterns.js
Normal file
@ -0,0 +1,44 @@
|
||||
//// [tests/cases/conformance/ambient/ambientDeclarationsPatterns.ts] ////
|
||||
|
||||
//// [declarations.d.ts]
|
||||
declare module "foo*baz" {
|
||||
export function foo(s: string): void;
|
||||
}
|
||||
// Augmentations still work
|
||||
declare module "foo*baz" {
|
||||
export const baz: string;
|
||||
}
|
||||
|
||||
// Longest prefix wins
|
||||
declare module "foos*" {
|
||||
export const foos: string;
|
||||
}
|
||||
|
||||
declare module "*!text" {
|
||||
const x: string;
|
||||
export default x;
|
||||
}
|
||||
|
||||
//// [user.ts]
|
||||
///<reference path="declarations.d.ts" />
|
||||
import {foo, baz} from "foobarbaz";
|
||||
foo(baz);
|
||||
|
||||
import {foos} from "foosball";
|
||||
foo(foos);
|
||||
|
||||
// Works with relative file name
|
||||
import fileText from "./file!text";
|
||||
foo(fileText);
|
||||
|
||||
|
||||
//// [user.js]
|
||||
"use strict";
|
||||
///<reference path="declarations.d.ts" />
|
||||
var foobarbaz_1 = require("foobarbaz");
|
||||
foobarbaz_1.foo(foobarbaz_1.baz);
|
||||
var foosball_1 = require("foosball");
|
||||
foobarbaz_1.foo(foosball_1.foos);
|
||||
// Works with relative file name
|
||||
var file_text_1 = require("./file!text");
|
||||
foobarbaz_1.foo(file_text_1["default"]);
|
||||
@ -0,0 +1,51 @@
|
||||
=== tests/cases/conformance/ambient/user.ts ===
|
||||
///<reference path="declarations.d.ts" />
|
||||
import {foo, baz} from "foobarbaz";
|
||||
>foo : Symbol(foo, Decl(user.ts, 1, 8))
|
||||
>baz : Symbol(baz, Decl(user.ts, 1, 12))
|
||||
|
||||
foo(baz);
|
||||
>foo : Symbol(foo, Decl(user.ts, 1, 8))
|
||||
>baz : Symbol(baz, Decl(user.ts, 1, 12))
|
||||
|
||||
import {foos} from "foosball";
|
||||
>foos : Symbol(foos, Decl(user.ts, 4, 8))
|
||||
|
||||
foo(foos);
|
||||
>foo : Symbol(foo, Decl(user.ts, 1, 8))
|
||||
>foos : Symbol(foos, Decl(user.ts, 4, 8))
|
||||
|
||||
// Works with relative file name
|
||||
import fileText from "./file!text";
|
||||
>fileText : Symbol(fileText, Decl(user.ts, 8, 6))
|
||||
|
||||
foo(fileText);
|
||||
>foo : Symbol(foo, Decl(user.ts, 1, 8))
|
||||
>fileText : Symbol(fileText, Decl(user.ts, 8, 6))
|
||||
|
||||
=== tests/cases/conformance/ambient/declarations.d.ts ===
|
||||
declare module "foo*baz" {
|
||||
export function foo(s: string): void;
|
||||
>foo : Symbol(foo, Decl(declarations.d.ts, 0, 26))
|
||||
>s : Symbol(s, Decl(declarations.d.ts, 1, 24))
|
||||
}
|
||||
// Augmentations still work
|
||||
declare module "foo*baz" {
|
||||
export const baz: string;
|
||||
>baz : Symbol(baz, Decl(declarations.d.ts, 5, 16))
|
||||
}
|
||||
|
||||
// Longest prefix wins
|
||||
declare module "foos*" {
|
||||
export const foos: string;
|
||||
>foos : Symbol(foos, Decl(declarations.d.ts, 10, 16))
|
||||
}
|
||||
|
||||
declare module "*!text" {
|
||||
const x: string;
|
||||
>x : Symbol(x, Decl(declarations.d.ts, 14, 9))
|
||||
|
||||
export default x;
|
||||
>x : Symbol(x, Decl(declarations.d.ts, 14, 9))
|
||||
}
|
||||
|
||||
54
tests/baselines/reference/ambientDeclarationsPatterns.types
Normal file
54
tests/baselines/reference/ambientDeclarationsPatterns.types
Normal file
@ -0,0 +1,54 @@
|
||||
=== tests/cases/conformance/ambient/user.ts ===
|
||||
///<reference path="declarations.d.ts" />
|
||||
import {foo, baz} from "foobarbaz";
|
||||
>foo : (s: string) => void
|
||||
>baz : string
|
||||
|
||||
foo(baz);
|
||||
>foo(baz) : void
|
||||
>foo : (s: string) => void
|
||||
>baz : string
|
||||
|
||||
import {foos} from "foosball";
|
||||
>foos : string
|
||||
|
||||
foo(foos);
|
||||
>foo(foos) : void
|
||||
>foo : (s: string) => void
|
||||
>foos : string
|
||||
|
||||
// Works with relative file name
|
||||
import fileText from "./file!text";
|
||||
>fileText : string
|
||||
|
||||
foo(fileText);
|
||||
>foo(fileText) : void
|
||||
>foo : (s: string) => void
|
||||
>fileText : string
|
||||
|
||||
=== tests/cases/conformance/ambient/declarations.d.ts ===
|
||||
declare module "foo*baz" {
|
||||
export function foo(s: string): void;
|
||||
>foo : (s: string) => void
|
||||
>s : string
|
||||
}
|
||||
// Augmentations still work
|
||||
declare module "foo*baz" {
|
||||
export const baz: string;
|
||||
>baz : string
|
||||
}
|
||||
|
||||
// Longest prefix wins
|
||||
declare module "foos*" {
|
||||
export const foos: string;
|
||||
>foos : string
|
||||
}
|
||||
|
||||
declare module "*!text" {
|
||||
const x: string;
|
||||
>x : string
|
||||
|
||||
export default x;
|
||||
>x : string
|
||||
}
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
tests/cases/conformance/ambient/ambientDeclarationsPatterns_tooManyAsterisks.ts(1,16): error TS5061: Pattern 'too*many*asterisks' can have at most one '*' character
|
||||
|
||||
|
||||
==== tests/cases/conformance/ambient/ambientDeclarationsPatterns_tooManyAsterisks.ts (1 errors) ====
|
||||
declare module "too*many*asterisks" { }
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
!!! error TS5061: Pattern 'too*many*asterisks' can have at most one '*' character
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
//// [ambientDeclarationsPatterns_tooManyAsterisks.ts]
|
||||
declare module "too*many*asterisks" { }
|
||||
|
||||
|
||||
//// [ambientDeclarationsPatterns_tooManyAsterisks.js]
|
||||
@ -0,0 +1,30 @@
|
||||
// @Filename: declarations.d.ts
|
||||
declare module "foo*baz" {
|
||||
export function foo(s: string): void;
|
||||
}
|
||||
// Augmentations still work
|
||||
declare module "foo*baz" {
|
||||
export const baz: string;
|
||||
}
|
||||
|
||||
// Longest prefix wins
|
||||
declare module "foos*" {
|
||||
export const foos: string;
|
||||
}
|
||||
|
||||
declare module "*!text" {
|
||||
const x: string;
|
||||
export default x;
|
||||
}
|
||||
|
||||
// @Filename: user.ts
|
||||
///<reference path="declarations.d.ts" />
|
||||
import {foo, baz} from "foobarbaz";
|
||||
foo(baz);
|
||||
|
||||
import {foos} from "foosball";
|
||||
foo(foos);
|
||||
|
||||
// Works with relative file name
|
||||
import fileText from "./file!text";
|
||||
foo(fileText);
|
||||
@ -0,0 +1 @@
|
||||
declare module "too*many*asterisks" { }
|
||||
Loading…
x
Reference in New Issue
Block a user