From 69daa1dac26e4dd22ee4ba458d44a53e3ddd4f3d Mon Sep 17 00:00:00 2001 From: Aleksand Shatoba Date: Tue, 18 Feb 2025 18:50:03 +0300 Subject: [PATCH] upadte_angular_v19 --- package.json | 5 +- src/client.ts | 555 +++++++++++++++++++++++++---------------- src/common/requests.ts | 58 +++-- tsconfig.json | 26 +- 4 files changed, 389 insertions(+), 255 deletions(-) diff --git a/package.json b/package.json index eaeecfe..6d261fe 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coc-angular", "description": "Editor services for Angular templates", - "version": "17.0.2", + "version": "19.1.0", "keywords": [ "coc.nvim", "angular", @@ -115,12 +115,11 @@ "@types/node": "^20.9.0", "coc.nvim": "^0.0.83-next.9", "ts-loader": "^9.5.0", - "vscode-languageserver-protocol": "^3.17.5", "webpack": "^5.89.0", "webpack-cli": "^5.1.4" }, "dependencies": { - "@angular/language-server": "17.0.2", + "@angular/language-server": "^19.1.0", "typescript": "5.2.2" } } diff --git a/src/client.ts b/src/client.ts index f2e2c34..fc7f92f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -6,17 +6,33 @@ * found in the LICENSE file at https://angular.io/license */ -import * as fs from 'fs'; -import * as path from 'path'; -import * as vscode from 'coc.nvim'; - -import {OpenOutputChannel, ProjectLoadingFinish, ProjectLoadingStart, SuggestStrictMode, SuggestStrictModeParams} from './common/notifications'; -import {GetCompleteItems, GetComponentsWithTemplateFile, GetHoverInfo, GetTcbRequest, GetTemplateLocationForComponent, IsInAngularProject} from './common/requests'; -import {NodeModule, resolve} from './common/resolver'; - -import {isInsideComponentDecorator, isInsideInlineTemplateRegion, isInsideStringLiteral} from './embedded_support'; -import {code2ProtocolConverter, protocol2CodeConverter} from './common/utils'; -import { DocumentUri } from 'vscode-languageserver-protocol'; +import * as fs from "fs"; +import * as path from "path"; +import * as vscode from "coc.nvim"; + +import { + OpenOutputChannel, + ProjectLoadingFinish, + ProjectLoadingStart, + SuggestStrictMode, + SuggestStrictModeParams, +} from "./common/notifications"; +import { + GetCompleteItems, + GetComponentsWithTemplateFile, + GetHoverInfo, + GetTcbRequest, + GetTemplateLocationForComponent, + IsInAngularProject, +} from "./common/requests"; +import { NodeModule, resolve } from "./common/resolver"; + +import { + isInsideComponentDecorator, + isInsideInlineTemplateRegion, + isInsideStringLiteral, +} from "./embedded_support"; +import { code2ProtocolConverter, protocol2CodeConverter } from "./common/utils"; interface GetTcbResponse { uri: vscode.Uri; @@ -24,24 +40,30 @@ interface GetTcbResponse { selections: vscode.Range[]; } -type GetComponentsForOpenExternalTemplateResponse = Array<{uri: DocumentUri; range: vscode.Range;}>; +type GetComponentsForOpenExternalTemplateResponse = Array<{ + uri: vscode.Uri; + range: vscode.Range; +}>; export class AngularLanguageClient implements vscode.Disposable { - private client: vscode.LanguageClient|null = null; + private client: vscode.LanguageClient | null = null; private readonly disposables: vscode.Disposable[] = []; private readonly outputChannel: vscode.OutputChannel; private readonly clientOptions: vscode.LanguageClientOptions; - private readonly name = 'Angular Language Service'; + private readonly name = "Angular Language Service"; private readonly virtualDocumentContents = new Map(); /** A map that indicates whether Angular could be found in the file's project. */ private readonly fileToIsInAngularProjectMap = new Map(); constructor(private readonly context: vscode.ExtensionContext) { - vscode.workspace.registerTextDocumentContentProvider('angular-embedded-content', { - provideTextDocumentContent: uri => { - return this.virtualDocumentContents.get(uri.toString()); + vscode.workspace.registerTextDocumentContentProvider( + "angular-embedded-content", + { + provideTextDocumentContent: (uri) => { + return this.virtualDocumentContents.get(uri.toString()); + }, } - }); + ); this.outputChannel = vscode.window.createOutputChannel(this.name); // Options to control the language client @@ -50,14 +72,14 @@ export class AngularLanguageClient implements vscode.Disposable { documentSelector: [ // scheme: 'file' means listen to changes to files on disk only // other option is 'untitled', for buffer in the editor (like a new doc) - {scheme: 'file', language: 'html'}, - {scheme: 'file', language: 'typescript'}, + { scheme: "file", language: "html" }, + { scheme: "file", language: "typescript" }, ], synchronize: { fileEvents: [ // Notify the server about file changes to tsconfig.json contained in the workspace - vscode.workspace.createFileSystemWatcher('**/tsconfig.json'), - ] + vscode.workspace.createFileSystemWatcher("**/tsconfig.json"), + ], }, // Don't let our output console pop open revealOutputChannelOn: vscode.RevealOutputChannelOn.Never, @@ -67,18 +89,27 @@ export class AngularLanguageClient implements vscode.Disposable { }, // middleware middleware: { - provideCodeActions: async ( - document: vscode.LinesTextDocument, range: vscode.Range, context: vscode.CodeActionContext, - token: vscode.CancellationToken, next: vscode.ProvideCodeActionsSignature) => { - if (await this.isInAngularProject(document) && - isInsideInlineTemplateRegion(document, range.start) && - isInsideInlineTemplateRegion(document, range.end)) { - return next(document, range, context, token); - } - }, - prepareRename: async ( - document: vscode.LinesTextDocument, position: vscode.Position, - token: vscode.CancellationToken, next: vscode.PrepareRenameSignature) => { + provideCodeActions: async ( + document: vscode.LinesTextDocument, + range: vscode.Range, + context: vscode.CodeActionContext, + token: vscode.CancellationToken, + next: vscode.ProvideCodeActionsSignature + ) => { + if ( + (await this.isInAngularProject(document)) && + isInsideInlineTemplateRegion(document, range.start) && + isInsideInlineTemplateRegion(document, range.end) + ) { + return next(document, range, context, token); + } + }, + prepareRename: async ( + document: vscode.LinesTextDocument, + position: vscode.Position, + token: vscode.CancellationToken, + next: vscode.PrepareRenameSignature + ) => { // We are able to provide renames for many types of string literals: template strings, // pipe names, and hopefully in the future selectors and input/output aliases. Because // TypeScript isn't able to provide renames for these, we can more or less @@ -87,140 +118,184 @@ export class AngularLanguageClient implements vscode.Disposable { // because we cannot ensure our extension is prioritized for renames in TS files (see // https://github.com/microsoft/vscode/issues/115354) we disable renaming completely so we // can provide consistent expectations. - if (await this.isInAngularProject(document) && - isInsideStringLiteral(document, position)) { + if ( + (await this.isInAngularProject(document)) && + isInsideStringLiteral(document, position) + ) { return next(document, position, token); } }, provideDefinition: async ( - document: vscode.LinesTextDocument, position: vscode.Position, - token: vscode.CancellationToken, next: vscode.ProvideDefinitionSignature) => { - if (await this.isInAngularProject(document) && - isInsideComponentDecorator(document, position)) { + document: vscode.LinesTextDocument, + position: vscode.Position, + token: vscode.CancellationToken, + next: vscode.ProvideDefinitionSignature + ) => { + if ( + (await this.isInAngularProject(document)) && + isInsideComponentDecorator(document, position) + ) { return next(document, position, token); } }, provideTypeDefinition: async ( - document: vscode.LinesTextDocument, position: vscode.Position, - token: vscode.CancellationToken, next) => { - if (await this.isInAngularProject(document) && - isInsideInlineTemplateRegion(document, position)) { + document: vscode.LinesTextDocument, + position: vscode.Position, + token: vscode.CancellationToken, + next + ) => { + if ( + (await this.isInAngularProject(document)) && + isInsideInlineTemplateRegion(document, position) + ) { return next(document, position, token); } }, provideHover: async ( - document: vscode.LinesTextDocument, position: vscode.Position, - token: vscode.CancellationToken, next: vscode.ProvideHoverSignature) => { - if (!(await this.isInAngularProject(document)) || - !isInsideInlineTemplateRegion(document, position)) { + document: vscode.LinesTextDocument, + position: vscode.Position, + token: vscode.CancellationToken, + next: vscode.ProvideHoverSignature + ) => { + if ( + !(await this.isInAngularProject(document)) || + !isInsideInlineTemplateRegion(document, position) + ) { return; } const angularResultsPromise = next(document, position, token); // Include results for inline HTML via virtual document and native html providers. - if (document.languageId === 'typescript') { + if (document.languageId === "typescript") { const vDocUri = this.createVirtualHtmlDoc(document); - const htmlProviderResultsPromise = await this.client.sendRequest( + const htmlProviderResultsPromise = await this.client.sendRequest( GetHoverInfo, { - textDocument: {uri: vDocUri.toString()}, - position + textDocument: { uri: vDocUri.toString() }, + position, }, token ); - const [angularResults, htmlProviderResults] = - await Promise.all([angularResultsPromise, htmlProviderResultsPromise]); + const [angularResults, htmlProviderResults] = await Promise.all([ + angularResultsPromise, + htmlProviderResultsPromise, + ]); return angularResults ?? htmlProviderResults?.[0]; } return angularResultsPromise; }, provideSignatureHelp: async ( - document: vscode.LinesTextDocument, position: vscode.Position, - context: vscode.SignatureHelpContext, token: vscode.CancellationToken, - next: vscode.ProvideSignatureHelpSignature) => { - if (await this.isInAngularProject(document) && - isInsideInlineTemplateRegion(document, position)) { + document: vscode.LinesTextDocument, + position: vscode.Position, + context: vscode.SignatureHelpContext, + token: vscode.CancellationToken, + next: vscode.ProvideSignatureHelpSignature + ) => { + if ( + (await this.isInAngularProject(document)) && + isInsideInlineTemplateRegion(document, position) + ) { return next(document, position, context, token); } }, provideCompletionItem: async ( - document: vscode.LinesTextDocument, position: vscode.Position, - context: vscode.CompletionContext, token: vscode.CancellationToken, - next: vscode.ProvideCompletionItemsSignature) => { + document: vscode.LinesTextDocument, + position: vscode.Position, + context: vscode.CompletionContext, + token: vscode.CancellationToken, + next: vscode.ProvideCompletionItemsSignature + ) => { // If not in inline template, do not perform request forwarding - if (!(await this.isInAngularProject(document)) || - !isInsideInlineTemplateRegion(document, position)) { + if ( + !(await this.isInAngularProject(document)) || + !isInsideInlineTemplateRegion(document, position) + ) { return; } - const angularCompletionsPromise = next(document, position, context, token) as - Promise; + const angularCompletionsPromise = next( + document, + position, + context, + token + ) as Promise; // Include results for inline HTML via virtual document and native html providers. - if (document.languageId === 'typescript') { + if (document.languageId === "typescript") { const vDocUri = this.createVirtualHtmlDoc(document); // This will not include angular stuff because the vDoc is not associated with an // angular component - const htmlProviderCompletionsPromise = await this.client.sendRequest( - GetCompleteItems, - { - textDocument: {uri: vDocUri.toString()}, - position, - context: { - triggerCharacter: context.triggerCharacter - } - }, - token - ); + const htmlProviderCompletionsPromise = + await this.client.sendRequest( + GetCompleteItems, + { + textDocument: { uri: vDocUri.toString() }, + position, + context: { + triggerCharacter: context.triggerCharacter, + }, + }, + token + ); const [angularCompletions, htmlProviderCompletions] = - await Promise.all([angularCompletionsPromise, htmlProviderCompletionsPromise]); - return [...(angularCompletions ?? []), ...(htmlProviderCompletions?.items ?? [])]; + await Promise.all([ + angularCompletionsPromise, + htmlProviderCompletionsPromise, + ]); + return [ + ...(angularCompletions ?? []), + ...(htmlProviderCompletions?.items ?? []), + ]; } return angularCompletionsPromise; }, provideFoldingRanges: async ( - document: vscode.LinesTextDocument, context: vscode.FoldingContext, - token: vscode.CancellationToken, next) => { - if (!await this.isInAngularProject(document)) { + document: vscode.LinesTextDocument, + context: vscode.FoldingContext, + token: vscode.CancellationToken, + next + ) => { + if (!(await this.isInAngularProject(document))) { return null; } return next(document, context, token); - } - } + }, + }, }; } - private async isInAngularProject(doc: vscode.TextDocument): Promise { - if (this.client === null) { - return false; - } - const uri = doc.uri.toString(); - if (this.fileToIsInAngularProjectMap.has(uri)) { - return this.fileToIsInAngularProjectMap.get(uri)!; - } - - try { - const response = await this.client.sendRequest(IsInAngularProject, { - textDocument: code2ProtocolConverter.asTextDocumentIdentifier(doc), - }); - if (response === null) { - // If the response indicates the answer can't be determined at the moment, return `false` - // but do not cache the result so we can try to get the real answer on follow-up requests. - return false; - } - this.fileToIsInAngularProjectMap.set(uri, response); - return response; - } catch { - return false; - } + private async isInAngularProject(doc: vscode.TextDocument): Promise { + if (this.client === null) { + return false; + } + const uri = doc.uri.toString(); + if (this.fileToIsInAngularProjectMap.has(uri)) { + return this.fileToIsInAngularProjectMap.get(uri)!; + } + + try { + const response = await this.client.sendRequest(IsInAngularProject, { + textDocument: code2ProtocolConverter.asTextDocumentIdentifier(doc), + }); + if (response === null) { + // If the response indicates the answer can't be determined at the moment, return `false` + // but do not cache the result so we can try to get the real answer on follow-up requests. + return false; + } + this.fileToIsInAngularProjectMap.set(uri, response); + return response; + } catch { + return false; + } } private createVirtualHtmlDoc(document: vscode.TextDocument): vscode.Uri { const originalUri = document.uri.toString(); - const vdocUri = vscode.Uri.file(encodeURIComponent(originalUri) + '.html') - .with({scheme: 'angular-embedded-content', authority: 'html'}); + const vdocUri = vscode.Uri.file( + encodeURIComponent(originalUri) + ".html" + ).with({ scheme: "angular-embedded-content", authority: "html" }); this.virtualDocumentContents.set(vdocUri.toString(), document.getText()); return vdocUri; } @@ -235,8 +310,12 @@ export class AngularLanguageClient implements vscode.Disposable { // Node module for the language server const args = this.constructArgs(); - const prodBundle = this.context.asAbsolutePath(path.join('node_modules', '@angular', 'language-server')); - const devBundle = this.context.asAbsolutePath(path.join('node_modules', '@angular', 'language-server')); + const prodBundle = this.context.asAbsolutePath( + path.join("node_modules", "@angular", "language-server") + ); + const devBundle = this.context.asAbsolutePath( + path.join("node_modules", "@angular", "language-server") + ); // If the extension is launched in debug mode then the debug server options are used // Otherwise the run options are used @@ -255,34 +334,35 @@ export class AngularLanguageClient implements vscode.Disposable { // Argv options for Node.js execArgv: [ // do not lazily evaluate the code so all breakpoints are respected - '--nolazy', + "--nolazy", // If debugging port is changed, update .vscode/launch.json as well - '--inspect=6009', + "--inspect=6009", ], env: { NG_DEBUG: true, - } + }, }, - args + args, }, }; if (!extensionVersionCompatibleWithAllProjects(serverOptions.run.module)) { vscode.window.showWarningMessage( - `A project in the workspace is using a newer version of Angular than the language service extension. ` + - `This may cause the extension to show incorrect diagnostics.`); + `A project in the workspace is using a newer version of Angular than the language service extension. ` + + `This may cause the extension to show incorrect diagnostics.` + ); } // Create the language client and start the client. - const forceDebug = process.env['NG_DEBUG'] === 'true'; + const forceDebug = process.env["NG_DEBUG"] === "true"; this.client = new vscode.LanguageClient( - // This is the ID for Angular-specific configurations, like "angular.log". - // See contributes.configuration in package.json. - 'angular', - this.name, - serverOptions, - this.clientOptions, - forceDebug, + // This is the ID for Angular-specific configurations, like "angular.log". + // See contributes.configuration in package.json. + "angular", + this.name, + serverOptions, + this.clientOptions, + forceDebug ); vscode.services.registerLanguageClient(this.client); await this.client.onReady(); @@ -291,58 +371,66 @@ export class AngularLanguageClient implements vscode.Disposable { this.disposables.push(registerNotificationHandlers(this.client)); } - /** + /** * Construct the arguments that's used to spawn the server process. * @param ctx vscode extension context */ private constructArgs(): string[] { const config = vscode.workspace.getConfiguration(); - const args: string[] = ['--logToConsole']; + const args: string[] = ["--logToConsole"]; - const ngLog: string = config.get('angular.log', 'off'); - if (ngLog !== 'off') { + const ngLog: string = config.get("angular.log", "off"); + if (ngLog !== "off") { // Log file does not yet exist on disk. It is up to the server to create the file. - const logFile = path.join(this.context.storagePath, 'nglangsvc.log'); - args.push('--logFile', logFile); - args.push('--logVerbosity', ngLog); + const logFile = path.join(this.context.storagePath, "nglangsvc.log"); + args.push("--logFile", logFile); + args.push("--logVerbosity", ngLog); } const ngProbeLocations = getProbeLocations(this.context.extensionPath); - args.push('--ngProbeLocations', ngProbeLocations.join(',')); + args.push("--ngProbeLocations", ngProbeLocations.join(",")); - const includeAutomaticOptionalChainCompletions = - config.get('angular.suggest.includeAutomaticOptionalChainCompletions'); + const includeAutomaticOptionalChainCompletions = config.get( + "angular.suggest.includeAutomaticOptionalChainCompletions" + ); if (includeAutomaticOptionalChainCompletions) { - args.push('--includeAutomaticOptionalChainCompletions'); + args.push("--includeAutomaticOptionalChainCompletions"); } - const includeCompletionsWithSnippetText = - config.get('angular.suggest.includeCompletionsWithSnippetText'); + const includeCompletionsWithSnippetText = config.get( + "angular.suggest.includeCompletionsWithSnippetText" + ); if (includeCompletionsWithSnippetText) { - args.push('--includeCompletionsWithSnippetText'); + args.push("--includeCompletionsWithSnippetText"); } const angularVersions = getAngularVersionsInWorkspace(); // Only disable block syntax if we find angular/core and every one we find does not support // block syntax - if (angularVersions.size > 0 && Array.from(angularVersions).every(v => v.version.major < 17)) { - args.push('--disableBlockSyntax'); + if ( + angularVersions.size > 0 && + Array.from(angularVersions).every((v) => v.version.major < 17) + ) { + args.push("--disableBlockSyntax"); this.outputChannel.appendLine( - `All workspace roots are using versions of Angular that do not support control flow block syntax.` + - ` Block syntax parsing in templates will be disabled.`); + `All workspace roots are using versions of Angular that do not support control flow block syntax.` + + ` Block syntax parsing in templates will be disabled.` + ); } - const forceStrictTemplates = config.get('angular.forceStrictTemplates'); + const forceStrictTemplates = config.get( + "angular.forceStrictTemplates" + ); if (forceStrictTemplates) { - args.push('--forceStrictTemplates'); + args.push("--forceStrictTemplates"); } - const tsdk = config.get('typescript.tsdk', ''); + const tsdk = config.get("typescript.tsdk", ""); if (tsdk.trim().length > 0) { - args.push('--tsdk', tsdk); + args.push("--tsdk", tsdk); } const tsProbeLocations = [...getProbeLocations(this.context.extensionPath)]; - args.push('--tsProbeLocations', tsProbeLocations.join(',')); + args.push("--tsProbeLocations", tsProbeLocations.join(",")); return args; } @@ -362,23 +450,23 @@ export class AngularLanguageClient implements vscode.Disposable { this.virtualDocumentContents.clear(); } - /** + /** * Requests a template typecheck block at the current cursor location in the * specified editor. */ - async getTcbUnderCursor(): Promise { + async getTcbUnderCursor(): Promise { if (this.client === null) { return undefined; } - const doc = await vscode.workspace.document + const doc = await vscode.workspace.document; if (!doc) { - return + return; } - const cursor = await vscode.window.getCursorPosition() + const cursor = await vscode.window.getCursorPosition(); if (!cursor) { - return + return; } - const textDocument = doc.textDocument + const textDocument = doc.textDocument; const c2pConverter = code2ProtocolConverter; // Craft a request by converting vscode params to LSP. The corresponding // response is in LSP. @@ -394,23 +482,28 @@ export class AngularLanguageClient implements vscode.Disposable { return { uri: p2cConverter.asUri(response.uri), content: response.content, - selections: response.selections || [] + selections: response.selections || [], }; } - get initializeResult(): vscode.InitializeResult|undefined { + get initializeResult(): vscode.InitializeResult | undefined { return this.client?.initializeResult; } - async getComponentsForOpenExternalTemplate(textDocument: vscode.TextDocument): - Promise { + async getComponentsForOpenExternalTemplate( + textDocument: vscode.TextDocument + ): Promise { if (this.client === null) { return undefined; } - const response = await this.client.sendRequest(GetComponentsWithTemplateFile, { - textDocument: code2ProtocolConverter.asTextDocumentIdentifier(textDocument), - }); + const response = await this.client.sendRequest( + GetComponentsWithTemplateFile, + { + textDocument: + code2ProtocolConverter.asTextDocumentIdentifier(textDocument), + } + ); if (response === undefined || response === null) { return undefined; } @@ -418,47 +511,59 @@ export class AngularLanguageClient implements vscode.Disposable { return response; } - async getTemplateLocationForComponent(document: vscode.Document): - Promise { + async getTemplateLocationForComponent( + document: vscode.Document + ): Promise { if (this.client === null) { return null; } - const position = await vscode.window.getCursorPosition() + const position = await vscode.window.getCursorPosition(); const c2pConverter = code2ProtocolConverter; // Craft a request by converting vscode params to LSP. The corresponding // response is in LSP. - const response = await this.client.sendRequest(GetTemplateLocationForComponent, { - textDocument: c2pConverter.asTextDocumentIdentifier(document.textDocument), - position: c2pConverter.asPosition(position), - }); + const response = await this.client.sendRequest( + GetTemplateLocationForComponent, + { + textDocument: c2pConverter.asTextDocumentIdentifier( + document.textDocument + ), + position: c2pConverter.asPosition(position), + } + ); if (response === null) { return null; } const p2cConverter = protocol2CodeConverter; return vscode.Location.create( - p2cConverter.asUri(response.uri).toString(), p2cConverter.asRange(response.range)); + p2cConverter.asUri(response.uri).toString(), + p2cConverter.asRange(response.range) + ); } dispose() { - for (let d = this.disposables.pop(); d !== undefined; d = this.disposables.pop()) { + for ( + let d = this.disposables.pop(); + d !== undefined; + d = this.disposables.pop() + ) { d.dispose(); } } } function registerNotificationHandlers(client: vscode.LanguageClient) { - let task: {resolve: () => void}|undefined; + let task: { resolve: () => void } | undefined; client.onNotification(ProjectLoadingStart, () => { - const statusBar = vscode.window.createStatusBarItem(0, { progress: true }) - statusBar.text = 'Angular' - statusBar.show() + const statusBar = vscode.window.createStatusBarItem(0, { progress: true }); + statusBar.text = "Angular"; + statusBar.show(); task = { resolve: () => { - statusBar.isProgress = false - statusBar.hide() - statusBar.dispose() - } - } + statusBar.isProgress = false; + statusBar.hide(); + statusBar.dispose(); + }, + }; client.onNotification(ProjectLoadingFinish, () => { task.resolve(); task = undefined; @@ -466,39 +571,47 @@ function registerNotificationHandlers(client: vscode.LanguageClient) { }); const disposable1 = vscode.Disposable.create(() => { if (task) { - task.resolve() - task = undefined - } - }) - client.onNotification(SuggestStrictMode, async (params: SuggestStrictModeParams) => { - const config = vscode.workspace.getConfiguration(); - if (config.get('angular.enable-strict-mode-prompt') === false || - config.get('angular.forceStrictTemplates')) { - return; - } - const openTsConfig = 'Open tsconfig.json'; - const doNotPromptAgain = 'Do not show this again'; - // Markdown is not generally supported in `showInformationMessage()`, - // but links are supported. See - // https://github.com/microsoft/vscode/issues/20595#issuecomment-281099832 - const selection = await vscode.window.showInformationMessage( - 'Some language features are not available. To access all features, enable ' + - '[strictTemplates](https://angular.io/guide/angular-compiler-options#stricttemplates) in ' + - '[angularCompilerOptions](https://angular.io/guide/angular-compiler-options).', - openTsConfig, - doNotPromptAgain, - ); - if (selection === openTsConfig) { - await vscode.workspace.openResource(params.configFilePath); - } else if (selection === doNotPromptAgain) { - config.update( - 'angular.enable-strict-mode-prompt', false, (vscode as any).ConfigurationTarget?.Global); + task.resolve(); + task = undefined; } }); + client.onNotification( + SuggestStrictMode, + async (params: SuggestStrictModeParams) => { + const config = vscode.workspace.getConfiguration(); + if ( + config.get("angular.enable-strict-mode-prompt") === false || + config.get("angular.forceStrictTemplates") + ) { + return; + } + const openTsConfig = "Open tsconfig.json"; + const doNotPromptAgain = "Do not show this again"; + // Markdown is not generally supported in `showInformationMessage()`, + // but links are supported. See + // https://github.com/microsoft/vscode/issues/20595#issuecomment-281099832 + const selection = await vscode.window.showInformationMessage( + "Some language features are not available. To access all features, enable " + + "[strictTemplates](https://angular.io/guide/angular-compiler-options#stricttemplates) in " + + "[angularCompilerOptions](https://angular.io/guide/angular-compiler-options).", + openTsConfig, + doNotPromptAgain + ); + if (selection === openTsConfig) { + await vscode.workspace.openResource(params.configFilePath); + } else if (selection === doNotPromptAgain) { + config.update( + "angular.enable-strict-mode-prompt", + false, + (vscode as any).ConfigurationTarget?.Global + ); + } + } + ); client.onNotification(OpenOutputChannel, () => { client.outputChannel.show(); - }) + }); return disposable1; } @@ -521,20 +634,29 @@ function getProbeLocations(bundled: string): string[] { return locations; } -function extensionVersionCompatibleWithAllProjects(serverModuleLocation: string): boolean { - const languageServiceVersion = - resolve('@angular/language-service', serverModuleLocation)?.version; +function extensionVersionCompatibleWithAllProjects( + serverModuleLocation: string +): boolean { + const languageServiceVersion = resolve( + "@angular/language-service", + serverModuleLocation + )?.version; if (languageServiceVersion === undefined) { return true; } const workspaceFolders = vscode.workspace.workspaceFolders || []; for (const workspaceFolder of workspaceFolders) { - const angularCore = resolve('@angular/core', vscode.Uri.parse(workspaceFolder.uri).fsPath); + const angularCore = resolve( + "@angular/core", + vscode.Uri.parse(workspaceFolder.uri).fsPath + ); if (angularCore === undefined) { continue; } - if (!languageServiceVersion.greaterThanOrEqual(angularCore.version, 'minor')) { + if ( + !languageServiceVersion.greaterThanOrEqual(angularCore.version, "minor") + ) { return false; } } @@ -548,7 +670,10 @@ function getAngularVersionsInWorkspace(): Set { const angularCoreModules = new Set(); const workspaceFolders = vscode.workspace.workspaceFolders || []; for (const workspaceFolder of workspaceFolders) { - const angularCore = resolve('@angular/core', vscode.Uri.parse(workspaceFolder.uri).fsPath); + const angularCore = resolve( + "@angular/core", + vscode.Uri.parse(workspaceFolder.uri).fsPath + ); if (angularCore === undefined) { continue; } diff --git a/src/common/requests.ts b/src/common/requests.ts index ea56083..11635b9 100644 --- a/src/common/requests.ts +++ b/src/common/requests.ts @@ -6,23 +6,29 @@ * found in the LICENSE file at https://angular.io/license */ -import { DocumentUri } from 'vscode-languageserver-protocol'; -import * as lsp from 'coc.nvim'; +import * as lsp from "coc.nvim"; export const GetComponentsWithTemplateFile = new lsp.RequestType< - GetComponentsWithTemplateFileParams, GetComponentsWithTemplateFileResponse, - /* error */ void>('angular/getComponentsWithTemplateFile'); + GetComponentsWithTemplateFileParams, + GetComponentsWithTemplateFileResponse, + /* error */ void +>("angular/getComponentsWithTemplateFile"); export interface GetComponentsWithTemplateFileParams { textDocument: lsp.TextDocumentIdentifier; } /** An array of locations that represent component declarations. */ -export type GetComponentsWithTemplateFileResponse = Array<{uri: DocumentUri , range: lsp.Range}>; +export type GetComponentsWithTemplateFileResponse = Array<{ + uri: lsp.Uri; + range: lsp.Range; +}>; export const GetTemplateLocationForComponent = new lsp.RequestType< - GetTemplateLocationForComponentParams, lsp.Location, - /* error */ void>('angular/getTemplateLocationForComponent'); + GetTemplateLocationForComponentParams, + lsp.Location, + /* error */ void +>("angular/getTemplateLocationForComponent"); export interface GetTemplateLocationForComponentParams { textDocument: lsp.TextDocumentIdentifier; @@ -34,19 +40,23 @@ export interface GetTcbParams { position: lsp.Position; } -export const GetTcbRequest = - new lsp.RequestType('angular/getTcb'); +export const GetTcbRequest = new lsp.RequestType< + GetTcbParams, + GetTcbResponse | null, + /* error */ void +>("angular/getTcb"); export interface GetTcbResponse { - uri: DocumentUri; + uri: lsp.Uri; content: string; - selections: lsp.Range[] + selections: lsp.Range[]; } - -export const IsInAngularProject = - new lsp.RequestType( - 'angular/isAngularCoreInOwningProject'); +export const IsInAngularProject = new lsp.RequestType< + IsInAngularProjectParams, + boolean | null, + /* error */ void +>("angular/isAngularCoreInOwningProject"); export interface IsInAngularProjectParams { textDocument: lsp.TextDocumentIdentifier; @@ -54,19 +64,21 @@ export interface IsInAngularProjectParams { export const GetHoverInfo = new lsp.RequestType< { - textDocument: { uri: string }, - position: lsp.Position + textDocument: { uri: string }; + position: lsp.Position; }, lsp.Hover | null, - /* error */ void>('textDocument/hover'); + /* error */ void +>("textDocument/hover"); export const GetCompleteItems = new lsp.RequestType< { - textDocument: { uri: string }, - position: lsp.Position, + textDocument: { uri: string }; + position: lsp.Position; context: { - triggerCharacter?: string - } + triggerCharacter?: string; + }; }, lsp.CompletionList | null, - /* error */ void>('textDocument/completion'); + /* error */ void +>("textDocument/completion"); diff --git a/tsconfig.json b/tsconfig.json index 682f53e..2d63862 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,26 +1,26 @@ { "compilerOptions": { /* Basic Options */ - "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, + "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, "lib": [ "es2015", "esnext" - ], /* Specify library files to be included in the compilation. */ + ] /* Specify library files to be included in the compilation. */, // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - "declaration": false, /* Generates corresponding '.d.ts' file. */ + "declaration": false /* Generates corresponding '.d.ts' file. */, // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - "sourceMap": false, /* Generates corresponding '.map' file. */ + "sourceMap": false /* Generates corresponding '.map' file. */, // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./out", /* Redirect output structure to the directory. */ - "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "outDir": "./out" /* Redirect output structure to the directory. */, + "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, // "composite": true, /* Enable project compilation */ // "removeComments": true, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ @@ -40,14 +40,14 @@ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ - "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ - "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ /* Source Map Options */ @@ -60,7 +60,5 @@ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ }, - "include": [ - "./src" - ] + "include": ["./src"] }