├── .npmignore ├── bin └── index.js ├── test └── test.sh ├── .vim └── coc-settings.json ├── src ├── common │ ├── hunkStream.ts │ ├── logger.ts │ ├── observable.ts │ ├── types.ts │ └── util.ts ├── handles │ ├── handleFormat.ts │ └── handleDiagnostic.ts └── index.ts ├── .github └── workflows │ └── test.yml ├── package.json ├── LICENSE ├── .gitignore ├── tsconfig.json ├── README.md └── yarn.lock /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | src/ 3 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib') 4 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo `ls -al` 4 | -------------------------------------------------------------------------------- /.vim/coc-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "textdocument" 4 | ] 5 | } -------------------------------------------------------------------------------- /src/common/hunkStream.ts: -------------------------------------------------------------------------------- 1 | import { Readable } from 'stream'; 2 | 3 | // create an readable stream 4 | export default class HunkStream extends Readable { 5 | constructor(private content: String) { 6 | super() 7 | } 8 | 9 | _read() { 10 | const ctx = this; 11 | ctx.push(this.content) 12 | ctx.push(null) 13 | } 14 | 15 | toString() { 16 | return this.content 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: push 4 | jobs: 5 | lint: 6 | runs-on: ubuntu-18.04 7 | env: 8 | CI: true 9 | steps: 10 | - uses: actions/checkout@v1 11 | - uses: actions/setup-node@v1 12 | - name: install dependencies 13 | run: yarn install 14 | - name: run unit tests 15 | run: yarn run test 16 | - name: check typescript build errors 17 | run: node_modules/.bin/tsc 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diagnostic-languageserver", 3 | "version": "1.8.1", 4 | "description": "diagnostic language server", 5 | "main": "./lib/index.js", 6 | "repository": "git@github.com:iamcco/diagnostic-languageserver.git", 7 | "author": "iamcco ", 8 | "license": "MIT", 9 | "scripts": { 10 | "build": "tsc -p ./", 11 | "watch": "tsc -w -p ./" 12 | }, 13 | "bin": { 14 | "diagnostic-languageserver": "./bin/index.js" 15 | }, 16 | "dependencies": { 17 | "commander": "^5.1.0", 18 | "find-up": "^4.1.0", 19 | "lodash": "^4.17.15", 20 | "rxjs": "^6.5.5", 21 | "tempy": "^0.7.1", 22 | "tslib": "^1.11.2", 23 | "vscode-languageserver": "^6.1.1", 24 | "vscode-languageserver-textdocument": "^1.0.1", 25 | "vscode-uri": "^2.1.1" 26 | }, 27 | "devDependencies": { 28 | "@types/lodash": "^4.14.150", 29 | "@types/node": "^11.11.3", 30 | "typescript": "^3.8.3" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 iamcco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # create by https://github.com/iamcco/gitignore.vim 3 | # gitignore templates from https://github.com/dvcs/gitignore 4 | 5 | ### Node.gitignore ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (http://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Typescript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | 65 | lib/ 66 | -------------------------------------------------------------------------------- /src/common/logger.ts: -------------------------------------------------------------------------------- 1 | import { IConnection, MessageType } from 'vscode-languageserver'; 2 | 3 | class Logger { 4 | isInit = false 5 | connection: IConnection 6 | level: MessageType 7 | logs: Array<{type: string, message: string}> = [] 8 | 9 | init (con: IConnection, lev: MessageType) { 10 | this.connection = con 11 | this.level = lev 12 | this.isInit = true 13 | if (this.logs.length) { 14 | this.logs.forEach(log => { 15 | if (this[log.type]) { 16 | this[log.type](log.message) 17 | } 18 | }) 19 | this.logs = [] 20 | } 21 | } 22 | error (message: string) { 23 | if (!this.isInit) { 24 | return this.logs.push({ 25 | type: 'error', 26 | message 27 | }) 28 | } 29 | if (this.connection && this.level >= MessageType.Error) { 30 | this.connection.console.error(message) 31 | } 32 | } 33 | warn (message: string) { 34 | if (!this.isInit) { 35 | return this.logs.push({ 36 | type: 'warn', 37 | message 38 | }) 39 | } 40 | if (this.connection && this.level >= MessageType.Warning) { 41 | this.connection.console.warn(message) 42 | } 43 | } 44 | info (message: string) { 45 | if (!this.isInit) { 46 | return this.logs.push({ 47 | type: 'info', 48 | message 49 | }) 50 | } 51 | if (this.connection && this.level >= MessageType.Info) { 52 | this.connection.console.info(message) 53 | } 54 | } 55 | log (message: string) { 56 | if (!this.isInit) { 57 | return this.logs.push({ 58 | type: 'log', 59 | message 60 | }) 61 | } 62 | if (this.connection && this.level >= MessageType.Log) { 63 | this.connection.console.log(message) 64 | } 65 | } 66 | } 67 | 68 | export default new Logger() 69 | -------------------------------------------------------------------------------- /src/common/observable.ts: -------------------------------------------------------------------------------- 1 | import { Observable, PartialObserver, Subscription } from 'rxjs'; 2 | 3 | /** 4 | * 5 | * if inner observable hasn't complete, and source observable trigger new value, the 6 | * inner observable's value will be abandon. and after inner observable complete the 7 | * lastest source observable value will be trigger. 8 | * 9 | */ 10 | export function waitMap(fn: (res: T) => Observable): (obs: Observable) => Observable { 11 | return (preObs: Observable) => { 12 | return Observable.create((observer: PartialObserver) => { 13 | let closed = false 14 | let latestRes: T 15 | let resultSubp: Subscription 16 | let subp: Subscription 17 | const run = (res: T) => { 18 | const obs = fn(res) 19 | return obs.subscribe({ 20 | next: res => { 21 | if (!latestRes) { 22 | observer.next(res) 23 | } 24 | }, 25 | error: err => { 26 | closed = true 27 | observer.error(err) 28 | resultSubp.unsubscribe() 29 | }, 30 | complete: () => { 31 | if (latestRes && !closed) { 32 | const res = latestRes 33 | latestRes = undefined 34 | run(res) 35 | } 36 | } 37 | }) 38 | } 39 | resultSubp = preObs.subscribe({ 40 | next: res => { 41 | latestRes = res 42 | if (!subp || subp.closed) { 43 | latestRes = undefined 44 | subp = run(res) 45 | } 46 | }, 47 | error: err => { 48 | closed = true 49 | observer.error(err) 50 | }, 51 | complete: () => { 52 | closed = true 53 | observer.complete() 54 | } 55 | }) 56 | return resultSubp 57 | }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/common/types.ts: -------------------------------------------------------------------------------- 1 | export type SecurityKey = 'error' | 'warning' | 'info' | 'hint' 2 | export interface ISecurities { 3 | [key: string]: SecurityKey 4 | } 5 | 6 | // config of per language 7 | export interface ILinterConfig { 8 | command: string 9 | rootPatterns: string[] | string 10 | isStdout?: boolean 11 | isStderr?: boolean 12 | debounce?: number 13 | args?: Array 14 | sourceName: string 15 | formatLines?: number 16 | formatPattern: [string, { 17 | sourceName?: string 18 | sourceNameFilter?: boolean 19 | line: number, 20 | column: number, 21 | endLine?: number, 22 | endColumn?: number, 23 | message: Array | number, 24 | security: number 25 | }] 26 | securities?: ISecurities 27 | offsetLine?: number 28 | offsetColumn?: number 29 | requiredFiles?: string[] 30 | parseJson?: { 31 | // Dot separated path. If empty, simply use the root. 32 | errorsRoot?: string | string[] 33 | 34 | sourceName?: string 35 | sourceNameFilter?: boolean 36 | line: string 37 | column: string 38 | 39 | // If left out, just use line / column 40 | endLine?: string 41 | endColumn?: string 42 | 43 | // Will be parsed from the error object. 44 | message: string 45 | security: string 46 | }, 47 | } 48 | 49 | export interface ILinterResult { 50 | sourceName: string 51 | security: string 52 | line: string | number 53 | column: string | number 54 | endLine?: string | number 55 | endColumn?: string | number 56 | message: string 57 | } 58 | 59 | // config of per formatter 60 | export interface IFormatterConfig { 61 | command: string 62 | args?: Array 63 | rootPatterns?: string[] | string 64 | isStdout?: boolean 65 | isStderr?: boolean 66 | doesWriteToFile?: boolean 67 | requiredFiles?: string[] 68 | } 69 | 70 | // initializationOptions config 71 | export interface IConfig { 72 | linters: { 73 | [linter: string]: ILinterConfig 74 | } 75 | filetypes: { 76 | [fileType: string]: string | string[] 77 | } 78 | formatters: { 79 | [formatter: string]: IFormatterConfig 80 | } 81 | formatFiletypes: { 82 | [fileType: string]: string | string[] 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/handles/handleFormat.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { TextEdit, TextDocument, CancellationToken, Range, Position } from 'vscode-languageserver'; 3 | import { URI } from 'vscode-uri'; 4 | 5 | import { IFormatterConfig } from '../common/types'; 6 | import { findWorkDirectory, findCommand, executeFile, checkAnyFileExists } from '../common/util'; 7 | import HunkStream from '../common/hunkStream'; 8 | 9 | type Handle = (text: string) => Promise 10 | 11 | async function handleFormat( 12 | config: IFormatterConfig, 13 | textDocument: TextDocument, 14 | text: string, 15 | next: Handle 16 | ): Promise { 17 | const { 18 | command, 19 | rootPatterns = [], 20 | isStdout, 21 | isStderr, 22 | args = [], 23 | } = config 24 | const workDir = await findWorkDirectory( 25 | URI.parse(textDocument.uri).fsPath, 26 | rootPatterns 27 | ) 28 | 29 | if (config.requiredFiles && config.requiredFiles.length) { 30 | if (!checkAnyFileExists(workDir, config.requiredFiles)) { 31 | return next(text) 32 | } 33 | } 34 | 35 | const cmd = await findCommand(command, workDir) 36 | const { 37 | stdout = '', 38 | stderr = '', 39 | code 40 | } = await executeFile( 41 | new HunkStream(text), 42 | textDocument, 43 | cmd, 44 | args, 45 | { 46 | cwd: workDir 47 | } 48 | ) 49 | let output = ''; 50 | if (code > 0) { 51 | output = text 52 | } else if (config.doesWriteToFile) { 53 | output = fs.readFileSync(URI.parse(textDocument.uri).fsPath, 'utf8') 54 | } else if (isStdout === undefined && isStderr === undefined) { 55 | output = stdout 56 | } else { 57 | if (isStdout) { 58 | output += stdout 59 | } 60 | if (isStderr) { 61 | output += stderr 62 | } 63 | } 64 | return next(output) 65 | } 66 | 67 | 68 | export async function formatDocument( 69 | formatterConfigs: IFormatterConfig[], 70 | textDocument: TextDocument, 71 | token: CancellationToken 72 | ): Promise { 73 | 74 | const resolve = formatterConfigs 75 | .reverse() 76 | .reduce((res: Handle, config: IFormatterConfig) => { 77 | return async (text: string): Promise => { 78 | if (token.isCancellationRequested) { 79 | return 80 | } 81 | return handleFormat(config, textDocument, text, res) 82 | } 83 | }, async (text: string) => text) 84 | 85 | const text = await resolve(textDocument.getText()) 86 | 87 | if (!text) { 88 | return 89 | } 90 | 91 | return [{ 92 | range: Range.create( 93 | Position.create(0, 0), 94 | Position.create(textDocument.lineCount + 1, 0) 95 | ), 96 | newText: text 97 | }] 98 | } 99 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | import { 3 | createConnection, 4 | TextDocuments, 5 | InitializeParams, 6 | IConnection, 7 | DocumentFormattingParams, 8 | CancellationToken, 9 | MessageType, 10 | TextDocumentChangeEvent, 11 | TextDocumentSyncKind, 12 | } from 'vscode-languageserver'; 13 | import { TextDocument } from 'vscode-languageserver-textdocument' 14 | 15 | import { IConfig } from './common/types'; 16 | import { 17 | next as diagnosticNext, 18 | unsubscribe as diagnosticUnsubscribe 19 | } from './handles/handleDiagnostic' 20 | import logger from './common/logger'; 21 | import { formatDocument } from './handles/handleFormat'; 22 | 23 | // parse command line options 24 | const options = new Command("diagnostic-languageserver") 25 | .version(require("../package.json").version) 26 | .option("--log-level ", "A number indicating the log level (4 = log, 3 = info, 2 = warn, 1 = error). Defaults to `2`.") 27 | .option("--stdio", "use stdio") 28 | .option("--node-ipc", "use node-ipc") 29 | .option("--socket ", "use socket. example: --socket=5000") 30 | .allowUnknownOption(true) 31 | .parse(process.argv); 32 | let logLevel: MessageType = MessageType.Warning 33 | if (options.logLevel) { 34 | logLevel = parseInt(options.logLevel, 10) as any; 35 | if (logLevel && (logLevel < 1 || logLevel > 4)) { 36 | logger.error("Invalid `--log-level " + logLevel + "`. Falling back to `log` level.") 37 | logLevel = MessageType.Log 38 | } 39 | } 40 | 41 | // create connection by command argv 42 | const connection: IConnection = createConnection(); 43 | 44 | // init logger 45 | logger.init(connection, logLevel) 46 | 47 | // sync text document manager 48 | const documents = new TextDocuments(TextDocument) 49 | 50 | // config of initializationOptions 51 | let config: IConfig 52 | 53 | // lsp initialize 54 | connection.onInitialize((param: InitializeParams) => { 55 | const { initializationOptions = {} } = param 56 | 57 | config = initializationOptions 58 | 59 | return { 60 | capabilities: { 61 | textDocumentSync: TextDocumentSyncKind.Incremental, 62 | documentFormattingProvider: true 63 | } 64 | }; 65 | }); 66 | 67 | const handleDiagnostic = ( change: TextDocumentChangeEvent ) => { 68 | const textDocument = change.document 69 | const { linters = {}, filetypes = {} } = config 70 | if (!filetypes[textDocument.languageId]) { 71 | return 72 | } 73 | const linter = [].concat(filetypes[textDocument.languageId]) 74 | const configItems = linter.map(l => linters[l]).filter(l => l) 75 | if (configItems.length === 0) { 76 | return 77 | } 78 | diagnosticNext(textDocument, connection, configItems) 79 | } 80 | 81 | // document change or open 82 | documents.onDidChangeContent(handleDiagnostic); 83 | 84 | // document will save 85 | documents.onDidSave(handleDiagnostic) 86 | 87 | documents.onDidClose((evt) => { 88 | diagnosticUnsubscribe(evt.document) 89 | }) 90 | 91 | // listen for document's open/close/change 92 | documents.listen(connection); 93 | 94 | // handle format request 95 | connection.onDocumentFormatting(async ( 96 | params: DocumentFormattingParams, 97 | token: CancellationToken 98 | ) => { 99 | const { textDocument } = params 100 | if (!textDocument || !textDocument.uri) { 101 | return 102 | } 103 | const doc = documents.get(textDocument.uri) 104 | if (!doc) { 105 | return 106 | } 107 | const { formatters, formatFiletypes } = config 108 | if (!formatFiletypes[doc.languageId]) { 109 | return 110 | } 111 | const formatterNames = [].concat(formatFiletypes[doc.languageId]) 112 | const formatterConfigs = formatterNames.map(n => formatters[n]).filter(n => n) 113 | if (formatterConfigs.length === 0) { 114 | return 115 | } 116 | return formatDocument(formatterConfigs, doc, token) 117 | }) 118 | 119 | // lsp start 120 | connection.listen(); 121 | -------------------------------------------------------------------------------- /src/common/util.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TextDocument, 3 | } from 'vscode-languageserver'; 4 | import { URI } from 'vscode-uri'; 5 | import fs from 'fs'; 6 | import os from 'os'; 7 | import path from 'path'; 8 | import { Readable } from 'stream'; 9 | import findUp from 'find-up'; 10 | import { SpawnOptions, spawn } from 'child_process'; 11 | import tempy from 'tempy'; 12 | import del from 'del'; 13 | import logger from './logger'; 14 | 15 | export async function executeFile( 16 | input: Readable, 17 | textDocument: TextDocument, 18 | command: string, 19 | args?: any[], 20 | option?: SpawnOptions 21 | ): Promise<{ 22 | code: number, 23 | stdout: string, 24 | stderr: string 25 | }> { 26 | const fpath = URI.parse(textDocument.uri).fsPath 27 | 28 | let usePipe = true 29 | let tempFilename: string | undefined; 30 | 31 | args = await Promise.all((args || []).map(async arg => { 32 | if (/%text/.test(arg)) { 33 | usePipe = false 34 | return arg.replace(/%text/g, input.toString()) 35 | } 36 | if (/%filepath/.test(arg)) { 37 | return arg.replace(/%filepath/g, fpath) 38 | } 39 | if (/%filename/.test(arg)) { 40 | return arg.replace(/%filename/g, path.basename(fpath)) 41 | } 42 | if (/%file/.test(arg)) { 43 | usePipe = false 44 | return arg.replace(/%file/g, fpath) 45 | } 46 | if (/%tempfile/.test(arg)) { 47 | usePipe = false 48 | tempFilename = await tempy.write(input, { extension: path.extname(fpath) }) 49 | return arg.replace(/%tempfile/g, tempFilename); 50 | } 51 | 52 | return arg 53 | })) 54 | 55 | logger.log(`linter run args: ${JSON.stringify(args)}`) 56 | 57 | const result = await spawnAsync( 58 | command, 59 | args, 60 | { 61 | ...option, 62 | shell: os.platform() === 'win32' ? true : undefined, 63 | input: usePipe ? input : undefined 64 | } 65 | ); 66 | 67 | if (tempFilename != null) { 68 | await del(tempFilename, { force: true }); 69 | } 70 | 71 | return result; 72 | } 73 | 74 | export interface SpawnAsyncOptions extends SpawnOptions { 75 | input?: Readable 76 | } 77 | 78 | export async function spawnAsync( 79 | command: string, 80 | args: ReadonlyArray, 81 | options: SpawnAsyncOptions 82 | ): Promise<{ 83 | code: number, 84 | stdout: string, 85 | stderr: string 86 | }> { 87 | return new Promise((resolve, reject) => { 88 | const child = spawn( 89 | command, 90 | args, 91 | { ...options, shell: os.platform() === 'win32' ? true : undefined } 92 | ); 93 | let stdout = '' 94 | let stderr = '' 95 | let error: Error 96 | 97 | child.stdout.on('data', (data) => { 98 | stdout += data 99 | }); 100 | 101 | child.stderr.on('data', (data) => { 102 | stderr += data 103 | }); 104 | 105 | child.on('error', (err: Error) => { 106 | error = err 107 | reject(error) 108 | }); 109 | 110 | child.on('close', (code) => { 111 | if (!error) { 112 | resolve({ code, stdout, stderr }) 113 | } 114 | }); 115 | 116 | // error will occur when cp get error 117 | if (options.input) { 118 | options.input.pipe(child.stdin).on('error', () => {}) 119 | } 120 | }); 121 | } 122 | 123 | // find work dirname by root patterns 124 | export async function findWorkDirectory( 125 | filePath: string, 126 | rootPatterns: string | string[] 127 | ): Promise { 128 | const dirname = path.dirname(filePath) 129 | let patterns = [].concat(rootPatterns) 130 | try { 131 | for(const pattern of patterns){ 132 | const dir = await findUp(async directory => { 133 | const hasMatch = await findUp.exists(path.join(directory, pattern)) 134 | logger.log(`searching working directory: ${directory}, cwd: ${dirname}, pattern: ${pattern}, matches: ${hasMatch}`) 135 | return hasMatch && directory 136 | }, {type: 'directory', cwd: dirname}) 137 | 138 | 139 | if (dir && dir !== "/") { 140 | return dir 141 | } 142 | } 143 | } catch (err) { 144 | // do nothing on error 145 | } 146 | return dirname 147 | } 148 | 149 | export async function findCommand(command: string, workDir: string) { 150 | if (/^(\.\.|\.)/.test(command)) { 151 | let cmd = path.join(workDir, command) 152 | if (fs.existsSync(cmd)) { 153 | return command 154 | } 155 | return path.basename(cmd) 156 | } 157 | return command 158 | } 159 | 160 | export function checkAnyFileExists(workDir: string, testPaths: string[]) { 161 | for (const testPath of testPaths) { 162 | if (fs.existsSync(path.join(workDir, testPath))) { 163 | return true 164 | } 165 | } 166 | 167 | return false 168 | } 169 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | "lib": [ 7 | "es2015", 8 | "esnext" 9 | ], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./lib", /* Redirect output structure to the directory. */ 18 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "removeComments": true, /* Do not emit comments to output. */ 21 | // "noEmit": true, /* Do not emit outputs. */ 22 | "importHelpers": true, /* Import emit helpers from 'tslib'. */ 23 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 24 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 25 | 26 | /* Strict Type-Checking Options */ 27 | // "strict": true, /* Enable all strict type-checking options. */ 28 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 29 | // "strictNullChecks": true, /* Enable strict null checks. */ 30 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | 52 | /* Source Map Options */ 53 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 54 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 55 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 56 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 57 | 58 | /* Experimental Options */ 59 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 60 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/handles/handleDiagnostic.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TextDocument, 3 | DiagnosticSeverity, 4 | Diagnostic, 5 | IConnection, 6 | } from 'vscode-languageserver'; 7 | import { URI } from 'vscode-uri'; 8 | import { Subscription, Subject, from, timer } from 'rxjs'; 9 | import { filter, switchMap, map } from 'rxjs/operators'; 10 | import { isAbsolute, join } from 'path'; 11 | 12 | import { waitMap } from '../common/observable'; 13 | import { ILinterConfig, SecurityKey, ILinterResult } from '../common/types'; 14 | import { executeFile, findWorkDirectory, findCommand, checkAnyFileExists } from '../common/util'; 15 | import HunkStream from '../common/hunkStream'; 16 | import logger from '../common/logger'; 17 | import lodashGet from 'lodash/get'; 18 | 19 | const securityMap = { 20 | 'error': DiagnosticSeverity.Error, 21 | 'warning': DiagnosticSeverity.Warning, 22 | 'info': DiagnosticSeverity.Information, 23 | 'hint': DiagnosticSeverity.Hint 24 | } 25 | 26 | const origin$: Subject = new Subject() 27 | 28 | const subscriptions: { 29 | [uri: string]: Subscription 30 | } = {} 31 | 32 | function sumNum(num: string | number | undefined, ...args: number[]) { 33 | if (num === undefined) { 34 | return 0 35 | } 36 | return args.reduce((res, next) => res + next, Number(num)) 37 | } 38 | 39 | function formatMessage( 40 | message: number | Array, 41 | match: RegExpMatchArray 42 | ) { 43 | return [].concat(message).reduce((res, next) => { 44 | if (typeof next === 'number') { 45 | res += match[next] 46 | } else { 47 | res += next 48 | } 49 | return res 50 | }, '') 51 | } 52 | 53 | function getSecurity( 54 | securityKey: SecurityKey 55 | ) { 56 | const security = securityMap[securityKey] 57 | return security !== undefined ? security : 1 58 | } 59 | 60 | function handleLinterRegex(currentSourceName: string, output: string, config: ILinterConfig): ILinterResult[] { 61 | const { 62 | formatLines = 1, 63 | formatPattern, 64 | } = config 65 | let linterResults: ILinterResult[] = []; 66 | 67 | if (!formatLines || !formatPattern) { 68 | throw new Error('missing formatLines or formatPattern') 69 | } 70 | 71 | const { sourceName, line, column, endLine, endColumn, message, security } = formatPattern[1] 72 | const lines = output.split('\n') 73 | 74 | let str: string = lines.shift() 75 | while(lines.length > 0 || str !== undefined) { 76 | str = [str].concat(lines.slice(0, formatLines - 1)).join('\n') 77 | const m = str.match(new RegExp(formatPattern[0])) 78 | logger.log(`match string: ${str}`) 79 | logger.log(`match result: ${JSON.stringify(m, null, 2)}`) 80 | if (m) { 81 | linterResults.push({ 82 | sourceName: sourceName ? m[sourceName] : currentSourceName, 83 | security: m[security], 84 | line: m[line], 85 | column: m[column], 86 | endLine: endLine != undefined ? m[endLine] : undefined, 87 | endColumn: endColumn != undefined ? m[endColumn] : undefined, 88 | message: formatMessage(message, m), 89 | }); 90 | } 91 | str = lines.shift() 92 | } 93 | 94 | return linterResults 95 | } 96 | 97 | const variableFinder = /\$\{[^}]+}/g; 98 | 99 | function formatStringWithObject | any[]>( 100 | str: string, 101 | obj: T 102 | ) { 103 | return str.replace(variableFinder, k => { 104 | // Remove `${` and `}` 105 | const lookup = k.slice(2, -1).trim(); 106 | 107 | return lodashGet(obj, lookup, ''); 108 | }); 109 | } 110 | 111 | function handleLinterJson(currentSourceName: string, output: string, config: ILinterConfig): ILinterResult[] { 112 | if (!config.parseJson) { 113 | throw new Error('missing parseJson') 114 | } 115 | 116 | const { 117 | sourceName, 118 | errorsRoot, 119 | line, 120 | column, 121 | endLine, 122 | endColumn, 123 | security, 124 | message, 125 | } = config.parseJson 126 | 127 | const resultsFromJson: any[] = errorsRoot 128 | ? lodashGet(JSON.parse(output), errorsRoot, []) 129 | : JSON.parse(output) 130 | 131 | return resultsFromJson.map(jsonObject => { 132 | return { 133 | sourceName: sourceName ? lodashGet(jsonObject, sourceName) : currentSourceName, 134 | security: lodashGet(jsonObject, security), 135 | line: lodashGet(jsonObject, line), 136 | column: lodashGet(jsonObject, column), 137 | endLine: endLine ? lodashGet(jsonObject, endLine) : undefined, 138 | endColumn: endColumn ? lodashGet(jsonObject, endColumn) : undefined, 139 | message: formatStringWithObject(message, jsonObject), 140 | } 141 | }); 142 | } 143 | 144 | async function handleLinter ( 145 | textDocument: TextDocument, 146 | config: ILinterConfig 147 | ): Promise { 148 | const { 149 | command, 150 | rootPatterns = [], 151 | args = [], 152 | offsetLine = 0, 153 | offsetColumn = 0, 154 | sourceName, 155 | isStdout, 156 | isStderr, 157 | securities = {} 158 | } = config 159 | const diagnostics: Diagnostic[] = []; 160 | // verify params 161 | if (!command || !sourceName) { 162 | logger.error(`[${textDocument.languageId}] missing config`) 163 | return diagnostics 164 | } 165 | 166 | // Validate when sourceNameFilter is given that sourceName is also given. 167 | if (config.formatPattern && (!config.formatPattern[1].sourceName && config.formatPattern[1].sourceNameFilter)) { 168 | logger.error( 169 | `[${textDocument.languageId}] formatPattern.sourceNameFilter can only be used when formatPattern.sourceName is defined`) 170 | return diagnostics 171 | } else if (config.parseJson && (!config.parseJson.sourceName && config.parseJson.sourceNameFilter)) { 172 | logger.error( 173 | `[${textDocument.languageId}] parseJson.sourceNameFilter can only be used when parseJson.sourceName is defined`) 174 | return diagnostics 175 | } 176 | 177 | try { 178 | const currentFile = URI.parse(textDocument.uri).fsPath 179 | const workDir = await findWorkDirectory(currentFile, rootPatterns) 180 | 181 | logger.info(`found working directory ${workDir}`) 182 | 183 | if (config.requiredFiles && config.requiredFiles.length) { 184 | if (!checkAnyFileExists(workDir, config.requiredFiles)) { 185 | return diagnostics 186 | } 187 | } 188 | 189 | const cmd = await findCommand(command, workDir) 190 | let output = '' 191 | const { 192 | stdout = '', 193 | stderr = '' 194 | } = await executeFile( 195 | new HunkStream(textDocument.getText()), 196 | textDocument, 197 | cmd, 198 | args, 199 | { 200 | cwd: workDir 201 | } 202 | ) 203 | 204 | logger.log(`Linter command: ${cmd}, args: ${JSON.stringify(args)}`) 205 | logger.log(`stdout: ${stdout}`) 206 | logger.log(`stderr: ${stderr}`) 207 | 208 | if (isStdout == undefined && isStderr === undefined) { 209 | output = stdout 210 | } else { 211 | if (isStdout) { 212 | output += stdout 213 | } 214 | if (isStderr) { 215 | output += stderr 216 | } 217 | } 218 | 219 | let linterResults: ILinterResult[] = config.parseJson 220 | ? handleLinterJson(sourceName, output, config) 221 | : handleLinterRegex(sourceName, output, config) 222 | 223 | // Check if we should filter based on the sourceName. 224 | if ((config.parseJson && config.parseJson.sourceNameFilter) 225 | || config.formatPattern && config.formatPattern[1].sourceNameFilter) { 226 | const lengthBefore = linterResults.length; 227 | // Only use results that belong to the current file. 228 | linterResults = linterResults.filter(x => { 229 | // Check if the linter returned an absolute or relative path. 230 | return isAbsolute(x.sourceName) 231 | ? currentFile === x.sourceName 232 | : currentFile === join(workDir, x.sourceName) 233 | }) 234 | logger.log(`Linting results after filtering: ${linterResults.length} (before: ${lengthBefore})`) 235 | } 236 | 237 | return linterResults.map((linterResult) => { 238 | let { line, column, endLine, endColumn } = linterResult 239 | if (line !== undefined && column === undefined && 240 | endLine === undefined && endColumn === undefined) { 241 | column = 1 242 | endLine = Number(line) + 1 243 | endColumn = 1 244 | } else { 245 | endLine = linterResult.endLine != undefined 246 | ? linterResult.endLine 247 | : linterResult.line 248 | endColumn = linterResult.endColumn != undefined 249 | ? linterResult.endColumn 250 | : linterResult.column 251 | } 252 | 253 | return { 254 | severity: getSecurity(securities[linterResult.security]), 255 | range: { 256 | start: { 257 | // line and character is base zero so need -1 258 | line: sumNum(line, -1, offsetLine), 259 | character: sumNum(column, -1, offsetColumn) 260 | }, 261 | end: { 262 | line: sumNum(endLine, -1, offsetLine), 263 | character: sumNum(endColumn, -1, offsetColumn) 264 | } 265 | }, 266 | message: linterResult.message, 267 | source: linterResult.sourceName, 268 | } 269 | }) 270 | } catch (error) { 271 | logger.error(`[${textDocument.languageId}] diagnostic handle fail: [${sourceName}] ${error.message}`) 272 | } 273 | return diagnostics 274 | } 275 | 276 | async function handleDiagnostics( 277 | textDocument: TextDocument, 278 | configs: ILinterConfig[] 279 | ) { 280 | let diagnostics: Diagnostic[] = [] 281 | for (const linter of configs) { 282 | const dias = await handleLinter(textDocument, linter) 283 | diagnostics = diagnostics.concat(dias) 284 | } 285 | return { 286 | uri: textDocument.uri, 287 | diagnostics 288 | } 289 | } 290 | 291 | export function next( 292 | textDocument: TextDocument, 293 | connection: IConnection, 294 | configs: ILinterConfig[] 295 | ) { 296 | const { uri } = textDocument 297 | if (!subscriptions[uri]) { 298 | const debounce = Math.max(...configs.map(i => i.debounce), 100) 299 | subscriptions[uri] = origin$.pipe( 300 | filter(textDocument => textDocument.uri === uri), 301 | switchMap((textDocument: TextDocument) => { 302 | return timer(debounce).pipe( 303 | map(() => textDocument) 304 | ) 305 | }), 306 | waitMap((textDocument: TextDocument) => { 307 | return from(handleDiagnostics(textDocument, configs)) 308 | }), 309 | ).subscribe( 310 | (diagnostics) => { 311 | connection.sendDiagnostics(diagnostics); 312 | }, 313 | (error: Error) => { 314 | logger.error(`[${textDocument.languageId}]: observable error: ${error.message}`) 315 | } 316 | ) 317 | } 318 | origin$.next(textDocument) 319 | } 320 | 321 | export function unsubscribe(textDocument: TextDocument) { 322 | const { uri } = textDocument 323 | const subp = subscriptions[uri] 324 | if (subp && !subp.closed) { 325 | subp.unsubscribe() 326 | } 327 | subscriptions[uri] = undefined 328 | } 329 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # diagnostic-languageserver 2 | 3 | > General purpose Language Server that integrate with 4 | > linter to support diagnostic features 5 | 6 | ## Main features 7 | 8 | - diagnostic with linters 9 | - document format 10 | 11 | **screenshot with neovim and coc** 12 | ![image](https://user-images.githubusercontent.com/5492542/54487533-15590b80-48d2-11e9-8cba-7e58c0edcf6f.png) 13 | 14 | ## Install 15 | 16 | ``` bash 17 | yarn global add diagnostic-languageserver 18 | ``` 19 | 20 | > make sure your yarn's global bin path is include in `PATH` 21 | 22 | > for example `export PATH="$(yarn global bin):$PATH"` 23 | 24 | ## Config & Document 25 | 26 | languageserver config: 27 | 28 | ``` jsonc 29 | { 30 | "languageserver": { 31 | "dls": { 32 | "command": "diagnostic-languageserver", 33 | "args": ["--stdio", "--log-level", "2"], 34 | "filetypes": [ "sh", "email" ], // filetypes that you want to enable this lsp 35 | "initializationOptions": { 36 | "linters": { 37 | ... 38 | }, 39 | "filetypes": { 40 | ... 41 | }, 42 | "formatters": { 43 | ... 44 | }, 45 | "formatFiletypes": { 46 | ... 47 | } 48 | } 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | `linters` field: 55 | 56 | ```jsonc 57 | { 58 | "linterName": { // linter name, for example: vint 59 | "command": "shellcheck", // linter command 60 | "rootPatterns": [], // root patterns, default empty array 61 | "isStdout": true, // use stdout output, default true 62 | "isStderr": false, // use stderr output, default false 63 | "debounce": 100, // debounce time 64 | "args": [ "--format=gcc", "-"], // args 65 | "offsetLine": 0, // offsetline 66 | "offsetColumn": 0, // offsetColumn 67 | "sourceName": "shellcheck", // source name 68 | 69 | // Using regular expressions: 70 | "formatLines": 1, // how much lines for formatPattern[0] to match 71 | "formatPattern": [ 72 | "^([^:]+):(\\d+):(\\d+):\\s+([^:]+):\\s+(.*)$", // line match pattern (javascript regex) 73 | { 74 | "sourceName": 1, // diagnostic file use match group 1. Will default to the file being linted. 75 | "sourceNameFilter:" true, // Display diagnostics only for the current file. 76 | // Only works when sourceName is defined and when it contains either an absolute 77 | // or relative path to the file being linted. Defaults to false. 78 | "line": 2, // diagnostic line use match group 2 79 | "column": 3, // diagnostic column use match group 3 80 | "endLine": 2, // diagnostic end line use match group 2. Will default to group from `line` 81 | "endColumn": 3, // diagnostic end column use match group 3. Will default to group from `column` 82 | "message": [5], // message to display use match group 5 83 | "security": 4 // security to use match group 4, ignore if linter do not support security 84 | } 85 | ], 86 | 87 | // Using JSON: 88 | "parseJson": { 89 | "errorsRoot": "[0].messages", // dot separated path. Will default to whatever JSON is output 90 | // for more information see examples at https://lodash.com/docs/#get 91 | 92 | // All of these support lodash.get syntax. 93 | "sourceName": "file", // propert that contains the `file`. Will default to the file being linted. 94 | "sourceNameFilter:" true, // Display diagnostics only for the current file. 95 | // Only works when sourceName is defined and when it contains either an absolute 96 | // or relative path to the file being linted. Defaults to false. 97 | "line": "line", // property that contains the `line` 98 | "column": "column", // property that contains the `column` 99 | "endLine": "endLine", // property that contains the `endLine`. Will default to `line` 100 | "endColumn": "endColumn", // property that contains the `endColumn`. Will default to `column` 101 | "security": "severity", // property that contains the `security` 102 | "message": "${message} [${code}]", // message to display 103 | }, 104 | 105 | "securities": { // security keys, ignore if linter do not support security 106 | "error": "error", // [key: string]?: "error" | "warning" | "info" | "hint" 107 | "warning": "warning", 108 | "note": "info" 109 | }, 110 | "requiredFiles": [ // only run linter if any of these files exist. option 111 | ".shellcheckrc", 112 | "shellcheckrc" 113 | ] 114 | } 115 | } 116 | ``` 117 | 118 | `filetypes` field: 119 | 120 | ```jsonc 121 | { 122 | "sh": "linterName", // filetype: linterName or linterName[] 123 | } 124 | ``` 125 | 126 | `formatters` field: 127 | 128 | ```jsonc 129 | "dartfmt": { // formatter name 130 | "command": "dartfmt", // format command 131 | "args": [ "--fix" ], // args 132 | "rootPatterns": [], // root patterns, default empty array 133 | "isStdout": true, // use stdout output, default true 134 | "isStderr": false, // use stderr output, default false 135 | "doesWriteToFile": false, // use if formatter doesn't support stdio. should be paired with `%file` 136 | "requiredFiles": [ // only run formatter if any of these files exist. optional 137 | ".run_dartfmt", 138 | ] 139 | } 140 | ``` 141 | 142 | `formatFiletypes` field: 143 | 144 | ```jsonc 145 | { 146 | "dart": "dartfmt", // filetype: formatterName or formatterName[] 147 | } 148 | ``` 149 | 150 | ## Args additional syntax 151 | 152 | `args: ["%text", "%filename", "%file", "%filepath", "%tempfile"]` 153 | 154 | - `%filename` will replace with basename of file 155 | - `%text` will replace with file content 156 | - `%file` will replace with full path to the file and not use stdio 157 | - `%filepath` will replace with full path to the file 158 | - `%tempfile` will replace with the full path to a temporary file written with the contents 159 | of the document and not use stdio; this file will automatically be deleted when the 160 | command completes 161 | 162 | ## How to config a new linter 163 | 164 | [shellcheck](https://github.com/koalaman/shellcheck) for example: 165 | 166 | file `test.sh`: 167 | 168 | ``` sh 169 | #!/usr/bin/env bash 170 | 171 | echo `ls -al` 172 | ``` 173 | 174 | then: 175 | 176 | ```bash 177 | shellcheck --format=gcc test.sh 178 | ``` 179 | 180 | output: 181 | 182 | ```text 183 | t.sh:3:6: warning: Quote this to prevent word splitting. [SC2046] 184 | t.sh:3:6: note: Useless echo? Instead of 'echo $(cmd)', just use 'cmd'. [SC2005] 185 | t.sh:3:6: note: Use $(...) notation instead of legacy backticked `...`. [SC2006] 186 | ``` 187 | 188 | write pattern to match the line for `line` `column` `message` `security`: 189 | 190 | ```javascript 191 | const line = "t.sh:3:6: warning: Quote this to prevent word splitting. [SC2046]" 192 | const formatPattern = "^[^:]+:(\\d+):(\\d+):\\s+([^:]+):\\s+(.*)$" 193 | const match = line.match(new RegExp(formatPattern)) 194 | console.log(match) 195 | ``` 196 | 197 | output: 198 | 199 | ``` jsonc 200 | { 201 | 0: "t.sh:3:6: warning: Quote this to prevent word splitting. [SC2046]" 202 | 1: "3" 203 | 2: "6" 204 | 3: "warning" 205 | 4: "Quote this to prevent word splitting. [SC2046]" 206 | } 207 | ``` 208 | 209 | so you got: 210 | 211 | - `line`: `match[1]` 212 | - `column`: `match[2]` 213 | - `message`: `match[4]` 214 | - `security`: `match[3]` 215 | 216 | and your `formatPattern` field will be: 217 | 218 | ```jsonc 219 | "formatPattern": [ 220 | "^[^:]+:(\\d+):(\\d+):\\s+([^:]+):\\s+(.*)$", // line match pattern (javascript regex) 221 | { 222 | "line": 1, // diagnostic line use match group 1 223 | "column": 2, // diagnostic column use match group 2 224 | "message": [4], // message to display use match group 4 225 | "security": 3 // security to use match group 3, ignore if linter do not support security 226 | } 227 | ] 228 | ``` 229 | 230 | > **Notes** 231 | > if the linter's message for per issue more then one line, you have to set the `formatLines` to fill your pattern, 232 | > and you can view the languagetool pattern for example which `formatLines = 2` 233 | 234 | ## Example with [coc.nvim](https://github.com/neoclide/coc.nvim) 235 | 236 | > Each LSP client should support `initializationOptions` option, 237 | > all you need for `diagnostic-languageserver` is put the config in `initializationOptions` option. 238 | 239 | 1. [shellcheck](https://github.com/koalaman/shellcheck) for shell 240 | 2. [languagetool](https://github.com/languagetool-org/languagetool) for grammer check 241 | 3. more [Linters](https://github.com/iamcco/diagnostic-languageserver/wiki/Linters) config example. 242 | 243 | coc-settings.json: 244 | 245 | > you can use this extension https://github.com/iamcco/coc-diagnostic 246 | 247 | ``` jsonc 248 | { 249 | "languageserver": { 250 | "dls": { 251 | "command": "diagnostic-languageserver", 252 | "args": ["--stdio"], 253 | "filetypes": [ "sh", "email", "dart" ], 254 | "initializationOptions": { 255 | "linters": { 256 | "shellcheck": { 257 | "command": "shellcheck", 258 | "debounce": 100, 259 | "args": [ "--format=gcc", "-"], 260 | "offsetLine": 0, 261 | "offsetColumn": 0, 262 | "sourceName": "shellcheck", 263 | "formatLines": 1, 264 | "formatPattern": [ 265 | "^[^:]+:(\\d+):(\\d+):\\s+([^:]+):\\s+(.*)$", 266 | { 267 | "line": 1, 268 | "column": 2, 269 | "message": 4, 270 | "security": 3 271 | } 272 | ], 273 | "securities": { 274 | "error": "error", 275 | "warning": "warning", 276 | "note": "info" 277 | } 278 | }, 279 | "languagetool": { 280 | "command": "languagetool", 281 | "debounce": 200, 282 | "args": ["-"], 283 | "offsetLine": 0, 284 | "offsetColumn": 0, 285 | "sourceName": "languagetool", 286 | "formatLines": 2, 287 | "formatPattern": [ 288 | "^\\d+?\\.\\)\\s+Line\\s+(\\d+),\\s+column\\s+(\\d+),\\s+([^\\n]+)\nMessage:\\s+(.*)$", 289 | { 290 | "line": 1, 291 | "column": 2, 292 | "message": [4, 3] 293 | } 294 | ], 295 | } 296 | }, 297 | "formatters": { 298 | "dartfmt": { 299 | "command": "dartfmt", 300 | "args": [ "--fix" ], 301 | } 302 | }, 303 | "filetypes": { 304 | "sh": "shellcheck", 305 | "email": "languagetool" 306 | }, 307 | "formatFiletypes": { 308 | "dart": "dartfmt" 309 | } 310 | } 311 | } 312 | } 313 | } 314 | ``` 315 | 316 | ## TODO 317 | 318 | - [x] local node_modules linter support like eslint or textlint 319 | - [x] diagnostic severity 320 | - [x] root pattern 321 | - [x] document format 322 | 323 | ## References 324 | 325 | - inspired by [efm-langserver](https://github.com/mattn/efm-langserver) 326 | 327 | ### Buy Me A Coffee ☕️ 328 | 329 | ![btc](https://img.shields.io/keybase/btc/iamcco.svg?style=popout-square) 330 | 331 | ![image](https://user-images.githubusercontent.com/5492542/42771079-962216b0-8958-11e8-81c0-520363ce1059.png) 332 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@nodelib/fs.scandir@2.1.3": 6 | version "2.1.3" 7 | resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" 8 | integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== 9 | dependencies: 10 | "@nodelib/fs.stat" "2.0.3" 11 | run-parallel "^1.1.9" 12 | 13 | "@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": 14 | version "2.0.3" 15 | resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" 16 | integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== 17 | 18 | "@nodelib/fs.walk@^1.2.3": 19 | version "1.2.4" 20 | resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" 21 | integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== 22 | dependencies: 23 | "@nodelib/fs.scandir" "2.1.3" 24 | fastq "^1.6.0" 25 | 26 | "@types/lodash@^4.14.150": 27 | version "4.14.150" 28 | resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.150.tgz#649fe44684c3f1fcb6164d943c5a61977e8cf0bd" 29 | integrity sha512-kMNLM5JBcasgYscD9x/Gvr6lTAv2NVgsKtet/hm93qMyf/D1pt+7jeEZklKJKxMVmXjxbRVQQGfqDSfipYCO6w== 30 | 31 | "@types/node@^11.11.3": 32 | version "11.11.3" 33 | resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.3.tgz#7c6b0f8eaf16ae530795de2ad1b85d34bf2f5c58" 34 | 35 | aggregate-error@^3.0.0: 36 | version "3.1.0" 37 | resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" 38 | integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== 39 | dependencies: 40 | clean-stack "^2.0.0" 41 | indent-string "^4.0.0" 42 | 43 | array-union@^2.1.0: 44 | version "2.1.0" 45 | resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" 46 | integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== 47 | 48 | balanced-match@^1.0.0: 49 | version "1.0.0" 50 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 51 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 52 | 53 | brace-expansion@^1.1.7: 54 | version "1.1.11" 55 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 56 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 57 | dependencies: 58 | balanced-match "^1.0.0" 59 | concat-map "0.0.1" 60 | 61 | braces@^3.0.1: 62 | version "3.0.2" 63 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 64 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 65 | dependencies: 66 | fill-range "^7.0.1" 67 | 68 | clean-stack@^2.0.0: 69 | version "2.2.0" 70 | resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" 71 | integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== 72 | 73 | commander@^5.1.0: 74 | version "5.1.0" 75 | resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" 76 | integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== 77 | 78 | concat-map@0.0.1: 79 | version "0.0.1" 80 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 81 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 82 | 83 | crypto-random-string@^2.0.0: 84 | version "2.0.0" 85 | resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" 86 | integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== 87 | 88 | del@^6.0.0: 89 | version "6.0.0" 90 | resolved "https://registry.yarnpkg.com/del/-/del-6.0.0.tgz#0b40d0332cea743f1614f818be4feb717714c952" 91 | integrity sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ== 92 | dependencies: 93 | globby "^11.0.1" 94 | graceful-fs "^4.2.4" 95 | is-glob "^4.0.1" 96 | is-path-cwd "^2.2.0" 97 | is-path-inside "^3.0.2" 98 | p-map "^4.0.0" 99 | rimraf "^3.0.2" 100 | slash "^3.0.0" 101 | 102 | dir-glob@^3.0.1: 103 | version "3.0.1" 104 | resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" 105 | integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== 106 | dependencies: 107 | path-type "^4.0.0" 108 | 109 | fast-glob@^3.1.1: 110 | version "3.2.4" 111 | resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" 112 | integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== 113 | dependencies: 114 | "@nodelib/fs.stat" "^2.0.2" 115 | "@nodelib/fs.walk" "^1.2.3" 116 | glob-parent "^5.1.0" 117 | merge2 "^1.3.0" 118 | micromatch "^4.0.2" 119 | picomatch "^2.2.1" 120 | 121 | fastq@^1.6.0: 122 | version "1.8.0" 123 | resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" 124 | integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== 125 | dependencies: 126 | reusify "^1.0.4" 127 | 128 | fill-range@^7.0.1: 129 | version "7.0.1" 130 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 131 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 132 | dependencies: 133 | to-regex-range "^5.0.1" 134 | 135 | find-up@^4.1.0: 136 | version "4.1.0" 137 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" 138 | integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== 139 | dependencies: 140 | locate-path "^5.0.0" 141 | path-exists "^4.0.0" 142 | 143 | fs.realpath@^1.0.0: 144 | version "1.0.0" 145 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 146 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 147 | 148 | glob-parent@^5.1.0: 149 | version "5.1.1" 150 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" 151 | integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== 152 | dependencies: 153 | is-glob "^4.0.1" 154 | 155 | glob@^7.1.3: 156 | version "7.1.6" 157 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 158 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 159 | dependencies: 160 | fs.realpath "^1.0.0" 161 | inflight "^1.0.4" 162 | inherits "2" 163 | minimatch "^3.0.4" 164 | once "^1.3.0" 165 | path-is-absolute "^1.0.0" 166 | 167 | globby@^11.0.1: 168 | version "11.0.1" 169 | resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" 170 | integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== 171 | dependencies: 172 | array-union "^2.1.0" 173 | dir-glob "^3.0.1" 174 | fast-glob "^3.1.1" 175 | ignore "^5.1.4" 176 | merge2 "^1.3.0" 177 | slash "^3.0.0" 178 | 179 | graceful-fs@^4.2.4: 180 | version "4.2.4" 181 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" 182 | integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== 183 | 184 | ignore@^5.1.4: 185 | version "5.1.8" 186 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" 187 | integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== 188 | 189 | indent-string@^4.0.0: 190 | version "4.0.0" 191 | resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" 192 | integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== 193 | 194 | inflight@^1.0.4: 195 | version "1.0.6" 196 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 197 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 198 | dependencies: 199 | once "^1.3.0" 200 | wrappy "1" 201 | 202 | inherits@2: 203 | version "2.0.4" 204 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 205 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 206 | 207 | is-extglob@^2.1.1: 208 | version "2.1.1" 209 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 210 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 211 | 212 | is-glob@^4.0.1: 213 | version "4.0.1" 214 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" 215 | integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== 216 | dependencies: 217 | is-extglob "^2.1.1" 218 | 219 | is-number@^7.0.0: 220 | version "7.0.0" 221 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 222 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 223 | 224 | is-path-cwd@^2.2.0: 225 | version "2.2.0" 226 | resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" 227 | integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== 228 | 229 | is-path-inside@^3.0.2: 230 | version "3.0.2" 231 | resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" 232 | integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== 233 | 234 | is-stream@^2.0.0: 235 | version "2.0.0" 236 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" 237 | integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== 238 | 239 | locate-path@^5.0.0: 240 | version "5.0.0" 241 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" 242 | integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== 243 | dependencies: 244 | p-locate "^4.1.0" 245 | 246 | lodash@^4.17.15: 247 | version "4.17.19" 248 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" 249 | integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== 250 | 251 | merge2@^1.3.0: 252 | version "1.4.1" 253 | resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" 254 | integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== 255 | 256 | micromatch@^4.0.2: 257 | version "4.0.2" 258 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" 259 | integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== 260 | dependencies: 261 | braces "^3.0.1" 262 | picomatch "^2.0.5" 263 | 264 | minimatch@^3.0.4: 265 | version "3.0.4" 266 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 267 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 268 | dependencies: 269 | brace-expansion "^1.1.7" 270 | 271 | once@^1.3.0: 272 | version "1.4.0" 273 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 274 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 275 | dependencies: 276 | wrappy "1" 277 | 278 | p-limit@^2.2.0: 279 | version "2.3.0" 280 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" 281 | integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== 282 | dependencies: 283 | p-try "^2.0.0" 284 | 285 | p-locate@^4.1.0: 286 | version "4.1.0" 287 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" 288 | integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== 289 | dependencies: 290 | p-limit "^2.2.0" 291 | 292 | p-map@^4.0.0: 293 | version "4.0.0" 294 | resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" 295 | integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== 296 | dependencies: 297 | aggregate-error "^3.0.0" 298 | 299 | p-try@^2.0.0: 300 | version "2.2.0" 301 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 302 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== 303 | 304 | path-exists@^4.0.0: 305 | version "4.0.0" 306 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 307 | integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 308 | 309 | path-is-absolute@^1.0.0: 310 | version "1.0.1" 311 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 312 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 313 | 314 | path-type@^4.0.0: 315 | version "4.0.0" 316 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" 317 | integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 318 | 319 | picomatch@^2.0.5, picomatch@^2.2.1: 320 | version "2.2.2" 321 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" 322 | integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== 323 | 324 | reusify@^1.0.4: 325 | version "1.0.4" 326 | resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" 327 | integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== 328 | 329 | rimraf@^3.0.2: 330 | version "3.0.2" 331 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" 332 | integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== 333 | dependencies: 334 | glob "^7.1.3" 335 | 336 | run-parallel@^1.1.9: 337 | version "1.1.9" 338 | resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" 339 | integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== 340 | 341 | rxjs@^6.5.5: 342 | version "6.5.5" 343 | resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" 344 | integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== 345 | dependencies: 346 | tslib "^1.9.0" 347 | 348 | slash@^3.0.0: 349 | version "3.0.0" 350 | resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" 351 | integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== 352 | 353 | temp-dir@^2.0.0: 354 | version "2.0.0" 355 | resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" 356 | integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== 357 | 358 | tempy@^0.7.1: 359 | version "0.7.1" 360 | resolved "https://registry.yarnpkg.com/tempy/-/tempy-0.7.1.tgz#5a654e6dbd1747cdd561efb112350b55cd9c1d46" 361 | integrity sha512-vXPxwOyaNVi9nyczO16mxmHGpl6ASC5/TVhRRHpqeYHvKQm58EaWNvZXxAhR0lYYnBOQFjXjhzeLsaXdjxLjRg== 362 | dependencies: 363 | del "^6.0.0" 364 | is-stream "^2.0.0" 365 | temp-dir "^2.0.0" 366 | type-fest "^0.16.0" 367 | unique-string "^2.0.0" 368 | 369 | to-regex-range@^5.0.1: 370 | version "5.0.1" 371 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 372 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 373 | dependencies: 374 | is-number "^7.0.0" 375 | 376 | tslib@^1.11.2: 377 | version "1.11.2" 378 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.2.tgz#9c79d83272c9a7aaf166f73915c9667ecdde3cc9" 379 | integrity sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg== 380 | 381 | tslib@^1.9.0: 382 | version "1.9.3" 383 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" 384 | 385 | type-fest@^0.16.0: 386 | version "0.16.0" 387 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860" 388 | integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg== 389 | 390 | typescript@^3.8.3: 391 | version "3.8.3" 392 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" 393 | integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== 394 | 395 | unique-string@^2.0.0: 396 | version "2.0.0" 397 | resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" 398 | integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== 399 | dependencies: 400 | crypto-random-string "^2.0.0" 401 | 402 | vscode-jsonrpc@^5.0.1: 403 | version "5.0.1" 404 | resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794" 405 | integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A== 406 | 407 | vscode-languageserver-protocol@^3.15.3: 408 | version "3.15.3" 409 | resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb" 410 | integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw== 411 | dependencies: 412 | vscode-jsonrpc "^5.0.1" 413 | vscode-languageserver-types "3.15.1" 414 | 415 | vscode-languageserver-textdocument@^1.0.1: 416 | version "1.0.1" 417 | resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz#178168e87efad6171b372add1dea34f53e5d330f" 418 | integrity sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA== 419 | 420 | vscode-languageserver-types@3.15.1: 421 | version "3.15.1" 422 | resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" 423 | integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== 424 | 425 | vscode-languageserver@^6.1.1: 426 | version "6.1.1" 427 | resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.1.1.tgz#d76afc68172c27d4327ee74332b468fbc740d762" 428 | integrity sha512-DueEpkUAkD5XTR4MLYNr6bQIp/UFR0/IPApgXU3YfCBCB08u2sm9hRCs6DxYZELkk++STPjpcjksR2H8qI3cDQ== 429 | dependencies: 430 | vscode-languageserver-protocol "^3.15.3" 431 | 432 | vscode-uri@^2.1.1: 433 | version "2.1.1" 434 | resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.1.1.tgz#5aa1803391b6ebdd17d047f51365cf62c38f6e90" 435 | integrity sha512-eY9jmGoEnVf8VE8xr5znSah7Qt1P/xsCdErz+g8HYZtJ7bZqKH5E3d+6oVNm1AC/c6IHUDokbmVXKOi4qPAC9A== 436 | 437 | wrappy@1: 438 | version "1.0.2" 439 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 440 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 441 | --------------------------------------------------------------------------------