forked from microsoft/TypeScript
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add isolatedModules error for ambiguous imports referenced in decorat…
…or metadata (microsoft#42915) * Add isolatedModules error for ambiguous imports referenced in decorator metadata * Improve test and accept baselines * Error only for es2015+ * Add namespace import to error message as workaround * Add codefix * Fix merge fallout
- Loading branch information
1 parent
b996287
commit df1faa0
Showing
20 changed files
with
1,175 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
src/services/codefixes/fixUnreferenceableDecoratorMetadata.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/* @internal */ | ||
namespace ts.codefix { | ||
const fixId = "fixUnreferenceableDecoratorMetadata"; | ||
const errorCodes = [Diagnostics.A_type_referenced_in_a_decorated_signature_must_be_imported_with_import_type_or_a_namespace_import_when_isolatedModules_and_emitDecoratorMetadata_are_enabled.code]; | ||
registerCodeFix({ | ||
errorCodes, | ||
getCodeActions: context => { | ||
const importDeclaration = getImportDeclaration(context.sourceFile, context.program, context.span.start); | ||
if (!importDeclaration) return; | ||
|
||
const namespaceChanges = textChanges.ChangeTracker.with(context, t => importDeclaration.kind === SyntaxKind.ImportSpecifier && doNamespaceImportChange(t, context.sourceFile, importDeclaration, context.program)); | ||
const typeOnlyChanges = textChanges.ChangeTracker.with(context, t => doTypeOnlyImportChange(t, context.sourceFile, importDeclaration, context.program)); | ||
let actions: CodeFixAction[] | undefined; | ||
if (namespaceChanges.length) { | ||
actions = append(actions, createCodeFixActionWithoutFixAll(fixId, namespaceChanges, Diagnostics.Convert_named_imports_to_namespace_import)); | ||
} | ||
if (typeOnlyChanges.length) { | ||
actions = append(actions, createCodeFixActionWithoutFixAll(fixId, typeOnlyChanges, Diagnostics.Convert_to_type_only_import)); | ||
} | ||
return actions; | ||
}, | ||
fixIds: [fixId], | ||
}); | ||
|
||
function getImportDeclaration(sourceFile: SourceFile, program: Program, start: number): ImportClause | ImportSpecifier | ImportEqualsDeclaration | undefined { | ||
const identifier = tryCast(getTokenAtPosition(sourceFile, start), isIdentifier); | ||
if (!identifier || identifier.parent.kind !== SyntaxKind.TypeReference) return; | ||
|
||
const checker = program.getTypeChecker(); | ||
const symbol = checker.getSymbolAtLocation(identifier); | ||
return find(symbol?.declarations || emptyArray, or(isImportClause, isImportSpecifier, isImportEqualsDeclaration) as (n: Node) => n is ImportClause | ImportSpecifier | ImportEqualsDeclaration); | ||
} | ||
|
||
// Converts the import declaration of the offending import to a type-only import, | ||
// only if it can be done without affecting other imported names. If the conversion | ||
// cannot be done cleanly, we could offer to *extract* the offending import to a | ||
// new type-only import declaration, but honestly I doubt anyone will ever use this | ||
// codefix at all, so it's probably not worth the lines of code. | ||
function doTypeOnlyImportChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, importDeclaration: ImportClause | ImportSpecifier | ImportEqualsDeclaration, program: Program) { | ||
if (importDeclaration.kind === SyntaxKind.ImportEqualsDeclaration) { | ||
changes.insertModifierBefore(sourceFile, SyntaxKind.TypeKeyword, importDeclaration.name); | ||
return; | ||
} | ||
|
||
const importClause = importDeclaration.kind === SyntaxKind.ImportClause ? importDeclaration : importDeclaration.parent.parent; | ||
if (importClause.name && importClause.namedBindings) { | ||
// Cannot convert an import with a default import and named bindings to type-only | ||
// (it's a grammar error). | ||
return; | ||
} | ||
|
||
const checker = program.getTypeChecker(); | ||
const importsValue = !!forEachImportClauseDeclaration(importClause, decl => { | ||
if (skipAlias(decl.symbol, checker).flags & SymbolFlags.Value) return true; | ||
}); | ||
|
||
if (importsValue) { | ||
// Assume that if someone wrote a non-type-only import that includes some values, | ||
// they intend to use those values in value positions, even if they haven't yet. | ||
// Don't convert it to type-only. | ||
return; | ||
} | ||
|
||
changes.insertModifierBefore(sourceFile, SyntaxKind.TypeKeyword, importClause); | ||
} | ||
|
||
function doNamespaceImportChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, importDeclaration: ImportSpecifier, program: Program) { | ||
refactor.doChangeNamedToNamespaceOrDefault(sourceFile, program, changes, importDeclaration.parent); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
108 changes: 108 additions & 0 deletions
108
tests/baselines/reference/emitDecoratorMetadata_isolatedModules(module=commonjs).js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
//// [tests/cases/compiler/emitDecoratorMetadata_isolatedModules.ts] //// | ||
|
||
//// [type1.ts] | ||
interface T1 {} | ||
export type { T1 } | ||
|
||
//// [type2.ts] | ||
export interface T2 {} | ||
|
||
//// [class3.ts] | ||
export class C3 {} | ||
|
||
//// [index.ts] | ||
import { T1 } from "./type1"; | ||
import * as t1 from "./type1"; | ||
import type { T2 } from "./type2"; | ||
import { C3 } from "./class3"; | ||
declare var EventListener: any; | ||
|
||
class HelloWorld { | ||
@EventListener('1') | ||
handleEvent1(event: T1) {} // Error | ||
|
||
@EventListener('2') | ||
handleEvent2(event: T2) {} // Ok | ||
|
||
@EventListener('1') | ||
p1!: T1; // Error | ||
|
||
@EventListener('1') | ||
p1_ns!: t1.T1; // Ok | ||
|
||
@EventListener('2') | ||
p2!: T2; // Ok | ||
|
||
@EventListener('3') | ||
handleEvent3(event: C3): T1 { return undefined! } // Ok, Error | ||
} | ||
|
||
|
||
//// [type1.js] | ||
"use strict"; | ||
exports.__esModule = true; | ||
//// [type2.js] | ||
"use strict"; | ||
exports.__esModule = true; | ||
//// [class3.js] | ||
"use strict"; | ||
exports.__esModule = true; | ||
exports.C3 = void 0; | ||
var C3 = /** @class */ (function () { | ||
function C3() { | ||
} | ||
return C3; | ||
}()); | ||
exports.C3 = C3; | ||
//// [index.js] | ||
"use strict"; | ||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { | ||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; | ||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); | ||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; | ||
return c > 3 && r && Object.defineProperty(target, key, r), r; | ||
}; | ||
var __metadata = (this && this.__metadata) || function (k, v) { | ||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); | ||
}; | ||
exports.__esModule = true; | ||
var t1 = require("./type1"); | ||
var class3_1 = require("./class3"); | ||
var HelloWorld = /** @class */ (function () { | ||
function HelloWorld() { | ||
} | ||
HelloWorld.prototype.handleEvent1 = function (event) { }; // Error | ||
HelloWorld.prototype.handleEvent2 = function (event) { }; // Ok | ||
HelloWorld.prototype.handleEvent3 = function (event) { return undefined; }; // Ok, Error | ||
__decorate([ | ||
EventListener('1'), | ||
__metadata("design:type", Function), | ||
__metadata("design:paramtypes", [Object]), | ||
__metadata("design:returntype", void 0) | ||
], HelloWorld.prototype, "handleEvent1"); | ||
__decorate([ | ||
EventListener('2'), | ||
__metadata("design:type", Function), | ||
__metadata("design:paramtypes", [Object]), | ||
__metadata("design:returntype", void 0) | ||
], HelloWorld.prototype, "handleEvent2"); | ||
__decorate([ | ||
EventListener('1'), | ||
__metadata("design:type", Object) | ||
], HelloWorld.prototype, "p1"); | ||
__decorate([ | ||
EventListener('1'), | ||
__metadata("design:type", Object) | ||
], HelloWorld.prototype, "p1_ns"); | ||
__decorate([ | ||
EventListener('2'), | ||
__metadata("design:type", Object) | ||
], HelloWorld.prototype, "p2"); | ||
__decorate([ | ||
EventListener('3'), | ||
__metadata("design:type", Function), | ||
__metadata("design:paramtypes", [class3_1.C3]), | ||
__metadata("design:returntype", Object) | ||
], HelloWorld.prototype, "handleEvent3"); | ||
return HelloWorld; | ||
}()); |
83 changes: 83 additions & 0 deletions
83
tests/baselines/reference/emitDecoratorMetadata_isolatedModules(module=commonjs).symbols
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
=== tests/cases/compiler/type1.ts === | ||
interface T1 {} | ||
>T1 : Symbol(T1, Decl(type1.ts, 0, 0)) | ||
|
||
export type { T1 } | ||
>T1 : Symbol(T1, Decl(type1.ts, 1, 13)) | ||
|
||
=== tests/cases/compiler/type2.ts === | ||
export interface T2 {} | ||
>T2 : Symbol(T2, Decl(type2.ts, 0, 0)) | ||
|
||
=== tests/cases/compiler/class3.ts === | ||
export class C3 {} | ||
>C3 : Symbol(C3, Decl(class3.ts, 0, 0)) | ||
|
||
=== tests/cases/compiler/index.ts === | ||
import { T1 } from "./type1"; | ||
>T1 : Symbol(T1, Decl(index.ts, 0, 8)) | ||
|
||
import * as t1 from "./type1"; | ||
>t1 : Symbol(t1, Decl(index.ts, 1, 6)) | ||
|
||
import type { T2 } from "./type2"; | ||
>T2 : Symbol(T2, Decl(index.ts, 2, 13)) | ||
|
||
import { C3 } from "./class3"; | ||
>C3 : Symbol(C3, Decl(index.ts, 3, 8)) | ||
|
||
declare var EventListener: any; | ||
>EventListener : Symbol(EventListener, Decl(index.ts, 4, 11)) | ||
|
||
class HelloWorld { | ||
>HelloWorld : Symbol(HelloWorld, Decl(index.ts, 4, 31)) | ||
|
||
@EventListener('1') | ||
>EventListener : Symbol(EventListener, Decl(index.ts, 4, 11)) | ||
|
||
handleEvent1(event: T1) {} // Error | ||
>handleEvent1 : Symbol(HelloWorld.handleEvent1, Decl(index.ts, 6, 18)) | ||
>event : Symbol(event, Decl(index.ts, 8, 15)) | ||
>T1 : Symbol(T1, Decl(index.ts, 0, 8)) | ||
|
||
@EventListener('2') | ||
>EventListener : Symbol(EventListener, Decl(index.ts, 4, 11)) | ||
|
||
handleEvent2(event: T2) {} // Ok | ||
>handleEvent2 : Symbol(HelloWorld.handleEvent2, Decl(index.ts, 8, 28)) | ||
>event : Symbol(event, Decl(index.ts, 11, 15)) | ||
>T2 : Symbol(T2, Decl(index.ts, 2, 13)) | ||
|
||
@EventListener('1') | ||
>EventListener : Symbol(EventListener, Decl(index.ts, 4, 11)) | ||
|
||
p1!: T1; // Error | ||
>p1 : Symbol(HelloWorld.p1, Decl(index.ts, 11, 28)) | ||
>T1 : Symbol(T1, Decl(index.ts, 0, 8)) | ||
|
||
@EventListener('1') | ||
>EventListener : Symbol(EventListener, Decl(index.ts, 4, 11)) | ||
|
||
p1_ns!: t1.T1; // Ok | ||
>p1_ns : Symbol(HelloWorld.p1_ns, Decl(index.ts, 14, 10)) | ||
>t1 : Symbol(t1, Decl(index.ts, 1, 6)) | ||
>T1 : Symbol(t1.T1, Decl(type1.ts, 1, 13)) | ||
|
||
@EventListener('2') | ||
>EventListener : Symbol(EventListener, Decl(index.ts, 4, 11)) | ||
|
||
p2!: T2; // Ok | ||
>p2 : Symbol(HelloWorld.p2, Decl(index.ts, 17, 16)) | ||
>T2 : Symbol(T2, Decl(index.ts, 2, 13)) | ||
|
||
@EventListener('3') | ||
>EventListener : Symbol(EventListener, Decl(index.ts, 4, 11)) | ||
|
||
handleEvent3(event: C3): T1 { return undefined! } // Ok, Error | ||
>handleEvent3 : Symbol(HelloWorld.handleEvent3, Decl(index.ts, 20, 10)) | ||
>event : Symbol(event, Decl(index.ts, 23, 15)) | ||
>C3 : Symbol(C3, Decl(index.ts, 3, 8)) | ||
>T1 : Symbol(T1, Decl(index.ts, 0, 8)) | ||
>undefined : Symbol(undefined) | ||
} | ||
|
Oops, something went wrong.