210 lines
6.9 KiB
TypeScript
210 lines
6.9 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
import { DiagnosticKind, disposeAll, workspace } from 'coc.nvim'
|
|
import { Range, Diagnostic, DiagnosticSeverity, Disposable, Position } from 'vscode-languageserver-protocol'
|
|
import Uri from 'vscode-uri'
|
|
import LanguageProvider from './languageProvider'
|
|
import * as Proto from './protocol'
|
|
import * as PConst from './protocol.const'
|
|
import TypeScriptServiceClient from './typescriptServiceClient'
|
|
import { LanguageDescription } from './utils/languageDescription'
|
|
import * as typeConverters from './utils/typeConverters'
|
|
import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus'
|
|
|
|
// Style check diagnostics that can be reported as warnings
|
|
const styleCheckDiagnostics = [
|
|
6133, // variable is declared but never used
|
|
6138, // property is declared but its value is never read
|
|
7027, // unreachable code detected
|
|
7028, // unused label
|
|
7029, // fall through case in switch
|
|
7030 // not all code paths return a value
|
|
]
|
|
|
|
export default class TypeScriptServiceClientHost implements Disposable {
|
|
private readonly ataProgressReporter: AtaProgressReporter
|
|
private readonly typingsStatus: TypingsStatus
|
|
private readonly client: TypeScriptServiceClient
|
|
private readonly languages: LanguageProvider[] = []
|
|
private readonly languagePerId = new Map<string, LanguageProvider>()
|
|
private readonly disposables: Disposable[] = []
|
|
private reportStyleCheckAsWarnings = true
|
|
|
|
constructor(descriptions: LanguageDescription[]) {
|
|
const handleProjectChange = () => {
|
|
setTimeout(() => {
|
|
this.triggerAllDiagnostics()
|
|
}, 1500)
|
|
}
|
|
|
|
const configFileWatcher = workspace.createFileSystemWatcher('**/[tj]sconfig.json')
|
|
this.disposables.push(configFileWatcher)
|
|
configFileWatcher.onDidCreate(
|
|
this.reloadProjects,
|
|
this,
|
|
this.disposables
|
|
)
|
|
configFileWatcher.onDidDelete(
|
|
this.reloadProjects,
|
|
this,
|
|
this.disposables
|
|
)
|
|
configFileWatcher.onDidChange(handleProjectChange, this, this.disposables)
|
|
|
|
this.client = new TypeScriptServiceClient()
|
|
this.disposables.push(this.client)
|
|
this.client.onDiagnosticsReceived(({ kind, resource, diagnostics }) => {
|
|
this.diagnosticsReceived(kind, resource, diagnostics)
|
|
}, null, this.disposables)
|
|
|
|
this.client.onConfigDiagnosticsReceived(diag => {
|
|
let { body } = diag
|
|
if (body) {
|
|
let { configFile, diagnostics } = body
|
|
let uri = Uri.file(configFile)
|
|
let language = this.findLanguage(uri)
|
|
if (!language) return
|
|
if (diagnostics.length == 0) {
|
|
language.configFileDiagnosticsReceived(uri, [])
|
|
} else {
|
|
let range = Range.create(Position.create(0, 0), Position.create(0, 1))
|
|
let { text, code, category } = diagnostics[0]
|
|
let severity = category == 'error' ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning
|
|
let diagnostic = Diagnostic.create(range, text, severity, code)
|
|
language.configFileDiagnosticsReceived(uri, [diagnostic])
|
|
}
|
|
}
|
|
}, null, this.disposables)
|
|
|
|
this.typingsStatus = new TypingsStatus(this.client)
|
|
this.ataProgressReporter = new AtaProgressReporter(this.client)
|
|
for (const description of descriptions) { // tslint:disable-line
|
|
const manager = new LanguageProvider(
|
|
this.client,
|
|
description,
|
|
this.typingsStatus
|
|
)
|
|
this.languages.push(manager)
|
|
this.disposables.push(manager)
|
|
this.languagePerId.set(description.id, manager)
|
|
}
|
|
|
|
this.client.ensureServiceStarted()
|
|
this.client.onTsServerStarted(() => {
|
|
this.triggerAllDiagnostics()
|
|
})
|
|
this.configurationChanged()
|
|
}
|
|
|
|
public dispose(): void {
|
|
disposeAll(this.disposables)
|
|
this.typingsStatus.dispose()
|
|
this.ataProgressReporter.dispose()
|
|
}
|
|
|
|
public reset(): void {
|
|
for (let lang of this.languages) {
|
|
lang.fileConfigurationManager.reset()
|
|
}
|
|
}
|
|
|
|
public get serviceClient(): TypeScriptServiceClient {
|
|
return this.client
|
|
}
|
|
|
|
public reloadProjects(): void {
|
|
this.client.execute('reloadProjects', null, false) // tslint:disable-line
|
|
this.triggerAllDiagnostics()
|
|
}
|
|
|
|
// typescript or javascript
|
|
public getProvider(languageId: string): LanguageProvider {
|
|
return this.languagePerId.get(languageId)
|
|
}
|
|
|
|
private configurationChanged(): void {
|
|
const config = workspace.getConfiguration('tsserver')
|
|
this.reportStyleCheckAsWarnings = config.get('reportStyleChecksAsWarnings', true)
|
|
}
|
|
|
|
public findLanguage(resource: Uri): LanguageProvider | null {
|
|
try {
|
|
return this.languages.find(language => language.handles(resource))
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
public handles(uri: string): boolean {
|
|
return this.findLanguage(Uri.parse(uri)) != null
|
|
}
|
|
|
|
private triggerAllDiagnostics(): void {
|
|
for (const language of this.languagePerId.values()) {
|
|
language.triggerAllDiagnostics()
|
|
}
|
|
}
|
|
|
|
private diagnosticsReceived(
|
|
kind: DiagnosticKind,
|
|
resource: Uri,
|
|
diagnostics: Proto.Diagnostic[]
|
|
): void {
|
|
const language = this.findLanguage(resource)
|
|
if (language) {
|
|
language.diagnosticsReceived(
|
|
kind,
|
|
resource,
|
|
this.createMarkerDatas(diagnostics))
|
|
}
|
|
}
|
|
|
|
private createMarkerDatas(diagnostics: Proto.Diagnostic[]): Diagnostic[] {
|
|
return diagnostics.map(tsDiag => this.tsDiagnosticToLspDiagnostic(tsDiag))
|
|
}
|
|
|
|
private tsDiagnosticToLspDiagnostic(diagnostic: Proto.Diagnostic): Diagnostic {
|
|
const { start, end, text } = diagnostic
|
|
const range = {
|
|
start: typeConverters.Position.fromLocation(start),
|
|
end: typeConverters.Position.fromLocation(end)
|
|
}
|
|
return {
|
|
range,
|
|
message: text,
|
|
code: diagnostic.code ? diagnostic.code : null,
|
|
severity: this.getDiagnosticSeverity(diagnostic),
|
|
source: diagnostic.source || 'tsserver',
|
|
}
|
|
}
|
|
|
|
private getDiagnosticSeverity(diagnostic: Proto.Diagnostic): DiagnosticSeverity {
|
|
if (
|
|
this.reportStyleCheckAsWarnings &&
|
|
this.isStyleCheckDiagnostic(diagnostic.code) &&
|
|
diagnostic.category === PConst.DiagnosticCategory.error
|
|
) {
|
|
return DiagnosticSeverity.Warning
|
|
}
|
|
|
|
switch (diagnostic.category) {
|
|
case PConst.DiagnosticCategory.error:
|
|
return DiagnosticSeverity.Error
|
|
|
|
case PConst.DiagnosticCategory.warning:
|
|
return DiagnosticSeverity.Warning
|
|
|
|
case PConst.DiagnosticCategory.suggestion:
|
|
return DiagnosticSeverity.Information
|
|
|
|
default:
|
|
return DiagnosticSeverity.Error
|
|
}
|
|
}
|
|
|
|
private isStyleCheckDiagnostic(code: number | undefined): boolean {
|
|
return code ? styleCheckDiagnostics.indexOf(code) !== -1 : false
|
|
}
|
|
}
|