diff --git a/packages/compiler-cli/src/ngtsc/typecheck/api/api.ts b/packages/compiler-cli/src/ngtsc/typecheck/api/api.ts index fc2cf31d3666e..8d6e455151415 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/api/api.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/api/api.ts @@ -8,8 +8,8 @@ import {AbsoluteSourceSpan, BoundTarget, DirectiveMeta, ParseSourceSpan, SchemaMetadata} from '@angular/compiler'; import ts from 'typescript'; -import {ErrorCode} from '../../diagnostics'; +import {ErrorCode} from '../../diagnostics'; import {AbsoluteFsPath} from '../../file_system'; import {Reference} from '../../imports'; import {ClassPropertyMapping, DirectiveTypeCheckMeta} from '../../metadata'; @@ -43,6 +43,16 @@ export interface TemplateDiagnostic extends ts.Diagnostic { * The template id of the component that resulted in this diagnostic. */ templateId: TemplateId; + + quickFix?: TemplateQuickFixData; +} + +export interface TemplateQuickFixData { + title: string; + + replacementSpan: ts.TextSpan; + + replacementText: string; } /** diff --git a/packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts b/packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts index ef2c9e516c8fd..8e7c99dace2bf 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/api/checker.ts @@ -12,7 +12,7 @@ import ts from 'typescript'; import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system'; import {ErrorCode} from '../../diagnostics'; -import {FullTemplateMapping, NgTemplateDiagnostic, TypeCheckableDirectiveMeta} from './api'; +import {FullTemplateMapping, NgTemplateDiagnostic, TemplateQuickFixData, TypeCheckableDirectiveMeta} from './api'; import {GlobalCompletion} from './completion'; import {DirectiveInScope, PipeInScope} from './scope'; import {ElementSymbol, Symbol, TcbLocation, TemplateSymbol} from './symbols'; @@ -180,7 +180,8 @@ export interface TemplateTypeChecker { start: number, end: number, sourceFile: ts.SourceFile, - }[]): NgTemplateDiagnostic; + }[], + quickFix?: TemplateQuickFixData): NgTemplateDiagnostic; } /** diff --git a/packages/compiler-cli/src/ngtsc/typecheck/diagnostics/src/diagnostic.ts b/packages/compiler-cli/src/ngtsc/typecheck/diagnostics/src/diagnostic.ts index dbaaf842a528c..45fc17aed88dd 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/diagnostics/src/diagnostic.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/diagnostics/src/diagnostic.ts @@ -9,7 +9,7 @@ import {ParseSourceSpan} from '@angular/compiler'; import ts from 'typescript'; -import {ExternalTemplateSourceMapping, TemplateDiagnostic, TemplateId, TemplateSourceMapping} from '../../api'; +import {ExternalTemplateSourceMapping, TemplateDiagnostic, TemplateId, TemplateQuickFixData, TemplateSourceMapping} from '../../api'; /** * Constructs a `ts.Diagnostic` for a given `ParseSourceSpan` within a template. @@ -22,7 +22,8 @@ export function makeTemplateDiagnostic( start: number, end: number, sourceFile: ts.SourceFile, - }[]): TemplateDiagnostic { + }[], + quickFix?: TemplateQuickFixData): TemplateDiagnostic { if (mapping.type === 'direct') { let relatedInformation: ts.DiagnosticRelatedInformation[]|undefined = undefined; if (relatedMessages !== undefined) { @@ -52,6 +53,7 @@ export function makeTemplateDiagnostic( start: span.start.offset, length: span.end.offset - span.start.offset, relatedInformation, + quickFix, }; } else if (mapping.type === 'indirect' || mapping.type === 'external') { // For indirect mappings (template was declared inline, but ngtsc couldn't map it directly @@ -107,6 +109,7 @@ export function makeTemplateDiagnostic( length: span.end.offset - span.start.offset, // Show a secondary message indicating the component whose template contains the error. relatedInformation, + quickFix, }; } else { throw new Error(`Unexpected source mapping type: ${(mapping as {type: string}).type}`); diff --git a/packages/compiler-cli/src/ngtsc/typecheck/extended/api/api.ts b/packages/compiler-cli/src/ngtsc/typecheck/extended/api/api.ts index b34cdb6058fed..c4cb4be1a9520 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/extended/api/api.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/extended/api/api.ts @@ -11,7 +11,7 @@ import ts from 'typescript'; import {NgCompilerOptions} from '../../../core/api'; import {ErrorCode, ExtendedTemplateDiagnosticName} from '../../../diagnostics'; -import {NgTemplateDiagnostic, TemplateTypeChecker} from '../../api'; +import {NgTemplateDiagnostic, TemplateQuickFixData, TemplateTypeChecker} from '../../api'; /** * A Template Check receives information about the template it's checking and returns @@ -43,12 +43,17 @@ export interface TemplateContext { * Creates a template diagnostic with the given information for the template being processed and * using the diagnostic category configured for the extended template diagnostic. */ - makeTemplateDiagnostic(sourceSpan: ParseSourceSpan, message: string, relatedInformation?: { - text: string, - start: number, - end: number, - sourceFile: ts.SourceFile, - }[]): NgTemplateDiagnostic; + makeTemplateDiagnostic( + sourceSpan: ParseSourceSpan, + message: string, + relatedInformation?: { + text: string, + start: number, + end: number, + sourceFile: ts.SourceFile, + }[], + quickFix?: TemplateQuickFixData, + ): NgTemplateDiagnostic; } /** diff --git a/packages/compiler-cli/src/ngtsc/typecheck/extended/checks/invalid_banana_in_box/index.ts b/packages/compiler-cli/src/ngtsc/typecheck/extended/checks/invalid_banana_in_box/index.ts index aee972012afc3..9d352cb324ac8 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/extended/checks/invalid_banana_in_box/index.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/extended/checks/invalid_banana_in_box/index.ts @@ -10,7 +10,7 @@ import {AST, TmplAstBoundEvent, TmplAstNode} from '@angular/compiler'; import ts from 'typescript'; import {ErrorCode, ExtendedTemplateDiagnosticName} from '../../../../diagnostics'; -import {NgTemplateDiagnostic} from '../../../api'; +import {NgTemplateDiagnostic, TemplateQuickFixData} from '../../../api'; import {TemplateCheckFactory, TemplateCheckWithVisitor, TemplateContext} from '../../api'; /** @@ -33,11 +33,20 @@ class InvalidBananaInBoxCheck extends TemplateCheckWithVisitor => { + makeTemplateDiagnostic: ( + span: ParseSourceSpan, + message: string, + relatedInformation?: { + text: string, + start: number, + end: number, + sourceFile: ts.SourceFile, + }[], + quickFix?: TemplateQuickFixData, + ): NgTemplateDiagnostic => { return this.partialCtx.templateTypeChecker.makeTemplateDiagnostic( - component, span, category, check.code, message, relatedInformation); + component, span, category, check.code, message, relatedInformation, quickFix); }, }; diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts index f56bf220f1cef..387d860e742b4 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/checker.ts @@ -19,7 +19,7 @@ import {ClassDeclaration, isNamedClassDeclaration, ReflectionHost} from '../../r import {ComponentScopeReader, TypeCheckScopeRegistry} from '../../scope'; import {isShim} from '../../shims'; import {getSourceFileOrNull, isSymbolWithValueDeclaration} from '../../util/src/typescript'; -import {DirectiveInScope, ElementSymbol, FullTemplateMapping, GlobalCompletion, NgTemplateDiagnostic, OptimizeFor, PipeInScope, ProgramTypeCheckAdapter, Symbol, TcbLocation, TemplateDiagnostic, TemplateId, TemplateSymbol, TemplateTypeChecker, TypeCheckableDirectiveMeta, TypeCheckingConfig} from '../api'; +import {DirectiveInScope, ElementSymbol, FullTemplateMapping, GlobalCompletion, NgTemplateDiagnostic, OptimizeFor, PipeInScope, ProgramTypeCheckAdapter, Symbol, TcbLocation, TemplateDiagnostic, TemplateId, TemplateQuickFixData, TemplateSymbol, TemplateTypeChecker, TypeCheckableDirectiveMeta, TypeCheckingConfig} from '../api'; import {makeTemplateDiagnostic} from '../diagnostics'; import {CompletionEngine} from './completion'; @@ -335,13 +335,19 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { } makeTemplateDiagnostic( - clazz: ts.ClassDeclaration, sourceSpan: ParseSourceSpan, category: ts.DiagnosticCategory, - errorCode: T, message: string, relatedInformation?: { + clazz: ts.ClassDeclaration, + sourceSpan: ParseSourceSpan, + category: ts.DiagnosticCategory, + errorCode: T, + message: string, + relatedInformation?: { text: string, start: number, end: number, sourceFile: ts.SourceFile, - }[]): NgTemplateDiagnostic { + }[], + quickFix?: TemplateQuickFixData, + ): NgTemplateDiagnostic { const sfPath = absoluteFromSourceFile(clazz.getSourceFile()); const fileRecord = this.state.get(sfPath)!; const templateId = fileRecord.sourceManager.getTemplateId(clazz); @@ -350,8 +356,8 @@ export class TemplateTypeCheckerImpl implements TemplateTypeChecker { return { ...makeTemplateDiagnostic( templateId, mapping, sourceSpan, category, ngErrorCode(errorCode), message, - relatedInformation), - __ngCode: errorCode + relatedInformation, quickFix), + __ngCode: errorCode, }; } diff --git a/packages/language-service/BUILD.bazel b/packages/language-service/BUILD.bazel index 458591b0c8ab7..9c1a19dffa43d 100644 --- a/packages/language-service/BUILD.bazel +++ b/packages/language-service/BUILD.bazel @@ -11,6 +11,7 @@ ts_library( ], prodmode_module = "commonjs", deps = [ + "//packages/compiler-cli/src/ngtsc/typecheck/api", "@npm//@types/node", "@npm//typescript", ], diff --git a/packages/language-service/api.ts b/packages/language-service/api.ts index 4f41bbf7ad460..12b31bb46524a 100644 --- a/packages/language-service/api.ts +++ b/packages/language-service/api.ts @@ -46,8 +46,15 @@ export type GetTcbResponse = { selections: ts.TextSpan[], }; +export interface TemplateQuickFixData { + title: string; + replacementSpan: ts.TextSpan; + replacementText: string; +} + export type GetComponentLocationsForTemplateResponse = ts.DocumentSpan[]; export type GetTemplateLocationForComponentResponse = ts.DocumentSpan|undefined; +export type GetQuickFixDataForDiagnosticResponse = TemplateQuickFixData|undefined /** * `NgLanguageService` describes an instance of an Angular language service, @@ -58,6 +65,7 @@ export interface NgLanguageService extends ts.LanguageService { getComponentLocationsForTemplate(fileName: string): GetComponentLocationsForTemplateResponse; getTemplateLocationForComponent(fileName: string, position: number): GetTemplateLocationForComponentResponse; + getQuickFixDataForDiagnostic(diagnostic: ts.Diagnostic): GetQuickFixDataForDiagnosticResponse; } export function isNgLanguageService(ls: ts.LanguageService| diff --git a/packages/language-service/src/BUILD.bazel b/packages/language-service/src/BUILD.bazel index e8c017f5be2bc..c6cc93e5c4ad8 100644 --- a/packages/language-service/src/BUILD.bazel +++ b/packages/language-service/src/BUILD.bazel @@ -21,6 +21,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/shims", "//packages/compiler-cli/src/ngtsc/typecheck", "//packages/compiler-cli/src/ngtsc/typecheck/api", + "//packages/compiler-cli/src/ngtsc/typecheck/diagnostics", "//packages/compiler-cli/src/ngtsc/util", "//packages/language-service:api", "@npm//@types/node", diff --git a/packages/language-service/src/ts_plugin.ts b/packages/language-service/src/ts_plugin.ts index c02dec405f2ac..bf43c44433c46 100644 --- a/packages/language-service/src/ts_plugin.ts +++ b/packages/language-service/src/ts_plugin.ts @@ -6,9 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ +import {isTemplateDiagnostic} from '@angular/compiler-cli/src/ngtsc/typecheck/diagnostics'; import * as ts from 'typescript/lib/tsserverlibrary'; -import {GetComponentLocationsForTemplateResponse, GetTcbResponse, GetTemplateLocationForComponentResponse, NgLanguageService} from '../api'; +import {GetComponentLocationsForTemplateResponse, GetTcbResponse, GetTemplateLocationForComponentResponse, NgLanguageService, TemplateQuickFixData} from '../api'; import {LanguageService} from './language_service'; @@ -164,6 +165,13 @@ export function create(info: ts.server.PluginCreateInfo): NgLanguageService { return ngLS.getTemplateLocationForComponent(fileName, position); } + function getQuickFixDataForDiagnostic(diagnostic: ts.Diagnostic): TemplateQuickFixData|undefined { + if (!isTemplateDiagnostic(diagnostic)) { + return undefined; + } + return diagnostic.quickFix; + } + return { ...tsLS, getSemanticDiagnostics, @@ -181,6 +189,7 @@ export function create(info: ts.server.PluginCreateInfo): NgLanguageService { getComponentLocationsForTemplate, getSignatureHelpItems, getTemplateLocationForComponent, + getQuickFixDataForDiagnostic, }; }