diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..1c999c2 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1 @@ +trailingComma: "all" \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 5efd804..ff61a47 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -28,6 +28,13 @@ "problemMatcher": [ "$tsc-watch" ] + }, + { + "type": "npm", + "script": "lint", + "problemMatcher": [ + "$tslint5" + ] } ] } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fd5f439..afd81b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -155,6 +155,16 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "eslint-plugin-prettier": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.7.0.tgz", + "integrity": "sha512-CStQYJgALoQBw3FsBzH0VOVDRnJ/ZimUlpLm226U8qgqYJfPOY/CPK6wyRInMxh73HSKg5wyRwdS4BVYYHwokA==", + "dev": true, + "requires": { + "fast-diff": "^1.1.1", + "jest-docblock": "^21.0.0" + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -167,6 +177,12 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -218,6 +234,12 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, + "jest-docblock": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-21.2.0.tgz", + "integrity": "sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw==", + "dev": true + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -234,6 +256,12 @@ "esprima": "^4.0.0" } }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -279,6 +307,12 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, + "prettier": { + "version": "1.16.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.4.tgz", + "integrity": "sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==", + "dev": true + }, "resolve": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", @@ -342,6 +376,23 @@ "tsutils": "^2.29.0" } }, + "tslint-config-prettier": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz", + "integrity": "sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==", + "dev": true + }, + "tslint-plugin-prettier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslint-plugin-prettier/-/tslint-plugin-prettier-2.0.1.tgz", + "integrity": "sha512-4FX9JIx/1rKHIPJNfMb+ooX1gPk5Vg3vNi7+dyFYpLO+O57F4g+b/fo1+W/G0SUOkBLHB/YKScxjX/P+7ZT/Tw==", + "dev": true, + "requires": { + "eslint-plugin-prettier": "^2.2.0", + "lines-and-columns": "^1.1.6", + "tslib": "^1.7.1" + } + }, "tsutils": { "version": "2.29.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", diff --git a/package.json b/package.json index 9a11616..a392d8f 100644 --- a/package.json +++ b/package.json @@ -1,88 +1,92 @@ { - "name": "elm-vscode-ls", - "displayName": "Elm", - "description": "VSCode plugin using the elm-language-server", - "author": "Kolja Lampe", - "license": "MIT", - "version": "1.0.0", - "repository": { - "type": "git", - "url": "https://github.com/razzeee/elm-language-server" - }, - "categories": [ - "Programming Languages" - ], - "keywords": [ - "elm" - ], - "engines": { - "vscode": "^1.30.0" - }, - "activationEvents": [ - "workspaceContains:**/elm.json" - ], - "main": "./client/out/extension", - "contributes": { - "languages": [ - { - "id": "elm", - "aliases": [ - "Elm", - "elm" - ], - "extensions": [ - ".elm" - ], - "configuration": "./language-configuration.json" - } + "name": "elm-vscode-ls", + "displayName": "Elm", + "description": "VSCode plugin using the elm-language-server", + "author": "Kolja Lampe", + "license": "MIT", + "version": "1.0.0", + "repository": { + "type": "git", + "url": "https://github.com/razzeee/elm-language-server" + }, + "categories": [ + "Programming Languages" + ], + "keywords": [ + "elm" + ], + "engines": { + "vscode": "^1.30.0" + }, + "activationEvents": [ + "workspaceContains:**/elm.json" + ], + "main": "./client/out/extension", + "contributes": { + "languages": [ + { + "id": "elm", + "aliases": [ + "Elm", + "elm" ], - "grammars": [ - { - "scopeName": "markdown.elm.codeblock", - "path": "./syntaxes/codeblock.json", - "injectTo": [ - "text.html.markdown" - ], - "embeddedLanguages": { - "meta.embedded.block.elm": "elm", - "meta.embedded.block.glsl": "glsl" - } - }, - { - "language": "elm", - "scopeName": "source.elm", - "path": "./syntaxes/elm.json" - } + "extensions": [ + ".elm" ], - "configuration": { - "type": "object", - "title": "Debug configuration", - "properties": { - "elmLS.trace.server": { - "scope": "window", - "type": "string", - "enum": [ - "off", - "messages", - "verbose" - ], - "default": "off", - "description": "Traces the communication between VS Code and the language server." - } - } + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "scopeName": "markdown.elm.codeblock", + "path": "./syntaxes/codeblock.json", + "injectTo": [ + "text.html.markdown" + ], + "embeddedLanguages": { + "meta.embedded.block.elm": "elm", + "meta.embedded.block.glsl": "glsl" } - }, - "scripts": { - "vscode:prepublish": "cd client && npm run update-vscode && cd .. && npm run compile", - "compile": "tsc -b", - "watch": "tsc -b -w", - "postinstall": "cd client && npm install && cd ../server && npm install && cd ..", - "test": "sh ./scripts/e2e.sh" - }, - "devDependencies": { - "@types/mocha": "^5.2.6", - "@types/node": "^11.13.4", - "tslint": "^5.15.0", - "typescript": "3.4.3" + }, + { + "language": "elm", + "scopeName": "source.elm", + "path": "./syntaxes/elm.json" + } + ], + "configuration": { + "type": "object", + "title": "Debug configuration", + "properties": { + "elmLS.trace.server": { + "scope": "window", + "type": "string", + "enum": [ + "off", + "messages", + "verbose" + ], + "default": "off", + "description": "Traces the communication between VS Code and the language server." + } + } } + }, + "scripts": { + "vscode:prepublish": "cd client && npm run update-vscode && cd .. && npm run compile", + "compile": "tsc -b", + "watch": "tsc -b -w", + "postinstall": "cd client && npm install && cd ../server && npm install && cd ..", + "test": "sh ./scripts/e2e.sh", + "lint": "tslint -p tsconfig.json" + }, + "devDependencies": { + "@types/mocha": "^5.2.6", + "@types/node": "^11.13.4", + "prettier": "^1.16.4", + "tslint": "^5.15.0", + "tslint-config-prettier": "^1.18.0", + "tslint-plugin-prettier": "^2.0.1", + "typescript": "3.4.3" + } } diff --git a/server/src/providers/elmMakeDiagnostics.ts b/server/src/providers/elmMakeDiagnostics.ts index a80d11c..b7bea97 100644 --- a/server/src/providers/elmMakeDiagnostics.ts +++ b/server/src/providers/elmMakeDiagnostics.ts @@ -48,13 +48,11 @@ export class ElmMakeDiagnostics { } else { make = cp.spawn(makeCommand, args, { cwd }); } - // output is actually optional - // (fixed in https://github.com/Microsoft/vscode/commit/b4917afe9bdee0e9e67f4094e764f6a72a997c70, - // but unreleased at this time) + + if (!make.stderr) { return; } const errorLinesFromElmMake: readline.ReadLine = readline.createInterface( { input: make.stderr, - output: undefined, }, ); const lines: IElmIssue[] = []; diff --git a/server/src/providers/foldingProvider.ts b/server/src/providers/foldingProvider.ts index 3493382..8608041 100644 --- a/server/src/providers/foldingProvider.ts +++ b/server/src/providers/foldingProvider.ts @@ -1,50 +1,66 @@ import { SyntaxNode, Tree } from "tree-sitter"; -import { FoldingRange, FoldingRangeKind, FoldingRangeRequest, - FoldingRangeRequestParam, IConnection } from "vscode-languageserver"; +import { + FoldingRange, + FoldingRangeKind, + FoldingRangeRequest, + FoldingRangeRequestParam, + IConnection +} from "vscode-languageserver"; import { IForest } from "../forest"; export class FoldingRangeProvider { - private connection: IConnection; - private forest: IForest; - private readonly FOLD_CONSTRUCTS: Set = new Set([ - "if", - "case", - "func_statement", - ]); + private connection: IConnection; + private forest: IForest; + private readonly FOLD_CONSTRUCTS: Set = new Set([ + "if", + "case", + "func_statement", + "block_comment", + "record_type", + "record_expr" + ]); - constructor(connection: IConnection, forest: IForest) { - this.connection = connection; - this.forest = forest; + constructor(connection: IConnection, forest: IForest) { + this.connection = connection; + this.forest = forest; - this.connection.onRequest(FoldingRangeRequest.type, this.handleFoldingRange); + this.connection.onRequest( + FoldingRangeRequest.type, + this.handleFoldingRange + ); + } + + protected handleFoldingRange = async ( + param: FoldingRangeRequestParam + ): Promise => { + const folds: FoldingRange[] = []; + + const tree: Tree | undefined = this.forest.getTree(param.textDocument.uri); + + const traverse: (node: SyntaxNode) => void = (node: SyntaxNode): void => { + if ( + node.parent && + node.parent.lastChild && + !node.isNamed && + this.FOLD_CONSTRUCTS.has(node.type) + ) { + const endNode: SyntaxNode = node.parent.lastChild; + folds.push({ + endCharacter: node.endPosition.column, + endLine: endNode.endPosition.row, + kind: FoldingRangeKind.Region, + startCharacter: node.startPosition.column, + startLine: node.startPosition.row + }); + } + for (const childNode of node.children) { + traverse(childNode); + } + }; + if (tree) { + traverse(tree.rootNode); } - protected handleFoldingRange = async ( - param: FoldingRangeRequestParam, - ): Promise => { - const folds: FoldingRange[] = []; - - const tree: Tree | undefined = this.forest.getTree(param.textDocument.uri); - - const traverse: (node: SyntaxNode) => void = (node: SyntaxNode): void => { - if (node.parent && node.parent.lastChild && !node.isNamed && this.FOLD_CONSTRUCTS.has(node.type)) { - const endNode: SyntaxNode = node.parent.lastChild; - folds.push({ - endCharacter: node.endPosition.column, - endLine: endNode.endPosition.row, - kind: FoldingRangeKind.Region, - startCharacter: node.startPosition.column, - startLine: node.startPosition.row, - }); - } - for (const childNode of node.children) { - traverse(childNode); - } - }; - if (tree) { - traverse(tree.rootNode); - } - - return folds; - } + return folds; + }; } diff --git a/server/src/util/elmUtils.ts b/server/src/util/elmUtils.ts index 0c2ab63..fb99292 100644 --- a/server/src/util/elmUtils.ts +++ b/server/src/util/elmUtils.ts @@ -6,124 +6,134 @@ export const isWindows = process.platform === "win32"; /** Options for execCmd */ export interface IExecCmdOptions { - /** Any arguments */ - cmdArguments?: string[]; - /** Shows a message if an error occurs (in particular the command not being */ - /* found), instead of rejecting. If this happens, the promise never resolves */ - showMessageOnError?: boolean; - /** Called after the process successfully starts */ - onStart?: () => void; - /** Called when data is sent to stdout */ - onStdout?: (data: string) => void; - /** Called when data is sent to stderr */ - onStderr?: (data: string) => void; - /** Called after the command (successfully or unsuccessfully) exits */ - onExit?: () => void; - /** Text to add when command is not found (maybe helping how to install) */ - notFoundText?: string; + /** Any arguments */ + cmdArguments?: string[]; + /** Shows a message if an error occurs (in particular the command not being */ + /* found), instead of rejecting. If this happens, the promise never resolves */ + showMessageOnError?: boolean; + /** Called after the process successfully starts */ + onStart?: () => void; + /** Called when data is sent to stdout */ + onStdout?: (data: string) => void; + /** Called when data is sent to stderr */ + onStderr?: (data: string) => void; + /** Called after the command (successfully or unsuccessfully) exits */ + onExit?: () => void; + /** Text to add when command is not found (maybe helping how to install) */ + notFoundText?: string; } /** Type returned from execCmd. Is a promise for when the command completes * and also a wrapper to access ChildProcess-like methods. */ export interface IExecutingCmd - extends Promise<{ stdout: string; stderr: string }> { - /** The process's stdin */ - stdin: NodeJS.WritableStream; - /** End the process */ - kill(): void; - /** Is the process running */ - isRunning: boolean; // tslint:disable-line + extends Promise<{ stdout: string; stderr: string }> { + /** The process's stdin */ + stdin: NodeJS.WritableStream; + /** End the process */ + kill(): void; + /** Is the process running */ + isRunning: boolean; // tslint:disable-line } /** Executes a command. Shows an error message if the command isn't found */ export function execCmd( - cmd: string, - options: IExecCmdOptions = {}, - elmRootPath: URI, - connection: IConnection, + cmd: string, + options: IExecCmdOptions = {}, + elmRootPath: URI, + connection: IConnection ): IExecutingCmd { - const { - onStart, - onStdout, - onStderr, - onExit, - } = options; - let childProcess: cp.ChildProcess; - let firstResponse = true; - let wasKilledbyUs = false; + const { onStart, onStdout, onStderr, onExit } = options; + let childProcess: cp.ChildProcess; + let firstResponse = true; + let wasKilledbyUs = false; - const IexecutingCmd: any = new Promise((resolve, reject) => { - const cmdArguments = options ? options.cmdArguments : []; + const IexecutingCmd: any = new Promise((resolve, reject) => { + const cmdArguments = options ? options.cmdArguments : []; - const fullCommand = cmd + " " + (cmdArguments || []).join(" "); - childProcess = cp.exec(fullCommand, { cwd: elmRootPath.fsPath }, handleExit); + const fullCommand = cmd + " " + (cmdArguments || []).join(" "); + childProcess = cp.exec( + fullCommand, + { cwd: elmRootPath.fsPath }, + handleExit + ); - childProcess.stdout.on("data", (data: Buffer) => { - if (firstResponse && onStart) { - onStart(); - } - firstResponse = false; - if (onStdout) { - onStdout(data.toString()); - } - }); - - childProcess.stderr.on("data", (data: Buffer) => { - if (firstResponse && onStart) { - onStart(); - } - firstResponse = false; - if (onStderr) { - onStderr(data.toString()); - } - }); - - function handleExit(error: cp.ExecException | null, stdout: string | Buffer, stderr: string | Buffer) { - IexecutingCmd.isRunning = false; - if (onExit) { - onExit(); - } - if (!wasKilledbyUs) { - if (error) { - if (options.showMessageOnError) { - const cmdName = cmd.split(" ", 1)[0]; - const cmdWasNotFound = - // Windows method apparently still works on non-English systems - (isWindows && - error.message.includes(`'${cmdName}' is not recognized`)) || - (!isWindows && (error as any).code === 127); - - if (cmdWasNotFound) { - const notFoundText = options ? options.notFoundText : ""; - connection.window.showErrorMessage( - `${cmdName} is not available in your path. ` + notFoundText, - ); - } else { - connection.window.showErrorMessage(error.message); - } - } else { - reject(error); - } - } else { - resolve({ stdout, stderr }); - } - } - } - }); - // @ts-ignore - IexecutingCmd.stdin = childProcess.stdin; - IexecutingCmd.kill = killProcess; - IexecutingCmd.isRunning = true; - - return IexecutingCmd as IExecutingCmd; - - function killProcess() { - wasKilledbyUs = true; - if (isWindows) { - cp.spawn("taskkill", ["/pid", childProcess.pid.toString(), "/f", "/t"]); - } else { - childProcess.kill("SIGINT"); - } + if (!childProcess.stdout) { + return; } + + childProcess.stdout.on("data", (data: Buffer) => { + if (firstResponse && onStart) { + onStart(); + } + firstResponse = false; + if (onStdout) { + onStdout(data.toString()); + } + }); + + if (!childProcess.stderr) { + return; + } + childProcess.stderr.on("data", (data: Buffer) => { + if (firstResponse && onStart) { + onStart(); + } + firstResponse = false; + if (onStderr) { + onStderr(data.toString()); + } + }); + + function handleExit( + error: cp.ExecException | null, + stdout: string | Buffer, + stderr: string | Buffer + ) { + IexecutingCmd.isRunning = false; + if (onExit) { + onExit(); + } + if (!wasKilledbyUs) { + if (error) { + if (options.showMessageOnError) { + const cmdName = cmd.split(" ", 1)[0]; + const cmdWasNotFound = + // Windows method apparently still works on non-English systems + (isWindows && + error.message.includes(`'${cmdName}' is not recognized`)) || + (!isWindows && (error as any).code === 127); + + if (cmdWasNotFound) { + const notFoundText = options ? options.notFoundText : ""; + connection.window.showErrorMessage( + `${cmdName} is not available in your path. ` + notFoundText + ); + } else { + connection.window.showErrorMessage(error.message); + } + } else { + reject(error); + } + } else { + resolve({ stdout, stderr }); + } + } + } + }); + // @ts-ignore + IexecutingCmd.stdin = childProcess.stdin; + IexecutingCmd.kill = killProcess; + IexecutingCmd.isRunning = true; + + return IexecutingCmd as IExecutingCmd; + + function killProcess() { + wasKilledbyUs = true; + if (isWindows) { + cp.spawn("taskkill", ["/pid", childProcess.pid.toString(), "/f", "/t"]); + } else { + childProcess.kill("SIGINT"); + } + } } diff --git a/tslint.json b/tslint.json index 32fa6e5..101fe49 100644 --- a/tslint.json +++ b/tslint.json @@ -1,9 +1,9 @@ { - "defaultSeverity": "error", - "extends": [ - "tslint:recommended" - ], - "jsRules": {}, - "rules": {}, - "rulesDirectory": [] -} \ No newline at end of file + "defaultSeverity": "error", + "extends": ["tslint:recommended", "tslint-config-prettier"], + "jsRules": {}, + "rules": { + "prettier": true + }, + "rulesDirectory": ["tslint-plugin-prettier"] +}