Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 4937d9c

Browse filesBrowse files
author
Andy Hanson
committed
Allow untyped imports
1 parent b5ba315 commit 4937d9c
Copy full SHA for 4937d9c

24 files changed

+402-25Lines changed: 402 additions & 25 deletions
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/compiler/checker.ts‎

Copy file name to clipboardExpand all lines: src/compiler/checker.ts
+29-6Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ namespace ts {
10691069
const moduleSymbol = resolveExternalModuleName(node, (<ImportDeclaration>node.parent).moduleSpecifier);
10701070

10711071
if (moduleSymbol) {
1072-
const exportDefaultSymbol = isShorthandAmbientModuleSymbol(moduleSymbol) ?
1072+
const exportDefaultSymbol = isUntypedModuleSymbol(moduleSymbol) ?
10731073
moduleSymbol :
10741074
moduleSymbol.exports["export="] ?
10751075
getPropertyOfType(getTypeOfSymbol(moduleSymbol.exports["export="]), "default") :
@@ -1145,7 +1145,7 @@ namespace ts {
11451145
if (targetSymbol) {
11461146
const name = specifier.propertyName || specifier.name;
11471147
if (name.text) {
1148-
if (isShorthandAmbientModuleSymbol(moduleSymbol)) {
1148+
if (isUntypedModuleSymbol(moduleSymbol)) {
11491149
return moduleSymbol;
11501150
}
11511151

@@ -1365,8 +1365,9 @@ namespace ts {
13651365
}
13661366

13671367
const isRelative = isExternalModuleNameRelative(moduleName);
1368+
const quotedName = '"' + moduleName + '"';
13681369
if (!isRelative) {
1369-
const symbol = getSymbol(globals, '"' + moduleName + '"', SymbolFlags.ValueModule);
1370+
const symbol = getSymbol(globals, quotedName, SymbolFlags.ValueModule);
13701371
if (symbol) {
13711372
// merged symbol is module declaration symbol combined with all augmentations
13721373
return getMergedSymbol(symbol);
@@ -1395,6 +1396,28 @@ namespace ts {
13951396
}
13961397
}
13971398

1399+
// May be an untyped module. If so, ignore resolutionDiagnostic.
1400+
if (!isRelative && resolvedModule && !extensionIsTypeScript(resolvedModule.extension)) {
1401+
if (compilerOptions.noImplicitAny) {
1402+
if (moduleNotFoundError) {
1403+
error(errorNode,
1404+
Diagnostics.A_package_for_0_was_found_at_1_but_is_untyped_Because_noImplicitAny_is_enabled_this_package_must_have_a_declaration,
1405+
moduleReference,
1406+
resolvedModule.resolvedFileName);
1407+
}
1408+
return undefined;
1409+
}
1410+
1411+
// Create a new symbol to represent the untyped module and store it in globals.
1412+
// This provides a name to the module. See the test tests/cases/fourslash/untypedModuleImport.ts
1413+
const newSymbol = createSymbol(SymbolFlags.ValueModule, quotedName);
1414+
// Module symbols are expected to have 'exports', although since this is an untyped module it can be empty.
1415+
newSymbol.exports = createMap<Symbol>();
1416+
// Cache it so subsequent accesses will return the same module.
1417+
globals[quotedName] = newSymbol;
1418+
return newSymbol;
1419+
}
1420+
13981421
if (moduleNotFoundError) {
13991422
// report errors only if it was requested
14001423
if (resolutionDiagnostic) {
@@ -3462,7 +3485,7 @@ namespace ts {
34623485
function getTypeOfFuncClassEnumModule(symbol: Symbol): Type {
34633486
const links = getSymbolLinks(symbol);
34643487
if (!links.type) {
3465-
if (symbol.valueDeclaration.kind === SyntaxKind.ModuleDeclaration && isShorthandAmbientModuleSymbol(symbol)) {
3488+
if (symbol.flags & SymbolFlags.Module && isUntypedModuleSymbol(symbol)) {
34663489
links.type = anyType;
34673490
}
34683491
else {
@@ -19011,7 +19034,7 @@ namespace ts {
1901119034

1901219035
function moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean {
1901319036
let moduleSymbol = resolveExternalModuleName(moduleReferenceExpression.parent, moduleReferenceExpression);
19014-
if (!moduleSymbol || isShorthandAmbientModuleSymbol(moduleSymbol)) {
19037+
if (!moduleSymbol || isUntypedModuleSymbol(moduleSymbol)) {
1901519038
// If the module is not found or is shorthand, assume that it may export a value.
1901619039
return true;
1901719040
}
@@ -19512,7 +19535,7 @@ namespace ts {
1951219535
(typeReferenceDirectives || (typeReferenceDirectives = [])).push(typeReferenceDirective);
1951319536
}
1951419537
else {
19515-
// found at least one entry that does not originate from type reference directive
19538+
// found at least one entry that does not originate from type reference directive
1951619539
return undefined;
1951719540
}
1951819541
}
Collapse file

‎src/compiler/diagnosticMessages.json‎

Copy file name to clipboardExpand all lines: src/compiler/diagnosticMessages.json
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2869,6 +2869,10 @@
28692869
"category": "Error",
28702870
"code": 6143
28712871
},
2872+
"A package for '{0}' was found at '{1}', but is untyped. Because '--noImplicitAny' is enabled, this package must have a declaration.": {
2873+
"category": "Error",
2874+
"code": 6144
2875+
},
28722876
"Variable '{0}' implicitly has an '{1}' type.": {
28732877
"category": "Error",
28742878
"code": 7005
Collapse file

‎src/compiler/program.ts‎

Copy file name to clipboardExpand all lines: src/compiler/program.ts
+3-1Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1324,6 +1324,7 @@ namespace ts {
13241324
// - it's not a top level JavaScript module that exceeded the search max
13251325
const elideImport = isJsFileFromNodeModules && currentNodeModulesDepth > maxNodeModuleJsDepth;
13261326
// Don't add the file if it has a bad extension (e.g. 'tsx' if we don't have '--allowJs')
1327+
// This may still end up being an untyped module -- the file won't be included but imports will be allowed.
13271328
const shouldAddFile = resolvedFileName && !getResolutionDiagnostic(options, resolution) && !options.noResolve && i < file.imports.length && !elideImport;
13281329

13291330
if (elideImport) {
@@ -1571,8 +1572,9 @@ namespace ts {
15711572

15721573
/* @internal */
15731574
/**
1574-
* Returns a DiagnosticMessage if we can't use a resolved module due to its extension.
1575+
* Returns a DiagnosticMessage if we won't include a resolved module due to its extension.
15751576
* The DiagnosticMessage's parameters are the imported module name, and the filename it resolved to.
1577+
* This returns a diagnostic even if the module will be an untyped module.
15761578
*/
15771579
export function getResolutionDiagnostic(options: CompilerOptions, { extension }: ResolvedModule): DiagnosticMessage | undefined {
15781580
switch (extension) {
Collapse file

‎src/compiler/utilities.ts‎

Copy file name to clipboardExpand all lines: src/compiler/utilities.ts
+3-2Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,8 +406,9 @@ namespace ts {
406406
((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral || isGlobalScopeAugmentation(<ModuleDeclaration>node));
407407
}
408408

409-
export function isShorthandAmbientModuleSymbol(moduleSymbol: Symbol): boolean {
410-
return isShorthandAmbientModule(moduleSymbol.valueDeclaration);
409+
/** Given a symbol for a module, checks that it is either an untyped import or a shorthand ambient module. */
410+
export function isUntypedModuleSymbol(moduleSymbol: Symbol): boolean {
411+
return !moduleSymbol.valueDeclaration || isShorthandAmbientModule(moduleSymbol.valueDeclaration);
411412
}
412413

413414
function isShorthandAmbientModule(node: Node): boolean {
Collapse file

‎src/harness/harness.ts‎

Copy file name to clipboardExpand all lines: src/harness/harness.ts
+22-16Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,22 +1108,7 @@ namespace Harness {
11081108
const option = getCommandLineOption(name);
11091109
if (option) {
11101110
const errors: ts.Diagnostic[] = [];
1111-
switch (option.type) {
1112-
case "boolean":
1113-
options[option.name] = value.toLowerCase() === "true";
1114-
break;
1115-
case "string":
1116-
options[option.name] = value;
1117-
break;
1118-
// If not a primitive, the possible types are specified in what is effectively a map of options.
1119-
case "list":
1120-
options[option.name] = ts.parseListTypeOption(<ts.CommandLineOptionOfListType>option, value, errors);
1121-
break;
1122-
default:
1123-
options[option.name] = ts.parseCustomTypeOption(<ts.CommandLineOptionOfCustomType>option, value, errors);
1124-
break;
1125-
}
1126-
1111+
options[option.name] = optionValue(option, value, errors);
11271112
if (errors.length > 0) {
11281113
throw new Error(`Unknown value '${value}' for compiler option '${name}'.`);
11291114
}
@@ -1135,6 +1120,27 @@ namespace Harness {
11351120
}
11361121
}
11371122

1123+
function optionValue(option: ts.CommandLineOption, value: string, errors: ts.Diagnostic[]): any {
1124+
switch (option.type) {
1125+
case "boolean":
1126+
return value.toLowerCase() === "true";
1127+
case "string":
1128+
return value;
1129+
case "number": {
1130+
const number = parseInt(value, 10);
1131+
if (isNaN(number)) {
1132+
throw new Error(`Value must be a number, got: ${JSON.stringify(value)}`);
1133+
}
1134+
return number;
1135+
}
1136+
// If not a primitive, the possible types are specified in what is effectively a map of options.
1137+
case "list":
1138+
return ts.parseListTypeOption(<ts.CommandLineOptionOfListType>option, value, errors);
1139+
default:
1140+
return ts.parseCustomTypeOption(<ts.CommandLineOptionOfCustomType>option, value, errors);
1141+
}
1142+
}
1143+
11381144
export interface TestFile {
11391145
unitName: string;
11401146
content: string;
Collapse file
+37Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//// [tests/cases/conformance/moduleResolution/untypedModuleImport.ts] ////
2+
3+
//// [index.js]
4+
// This tests that importing from a JS file globally works in an untyped way.
5+
// (Assuming we don't have `--noImplicitAny` or `--allowJs`.)
6+
7+
This file is not processed.
8+
9+
//// [a.ts]
10+
import * as foo from "foo";
11+
foo.bar();
12+
13+
//// [b.ts]
14+
import foo = require("foo");
15+
foo();
16+
17+
//// [c.ts]
18+
import foo, { bar } from "foo";
19+
import "./a";
20+
import "./b";
21+
foo(bar());
22+
23+
24+
//// [a.js]
25+
"use strict";
26+
var foo = require("foo");
27+
foo.bar();
28+
//// [b.js]
29+
"use strict";
30+
var foo = require("foo");
31+
foo();
32+
//// [c.js]
33+
"use strict";
34+
var foo_1 = require("foo");
35+
require("./a");
36+
require("./b");
37+
foo_1["default"](foo_1.bar());
Collapse file
+25Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
=== /c.ts ===
2+
import foo, { bar } from "foo";
3+
>foo : Symbol(foo, Decl(c.ts, 0, 6))
4+
>bar : Symbol(bar, Decl(c.ts, 0, 13))
5+
6+
import "./a";
7+
import "./b";
8+
foo(bar());
9+
>foo : Symbol(foo, Decl(c.ts, 0, 6))
10+
>bar : Symbol(bar, Decl(c.ts, 0, 13))
11+
12+
=== /a.ts ===
13+
import * as foo from "foo";
14+
>foo : Symbol(foo, Decl(a.ts, 0, 6))
15+
16+
foo.bar();
17+
>foo : Symbol(foo, Decl(a.ts, 0, 6))
18+
19+
=== /b.ts ===
20+
import foo = require("foo");
21+
>foo : Symbol(foo, Decl(b.ts, 0, 0))
22+
23+
foo();
24+
>foo : Symbol(foo, Decl(b.ts, 0, 0))
25+
Collapse file
+31Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
=== /c.ts ===
2+
import foo, { bar } from "foo";
3+
>foo : any
4+
>bar : any
5+
6+
import "./a";
7+
import "./b";
8+
foo(bar());
9+
>foo(bar()) : any
10+
>foo : any
11+
>bar() : any
12+
>bar : any
13+
14+
=== /a.ts ===
15+
import * as foo from "foo";
16+
>foo : any
17+
18+
foo.bar();
19+
>foo.bar() : any
20+
>foo.bar : any
21+
>foo : any
22+
>bar : any
23+
24+
=== /b.ts ===
25+
import foo = require("foo");
26+
>foo : any
27+
28+
foo();
29+
>foo() : any
30+
>foo : any
31+
Collapse file
+16Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//// [tests/cases/conformance/moduleResolution/untypedModuleImport_allowJs.ts] ////
2+
3+
//// [index.js]
4+
// Same as untypedModuleImport.ts but with --allowJs, so the package will actually be typed.
5+
6+
exports.default = { bar() { return 0; } }
7+
8+
//// [a.ts]
9+
import foo from "foo";
10+
foo.bar();
11+
12+
13+
//// [a.js]
14+
"use strict";
15+
var foo_1 = require("foo");
16+
foo_1["default"].bar();
Collapse file
+17Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
=== /a.ts ===
2+
import foo from "foo";
3+
>foo : Symbol(foo, Decl(a.ts, 0, 6))
4+
5+
foo.bar();
6+
>foo.bar : Symbol(bar, Decl(index.js, 2, 19))
7+
>foo : Symbol(foo, Decl(a.ts, 0, 6))
8+
>bar : Symbol(bar, Decl(index.js, 2, 19))
9+
10+
=== /node_modules/foo/index.js ===
11+
// Same as untypedModuleImport.ts but with --allowJs, so the package will actually be typed.
12+
13+
exports.default = { bar() { return 0; } }
14+
>exports : Symbol(default, Decl(index.js, 0, 0))
15+
>default : Symbol(default, Decl(index.js, 0, 0))
16+
>bar : Symbol(bar, Decl(index.js, 2, 19))
17+

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.