Skip to content

Commit fa16a99

Browse files
author
Andy Hanson
committed
Allow an import of "foo.js" to be matched by a file "foo.ts"
1 parent 13900aa commit fa16a99

14 files changed

+220
-33
lines changed

src/compiler/core.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -849,13 +849,18 @@ namespace ts {
849849
const extensionsToRemove = [".d.ts", ".ts", ".js", ".tsx", ".jsx"];
850850
export function removeFileExtension(path: string): string {
851851
for (const ext of extensionsToRemove) {
852-
if (fileExtensionIs(path, ext)) {
853-
return path.substr(0, path.length - ext.length);
852+
const extensionless = tryRemoveExtension(path, ext);
853+
if (extensionless !== undefined) {
854+
return extensionless;
854855
}
855856
}
856857
return path;
857858
}
858859

860+
export function tryRemoveExtension(path: string, extension: string): string {
861+
return fileExtensionIs(path, extension) ? path.substring(0, path.length - extension.length) : undefined;
862+
}
863+
859864
export interface ObjectAllocator {
860865
getNodeConstructor(): new (kind: SyntaxKind, pos?: number, end?: number) => Node;
861866
getSourceFileConstructor(): new (kind: SyntaxKind, pos?: number, end?: number) => SourceFile;

src/compiler/program.ts

+22-5
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,7 @@ namespace ts {
615615
}
616616

617617
/**
618+
* @param extensions - Either supportedTypeScriptExtensions or if --allowJs, allSupportedExtensions
618619
* @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
619620
* 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.
620621
*/
@@ -626,13 +627,29 @@ namespace ts {
626627
onlyRecordFailures = !directoryProbablyExists(directory, state.host);
627628
}
628629
}
629-
return forEach(extensions, tryLoad);
630630

631-
function tryLoad(ext: string): string {
632-
if (ext === ".tsx" && state.skipTsx) {
633-
return undefined;
631+
if (state.skipTsx)
632+
extensions = filter(extensions, ext => ext !== "tsx");
633+
634+
// First try to keep/add an extension: importing "./foo.ts" can be matched by a file "./foo.ts", and "./foo" by "./foo.d.ts"
635+
const keepOrAddExtension = forEach(extensions, ext =>
636+
tryLoad(fileExtensionIs(candidate, ext) ? candidate : candidate + ext));
637+
if (keepOrAddExtension) {
638+
return keepOrAddExtension;
639+
}
640+
641+
// 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"
642+
return forEach(supportedJavascriptExtensions, jsExt => {
643+
const extensionless = tryRemoveExtension(candidate, jsExt);
644+
if (extensionless !== undefined) {
645+
return forEach(supportedTypeScriptExtensions, ext => {
646+
if (ext !== jsExt)
647+
return tryLoad(extensionless + ext);
648+
});
634649
}
635-
const fileName = fileExtensionIs(candidate, ext) ? candidate : candidate + ext;
650+
});
651+
652+
function tryLoad(fileName: string): string {
636653
if (!onlyRecordFailures && state.host.fileExists(fileName)) {
637654
if (state.traceEnabled) {
638655
trace(state.host, Diagnostics.File_0_exist_use_it_as_a_name_resolution_result, fileName);

src/harness/harness.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
//
33
// Copyright (c) Microsoft Corporation. All rights reserved.
4-
//
4+
//
55
// Licensed under the Apache License, Version 2.0 (the "License");
66
// you may not use this file except in compliance with the License.
77
// You may obtain a copy of the License at
@@ -350,7 +350,7 @@ namespace Utils {
350350
assert.equal(node1.end, node2.end, "node1.end !== node2.end");
351351
assert.equal(node1.kind, node2.kind, "node1.kind !== node2.kind");
352352

353-
// call this on both nodes to ensure all propagated flags have been set (and thus can be
353+
// call this on both nodes to ensure all propagated flags have been set (and thus can be
354354
// compared).
355355
assert.equal(ts.containsParseError(node1), ts.containsParseError(node2));
356356
assert.equal(node1.flags, node2.flags, "node1.flags !== node2.flags");
@@ -751,7 +751,7 @@ namespace Harness {
751751
(emittedFile: string, emittedLine: number, emittedColumn: number, sourceFile: string, sourceLine: number, sourceColumn: number, sourceName: string): void;
752752
}
753753

754-
// Settings
754+
// Settings
755755
export let userSpecifiedRoot = "";
756756
export let lightMode = false;
757757

@@ -790,7 +790,7 @@ namespace Harness {
790790
fileName: string,
791791
sourceText: string,
792792
languageVersion: ts.ScriptTarget) {
793-
// We'll only assert invariants outside of light mode.
793+
// We'll only assert invariants outside of light mode.
794794
const shouldAssertInvariants = !Harness.lightMode;
795795

796796
// Only set the parent nodes if we're asserting invariants. We don't need them otherwise.
@@ -935,7 +935,7 @@ namespace Harness {
935935
libFiles?: string;
936936
}
937937

938-
// Additional options not already in ts.optionDeclarations
938+
// Additional options not already in ts.optionDeclarations
939939
const harnessOptionDeclarations: ts.CommandLineOption[] = [
940940
{ name: "allowNonTsExtensions", type: "boolean" },
941941
{ name: "useCaseSensitiveFileNames", type: "boolean" },
@@ -1187,7 +1187,7 @@ namespace Harness {
11871187
errLines.forEach(e => outputLines.push(e));
11881188

11891189
// do not count errors from lib.d.ts here, they are computed separately as numLibraryDiagnostics
1190-
// if lib.d.ts is explicitly included in input files and there are some errors in it (i.e. because of duplicate identifiers)
1190+
// if lib.d.ts is explicitly included in input files and there are some errors in it (i.e. because of duplicate identifiers)
11911191
// then they will be added twice thus triggering 'total errors' assertion with condition
11921192
// 'totalErrorsReportedInNonLibraryFiles + numLibraryDiagnostics + numTest262HarnessDiagnostics, diagnostics.length
11931193

@@ -1497,7 +1497,7 @@ namespace Harness {
14971497
};
14981498
testUnitData.push(newTestFile2);
14991499

1500-
// unit tests always list files explicitly
1500+
// unit tests always list files explicitly
15011501
const parseConfigHost: ts.ParseConfigHost = {
15021502
readDirectory: (name) => []
15031503
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//// [tests/cases/compiler/moduleResolution.ts] ////
2+
3+
//// [a.ts]
4+
export default 0;
5+
6+
// No extension: '.ts' added
7+
//// [b.ts]
8+
import a from './a';
9+
10+
// Matching extension
11+
//// [c.ts]
12+
import a from './a.ts';
13+
14+
// '.js' extension: stripped and replaced with '.ts'
15+
//// [d.ts]
16+
import a from './a.js';
17+
18+
//// [jquery.d.ts]
19+
declare var x: number;
20+
export default x;
21+
22+
// No extension: '.d.ts' added
23+
//// [jquery_user_1.ts]
24+
import j from "./jquery";
25+
26+
// '.js' extension: stripped and replaced with '.d.ts'
27+
//// [jquery_user_1.ts]
28+
import j from "./jquery.js"
29+
30+
31+
//// [a.js]
32+
"use strict";
33+
exports.__esModule = true;
34+
exports["default"] = 0;
35+
// No extension: '.ts' added
36+
//// [b.js]
37+
"use strict";
38+
// Matching extension
39+
//// [c.js]
40+
"use strict";
41+
// '.js' extension: stripped and replaced with '.ts'
42+
//// [d.js]
43+
"use strict";
44+
//// [jquery_user_1.js]
45+
"use strict";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
=== tests/cases/compiler/a.ts ===
2+
export default 0;
3+
No type information for this code.
4+
No type information for this code.// No extension: '.ts' added
5+
No type information for this code.=== tests/cases/compiler/b.ts ===
6+
import a from './a';
7+
>a : Symbol(a, Decl(b.ts, 0, 6))
8+
9+
// Matching extension
10+
=== tests/cases/compiler/c.ts ===
11+
import a from './a.ts';
12+
>a : Symbol(a, Decl(c.ts, 0, 6))
13+
14+
// '.js' extension: stripped and replaced with '.ts'
15+
=== tests/cases/compiler/d.ts ===
16+
import a from './a.js';
17+
>a : Symbol(a, Decl(d.ts, 0, 6))
18+
19+
=== tests/cases/compiler/jquery.d.ts ===
20+
declare var x: number;
21+
>x : Symbol(x, Decl(jquery.d.ts, 0, 11))
22+
23+
export default x;
24+
>x : Symbol(x, Decl(jquery.d.ts, 0, 11))
25+
26+
// No extension: '.d.ts' added
27+
=== tests/cases/compiler/jquery_user_1.ts ===
28+
import j from "./jquery";
29+
>j : Symbol(j, Decl(jquery_user_1.ts, 0, 6))
30+
31+
// '.js' extension: stripped and replaced with '.d.ts'
32+
=== tests/cases/compiler/jquery_user_1.ts ===
33+
import j from "./jquery.js"
34+
>j : Symbol(j, Decl(jquery_user_1.ts, 0, 6))
35+
>j : Symbol(j, Decl(jquery_user_1.ts, 0, 6))
36+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
=== tests/cases/compiler/a.ts ===
2+
export default 0;
3+
No type information for this code.
4+
No type information for this code.// No extension: '.ts' added
5+
No type information for this code.=== tests/cases/compiler/b.ts ===
6+
import a from './a';
7+
>a : number
8+
9+
// Matching extension
10+
=== tests/cases/compiler/c.ts ===
11+
import a from './a.ts';
12+
>a : number
13+
14+
// '.js' extension: stripped and replaced with '.ts'
15+
=== tests/cases/compiler/d.ts ===
16+
import a from './a.js';
17+
>a : number
18+
19+
=== tests/cases/compiler/jquery.d.ts ===
20+
declare var x: number;
21+
>x : number
22+
23+
export default x;
24+
>x : number
25+
26+
// No extension: '.d.ts' added
27+
=== tests/cases/compiler/jquery_user_1.ts ===
28+
import j from "./jquery";
29+
>j : number
30+
31+
// '.js' extension: stripped and replaced with '.d.ts'
32+
=== tests/cases/compiler/jquery_user_1.ts ===
33+
import j from "./jquery.js"
34+
>j : number
35+
>j : number
36+

tests/baselines/reference/nameWithFileExtension.errors.txt

-12
This file was deleted.

tests/baselines/reference/nameWithFileExtension.js

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import foo = require('./foo_0.js');
88
var x = foo.foo + 42;
99

1010

11+
//// [foo_0.js]
12+
"use strict";
13+
exports.foo = 42;
1114
//// [foo_1.js]
1215
"use strict";
1316
var foo = require('./foo_0.js');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
=== tests/cases/conformance/externalModules/foo_1.ts ===
2+
import foo = require('./foo_0.js');
3+
>foo : Symbol(foo, Decl(foo_1.ts, 0, 0))
4+
5+
var x = foo.foo + 42;
6+
>x : Symbol(x, Decl(foo_1.ts, 1, 3))
7+
>foo.foo : Symbol(foo.foo, Decl(foo_0.ts, 0, 10))
8+
>foo : Symbol(foo, Decl(foo_1.ts, 0, 0))
9+
>foo : Symbol(foo.foo, Decl(foo_0.ts, 0, 10))
10+
11+
=== tests/cases/conformance/externalModules/foo_0.ts ===
12+
export var foo = 42;
13+
>foo : Symbol(foo, Decl(foo_0.ts, 0, 10))
14+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
=== tests/cases/conformance/externalModules/foo_1.ts ===
2+
import foo = require('./foo_0.js');
3+
>foo : typeof foo
4+
5+
var x = foo.foo + 42;
6+
>x : number
7+
>foo.foo + 42 : number
8+
>foo.foo : number
9+
>foo : typeof foo
10+
>foo : number
11+
>42 : number
12+
13+
=== tests/cases/conformance/externalModules/foo_0.ts ===
14+
export var foo = 42;
15+
>foo : number
16+
>42 : number
17+

tests/baselines/reference/project/cantFindTheModule/amd/cantFindTheModule.errors.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
decl.ts(1,26): error TS2307: Cannot find module './foo/bar.js'.
1+
decl.ts(1,26): error TS2307: Cannot find module './foo/bar.tx'.
22
decl.ts(2,26): error TS2307: Cannot find module 'baz'.
33
decl.ts(3,26): error TS2307: Cannot find module './baz'.
44

55

66
==== decl.ts (3 errors) ====
7-
import modErr = require("./foo/bar.js");
7+
import modErr = require("./foo/bar.tx");
88
~~~~~~~~~~~~~~
9-
!!! error TS2307: Cannot find module './foo/bar.js'.
9+
!!! error TS2307: Cannot find module './foo/bar.tx'.
1010
import modErr1 = require("baz");
1111
~~~~~
1212
!!! error TS2307: Cannot find module 'baz'.

tests/baselines/reference/project/cantFindTheModule/node/cantFindTheModule.errors.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
decl.ts(1,26): error TS2307: Cannot find module './foo/bar.js'.
1+
decl.ts(1,26): error TS2307: Cannot find module './foo/bar.tx'.
22
decl.ts(2,26): error TS2307: Cannot find module 'baz'.
33
decl.ts(3,26): error TS2307: Cannot find module './baz'.
44

55

66
==== decl.ts (3 errors) ====
7-
import modErr = require("./foo/bar.js");
7+
import modErr = require("./foo/bar.tx");
88
~~~~~~~~~~~~~~
9-
!!! error TS2307: Cannot find module './foo/bar.js'.
9+
!!! error TS2307: Cannot find module './foo/bar.tx'.
1010
import modErr1 = require("baz");
1111
~~~~~
1212
!!! error TS2307: Cannot find module 'baz'.
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// @Filename: a.ts
2+
export default 0;
3+
4+
// No extension: '.ts' added
5+
// @Filename: b.ts
6+
import a from './a';
7+
8+
// Matching extension
9+
// @Filename: c.ts
10+
import a from './a.ts';
11+
12+
// '.js' extension: stripped and replaced with '.ts'
13+
// @Filename: d.ts
14+
import a from './a.js';
15+
16+
// @Filename: jquery.d.ts
17+
declare var x: number;
18+
export default x;
19+
20+
// No extension: '.d.ts' added
21+
// @Filename: jquery_user_1.ts
22+
import j from "./jquery";
23+
24+
// '.js' extension: stripped and replaced with '.d.ts'
25+
// @Filename: jquery_user_1.ts
26+
import j from "./jquery.js"

tests/cases/projects/NoModule/decl.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import modErr = require("./foo/bar.js");
1+
import modErr = require("./foo/bar.tx");
22
import modErr1 = require("baz");
33
import modErr2 = require("./baz");
44

0 commit comments

Comments
 (0)