mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-30 01:04:49 -05:00
Merge pull request #7507 from Microsoft/preprocessFindAugmentations
find module augmentations in preprocessor
This commit is contained in:
@@ -2147,8 +2147,23 @@ namespace ts {
|
||||
export function preProcessFile(sourceText: string, readImportFiles = true, detectJavaScriptImports = false): PreProcessedFileInfo {
|
||||
const referencedFiles: FileReference[] = [];
|
||||
const importedFiles: FileReference[] = [];
|
||||
let ambientExternalModules: string[];
|
||||
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
|
||||
let externalModule = false;
|
||||
|
||||
function nextToken() {
|
||||
const token = scanner.scan();
|
||||
if (token === SyntaxKind.OpenBraceToken) {
|
||||
braceNesting++;
|
||||
}
|
||||
else if (token === SyntaxKind.CloseBraceToken) {
|
||||
braceNesting--;
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
function processTripleSlashDirectives(): void {
|
||||
const commentRanges = getLeadingCommentRanges(sourceText, 0);
|
||||
@@ -2165,21 +2180,33 @@ namespace ts {
|
||||
});
|
||||
}
|
||||
|
||||
function getFileReference() {
|
||||
const file = scanner.getTokenValue();
|
||||
const pos = scanner.getTokenPos();
|
||||
return {
|
||||
fileName: file,
|
||||
pos: pos,
|
||||
end: pos + file.length
|
||||
};
|
||||
}
|
||||
|
||||
function recordAmbientExternalModule(): void {
|
||||
if (!ambientExternalModules) {
|
||||
ambientExternalModules = [];
|
||||
}
|
||||
ambientExternalModules.push(scanner.getTokenValue());
|
||||
ambientExternalModules.push({ ref: getFileReference(), depth: braceNesting });
|
||||
}
|
||||
|
||||
function recordModuleName() {
|
||||
const importPath = scanner.getTokenValue();
|
||||
const pos = scanner.getTokenPos();
|
||||
importedFiles.push({
|
||||
fileName: importPath,
|
||||
pos: pos,
|
||||
end: pos + importPath.length
|
||||
});
|
||||
importedFiles.push(getFileReference());
|
||||
|
||||
markAsExternalModuleIfTopLevel();
|
||||
}
|
||||
|
||||
function markAsExternalModuleIfTopLevel() {
|
||||
if (braceNesting === 0) {
|
||||
externalModule = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2189,9 +2216,9 @@ namespace ts {
|
||||
let token = scanner.getToken();
|
||||
if (token === SyntaxKind.DeclareKeyword) {
|
||||
// declare module "mod"
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.ModuleKeyword) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
recordAmbientExternalModule();
|
||||
}
|
||||
@@ -2208,7 +2235,8 @@ namespace ts {
|
||||
function tryConsumeImport(): boolean {
|
||||
let token = scanner.getToken();
|
||||
if (token === SyntaxKind.ImportKeyword) {
|
||||
token = scanner.scan();
|
||||
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// import "mod";
|
||||
recordModuleName();
|
||||
@@ -2216,9 +2244,9 @@ namespace ts {
|
||||
}
|
||||
else {
|
||||
if (token === SyntaxKind.Identifier || isKeyword(token)) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.FromKeyword) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// import d from "mod";
|
||||
recordModuleName();
|
||||
@@ -2232,7 +2260,7 @@ namespace ts {
|
||||
}
|
||||
else if (token === SyntaxKind.CommaToken) {
|
||||
// consume comma and keep going
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
}
|
||||
else {
|
||||
// unknown syntax
|
||||
@@ -2241,17 +2269,17 @@ namespace ts {
|
||||
}
|
||||
|
||||
if (token === SyntaxKind.OpenBraceToken) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
// consume "{ a as B, c, d as D}" clauses
|
||||
// make sure that it stops on EOF
|
||||
while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
}
|
||||
|
||||
if (token === SyntaxKind.CloseBraceToken) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.FromKeyword) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// import {a as A} from "mod";
|
||||
// import d, {a, b as B} from "mod"
|
||||
@@ -2261,13 +2289,13 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
else if (token === SyntaxKind.AsteriskToken) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.AsKeyword) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.Identifier || isKeyword(token)) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.FromKeyword) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// import * as NS from "mod"
|
||||
// import d, * as NS from "mod"
|
||||
@@ -2288,19 +2316,20 @@ namespace ts {
|
||||
function tryConsumeExport(): boolean {
|
||||
let token = scanner.getToken();
|
||||
if (token === SyntaxKind.ExportKeyword) {
|
||||
token = scanner.scan();
|
||||
markAsExternalModuleIfTopLevel();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.OpenBraceToken) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
// consume "{ a as B, c, d as D}" clauses
|
||||
// make sure it stops on EOF
|
||||
while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
}
|
||||
|
||||
if (token === SyntaxKind.CloseBraceToken) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.FromKeyword) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// export {a as A} from "mod";
|
||||
// export {a, b as B} from "mod"
|
||||
@@ -2310,9 +2339,9 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
else if (token === SyntaxKind.AsteriskToken) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.FromKeyword) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// export * from "mod"
|
||||
recordModuleName();
|
||||
@@ -2320,9 +2349,9 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
else if (token === SyntaxKind.ImportKeyword) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.Identifier || isKeyword(token)) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.EqualsToken) {
|
||||
if (tryConsumeRequireCall(/*skipCurrentToken*/ true)) {
|
||||
return true;
|
||||
@@ -2338,11 +2367,11 @@ namespace ts {
|
||||
}
|
||||
|
||||
function tryConsumeRequireCall(skipCurrentToken: boolean): boolean {
|
||||
let token = skipCurrentToken ? scanner.scan() : scanner.getToken();
|
||||
let token = skipCurrentToken ? nextToken() : scanner.getToken();
|
||||
if (token === SyntaxKind.RequireKeyword) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.OpenParenToken) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// require("mod");
|
||||
recordModuleName();
|
||||
@@ -2356,17 +2385,17 @@ namespace ts {
|
||||
function tryConsumeDefine(): boolean {
|
||||
let token = scanner.getToken();
|
||||
if (token === SyntaxKind.Identifier && scanner.getTokenValue() === "define") {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token !== SyntaxKind.OpenParenToken) {
|
||||
return true;
|
||||
}
|
||||
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.StringLiteral) {
|
||||
// looks like define ("modname", ... - skip string literal and comma
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
if (token === SyntaxKind.CommaToken) {
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
}
|
||||
else {
|
||||
// unexpected token
|
||||
@@ -2380,7 +2409,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
// skip open bracket
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
let i = 0;
|
||||
// scan until ']' or EOF
|
||||
while (token !== SyntaxKind.CloseBracketToken && token !== SyntaxKind.EndOfFileToken) {
|
||||
@@ -2390,7 +2419,7 @@ namespace ts {
|
||||
i++;
|
||||
}
|
||||
|
||||
token = scanner.scan();
|
||||
token = nextToken();
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -2400,7 +2429,7 @@ namespace ts {
|
||||
|
||||
function processImports(): void {
|
||||
scanner.setText(sourceText);
|
||||
scanner.scan();
|
||||
nextToken();
|
||||
// Look for:
|
||||
// import "mod";
|
||||
// import d from "mod"
|
||||
@@ -2427,7 +2456,7 @@ namespace ts {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
scanner.scan();
|
||||
nextToken();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2438,7 +2467,34 @@ namespace ts {
|
||||
processImports();
|
||||
}
|
||||
processTripleSlashDirectives();
|
||||
return { referencedFiles, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules };
|
||||
if (externalModule) {
|
||||
// for external modules module all nested ambient modules are augmentations
|
||||
if (ambientExternalModules) {
|
||||
// move all detected ambient modules to imported files since they need to be resolved
|
||||
for (const decl of ambientExternalModules) {
|
||||
importedFiles.push(decl.ref);
|
||||
}
|
||||
}
|
||||
return { referencedFiles, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules: undefined };
|
||||
}
|
||||
else {
|
||||
// for global scripts ambient modules still can have augmentations - look for ambient modules with depth > 0
|
||||
let ambientModuleNames: string[];
|
||||
if (ambientExternalModules) {
|
||||
for (const decl of ambientExternalModules) {
|
||||
if (decl.depth === 0) {
|
||||
if (!ambientModuleNames) {
|
||||
ambientModuleNames = [];
|
||||
}
|
||||
ambientModuleNames.push(decl.ref.fileName);
|
||||
}
|
||||
else {
|
||||
importedFiles.push(decl.ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { referencedFiles, importedFiles, isLibFile: isNoDefaultLib, ambientExternalModules: ambientModuleNames };
|
||||
}
|
||||
}
|
||||
|
||||
/// Helpers
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
/// <reference path="..\..\..\..\src\harness\external\mocha.d.ts" />
|
||||
/// <reference path="..\..\..\..\src\harness\harnessLanguageService.ts" />
|
||||
|
||||
declare namespace chai.assert {
|
||||
function deepEqual(actual: any, expected: any): void;
|
||||
}
|
||||
|
||||
describe('PreProcessFile:', function () {
|
||||
function test(sourceText: string, readImportFile: boolean, detectJavaScriptImports: boolean, expectedPreProcess: ts.PreProcessedFileInfo): void {
|
||||
var resultPreProcess = ts.preProcessFile(sourceText, readImportFile, detectJavaScriptImports);
|
||||
@@ -15,34 +19,30 @@ describe('PreProcessFile:', function () {
|
||||
|
||||
assert.equal(resultIsLibFile, expectedIsLibFile, "Pre-processed file has different value for isLibFile. Expected: " + expectedPreProcess + ". Actual: " + resultIsLibFile);
|
||||
|
||||
assert.equal(resultImportedFiles.length, expectedImportedFiles.length,
|
||||
"Array's length of imported files does not match expected. Expected: " + expectedImportedFiles.length + ". Actual: " + resultImportedFiles.length);
|
||||
checkFileReferenceList("Imported files", expectedImportedFiles, resultImportedFiles);
|
||||
checkFileReferenceList("Referenced files", expectedReferencedFiles, resultReferencedFiles);
|
||||
|
||||
assert.equal(resultReferencedFiles.length, expectedReferencedFiles.length,
|
||||
"Array's length of referenced files does not match expected. Expected: " + expectedReferencedFiles.length + ". Actual: " + resultReferencedFiles.length);
|
||||
assert.deepEqual(resultPreProcess.ambientExternalModules, expectedPreProcess.ambientExternalModules);
|
||||
}
|
||||
|
||||
for (var i = 0; i < expectedImportedFiles.length; ++i) {
|
||||
var resultImportedFile = resultImportedFiles[i];
|
||||
var expectedImportedFile = expectedImportedFiles[i];
|
||||
|
||||
assert.equal(resultImportedFile.fileName, expectedImportedFile.fileName, "Imported file path does not match expected. Expected: " + expectedImportedFile.fileName + ". Actual: " + resultImportedFile.fileName + ".");
|
||||
|
||||
assert.equal(resultImportedFile.pos, expectedImportedFile.pos, "Imported file position does not match expected. Expected: " + expectedImportedFile.pos + ". Actual: " + resultImportedFile.pos + ".");
|
||||
|
||||
assert.equal(resultImportedFile.end, expectedImportedFile.end, "Imported file length does not match expected. Expected: " + expectedImportedFile.end + ". Actual: " + resultImportedFile.end + ".");
|
||||
function checkFileReferenceList(kind: string, expected: ts.FileReference[], actual: ts.FileReference[]) {
|
||||
if (expected === actual) {
|
||||
return;
|
||||
}
|
||||
if (!expected) {
|
||||
assert.isTrue(false, `Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
|
||||
}
|
||||
assert.equal(actual.length, expected.length, `[${kind}] Actual array's length does not match expected length. Expected files: ${JSON.stringify(expected)}, actual files: ${JSON.stringify(actual)}`);
|
||||
|
||||
for (var i = 0; i < expectedReferencedFiles.length; ++i) {
|
||||
var resultReferencedFile = resultReferencedFiles[i];
|
||||
var expectedReferencedFile = expectedReferencedFiles[i];
|
||||
|
||||
assert.equal(resultReferencedFile.fileName, expectedReferencedFile.fileName, "Referenced file path does not match expected. Expected: " + expectedReferencedFile.fileName + ". Actual: " + resultReferencedFile.fileName + ".");
|
||||
|
||||
assert.equal(resultReferencedFile.pos, expectedReferencedFile.pos, "Referenced file position does not match expected. Expected: " + expectedReferencedFile.pos + ". Actual: " + resultReferencedFile.pos + ".");
|
||||
|
||||
assert.equal(resultReferencedFile.end, expectedReferencedFile.end, "Referenced file length does not match expected. Expected: " + expectedReferencedFile.end + ". Actual: " + resultReferencedFile.end + ".");
|
||||
for (var i = 0; i < expected.length; ++i) {
|
||||
var actualReference = actual[i];
|
||||
var expectedReference = expected[i];
|
||||
assert.equal(actualReference.fileName, expectedReference.fileName, `[${kind}] actual file path does not match expected. Expected: "${expectedReference.fileName}". Actual: "${actualReference.fileName}".`);
|
||||
assert.equal(actualReference.pos, expectedReference.pos, `[${kind}] actual file start position does not match expected. Expected: "${expectedReference.pos}". Actual: "${actualReference.pos}".`);
|
||||
assert.equal(actualReference.end, expectedReference.end, `[${kind}] actual file end pos does not match expected. Expected: "${expectedReference.end}". Actual: "${actualReference.end}".`);
|
||||
}
|
||||
}
|
||||
|
||||
describe("Test preProcessFiles,", function () {
|
||||
it("Correctly return referenced files from triple slash", function () {
|
||||
test("///<reference path = \"refFile1.ts\" />" + "\n" + "///<reference path =\"refFile2.ts\"/>" + "\n" + "///<reference path=\"refFile3.ts\" />" + "\n" + "///<reference path= \"..\\refFile4d.ts\" />",
|
||||
@@ -183,7 +183,7 @@ describe('PreProcessFile:', function () {
|
||||
function foo() {
|
||||
}
|
||||
`,
|
||||
/* readImports */ false,
|
||||
/* readImports */ true,
|
||||
/* detectJavaScriptImports */ false,
|
||||
|
||||
{
|
||||
@@ -262,6 +262,165 @@ describe('PreProcessFile:', function () {
|
||||
isLibFile: false
|
||||
})
|
||||
});
|
||||
it("correctly handles augmentations in external modules - 1", () => {
|
||||
test(`
|
||||
declare module "../Observable" {
|
||||
interface I {}
|
||||
}
|
||||
|
||||
export {}
|
||||
`,
|
||||
/*readImportFile*/ true,
|
||||
/*detectJavaScriptImports*/ false,
|
||||
{
|
||||
referencedFiles: [],
|
||||
importedFiles: [
|
||||
{ "fileName": "../Observable", "pos": 28, "end": 41 }
|
||||
],
|
||||
ambientExternalModules: undefined,
|
||||
isLibFile: false
|
||||
})
|
||||
});
|
||||
it("correctly handles augmentations in external modules - 2", () => {
|
||||
test(`
|
||||
declare module "../Observable" {
|
||||
interface I {}
|
||||
}
|
||||
|
||||
import * as x from "m";
|
||||
`,
|
||||
/*readImportFile*/ true,
|
||||
/*detectJavaScriptImports*/ false,
|
||||
{
|
||||
referencedFiles: [],
|
||||
importedFiles: [
|
||||
{ "fileName": "m", "pos": 135, "end": 136 },
|
||||
{ "fileName": "../Observable", "pos": 28, "end": 41 }
|
||||
],
|
||||
ambientExternalModules: undefined,
|
||||
isLibFile: false
|
||||
})
|
||||
});
|
||||
it("correctly handles augmentations in external modules - 3", () => {
|
||||
test(`
|
||||
declare module "../Observable" {
|
||||
interface I {}
|
||||
}
|
||||
|
||||
import m = require("m");
|
||||
`,
|
||||
/*readImportFile*/ true,
|
||||
/*detectJavaScriptImports*/ false,
|
||||
{
|
||||
referencedFiles: [],
|
||||
importedFiles: [
|
||||
{ "fileName": "m", "pos": 135, "end": 136 },
|
||||
{ "fileName": "../Observable", "pos": 28, "end": 41 }
|
||||
],
|
||||
ambientExternalModules: undefined,
|
||||
isLibFile: false
|
||||
})
|
||||
});
|
||||
it("correctly handles augmentations in external modules - 4", () => {
|
||||
test(`
|
||||
declare module "../Observable" {
|
||||
interface I {}
|
||||
}
|
||||
namespace N {}
|
||||
export = N;
|
||||
`,
|
||||
/*readImportFile*/ true,
|
||||
/*detectJavaScriptImports*/ false,
|
||||
{
|
||||
referencedFiles: [],
|
||||
importedFiles: [
|
||||
{ "fileName": "../Observable", "pos": 28, "end": 41 }
|
||||
],
|
||||
ambientExternalModules: undefined,
|
||||
isLibFile: false
|
||||
})
|
||||
});
|
||||
it("correctly handles augmentations in external modules - 5", () => {
|
||||
test(`
|
||||
declare module "../Observable" {
|
||||
interface I {}
|
||||
}
|
||||
namespace N {}
|
||||
export import IN = N;
|
||||
`,
|
||||
/*readImportFile*/ true,
|
||||
/*detectJavaScriptImports*/ false,
|
||||
{
|
||||
referencedFiles: [],
|
||||
importedFiles: [
|
||||
{ "fileName": "../Observable", "pos": 28, "end": 41 }
|
||||
],
|
||||
ambientExternalModules: undefined,
|
||||
isLibFile: false
|
||||
})
|
||||
});
|
||||
it("correctly handles augmentations in external modules - 6", () => {
|
||||
test(`
|
||||
declare module "../Observable" {
|
||||
interface I {}
|
||||
}
|
||||
export let x = 1;
|
||||
`,
|
||||
/*readImportFile*/ true,
|
||||
/*detectJavaScriptImports*/ false,
|
||||
{
|
||||
referencedFiles: [],
|
||||
importedFiles: [
|
||||
{ "fileName": "../Observable", "pos": 28, "end": 41 }
|
||||
],
|
||||
ambientExternalModules: undefined,
|
||||
isLibFile: false
|
||||
})
|
||||
});
|
||||
it ("correctly handles augmentations in ambient external modules - 1", () => {
|
||||
test(`
|
||||
declare module "m1" {
|
||||
export * from "m2";
|
||||
declare module "augmentation" {
|
||||
interface I {}
|
||||
}
|
||||
}
|
||||
`,
|
||||
/*readImportFile*/ true,
|
||||
/*detectJavaScriptImports*/ false,
|
||||
{
|
||||
referencedFiles: [],
|
||||
importedFiles: [
|
||||
{ "fileName": "m2", "pos": 65, "end": 67 },
|
||||
{ "fileName": "augmentation", "pos": 102, "end": 114 }
|
||||
],
|
||||
ambientExternalModules: ["m1"],
|
||||
isLibFile: false
|
||||
});
|
||||
});
|
||||
it ("correctly handles augmentations in ambient external modules - 2", () => {
|
||||
test(`
|
||||
namespace M { var x; }
|
||||
import IM = M;
|
||||
declare module "m1" {
|
||||
export * from "m2";
|
||||
declare module "augmentation" {
|
||||
interface I {}
|
||||
}
|
||||
}
|
||||
`,
|
||||
/*readImportFile*/ true,
|
||||
/*detectJavaScriptImports*/ false,
|
||||
{
|
||||
referencedFiles: [],
|
||||
importedFiles: [
|
||||
{ "fileName": "m2", "pos": 127, "end": 129 },
|
||||
{ "fileName": "augmentation", "pos": 164, "end": 176 }
|
||||
],
|
||||
ambientExternalModules: ["m1"],
|
||||
isLibFile: false
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user