Skip to content

Commit 3014dec

Browse files
authoredSep 19, 2022
Don't elide imports when transforming JS files (#50404)
* don't elide imports in JS files * WIP: get rid of caching of resolved symbol, add transform tests * get rid of caching only for resolver functions * use getReferencedSymbol instead of getReferencedValueSymbol in module transform * WIP: add reportErrors flag to resolveName * Import transformations now work correctly * don't emit diagnostics when looking up referenced symbol * small fixes and get rid of unnecessary comments * update tests * clean up * CR: use nameNotFoundMessage to decide whether to report errors in resolveName
1 parent 57c7aa7 commit 3014dec

23 files changed

+783
-12
lines changed
 

‎src/compiler/checker.ts

+36-6
Original file line numberDiff line numberDiff line change
@@ -1831,6 +1831,7 @@ namespace ts {
18311831
* the nameNotFoundMessage argument is not undefined. Returns the resolved symbol, or undefined if no symbol with
18321832
* the given name can be found.
18331833
*
1834+
* @param nameNotFoundMessage If defined, we will report errors found during resolve.
18341835
* @param isUse If true, this will count towards --noUnusedLocals / --noUnusedParameters.
18351836
*/
18361837
function resolveName(
@@ -1841,8 +1842,8 @@ namespace ts {
18411842
nameArg: __String | Identifier | undefined,
18421843
isUse: boolean,
18431844
excludeGlobals = false,
1844-
getSpellingSuggstions = true): Symbol | undefined {
1845-
return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSpellingSuggstions, getSymbol);
1845+
getSpellingSuggestions = true): Symbol | undefined {
1846+
return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSpellingSuggestions, getSymbol);
18461847
}
18471848

18481849
function resolveNameHelper(
@@ -2015,7 +2016,9 @@ namespace ts {
20152016
// TypeScript 1.0 spec (April 2014): 3.4.1
20162017
// The scope of a type parameter extends over the entire declaration with which the type
20172018
// parameter list is associated, with the exception of static member declarations in classes.
2018-
error(errorLocation, Diagnostics.Static_members_cannot_reference_class_type_parameters);
2019+
if (nameNotFoundMessage) {
2020+
error(errorLocation, Diagnostics.Static_members_cannot_reference_class_type_parameters);
2021+
}
20192022
return undefined;
20202023
}
20212024
break loop;
@@ -2053,7 +2056,9 @@ namespace ts {
20532056
if (isClassLike(grandparent) || grandparent.kind === SyntaxKind.InterfaceDeclaration) {
20542057
// A reference to this grandparent's type parameters would be an error
20552058
if (result = lookup(getSymbolOfNode(grandparent as ClassLikeDeclaration | InterfaceDeclaration).members!, name, meaning & SymbolFlags.Type)) {
2056-
error(errorLocation, Diagnostics.A_computed_property_name_cannot_reference_a_type_parameter_from_its_containing_type);
2059+
if (nameNotFoundMessage) {
2060+
error(errorLocation, Diagnostics.A_computed_property_name_cannot_reference_a_type_parameter_from_its_containing_type);
2061+
}
20572062
return undefined;
20582063
}
20592064
}
@@ -2263,7 +2268,7 @@ namespace ts {
22632268
}
22642269
return undefined;
22652270
}
2266-
else if (checkAndReportErrorForInvalidInitializer()) {
2271+
else if (nameNotFoundMessage && checkAndReportErrorForInvalidInitializer()) {
22672272
return undefined;
22682273
}
22692274

@@ -43283,7 +43288,8 @@ namespace ts {
4328343288
}
4328443289
const node = getParseTreeNode(nodeIn, isIdentifier);
4328543290
if (node) {
43286-
const symbol = getReferencedValueSymbol(node);
43291+
const symbol = getReferencedValueOrAliasSymbol(node);
43292+
4328743293
// We should only get the declaration of an alias if there isn't a local value
4328843294
// declaration for the symbol
4328943295
if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !getTypeOnlyAliasDeclaration(symbol)) {
@@ -43687,6 +43693,30 @@ namespace ts {
4368743693
return resolveName(location, reference.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true);
4368843694
}
4368943695

43696+
/**
43697+
* Get either a value-meaning symbol or an alias symbol.
43698+
* Unlike `getReferencedValueSymbol`, if the cached resolved symbol is the unknown symbol,
43699+
* we call `resolveName` to find a symbol.
43700+
* This is because when caching the resolved symbol, we only consider value symbols, but here
43701+
* we want to also get an alias symbol if one exists.
43702+
*/
43703+
function getReferencedValueOrAliasSymbol(reference: Identifier): Symbol | undefined {
43704+
const resolvedSymbol = getNodeLinks(reference).resolvedSymbol;
43705+
if (resolvedSymbol && resolvedSymbol !== unknownSymbol) {
43706+
return resolvedSymbol;
43707+
}
43708+
43709+
return resolveName(
43710+
reference,
43711+
reference.escapedText,
43712+
SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias,
43713+
/*nodeNotFoundMessage*/ undefined,
43714+
/*nameArg*/ undefined,
43715+
/*isUse*/ true,
43716+
/*excludeGlobals*/ undefined,
43717+
/*getSpellingSuggestions*/ undefined);
43718+
}
43719+
4369043720
function getReferencedValueDeclaration(referenceIn: Identifier): Declaration | undefined {
4369143721
if (!isGeneratedIdentifier(referenceIn)) {
4369243722
const reference = getParseTreeNode(referenceIn, isIdentifier);

‎src/compiler/transformers/ts.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -2463,9 +2463,11 @@ namespace ts {
24632463
}
24642464

24652465
function shouldEmitAliasDeclaration(node: Node): boolean {
2466-
return compilerOptions.preserveValueImports
2467-
? resolver.isValueAliasDeclaration(node)
2468-
: resolver.isReferencedAliasDeclaration(node);
2466+
return isInJSFile(node)
2467+
? true
2468+
: compilerOptions.preserveValueImports
2469+
? resolver.isValueAliasDeclaration(node)
2470+
: resolver.isReferencedAliasDeclaration(node);
24692471
}
24702472
}
24712473
}

‎tests/baselines/reference/allowImportClausesToMergeWithTypes.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ b_1["default"];
4545
"use strict";
4646
exports.__esModule = true;
4747
var x = { x: "" };
48-
zzz;
48+
a_1["default"];
4949
var b_1 = require("./b");
5050
b_1["default"];
5151
var y = x;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
tests/cases/compiler/caller.js(1,21): error TS2307: Cannot find module 'fs' or its corresponding type declarations.
2+
tests/cases/compiler/caller.js(2,8): error TS18042: 'TruffleContract' is a type and cannot be imported in JavaScript files. Use 'import("@truffle/contract").TruffleContract' in a JSDoc type annotation.
3+
tests/cases/compiler/caller.js(4,43): error TS2708: Cannot use namespace 'TruffleContract' as a value.
4+
tests/cases/compiler/caller.js(4,60): error TS2708: Cannot use namespace 'TruffleContract' as a value.
5+
6+
7+
==== tests/cases/compiler/caller.js (4 errors) ====
8+
import * as fs from 'fs';
9+
~~~~
10+
!!! error TS2307: Cannot find module 'fs' or its corresponding type declarations.
11+
import TruffleContract from '@truffle/contract'; // Runtime err: this import is elided in transform
12+
~~~~~~~~~~~~~~~
13+
!!! error TS18042: 'TruffleContract' is a type and cannot be imported in JavaScript files. Use 'import("@truffle/contract").TruffleContract' in a JSDoc type annotation.
14+
console.log(fs);
15+
console.log('TruffleContract is ', typeof TruffleContract, TruffleContract); // `TruffleContract` is considered 'unused'
16+
~~~~~~~~~~~~~~~
17+
!!! error TS2708: Cannot use namespace 'TruffleContract' as a value.
18+
~~~~~~~~~~~~~~~
19+
!!! error TS2708: Cannot use namespace 'TruffleContract' as a value.
20+
21+
22+
==== tests/cases/compiler/node_modules/@truffle/contract/index.d.ts (0 errors) ====
23+
declare module "@truffle/contract" {
24+
interface ContractObject {
25+
foo: number;
26+
}
27+
namespace TruffleContract {
28+
export type Contract = ContractObject;
29+
}
30+
export default TruffleContract;
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//// [tests/cases/compiler/elidedJSImport1.ts] ////
2+
3+
//// [caller.js]
4+
import * as fs from 'fs';
5+
import TruffleContract from '@truffle/contract'; // Runtime err: this import is elided in transform
6+
console.log(fs);
7+
console.log('TruffleContract is ', typeof TruffleContract, TruffleContract); // `TruffleContract` is considered 'unused'
8+
9+
10+
//// [index.d.ts]
11+
declare module "@truffle/contract" {
12+
interface ContractObject {
13+
foo: number;
14+
}
15+
namespace TruffleContract {
16+
export type Contract = ContractObject;
17+
}
18+
export default TruffleContract;
19+
}
20+
21+
//// [caller.js]
22+
import * as fs from 'fs';
23+
import TruffleContract from '@truffle/contract'; // Runtime err: this import is elided in transform
24+
console.log(fs);
25+
console.log('TruffleContract is ', typeof TruffleContract, TruffleContract); // `TruffleContract` is considered 'unused'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
=== tests/cases/compiler/caller.js ===
2+
import * as fs from 'fs';
3+
>fs : Symbol(fs, Decl(caller.js, 0, 6))
4+
5+
import TruffleContract from '@truffle/contract'; // Runtime err: this import is elided in transform
6+
>TruffleContract : Symbol(TruffleContract, Decl(caller.js, 1, 6))
7+
8+
console.log(fs);
9+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
10+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
11+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
12+
>fs : Symbol(fs, Decl(caller.js, 0, 6))
13+
14+
console.log('TruffleContract is ', typeof TruffleContract, TruffleContract); // `TruffleContract` is considered 'unused'
15+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
16+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
17+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
18+
19+
20+
=== tests/cases/compiler/node_modules/@truffle/contract/index.d.ts ===
21+
declare module "@truffle/contract" {
22+
>"@truffle/contract" : Symbol("@truffle/contract", Decl(index.d.ts, 0, 0))
23+
24+
interface ContractObject {
25+
>ContractObject : Symbol(ContractObject, Decl(index.d.ts, 0, 36))
26+
27+
foo: number;
28+
>foo : Symbol(ContractObject.foo, Decl(index.d.ts, 1, 30))
29+
}
30+
namespace TruffleContract {
31+
>TruffleContract : Symbol(TruffleContract, Decl(index.d.ts, 3, 5))
32+
33+
export type Contract = ContractObject;
34+
>Contract : Symbol(Contract, Decl(index.d.ts, 4, 31))
35+
>ContractObject : Symbol(ContractObject, Decl(index.d.ts, 0, 36))
36+
}
37+
export default TruffleContract;
38+
>TruffleContract : Symbol(TruffleContract, Decl(index.d.ts, 3, 5))
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
=== tests/cases/compiler/caller.js ===
2+
import * as fs from 'fs';
3+
>fs : any
4+
5+
import TruffleContract from '@truffle/contract'; // Runtime err: this import is elided in transform
6+
>TruffleContract : any
7+
8+
console.log(fs);
9+
>console.log(fs) : void
10+
>console.log : (...data: any[]) => void
11+
>console : Console
12+
>log : (...data: any[]) => void
13+
>fs : any
14+
15+
console.log('TruffleContract is ', typeof TruffleContract, TruffleContract); // `TruffleContract` is considered 'unused'
16+
>console.log('TruffleContract is ', typeof TruffleContract, TruffleContract) : void
17+
>console.log : (...data: any[]) => void
18+
>console : Console
19+
>log : (...data: any[]) => void
20+
>'TruffleContract is ' : "TruffleContract is "
21+
>typeof TruffleContract : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
22+
>TruffleContract : any
23+
>TruffleContract : any
24+
25+
26+
=== tests/cases/compiler/node_modules/@truffle/contract/index.d.ts ===
27+
declare module "@truffle/contract" {
28+
>"@truffle/contract" : typeof import("@truffle/contract")
29+
30+
interface ContractObject {
31+
foo: number;
32+
>foo : number
33+
}
34+
namespace TruffleContract {
35+
export type Contract = ContractObject;
36+
>Contract : ContractObject
37+
}
38+
export default TruffleContract;
39+
>TruffleContract : any
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//// [tests/cases/compiler/elidedJSImport2.ts] ////
2+
3+
//// [index.js]
4+
import { Foo } from "./other.js";
5+
import * as other from "./other.js";
6+
import defaultFoo from "./other.js";
7+
8+
const x = new Foo();
9+
const y = other.Foo();
10+
const z = new defaultFoo();
11+
12+
//// [other.d.ts]
13+
export interface Foo {
14+
bar: number;
15+
}
16+
17+
export default interface Bar {
18+
foo: number;
19+
}
20+
21+
//// [other.js]
22+
export class Foo {
23+
bar = 2.4;
24+
}
25+
26+
export default class Bar {
27+
foo = 1.2;
28+
}
29+
30+
31+
//// [index.js]
32+
"use strict";
33+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
34+
if (k2 === undefined) k2 = k;
35+
var desc = Object.getOwnPropertyDescriptor(m, k);
36+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
37+
desc = { enumerable: true, get: function() { return m[k]; } };
38+
}
39+
Object.defineProperty(o, k2, desc);
40+
}) : (function(o, m, k, k2) {
41+
if (k2 === undefined) k2 = k;
42+
o[k2] = m[k];
43+
}));
44+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
45+
Object.defineProperty(o, "default", { enumerable: true, value: v });
46+
}) : function(o, v) {
47+
o["default"] = v;
48+
});
49+
var __importStar = (this && this.__importStar) || function (mod) {
50+
if (mod && mod.__esModule) return mod;
51+
var result = {};
52+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
53+
__setModuleDefault(result, mod);
54+
return result;
55+
};
56+
var __importDefault = (this && this.__importDefault) || function (mod) {
57+
return (mod && mod.__esModule) ? mod : { "default": mod };
58+
};
59+
exports.__esModule = true;
60+
var other_js_1 = require("./other.js");
61+
var other = __importStar(require("./other.js"));
62+
var other_js_2 = __importDefault(require("./other.js"));
63+
var x = new other_js_1.Foo();
64+
var y = other.Foo();
65+
var z = new other_js_2["default"]();
66+
//// [other.js]
67+
"use strict";
68+
exports.__esModule = true;
69+
exports.Foo = void 0;
70+
var Foo = /** @class */ (function () {
71+
function Foo() {
72+
this.bar = 2.4;
73+
}
74+
return Foo;
75+
}());
76+
exports.Foo = Foo;
77+
var Bar = /** @class */ (function () {
78+
function Bar() {
79+
this.foo = 1.2;
80+
}
81+
return Bar;
82+
}());
83+
exports["default"] = Bar;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
=== tests/cases/compiler/index.js ===
2+
import { Foo } from "./other.js";
3+
>Foo : Symbol(Foo, Decl(index.js, 0, 8))
4+
5+
import * as other from "./other.js";
6+
>other : Symbol(other, Decl(index.js, 1, 6))
7+
8+
import defaultFoo from "./other.js";
9+
>defaultFoo : Symbol(defaultFoo, Decl(index.js, 2, 6))
10+
11+
const x = new Foo();
12+
>x : Symbol(x, Decl(index.js, 4, 5))
13+
14+
const y = other.Foo();
15+
>y : Symbol(y, Decl(index.js, 5, 5))
16+
>other : Symbol(other, Decl(index.js, 1, 6))
17+
18+
const z = new defaultFoo();
19+
>z : Symbol(z, Decl(index.js, 6, 5))
20+
21+
=== tests/cases/compiler/other.d.ts ===
22+
export interface Foo {
23+
>Foo : Symbol(Foo, Decl(other.d.ts, 0, 0))
24+
25+
bar: number;
26+
>bar : Symbol(Foo.bar, Decl(other.d.ts, 0, 22))
27+
}
28+
29+
export default interface Bar {
30+
>Bar : Symbol(Bar, Decl(other.d.ts, 2, 1))
31+
32+
foo: number;
33+
>foo : Symbol(Bar.foo, Decl(other.d.ts, 4, 30))
34+
}
35+
36+
=== tests/cases/compiler/other.js ===
37+
export class Foo {
38+
>Foo : Symbol(Foo, Decl(other.js, 0, 0))
39+
40+
bar = 2.4;
41+
>bar : Symbol(Foo.bar, Decl(other.js, 0, 18))
42+
}
43+
44+
export default class Bar {
45+
>Bar : Symbol(Bar, Decl(other.js, 2, 1))
46+
47+
foo = 1.2;
48+
>foo : Symbol(Bar.foo, Decl(other.js, 4, 26))
49+
}
50+

0 commit comments

Comments
 (0)
Please sign in to comment.