Merge pull request #7507 from Microsoft/preprocessFindAugmentations

find module augmentations in preprocessor
This commit is contained in:
Vladimir Matveev
2016-03-14 13:57:49 -07:00
2 changed files with 282 additions and 67 deletions

View File

@@ -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

View File

@@ -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
});
});
});
});