Skip to content

Convert 'installTypesForPackge' refactor to a suggestion #22267

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
4 commits merged into from
Mar 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 32 additions & 9 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ namespace ts {
node = getParseTreeNode(node, isTypeNode);
return node && getTypeArgumentConstraint(node);
},

getSuggestionDiagnostics: file => suggestionDiagnostics.get(file.fileName) || emptyArray,
};

const tupleTypes: GenericType[] = [];
Expand Down Expand Up @@ -449,6 +451,19 @@ namespace ts {
const awaitedTypeStack: number[] = [];

const diagnostics = createDiagnosticCollection();
// Suggestion diagnostics must have a file. Keyed by source file name.
const suggestionDiagnostics = createMultiMap<Diagnostic>();
function addSuggestionDiagnostic(diag: Diagnostic): void {
suggestionDiagnostics.add(diag.file.fileName, { ...diag, category: DiagnosticCategory.Suggestion });
}
function addErrorOrSuggestionDiagnostic(isError: boolean, diag: Diagnostic): void {
if (isError) {
diagnostics.add(diag);
}
else {
addSuggestionDiagnostic(diag);
}
}

const enum TypeFacts {
None = 0,
Expand Down Expand Up @@ -2072,6 +2087,9 @@ namespace ts {
const sourceFile = resolvedModule && !resolutionDiagnostic && host.getSourceFile(resolvedModule.resolvedFileName);
if (sourceFile) {
if (sourceFile.symbol) {
if (resolvedModule.isExternalLibraryImport && !extensionIsTypeScript(resolvedModule.extension)) {
addSuggestionDiagnostic(createModuleImplicitlyAnyDiagnostic(errorNode, resolvedModule, moduleReference));
}
// merged symbol is module declaration symbol combined with all augmentations
return getMergedSymbol(sourceFile.symbol);
}
Expand All @@ -2095,15 +2113,8 @@ namespace ts {
const diag = Diagnostics.Invalid_module_name_in_augmentation_Module_0_resolves_to_an_untyped_module_at_1_which_cannot_be_augmented;
error(errorNode, diag, moduleReference, resolvedModule.resolvedFileName);
}
else if (noImplicitAny && moduleNotFoundError) {
let errorInfo = resolvedModule.packageId && chainDiagnosticMessages(/*details*/ undefined,
Diagnostics.Try_npm_install_types_Slash_0_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0,
getMangledNameForScopedPackage(resolvedModule.packageId.name));
errorInfo = chainDiagnosticMessages(errorInfo,
Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type,
moduleReference,
resolvedModule.resolvedFileName);
diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode, errorInfo));
else {
addErrorOrSuggestionDiagnostic(noImplicitAny && !!moduleNotFoundError, createModuleImplicitlyAnyDiagnostic(errorNode, resolvedModule, moduleReference));
}
// Failed imports and untyped modules are both treated in an untyped manner; only difference is whether we give a diagnostic first.
return undefined;
Expand All @@ -2128,6 +2139,18 @@ namespace ts {
return undefined;
}

function createModuleImplicitlyAnyDiagnostic(errorNode: Node, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): Diagnostic {
const errorInfo = packageId && chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.Try_npm_install_types_Slash_0_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0,
getMangledNameForScopedPackage(packageId.name));
return createDiagnosticForNodeFromMessageChain(errorNode, chainDiagnosticMessages(
errorInfo,
Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type,
moduleReference,
resolvedFileName));
}

// An external module with an 'export =' declaration resolves to the target of the 'export =' declaration,
// and an external module with no 'export =' declaration resolves to the module itself.
function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol {
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2954,6 +2954,12 @@ namespace ts {
/** @param node A location where we might consider accessing `this`. Not necessarily a ThisExpression. */
/* @internal */ tryGetThisTypeAt(node: Node): Type | undefined;
/* @internal */ getTypeArgumentConstraint(node: TypeNode): Type | undefined;

/**
* Does *not* get *all* suggestion diagnostics, just the ones that were convenient to report in the checker.
* Others are added in computeSuggestionDiagnostics.
*/
/* @internal */ getSuggestionDiagnostics(file: SourceFile): ReadonlyArray<Diagnostic>;
}

/* @internal */
Expand Down
9 changes: 5 additions & 4 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,11 +505,11 @@ namespace FourSlash {
return "\nMarker: " + this.lastKnownMarker + "\nChecking: " + msg + "\n\n";
}

private getDiagnostics(fileName: string): ts.Diagnostic[] {
private getDiagnostics(fileName: string, includeSuggestions = false): ts.Diagnostic[] {
return [
...this.languageService.getSyntacticDiagnostics(fileName),
...this.languageService.getSemanticDiagnostics(fileName),
...this.languageService.getSuggestionDiagnostics(fileName),
...(includeSuggestions ? this.languageService.getSuggestionDiagnostics(fileName) : ts.emptyArray),
];
}

Expand Down Expand Up @@ -583,7 +583,8 @@ namespace FourSlash {

public verifyNoErrors() {
ts.forEachKey(this.inputFiles, fileName => {
if (!ts.isAnySupportedFileExtension(fileName)) return;
if (!ts.isAnySupportedFileExtension(fileName)
|| !this.getProgram().getCompilerOptions().allowJs && !ts.extensionIsTypeScript(ts.extensionFromPath(fileName))) return;
const errors = this.getDiagnostics(fileName).filter(e => e.category !== ts.DiagnosticCategory.Suggestion);
if (errors.length) {
this.printErrorLog(/*expectErrors*/ false, errors);
Expand Down Expand Up @@ -2522,7 +2523,7 @@ Actual: ${stringify(fullActual)}`);
* @param fileName Path to file where error should be retrieved from.
*/
private getCodeFixes(fileName: string, errorCode?: number): ts.CodeFixAction[] {
const diagnosticsForCodeFix = this.getDiagnostics(fileName).map(diagnostic => ({
const diagnosticsForCodeFix = this.getDiagnostics(fileName, /*includeSuggestions*/ true).map(diagnostic => ({
start: diagnostic.start,
length: diagnostic.length,
code: diagnostic.code
Expand Down
2 changes: 1 addition & 1 deletion src/services/codefixes/fixCannotFindModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ namespace ts.codefix {
return host.isKnownTypesPackageName(packageName) ? getTypesPackageName(packageName) : undefined;
}

export function tryGetCodeActionForInstallPackageTypes(host: LanguageServiceHost, fileName: string, moduleName: string): CodeAction | undefined {
function tryGetCodeActionForInstallPackageTypes(host: LanguageServiceHost, fileName: string, moduleName: string): CodeAction | undefined {
const packageName = getTypesPackageNameToInstall(host, moduleName);
return packageName === undefined ? undefined : {
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Install_0), [packageName]),
Expand Down
67 changes: 0 additions & 67 deletions src/services/refactors/installTypesForPackage.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/services/refactors/refactors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/// <reference path="annotateWithTypeFromJSDoc.ts" />
/// <reference path="convertFunctionToEs6Class.ts" />
/// <reference path="extractSymbol.ts" />
/// <reference path="installTypesForPackage.ts" />
/// <reference path="useDefaultImport.ts" />
2 changes: 1 addition & 1 deletion src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1422,7 +1422,7 @@ namespace ts {

function getSuggestionDiagnostics(fileName: string): Diagnostic[] {
synchronizeHostData();
return computeSuggestionDiagnostics(getValidSourceFile(fileName));
return computeSuggestionDiagnostics(getValidSourceFile(fileName), program);
}

function getCompilerOptionsDiagnostics() {
Expand Down
14 changes: 10 additions & 4 deletions src/services/suggestionDiagnostics.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
/* @internal */
namespace ts {
export function computeSuggestionDiagnostics(sourceFile: SourceFile): Diagnostic[] {
return sourceFile.commonJsModuleIndicator
? [createDiagnosticForNode(sourceFile.commonJsModuleIndicator, Diagnostics.File_is_a_CommonJS_module_it_may_be_converted_to_an_ES6_module)]
: emptyArray;
export function computeSuggestionDiagnostics(sourceFile: SourceFile, program: Program): Diagnostic[] {
program.getSemanticDiagnostics(sourceFile);
const checker = program.getDiagnosticsProducingTypeChecker();
const diags: Diagnostic[] = [];

if (sourceFile.commonJsModuleIndicator) {
diags.push(createDiagnosticForNode(sourceFile.commonJsModuleIndicator, Diagnostics.File_is_a_CommonJS_module_it_may_be_converted_to_an_ES6_module));
}

return diags.concat(checker.getSuggestionDiagnostics(sourceFile));
}
}
29 changes: 29 additions & 0 deletions tests/cases/fourslash/codeFixCannotFindModule_suggestion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/// <reference path='fourslash.ts' />

// @moduleResolution: node

// @Filename: /node_modules/abs/subModule.js
////export const x = 0;

// @Filename: /a.ts
////import * as abs from [|"abs/subModule"|];

test.setTypesRegistry({
"abs": undefined,
});

verify.noErrors();
goTo.file("/a.ts");
verify.getSuggestionDiagnostics([{
message: "Could not find a declaration file for module 'abs/subModule'. '/node_modules/abs/subModule.js' implicitly has an 'any' type.",
code: 7016,
}]);

verify.codeFixAvailable([{
description: "Install '@types/abs'",
commands: [{
type: "install package",
file: "/a.ts",
packageName: "@types/abs",
}],
}]);
32 changes: 32 additions & 0 deletions tests/cases/fourslash/codeFixCannotFindModule_suggestion_js.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/// <reference path='fourslash.ts' />

// @allowJs: true
// @checkJs: true

// @Filename: /node_modules/abs/index.js
////export default function abs() {}

// @Filename: /a.js
////import abs from [|"abs"|];

test.setTypesRegistry({ "abs": undefined });

verify.noErrors();
goTo.file("/a.js");
verify.getSuggestionDiagnostics([{
message: "Could not find a declaration file for module 'abs'. '/node_modules/abs/index.js' implicitly has an 'any' type.",
code: 7016,
}]);

verify.codeFixAvailable([
{
description: "Install '@types/abs'",
commands: [{
type: "install package",
file: "/a.js",
packageName: "@types/abs",
}],
},
{ description: "Ignore this error message" },
{ description: "Disable checking for this file" },
]);
25 changes: 0 additions & 25 deletions tests/cases/fourslash/refactorInstallTypesForPackage.ts

This file was deleted.

This file was deleted.

29 changes: 0 additions & 29 deletions tests/cases/fourslash/refactorInstallTypesForPackage_js.ts

This file was deleted.