From fa16a99514e53e97cb2ea95269ba71964c604d03 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 31 May 2016 09:26:21 -0700 Subject: [PATCH] Allow an import of "foo.js" to be matched by a file "foo.ts" --- src/compiler/core.ts | 9 +++- src/compiler/program.ts | 27 ++++++++--- src/harness/harness.ts | 14 +++--- tests/baselines/reference/moduleResolution.js | 45 +++++++++++++++++++ .../reference/moduleResolution.symbols | 36 +++++++++++++++ .../reference/moduleResolution.types | 36 +++++++++++++++ .../nameWithFileExtension.errors.txt | 12 ----- .../reference/nameWithFileExtension.js | 3 ++ .../reference/nameWithFileExtension.symbols | 14 ++++++ .../reference/nameWithFileExtension.types | 17 +++++++ .../amd/cantFindTheModule.errors.txt | 6 +-- .../node/cantFindTheModule.errors.txt | 6 +-- tests/cases/compiler/moduleResolution.ts | 26 +++++++++++ tests/cases/projects/NoModule/decl.ts | 2 +- 14 files changed, 220 insertions(+), 33 deletions(-) create mode 100644 tests/baselines/reference/moduleResolution.js create mode 100644 tests/baselines/reference/moduleResolution.symbols create mode 100644 tests/baselines/reference/moduleResolution.types delete mode 100644 tests/baselines/reference/nameWithFileExtension.errors.txt create mode 100644 tests/baselines/reference/nameWithFileExtension.symbols create mode 100644 tests/baselines/reference/nameWithFileExtension.types create mode 100644 tests/cases/compiler/moduleResolution.ts diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 79b6249539dbc..ba81ee7d103d4 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -849,13 +849,18 @@ namespace ts { const extensionsToRemove = [".d.ts", ".ts", ".js", ".tsx", ".jsx"]; export function removeFileExtension(path: string): string { for (const ext of extensionsToRemove) { - if (fileExtensionIs(path, ext)) { - return path.substr(0, path.length - ext.length); + const extensionless = tryRemoveExtension(path, ext); + if (extensionless !== undefined) { + return extensionless; } } return path; } + export function tryRemoveExtension(path: string, extension: string): string { + return fileExtensionIs(path, extension) ? path.substring(0, path.length - extension.length) : undefined; + } + export interface ObjectAllocator { getNodeConstructor(): new (kind: SyntaxKind, pos?: number, end?: number) => Node; getSourceFileConstructor(): new (kind: SyntaxKind, pos?: number, end?: number) => SourceFile; diff --git a/src/compiler/program.ts b/src/compiler/program.ts index bab554927e726..f9a583da0cc8b 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -615,6 +615,7 @@ namespace ts { } /** + * @param extensions - Either supportedTypeScriptExtensions or if --allowJs, allSupportedExtensions * @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary * in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations. */ @@ -626,13 +627,29 @@ namespace ts { onlyRecordFailures = !directoryProbablyExists(directory, state.host); } } - return forEach(extensions, tryLoad); - function tryLoad(ext: string): string { - if (ext === ".tsx" && state.skipTsx) { - return undefined; + if (state.skipTsx) + extensions = filter(extensions, ext => ext !== "tsx"); + + // First try to keep/add an extension: importing "./foo.ts" can be matched by a file "./foo.ts", and "./foo" by "./foo.d.ts" + const keepOrAddExtension = forEach(extensions, ext => + tryLoad(fileExtensionIs(candidate, ext) ? candidate : candidate + ext)); + if (keepOrAddExtension) { + return keepOrAddExtension; + } + + // Then try stripping a ".js" or ".jsx" extension and replacing it with a different one, e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts" + return forEach(supportedJavascriptExtensions, jsExt => { + const extensionless = tryRemoveExtension(candidate, jsExt); + if (extensionless !== undefined) { + return forEach(supportedTypeScriptExtensions, ext => { + if (ext !== jsExt) + return tryLoad(extensionless + ext); + }); } - const fileName = fileExtensionIs(candidate, ext) ? candidate : candidate + ext; + }); + + function tryLoad(fileName: string): string { if (!onlyRecordFailures && state.host.fileExists(fileName)) { if (state.traceEnabled) { trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, fileName); diff --git a/src/harness/harness.ts b/src/harness/harness.ts index d6959c2a54fdd..56d66e753969b 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1,7 +1,7 @@ // // Copyright (c) Microsoft Corporation. All rights reserved. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -350,7 +350,7 @@ namespace Utils { assert.equal(node1.end, node2.end, "node1.end !== node2.end"); assert.equal(node1.kind, node2.kind, "node1.kind !== node2.kind"); - // call this on both nodes to ensure all propagated flags have been set (and thus can be + // call this on both nodes to ensure all propagated flags have been set (and thus can be // compared). assert.equal(ts.containsParseError(node1), ts.containsParseError(node2)); assert.equal(node1.flags, node2.flags, "node1.flags !== node2.flags"); @@ -751,7 +751,7 @@ namespace Harness { (emittedFile: string, emittedLine: number, emittedColumn: number, sourceFile: string, sourceLine: number, sourceColumn: number, sourceName: string): void; } - // Settings + // Settings export let userSpecifiedRoot = ""; export let lightMode = false; @@ -790,7 +790,7 @@ namespace Harness { fileName: string, sourceText: string, languageVersion: ts.ScriptTarget) { - // We'll only assert invariants outside of light mode. + // We'll only assert invariants outside of light mode. const shouldAssertInvariants = !Harness.lightMode; // Only set the parent nodes if we're asserting invariants. We don't need them otherwise. @@ -935,7 +935,7 @@ namespace Harness { libFiles?: string; } - // Additional options not already in ts.optionDeclarations + // Additional options not already in ts.optionDeclarations const harnessOptionDeclarations: ts.CommandLineOption[] = [ { name: "allowNonTsExtensions", type: "boolean" }, { name: "useCaseSensitiveFileNames", type: "boolean" }, @@ -1187,7 +1187,7 @@ namespace Harness { errLines.forEach(e => outputLines.push(e)); // do not count errors from lib.d.ts here, they are computed separately as numLibraryDiagnostics - // if lib.d.ts is explicitly included in input files and there are some errors in it (i.e. because of duplicate identifiers) + // if lib.d.ts is explicitly included in input files and there are some errors in it (i.e. because of duplicate identifiers) // then they will be added twice thus triggering 'total errors' assertion with condition // 'totalErrorsReportedInNonLibraryFiles + numLibraryDiagnostics + numTest262HarnessDiagnostics, diagnostics.length @@ -1497,7 +1497,7 @@ namespace Harness { }; testUnitData.push(newTestFile2); - // unit tests always list files explicitly + // unit tests always list files explicitly const parseConfigHost: ts.ParseConfigHost = { readDirectory: (name) => [] }; diff --git a/tests/baselines/reference/moduleResolution.js b/tests/baselines/reference/moduleResolution.js new file mode 100644 index 0000000000000..be81b926062d6 --- /dev/null +++ b/tests/baselines/reference/moduleResolution.js @@ -0,0 +1,45 @@ +//// [tests/cases/compiler/moduleResolution.ts] //// + +//// [a.ts] +export default 0; + +// No extension: '.ts' added +//// [b.ts] +import a from './a'; + +// Matching extension +//// [c.ts] +import a from './a.ts'; + +// '.js' extension: stripped and replaced with '.ts' +//// [d.ts] +import a from './a.js'; + +//// [jquery.d.ts] +declare var x: number; +export default x; + +// No extension: '.d.ts' added +//// [jquery_user_1.ts] +import j from "./jquery"; + +// '.js' extension: stripped and replaced with '.d.ts' +//// [jquery_user_1.ts] +import j from "./jquery.js" + + +//// [a.js] +"use strict"; +exports.__esModule = true; +exports["default"] = 0; +// No extension: '.ts' added +//// [b.js] +"use strict"; +// Matching extension +//// [c.js] +"use strict"; +// '.js' extension: stripped and replaced with '.ts' +//// [d.js] +"use strict"; +//// [jquery_user_1.js] +"use strict"; diff --git a/tests/baselines/reference/moduleResolution.symbols b/tests/baselines/reference/moduleResolution.symbols new file mode 100644 index 0000000000000..2a51d416a4af6 --- /dev/null +++ b/tests/baselines/reference/moduleResolution.symbols @@ -0,0 +1,36 @@ +=== tests/cases/compiler/a.ts === +export default 0; +No type information for this code. +No type information for this code.// No extension: '.ts' added +No type information for this code.=== tests/cases/compiler/b.ts === +import a from './a'; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +// Matching extension +=== tests/cases/compiler/c.ts === +import a from './a.ts'; +>a : Symbol(a, Decl(c.ts, 0, 6)) + +// '.js' extension: stripped and replaced with '.ts' +=== tests/cases/compiler/d.ts === +import a from './a.js'; +>a : Symbol(a, Decl(d.ts, 0, 6)) + +=== tests/cases/compiler/jquery.d.ts === +declare var x: number; +>x : Symbol(x, Decl(jquery.d.ts, 0, 11)) + +export default x; +>x : Symbol(x, Decl(jquery.d.ts, 0, 11)) + +// No extension: '.d.ts' added +=== tests/cases/compiler/jquery_user_1.ts === +import j from "./jquery"; +>j : Symbol(j, Decl(jquery_user_1.ts, 0, 6)) + +// '.js' extension: stripped and replaced with '.d.ts' +=== tests/cases/compiler/jquery_user_1.ts === +import j from "./jquery.js" +>j : Symbol(j, Decl(jquery_user_1.ts, 0, 6)) +>j : Symbol(j, Decl(jquery_user_1.ts, 0, 6)) + diff --git a/tests/baselines/reference/moduleResolution.types b/tests/baselines/reference/moduleResolution.types new file mode 100644 index 0000000000000..2e1b0f560bc21 --- /dev/null +++ b/tests/baselines/reference/moduleResolution.types @@ -0,0 +1,36 @@ +=== tests/cases/compiler/a.ts === +export default 0; +No type information for this code. +No type information for this code.// No extension: '.ts' added +No type information for this code.=== tests/cases/compiler/b.ts === +import a from './a'; +>a : number + +// Matching extension +=== tests/cases/compiler/c.ts === +import a from './a.ts'; +>a : number + +// '.js' extension: stripped and replaced with '.ts' +=== tests/cases/compiler/d.ts === +import a from './a.js'; +>a : number + +=== tests/cases/compiler/jquery.d.ts === +declare var x: number; +>x : number + +export default x; +>x : number + +// No extension: '.d.ts' added +=== tests/cases/compiler/jquery_user_1.ts === +import j from "./jquery"; +>j : number + +// '.js' extension: stripped and replaced with '.d.ts' +=== tests/cases/compiler/jquery_user_1.ts === +import j from "./jquery.js" +>j : number +>j : number + diff --git a/tests/baselines/reference/nameWithFileExtension.errors.txt b/tests/baselines/reference/nameWithFileExtension.errors.txt deleted file mode 100644 index 2aa341271cddb..0000000000000 --- a/tests/baselines/reference/nameWithFileExtension.errors.txt +++ /dev/null @@ -1,12 +0,0 @@ -tests/cases/conformance/externalModules/foo_1.ts(1,22): error TS2307: Cannot find module './foo_0.js'. - - -==== tests/cases/conformance/externalModules/foo_1.ts (1 errors) ==== - import foo = require('./foo_0.js'); - ~~~~~~~~~~~~ -!!! error TS2307: Cannot find module './foo_0.js'. - var x = foo.foo + 42; - -==== tests/cases/conformance/externalModules/foo_0.ts (0 errors) ==== - export var foo = 42; - \ No newline at end of file diff --git a/tests/baselines/reference/nameWithFileExtension.js b/tests/baselines/reference/nameWithFileExtension.js index 2d487ede24572..8a7506c2d4298 100644 --- a/tests/baselines/reference/nameWithFileExtension.js +++ b/tests/baselines/reference/nameWithFileExtension.js @@ -8,6 +8,9 @@ import foo = require('./foo_0.js'); var x = foo.foo + 42; +//// [foo_0.js] +"use strict"; +exports.foo = 42; //// [foo_1.js] "use strict"; var foo = require('./foo_0.js'); diff --git a/tests/baselines/reference/nameWithFileExtension.symbols b/tests/baselines/reference/nameWithFileExtension.symbols new file mode 100644 index 0000000000000..61e6a768ea6aa --- /dev/null +++ b/tests/baselines/reference/nameWithFileExtension.symbols @@ -0,0 +1,14 @@ +=== tests/cases/conformance/externalModules/foo_1.ts === +import foo = require('./foo_0.js'); +>foo : Symbol(foo, Decl(foo_1.ts, 0, 0)) + +var x = foo.foo + 42; +>x : Symbol(x, Decl(foo_1.ts, 1, 3)) +>foo.foo : Symbol(foo.foo, Decl(foo_0.ts, 0, 10)) +>foo : Symbol(foo, Decl(foo_1.ts, 0, 0)) +>foo : Symbol(foo.foo, Decl(foo_0.ts, 0, 10)) + +=== tests/cases/conformance/externalModules/foo_0.ts === +export var foo = 42; +>foo : Symbol(foo, Decl(foo_0.ts, 0, 10)) + diff --git a/tests/baselines/reference/nameWithFileExtension.types b/tests/baselines/reference/nameWithFileExtension.types new file mode 100644 index 0000000000000..a0fca3d456219 --- /dev/null +++ b/tests/baselines/reference/nameWithFileExtension.types @@ -0,0 +1,17 @@ +=== tests/cases/conformance/externalModules/foo_1.ts === +import foo = require('./foo_0.js'); +>foo : typeof foo + +var x = foo.foo + 42; +>x : number +>foo.foo + 42 : number +>foo.foo : number +>foo : typeof foo +>foo : number +>42 : number + +=== tests/cases/conformance/externalModules/foo_0.ts === +export var foo = 42; +>foo : number +>42 : number + diff --git a/tests/baselines/reference/project/cantFindTheModule/amd/cantFindTheModule.errors.txt b/tests/baselines/reference/project/cantFindTheModule/amd/cantFindTheModule.errors.txt index 7c071fdc208c2..e9f8842d5cbb5 100644 --- a/tests/baselines/reference/project/cantFindTheModule/amd/cantFindTheModule.errors.txt +++ b/tests/baselines/reference/project/cantFindTheModule/amd/cantFindTheModule.errors.txt @@ -1,12 +1,12 @@ -decl.ts(1,26): error TS2307: Cannot find module './foo/bar.js'. +decl.ts(1,26): error TS2307: Cannot find module './foo/bar.tx'. decl.ts(2,26): error TS2307: Cannot find module 'baz'. decl.ts(3,26): error TS2307: Cannot find module './baz'. ==== decl.ts (3 errors) ==== - import modErr = require("./foo/bar.js"); + import modErr = require("./foo/bar.tx"); ~~~~~~~~~~~~~~ -!!! error TS2307: Cannot find module './foo/bar.js'. +!!! error TS2307: Cannot find module './foo/bar.tx'. import modErr1 = require("baz"); ~~~~~ !!! error TS2307: Cannot find module 'baz'. diff --git a/tests/baselines/reference/project/cantFindTheModule/node/cantFindTheModule.errors.txt b/tests/baselines/reference/project/cantFindTheModule/node/cantFindTheModule.errors.txt index 7c071fdc208c2..e9f8842d5cbb5 100644 --- a/tests/baselines/reference/project/cantFindTheModule/node/cantFindTheModule.errors.txt +++ b/tests/baselines/reference/project/cantFindTheModule/node/cantFindTheModule.errors.txt @@ -1,12 +1,12 @@ -decl.ts(1,26): error TS2307: Cannot find module './foo/bar.js'. +decl.ts(1,26): error TS2307: Cannot find module './foo/bar.tx'. decl.ts(2,26): error TS2307: Cannot find module 'baz'. decl.ts(3,26): error TS2307: Cannot find module './baz'. ==== decl.ts (3 errors) ==== - import modErr = require("./foo/bar.js"); + import modErr = require("./foo/bar.tx"); ~~~~~~~~~~~~~~ -!!! error TS2307: Cannot find module './foo/bar.js'. +!!! error TS2307: Cannot find module './foo/bar.tx'. import modErr1 = require("baz"); ~~~~~ !!! error TS2307: Cannot find module 'baz'. diff --git a/tests/cases/compiler/moduleResolution.ts b/tests/cases/compiler/moduleResolution.ts new file mode 100644 index 0000000000000..c21614577c0e5 --- /dev/null +++ b/tests/cases/compiler/moduleResolution.ts @@ -0,0 +1,26 @@ +// @Filename: a.ts +export default 0; + +// No extension: '.ts' added +// @Filename: b.ts +import a from './a'; + +// Matching extension +// @Filename: c.ts +import a from './a.ts'; + +// '.js' extension: stripped and replaced with '.ts' +// @Filename: d.ts +import a from './a.js'; + +// @Filename: jquery.d.ts +declare var x: number; +export default x; + +// No extension: '.d.ts' added +// @Filename: jquery_user_1.ts +import j from "./jquery"; + +// '.js' extension: stripped and replaced with '.d.ts' +// @Filename: jquery_user_1.ts +import j from "./jquery.js" diff --git a/tests/cases/projects/NoModule/decl.ts b/tests/cases/projects/NoModule/decl.ts index a8a29852ac267..064f777c5512e 100644 --- a/tests/cases/projects/NoModule/decl.ts +++ b/tests/cases/projects/NoModule/decl.ts @@ -1,4 +1,4 @@ -import modErr = require("./foo/bar.js"); +import modErr = require("./foo/bar.tx"); import modErr1 = require("baz"); import modErr2 = require("./baz");