mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-13 06:20:23 -06:00
Allow untyped imports
This commit is contained in:
parent
b5ba3152ff
commit
4937d9c8b4
@ -1069,7 +1069,7 @@ namespace ts {
|
||||
const moduleSymbol = resolveExternalModuleName(node, (<ImportDeclaration>node.parent).moduleSpecifier);
|
||||
|
||||
if (moduleSymbol) {
|
||||
const exportDefaultSymbol = isShorthandAmbientModuleSymbol(moduleSymbol) ?
|
||||
const exportDefaultSymbol = isUntypedModuleSymbol(moduleSymbol) ?
|
||||
moduleSymbol :
|
||||
moduleSymbol.exports["export="] ?
|
||||
getPropertyOfType(getTypeOfSymbol(moduleSymbol.exports["export="]), "default") :
|
||||
@ -1145,7 +1145,7 @@ namespace ts {
|
||||
if (targetSymbol) {
|
||||
const name = specifier.propertyName || specifier.name;
|
||||
if (name.text) {
|
||||
if (isShorthandAmbientModuleSymbol(moduleSymbol)) {
|
||||
if (isUntypedModuleSymbol(moduleSymbol)) {
|
||||
return moduleSymbol;
|
||||
}
|
||||
|
||||
@ -1365,8 +1365,9 @@ namespace ts {
|
||||
}
|
||||
|
||||
const isRelative = isExternalModuleNameRelative(moduleName);
|
||||
const quotedName = '"' + moduleName + '"';
|
||||
if (!isRelative) {
|
||||
const symbol = getSymbol(globals, '"' + moduleName + '"', SymbolFlags.ValueModule);
|
||||
const symbol = getSymbol(globals, quotedName, SymbolFlags.ValueModule);
|
||||
if (symbol) {
|
||||
// merged symbol is module declaration symbol combined with all augmentations
|
||||
return getMergedSymbol(symbol);
|
||||
@ -1395,6 +1396,28 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
// May be an untyped module. If so, ignore resolutionDiagnostic.
|
||||
if (!isRelative && resolvedModule && !extensionIsTypeScript(resolvedModule.extension)) {
|
||||
if (compilerOptions.noImplicitAny) {
|
||||
if (moduleNotFoundError) {
|
||||
error(errorNode,
|
||||
Diagnostics.A_package_for_0_was_found_at_1_but_is_untyped_Because_noImplicitAny_is_enabled_this_package_must_have_a_declaration,
|
||||
moduleReference,
|
||||
resolvedModule.resolvedFileName);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Create a new symbol to represent the untyped module and store it in globals.
|
||||
// This provides a name to the module. See the test tests/cases/fourslash/untypedModuleImport.ts
|
||||
const newSymbol = createSymbol(SymbolFlags.ValueModule, quotedName);
|
||||
// Module symbols are expected to have 'exports', although since this is an untyped module it can be empty.
|
||||
newSymbol.exports = createMap<Symbol>();
|
||||
// Cache it so subsequent accesses will return the same module.
|
||||
globals[quotedName] = newSymbol;
|
||||
return newSymbol;
|
||||
}
|
||||
|
||||
if (moduleNotFoundError) {
|
||||
// report errors only if it was requested
|
||||
if (resolutionDiagnostic) {
|
||||
@ -3462,7 +3485,7 @@ namespace ts {
|
||||
function getTypeOfFuncClassEnumModule(symbol: Symbol): Type {
|
||||
const links = getSymbolLinks(symbol);
|
||||
if (!links.type) {
|
||||
if (symbol.valueDeclaration.kind === SyntaxKind.ModuleDeclaration && isShorthandAmbientModuleSymbol(symbol)) {
|
||||
if (symbol.flags & SymbolFlags.Module && isUntypedModuleSymbol(symbol)) {
|
||||
links.type = anyType;
|
||||
}
|
||||
else {
|
||||
@ -19011,7 +19034,7 @@ namespace ts {
|
||||
|
||||
function moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean {
|
||||
let moduleSymbol = resolveExternalModuleName(moduleReferenceExpression.parent, moduleReferenceExpression);
|
||||
if (!moduleSymbol || isShorthandAmbientModuleSymbol(moduleSymbol)) {
|
||||
if (!moduleSymbol || isUntypedModuleSymbol(moduleSymbol)) {
|
||||
// If the module is not found or is shorthand, assume that it may export a value.
|
||||
return true;
|
||||
}
|
||||
@ -19512,7 +19535,7 @@ namespace ts {
|
||||
(typeReferenceDirectives || (typeReferenceDirectives = [])).push(typeReferenceDirective);
|
||||
}
|
||||
else {
|
||||
// found at least one entry that does not originate from type reference directive
|
||||
// found at least one entry that does not originate from type reference directive
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2869,6 +2869,10 @@
|
||||
"category": "Error",
|
||||
"code": 6143
|
||||
},
|
||||
"A package for '{0}' was found at '{1}', but is untyped. Because '--noImplicitAny' is enabled, this package must have a declaration.": {
|
||||
"category": "Error",
|
||||
"code": 6144
|
||||
},
|
||||
"Variable '{0}' implicitly has an '{1}' type.": {
|
||||
"category": "Error",
|
||||
"code": 7005
|
||||
|
||||
@ -1324,6 +1324,7 @@ namespace ts {
|
||||
// - it's not a top level JavaScript module that exceeded the search max
|
||||
const elideImport = isJsFileFromNodeModules && currentNodeModulesDepth > maxNodeModuleJsDepth;
|
||||
// Don't add the file if it has a bad extension (e.g. 'tsx' if we don't have '--allowJs')
|
||||
// This may still end up being an untyped module -- the file won't be included but imports will be allowed.
|
||||
const shouldAddFile = resolvedFileName && !getResolutionDiagnostic(options, resolution) && !options.noResolve && i < file.imports.length && !elideImport;
|
||||
|
||||
if (elideImport) {
|
||||
@ -1571,8 +1572,9 @@ namespace ts {
|
||||
|
||||
/* @internal */
|
||||
/**
|
||||
* Returns a DiagnosticMessage if we can't use a resolved module due to its extension.
|
||||
* Returns a DiagnosticMessage if we won't include a resolved module due to its extension.
|
||||
* The DiagnosticMessage's parameters are the imported module name, and the filename it resolved to.
|
||||
* This returns a diagnostic even if the module will be an untyped module.
|
||||
*/
|
||||
export function getResolutionDiagnostic(options: CompilerOptions, { extension }: ResolvedModule): DiagnosticMessage | undefined {
|
||||
switch (extension) {
|
||||
|
||||
@ -406,8 +406,9 @@ namespace ts {
|
||||
((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral || isGlobalScopeAugmentation(<ModuleDeclaration>node));
|
||||
}
|
||||
|
||||
export function isShorthandAmbientModuleSymbol(moduleSymbol: Symbol): boolean {
|
||||
return isShorthandAmbientModule(moduleSymbol.valueDeclaration);
|
||||
/** Given a symbol for a module, checks that it is either an untyped import or a shorthand ambient module. */
|
||||
export function isUntypedModuleSymbol(moduleSymbol: Symbol): boolean {
|
||||
return !moduleSymbol.valueDeclaration || isShorthandAmbientModule(moduleSymbol.valueDeclaration);
|
||||
}
|
||||
|
||||
function isShorthandAmbientModule(node: Node): boolean {
|
||||
|
||||
@ -1108,22 +1108,7 @@ namespace Harness {
|
||||
const option = getCommandLineOption(name);
|
||||
if (option) {
|
||||
const errors: ts.Diagnostic[] = [];
|
||||
switch (option.type) {
|
||||
case "boolean":
|
||||
options[option.name] = value.toLowerCase() === "true";
|
||||
break;
|
||||
case "string":
|
||||
options[option.name] = value;
|
||||
break;
|
||||
// If not a primitive, the possible types are specified in what is effectively a map of options.
|
||||
case "list":
|
||||
options[option.name] = ts.parseListTypeOption(<ts.CommandLineOptionOfListType>option, value, errors);
|
||||
break;
|
||||
default:
|
||||
options[option.name] = ts.parseCustomTypeOption(<ts.CommandLineOptionOfCustomType>option, value, errors);
|
||||
break;
|
||||
}
|
||||
|
||||
options[option.name] = optionValue(option, value, errors);
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`Unknown value '${value}' for compiler option '${name}'.`);
|
||||
}
|
||||
@ -1135,6 +1120,27 @@ namespace Harness {
|
||||
}
|
||||
}
|
||||
|
||||
function optionValue(option: ts.CommandLineOption, value: string, errors: ts.Diagnostic[]): any {
|
||||
switch (option.type) {
|
||||
case "boolean":
|
||||
return value.toLowerCase() === "true";
|
||||
case "string":
|
||||
return value;
|
||||
case "number": {
|
||||
const number = parseInt(value, 10);
|
||||
if (isNaN(number)) {
|
||||
throw new Error(`Value must be a number, got: ${JSON.stringify(value)}`);
|
||||
}
|
||||
return number;
|
||||
}
|
||||
// If not a primitive, the possible types are specified in what is effectively a map of options.
|
||||
case "list":
|
||||
return ts.parseListTypeOption(<ts.CommandLineOptionOfListType>option, value, errors);
|
||||
default:
|
||||
return ts.parseCustomTypeOption(<ts.CommandLineOptionOfCustomType>option, value, errors);
|
||||
}
|
||||
}
|
||||
|
||||
export interface TestFile {
|
||||
unitName: string;
|
||||
content: string;
|
||||
|
||||
37
tests/baselines/reference/untypedModuleImport.js
Normal file
37
tests/baselines/reference/untypedModuleImport.js
Normal file
@ -0,0 +1,37 @@
|
||||
//// [tests/cases/conformance/moduleResolution/untypedModuleImport.ts] ////
|
||||
|
||||
//// [index.js]
|
||||
// This tests that importing from a JS file globally works in an untyped way.
|
||||
// (Assuming we don't have `--noImplicitAny` or `--allowJs`.)
|
||||
|
||||
This file is not processed.
|
||||
|
||||
//// [a.ts]
|
||||
import * as foo from "foo";
|
||||
foo.bar();
|
||||
|
||||
//// [b.ts]
|
||||
import foo = require("foo");
|
||||
foo();
|
||||
|
||||
//// [c.ts]
|
||||
import foo, { bar } from "foo";
|
||||
import "./a";
|
||||
import "./b";
|
||||
foo(bar());
|
||||
|
||||
|
||||
//// [a.js]
|
||||
"use strict";
|
||||
var foo = require("foo");
|
||||
foo.bar();
|
||||
//// [b.js]
|
||||
"use strict";
|
||||
var foo = require("foo");
|
||||
foo();
|
||||
//// [c.js]
|
||||
"use strict";
|
||||
var foo_1 = require("foo");
|
||||
require("./a");
|
||||
require("./b");
|
||||
foo_1["default"](foo_1.bar());
|
||||
25
tests/baselines/reference/untypedModuleImport.symbols
Normal file
25
tests/baselines/reference/untypedModuleImport.symbols
Normal file
@ -0,0 +1,25 @@
|
||||
=== /c.ts ===
|
||||
import foo, { bar } from "foo";
|
||||
>foo : Symbol(foo, Decl(c.ts, 0, 6))
|
||||
>bar : Symbol(bar, Decl(c.ts, 0, 13))
|
||||
|
||||
import "./a";
|
||||
import "./b";
|
||||
foo(bar());
|
||||
>foo : Symbol(foo, Decl(c.ts, 0, 6))
|
||||
>bar : Symbol(bar, Decl(c.ts, 0, 13))
|
||||
|
||||
=== /a.ts ===
|
||||
import * as foo from "foo";
|
||||
>foo : Symbol(foo, Decl(a.ts, 0, 6))
|
||||
|
||||
foo.bar();
|
||||
>foo : Symbol(foo, Decl(a.ts, 0, 6))
|
||||
|
||||
=== /b.ts ===
|
||||
import foo = require("foo");
|
||||
>foo : Symbol(foo, Decl(b.ts, 0, 0))
|
||||
|
||||
foo();
|
||||
>foo : Symbol(foo, Decl(b.ts, 0, 0))
|
||||
|
||||
31
tests/baselines/reference/untypedModuleImport.types
Normal file
31
tests/baselines/reference/untypedModuleImport.types
Normal file
@ -0,0 +1,31 @@
|
||||
=== /c.ts ===
|
||||
import foo, { bar } from "foo";
|
||||
>foo : any
|
||||
>bar : any
|
||||
|
||||
import "./a";
|
||||
import "./b";
|
||||
foo(bar());
|
||||
>foo(bar()) : any
|
||||
>foo : any
|
||||
>bar() : any
|
||||
>bar : any
|
||||
|
||||
=== /a.ts ===
|
||||
import * as foo from "foo";
|
||||
>foo : any
|
||||
|
||||
foo.bar();
|
||||
>foo.bar() : any
|
||||
>foo.bar : any
|
||||
>foo : any
|
||||
>bar : any
|
||||
|
||||
=== /b.ts ===
|
||||
import foo = require("foo");
|
||||
>foo : any
|
||||
|
||||
foo();
|
||||
>foo() : any
|
||||
>foo : any
|
||||
|
||||
16
tests/baselines/reference/untypedModuleImport_allowJs.js
Normal file
16
tests/baselines/reference/untypedModuleImport_allowJs.js
Normal file
@ -0,0 +1,16 @@
|
||||
//// [tests/cases/conformance/moduleResolution/untypedModuleImport_allowJs.ts] ////
|
||||
|
||||
//// [index.js]
|
||||
// Same as untypedModuleImport.ts but with --allowJs, so the package will actually be typed.
|
||||
|
||||
exports.default = { bar() { return 0; } }
|
||||
|
||||
//// [a.ts]
|
||||
import foo from "foo";
|
||||
foo.bar();
|
||||
|
||||
|
||||
//// [a.js]
|
||||
"use strict";
|
||||
var foo_1 = require("foo");
|
||||
foo_1["default"].bar();
|
||||
@ -0,0 +1,17 @@
|
||||
=== /a.ts ===
|
||||
import foo from "foo";
|
||||
>foo : Symbol(foo, Decl(a.ts, 0, 6))
|
||||
|
||||
foo.bar();
|
||||
>foo.bar : Symbol(bar, Decl(index.js, 2, 19))
|
||||
>foo : Symbol(foo, Decl(a.ts, 0, 6))
|
||||
>bar : Symbol(bar, Decl(index.js, 2, 19))
|
||||
|
||||
=== /node_modules/foo/index.js ===
|
||||
// Same as untypedModuleImport.ts but with --allowJs, so the package will actually be typed.
|
||||
|
||||
exports.default = { bar() { return 0; } }
|
||||
>exports : Symbol(default, Decl(index.js, 0, 0))
|
||||
>default : Symbol(default, Decl(index.js, 0, 0))
|
||||
>bar : Symbol(bar, Decl(index.js, 2, 19))
|
||||
|
||||
22
tests/baselines/reference/untypedModuleImport_allowJs.types
Normal file
22
tests/baselines/reference/untypedModuleImport_allowJs.types
Normal file
@ -0,0 +1,22 @@
|
||||
=== /a.ts ===
|
||||
import foo from "foo";
|
||||
>foo : { bar(): number; }
|
||||
|
||||
foo.bar();
|
||||
>foo.bar() : number
|
||||
>foo.bar : () => number
|
||||
>foo : { bar(): number; }
|
||||
>bar : () => number
|
||||
|
||||
=== /node_modules/foo/index.js ===
|
||||
// Same as untypedModuleImport.ts but with --allowJs, so the package will actually be typed.
|
||||
|
||||
exports.default = { bar() { return 0; } }
|
||||
>exports.default = { bar() { return 0; } } : { bar(): number; }
|
||||
>exports.default : any
|
||||
>exports : any
|
||||
>default : any
|
||||
>{ bar() { return 0; } } : { bar(): number; }
|
||||
>bar : () => number
|
||||
>0 : 0
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
/a.ts(1,22): error TS6144: A package for 'foo' was found at '/node_modules/foo/index.js', but is untyped. Because '--noImplicitAny' is enabled, this package must have a declaration.
|
||||
|
||||
|
||||
==== /a.ts (1 errors) ====
|
||||
import * as foo from "foo";
|
||||
~~~~~
|
||||
!!! error TS6144: A package for 'foo' was found at '/node_modules/foo/index.js', but is untyped. Because '--noImplicitAny' is enabled, this package must have a declaration.
|
||||
|
||||
==== /node_modules/foo/index.js (0 errors) ====
|
||||
// This tests that `--noImplicitAny` disables untyped modules.
|
||||
|
||||
This file is not processed.
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
//// [tests/cases/conformance/moduleResolution/untypedModuleImport_noImplicitAny.ts] ////
|
||||
|
||||
//// [index.js]
|
||||
// This tests that `--noImplicitAny` disables untyped modules.
|
||||
|
||||
This file is not processed.
|
||||
|
||||
//// [a.ts]
|
||||
import * as foo from "foo";
|
||||
|
||||
|
||||
//// [a.js]
|
||||
"use strict";
|
||||
@ -0,0 +1,13 @@
|
||||
/a.ts(1,22): error TS6143: Module './foo' was resolved to '/foo.js', but '--allowJs' is not set.
|
||||
|
||||
|
||||
==== /a.ts (1 errors) ====
|
||||
import * as foo from "./foo";
|
||||
~~~~~~~
|
||||
!!! error TS6143: Module './foo' was resolved to '/foo.js', but '--allowJs' is not set.
|
||||
|
||||
==== /foo.js (0 errors) ====
|
||||
// This tests that untyped module imports don't happen with local imports.
|
||||
|
||||
This file is not processed.
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
//// [tests/cases/conformance/moduleResolution/untypedModuleImport_noLocalImports.ts] ////
|
||||
|
||||
//// [foo.js]
|
||||
// This tests that untyped module imports don't happen with local imports.
|
||||
|
||||
This file is not processed.
|
||||
|
||||
//// [a.ts]
|
||||
import * as foo from "./foo";
|
||||
|
||||
|
||||
//// [a.js]
|
||||
"use strict";
|
||||
23
tests/baselines/reference/untypedModuleImport_vsAmbient.js
Normal file
23
tests/baselines/reference/untypedModuleImport_vsAmbient.js
Normal file
@ -0,0 +1,23 @@
|
||||
//// [tests/cases/conformance/moduleResolution/untypedModuleImport_vsAmbient.ts] ////
|
||||
|
||||
//// [index.js]
|
||||
// This tests that an ambient module declaration overrides an untyped import.
|
||||
|
||||
This file is not processed.
|
||||
|
||||
//// [declarations.d.ts]
|
||||
declare module "foo" {
|
||||
export const x: number;
|
||||
}
|
||||
|
||||
//// [a.ts]
|
||||
/// <reference path="./declarations.d.ts" />
|
||||
import { x } from "foo";
|
||||
x;
|
||||
|
||||
|
||||
//// [a.js]
|
||||
"use strict";
|
||||
/// <reference path="./declarations.d.ts" />
|
||||
var foo_1 = require("foo");
|
||||
foo_1.x;
|
||||
@ -0,0 +1,14 @@
|
||||
=== /a.ts ===
|
||||
/// <reference path="./declarations.d.ts" />
|
||||
import { x } from "foo";
|
||||
>x : Symbol(x, Decl(a.ts, 1, 8))
|
||||
|
||||
x;
|
||||
>x : Symbol(x, Decl(a.ts, 1, 8))
|
||||
|
||||
=== /declarations.d.ts ===
|
||||
declare module "foo" {
|
||||
export const x: number;
|
||||
>x : Symbol(x, Decl(declarations.d.ts, 1, 16))
|
||||
}
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
=== /a.ts ===
|
||||
/// <reference path="./declarations.d.ts" />
|
||||
import { x } from "foo";
|
||||
>x : number
|
||||
|
||||
x;
|
||||
>x : number
|
||||
|
||||
=== /declarations.d.ts ===
|
||||
declare module "foo" {
|
||||
export const x: number;
|
||||
>x : number
|
||||
}
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
// @noImplicitReferences: true
|
||||
// @currentDirectory: /
|
||||
// This tests that importing from a JS file globally works in an untyped way.
|
||||
// (Assuming we don't have `--noImplicitAny` or `--allowJs`.)
|
||||
|
||||
// @filename: /node_modules/foo/index.js
|
||||
This file is not processed.
|
||||
|
||||
// @filename: /a.ts
|
||||
import * as foo from "foo";
|
||||
foo.bar();
|
||||
|
||||
// @filename: /b.ts
|
||||
import foo = require("foo");
|
||||
foo();
|
||||
|
||||
// @filename: /c.ts
|
||||
import foo, { bar } from "foo";
|
||||
import "./a";
|
||||
import "./b";
|
||||
foo(bar());
|
||||
@ -0,0 +1,12 @@
|
||||
// @noImplicitReferences: true
|
||||
// @currentDirectory: /
|
||||
// @allowJs: true
|
||||
// @maxNodeModuleJsDepth: 1
|
||||
// Same as untypedModuleImport.ts but with --allowJs, so the package will actually be typed.
|
||||
|
||||
// @filename: /node_modules/foo/index.js
|
||||
exports.default = { bar() { return 0; } }
|
||||
|
||||
// @filename: /a.ts
|
||||
import foo from "foo";
|
||||
foo.bar();
|
||||
@ -0,0 +1,10 @@
|
||||
// @noImplicitReferences: true
|
||||
// @currentDirectory: /
|
||||
// @noImplicitAny: true
|
||||
// This tests that `--noImplicitAny` disables untyped modules.
|
||||
|
||||
// @filename: /node_modules/foo/index.js
|
||||
This file is not processed.
|
||||
|
||||
// @filename: /a.ts
|
||||
import * as foo from "foo";
|
||||
@ -0,0 +1,9 @@
|
||||
// @noImplicitReferences: true
|
||||
// @currentDirectory: /
|
||||
// This tests that untyped module imports don't happen with local imports.
|
||||
|
||||
// @filename: /foo.js
|
||||
This file is not processed.
|
||||
|
||||
// @filename: /a.ts
|
||||
import * as foo from "./foo";
|
||||
@ -0,0 +1,16 @@
|
||||
// @noImplicitReferences: true
|
||||
// @currentDirectory: /
|
||||
// This tests that an ambient module declaration overrides an untyped import.
|
||||
|
||||
// @filename: /node_modules/foo/index.js
|
||||
This file is not processed.
|
||||
|
||||
// @filename: /declarations.d.ts
|
||||
declare module "foo" {
|
||||
export const x: number;
|
||||
}
|
||||
|
||||
// @filename: /a.ts
|
||||
/// <reference path="./declarations.d.ts" />
|
||||
import { x } from "foo";
|
||||
x;
|
||||
22
tests/cases/fourslash/untypedModuleImport.ts
Normal file
22
tests/cases/fourslash/untypedModuleImport.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @Filename: node_modules/foo/index.js
|
||||
////{}
|
||||
|
||||
// @Filename: a.ts
|
||||
////import /*foo*/[|foo|] from /*fooModule*/"foo";
|
||||
////[|foo|]();
|
||||
|
||||
goTo.file("a.ts");
|
||||
debug.printErrorList();
|
||||
verify.numberOfErrorsInCurrentFile(0);
|
||||
|
||||
goTo.marker("fooModule");
|
||||
verify.goToDefinitionIs([]);
|
||||
verify.quickInfoIs('module "foo"');
|
||||
verify.referencesAre([])
|
||||
|
||||
goTo.marker("foo");
|
||||
verify.goToDefinitionIs([]);
|
||||
verify.quickInfoIs("import foo");
|
||||
verify.rangesReferenceEachOther();
|
||||
Loading…
x
Reference in New Issue
Block a user