-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5f45eff
commit cf94192
Showing
8 changed files
with
1,306 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
out | ||
README.md | ||
LICENSE.txt | ||
assets |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Changelog | ||
|
||
## [0.3.3](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-lsp-v0.3.2...does-it-throw-lsp-v0.3.3) (2023-12-24) | ||
|
||
|
||
### Bug Fixes | ||
|
||
* catch with throw statement not included ([#95](https://github.com/michaelangeloio/does-it-throw/issues/95)) ([fd223db](https://github.com/michaelangeloio/does-it-throw/commit/fd223db4f56e87439999b9b33a393769bd2b7c5b)) | ||
|
||
* **deps-dev:** remove remaining eslint dev dependencies ([#97](https://github.com/michaelangeloio/does-it-throw/issues/97)) ([5f173a6](https://github.com/michaelangeloio/does-it-throw/commit/5f173a69cb86570a526a665d453b86ae776538d0)) | ||
|
||
## [0.3.2](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-lsp-v0.3.1...does-it-throw-lsp-v0.3.2) (2023-12-17) | ||
|
||
|
||
### Bug Fixes | ||
|
||
* update server package.json keywords ([#87](https://github.com/michaelangeloio/does-it-throw/issues/87)) ([c19717d](https://github.com/michaelangeloio/does-it-throw/commit/c19717d96a09152d959bfd7d5c3a34ac62f5e26d)) | ||
|
||
## [0.3.1](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-lsp-v0.3.0...does-it-throw-lsp-v0.3.1) (2023-12-17) | ||
|
||
|
||
### Bug Fixes | ||
|
||
* functions and throw statements are underlined even if caught ([#81](https://github.com/michaelangeloio/does-it-throw/issues/81)) ([16adf85](https://github.com/michaelangeloio/does-it-throw/commit/16adf85b05b92542fa6c09ac1611dd56c7603c99)) | ||
|
||
## [0.3.0](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-lsp-v0.2.5...does-it-throw-lsp-v0.3.0) (2023-12-16) | ||
|
||
|
||
### Features | ||
|
||
* neovim support ([#78](https://github.com/michaelangeloio/does-it-throw/issues/78)) ([6152786](https://github.com/michaelangeloio/does-it-throw/commit/61527869e70f54e99616375f7efd53b24e0fa01a)) | ||
|
||
|
||
### Bug Fixes | ||
|
||
* add biome for standardization, ensure the builder reports errors correctly ([#72](https://github.com/michaelangeloio/does-it-throw/issues/72)) ([0d18392](https://github.com/michaelangeloio/does-it-throw/commit/0d18392268516abb79d015f90495dd331e7ef998)) | ||
* re-organize primary crate into modules ([#42](https://github.com/michaelangeloio/does-it-throw/issues/42)) ([badb106](https://github.com/michaelangeloio/does-it-throw/commit/badb1061d0dfc679458d55609e43cccfdca01794)) | ||
* results should still show even if file cannot resolve (calls to throws) ([#76](https://github.com/michaelangeloio/does-it-throw/issues/76)) ([f908556](https://github.com/michaelangeloio/does-it-throw/commit/f908556dfda8eca9195c87269fac71bc6d3e8bf9)) | ||
* update details, fix logic in some call expressions, including spread operators ([#40](https://github.com/michaelangeloio/does-it-throw/issues/40)) ([cdfdf47](https://github.com/michaelangeloio/does-it-throw/commit/cdfdf47a2d657364abc1b3b3ce97e89405b842b3)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
{ | ||
"name": "does-it-throw-eslint", | ||
"description": "ESLint Plugin for Does It Throw", | ||
"version": "0.3.3", | ||
"author": { | ||
"name": "Michael Angelo", | ||
"email": "[email protected]" | ||
}, | ||
"license": "MIT", | ||
"licenses": [ | ||
{ | ||
"type": "MIT", | ||
"url": "https://github.com/michaelangeloio/does-it-throw/blob/main/LICENSE" | ||
} | ||
], | ||
"engines": { | ||
"node": "*" | ||
}, | ||
"repository": "https://github.com/michaelangeloio/does-it-throw", | ||
"categories": [ | ||
"Programming Languages", | ||
"Linters", | ||
"Debuggers" | ||
], | ||
"keywords": [ | ||
"does it throw", | ||
"throw finder", | ||
"throw", | ||
"javascript", | ||
"typescript", | ||
"lsp", | ||
"language server", | ||
"exceptions", | ||
"extension" | ||
], | ||
"qna": "https://github.com/michaelangeloio/does-it-throw/discussions", | ||
"dependencies": { | ||
"@typescript-eslint/utils": "^6.17.0", | ||
"eslint": "^8.56.0", | ||
"vscode-languageserver": "^9.0.1", | ||
"vscode-languageserver-textdocument": "^1.0.8" | ||
}, | ||
"files": [ | ||
"out", | ||
"package.json", | ||
"README.md", | ||
"LICENSE.txt", | ||
"bin" | ||
], | ||
"main": "out/server.js", | ||
"bin": { | ||
"does-it-throw-lsp": "./bin/does-it-throw" | ||
}, | ||
"scripts": {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,261 @@ | ||
import { | ||
DidChangeConfigurationNotification, | ||
InitializeParams, | ||
InitializeResult, | ||
ProposedFeatures, | ||
TextDocumentSyncKind, | ||
TextDocuments, | ||
createConnection | ||
} from 'vscode-languageserver/node' | ||
|
||
import { access, constants, readFile } from 'fs/promises' | ||
import { TextDocument } from 'vscode-languageserver-textdocument' | ||
import { InputData, ParseResult, parse_js } from './rust/does_it_throw_wasm' | ||
import path = require('path') | ||
import { inspect } from 'util' | ||
|
||
const connection = createConnection(ProposedFeatures.all) | ||
|
||
const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument) | ||
let hasConfigurationCapability = false | ||
let hasWorkspaceFolderCapability = false | ||
// use if needed later | ||
// let hasDiagnosticRelatedInformationCapability = false | ||
|
||
let rootUri: string | undefined | null | ||
|
||
connection.onInitialize((params: InitializeParams) => { | ||
const capabilities = params.capabilities | ||
|
||
hasConfigurationCapability = !!(capabilities.workspace && !!capabilities.workspace.configuration) | ||
hasWorkspaceFolderCapability = !!(capabilities.workspace && !!capabilities.workspace.workspaceFolders) | ||
// use if needed later | ||
// hasDiagnosticRelatedInformationCapability = !!( | ||
// capabilities.textDocument && | ||
// capabilities.textDocument.publishDiagnostics && | ||
// capabilities.textDocument.publishDiagnostics.relatedInformation | ||
// ) | ||
|
||
const result: InitializeResult = { | ||
capabilities: { | ||
textDocumentSync: TextDocumentSyncKind.Incremental | ||
} | ||
} | ||
if (params?.workspaceFolders && params.workspaceFolders.length > 1) { | ||
throw new Error('This extension only supports one workspace folder at this time') | ||
} | ||
if (hasWorkspaceFolderCapability) { | ||
result.capabilities.workspace = { | ||
workspaceFolders: { | ||
supported: false | ||
} | ||
} | ||
} | ||
if (!hasWorkspaceFolderCapability) { | ||
rootUri = params.rootUri | ||
} else { | ||
rootUri = params?.workspaceFolders?.[0]?.uri | ||
} | ||
|
||
return result | ||
}) | ||
|
||
connection.onInitialized(() => { | ||
if (hasConfigurationCapability) { | ||
// Register for all configuration changes. | ||
connection.client.register(DidChangeConfigurationNotification.type, undefined) | ||
} | ||
if (hasWorkspaceFolderCapability) { | ||
connection.workspace.onDidChangeWorkspaceFolders((_event) => { | ||
connection.console.log(`Workspace folder change event received. ${JSON.stringify(_event)}`) | ||
}) | ||
} | ||
}) | ||
|
||
type DiagnosticSeverity = 'Error' | 'Warning' | 'Information' | 'Hint' | ||
|
||
// The server settings | ||
interface Settings { | ||
maxNumberOfProblems: number | ||
throwStatementSeverity: DiagnosticSeverity | ||
functionThrowSeverity: DiagnosticSeverity | ||
callToThrowSeverity: DiagnosticSeverity | ||
callToImportedThrowSeverity: DiagnosticSeverity | ||
includeTryStatementThrows: boolean | ||
} | ||
|
||
// The global settings, used when the `workspace/configuration` request is not supported by the client. | ||
// Please note that this is not the case when using this server with the client provided in this example | ||
// but could happen with other clients. | ||
const defaultSettings: Settings = { | ||
maxNumberOfProblems: 1000000, | ||
throwStatementSeverity: 'Hint', | ||
functionThrowSeverity: 'Hint', | ||
callToThrowSeverity: 'Hint', | ||
callToImportedThrowSeverity: 'Hint', | ||
includeTryStatementThrows: false | ||
} | ||
// 👆 very unlikely someone will have more than 1 million throw statements, lol | ||
// if you do, might want to rethink your code? | ||
let globalSettings: Settings = defaultSettings | ||
|
||
// Cache the settings of all open documents | ||
const documentSettings: Map<string, Thenable<Settings>> = new Map() | ||
|
||
connection.onDidChangeConfiguration((change) => { | ||
if (hasConfigurationCapability) { | ||
// Reset all cached document settings | ||
documentSettings.clear() | ||
} else { | ||
globalSettings = <Settings>(change.settings.doesItThrow || defaultSettings) | ||
} | ||
|
||
// Revalidate all open text documents | ||
// biome-ignore lint/complexity/noForEach: original vscode-languageserver code | ||
documents.all().forEach(validateTextDocument) | ||
}) | ||
|
||
function getDocumentSettings(resource: string): Thenable<Settings> { | ||
if (!hasConfigurationCapability) { | ||
connection.console.info(`does not have config capability, using global settings: ${JSON.stringify(globalSettings)}`) | ||
return Promise.resolve(globalSettings) | ||
} | ||
let result = documentSettings.get(resource) | ||
if (!result) { | ||
result = connection.workspace.getConfiguration({ | ||
scopeUri: resource, | ||
section: 'doesItThrow' | ||
}) | ||
documentSettings.set(resource, result) | ||
} | ||
return result | ||
} | ||
|
||
// Only keep settings for open documents | ||
documents.onDidClose((e) => { | ||
documentSettings.delete(e.document.uri) | ||
}) | ||
|
||
// The content of a text document has changed. This event is emitted | ||
// when the text document first opened or when its content has changed. | ||
documents.onDidChangeContent(async (change) => { | ||
validateTextDocument(change.document) | ||
}) | ||
|
||
documents.onDidSave((change) => { | ||
validateTextDocument(change.document) | ||
}) | ||
|
||
const _checkAccessOnFile = async (file: string) => { | ||
try { | ||
await access(file, constants.R_OK) | ||
return Promise.resolve(file) | ||
} catch (e) { | ||
return Promise.reject(e) | ||
} | ||
} | ||
|
||
const findFirstFileThatExists = async (uri: string, relative_import: string) => { | ||
const isTs = uri.endsWith('.ts') || uri.endsWith('.tsx') | ||
const baseUri = `${path.resolve(path.dirname(uri.replace('file://', '')), relative_import)}` | ||
let files = Array(4) | ||
if (isTs) { | ||
files = [`${baseUri}.ts`, `${baseUri}.tsx`, `${baseUri}.js`, `${baseUri}.jsx`] | ||
} else { | ||
files = [`${baseUri}.js`, `${baseUri}.jsx`, `${baseUri}.ts`, `${baseUri}.tsx`] | ||
} | ||
return Promise.any(files.map(_checkAccessOnFile)) | ||
} | ||
|
||
async function validateTextDocument(textDocument: TextDocument): Promise<void> { | ||
let settings = await getDocumentSettings(textDocument.uri) | ||
if (!settings) { | ||
// this should never happen, but just in case | ||
connection.console.warn(`No settings found for ${textDocument.uri}, using defaults`) | ||
settings = defaultSettings | ||
} | ||
try { | ||
const opts = { | ||
uri: textDocument.uri, | ||
file_content: textDocument.getText(), | ||
ids_to_check: [], | ||
typescript_settings: { | ||
decorators: true | ||
}, | ||
function_throw_severity: settings?.functionThrowSeverity ?? defaultSettings.functionThrowSeverity, | ||
throw_statement_severity: settings?.throwStatementSeverity ?? defaultSettings.throwStatementSeverity, | ||
call_to_imported_throw_severity: | ||
settings?.callToImportedThrowSeverity ?? defaultSettings.callToImportedThrowSeverity, | ||
call_to_throw_severity: settings?.callToThrowSeverity ?? defaultSettings.callToThrowSeverity, | ||
include_try_statement_throws: settings?.includeTryStatementThrows ?? defaultSettings.includeTryStatementThrows | ||
} satisfies InputData | ||
const analysis = parse_js(opts) as ParseResult | ||
|
||
if (analysis.relative_imports.length > 0) { | ||
const filePromises = analysis.relative_imports.map(async (relative_import) => { | ||
try { | ||
const file = await findFirstFileThatExists(textDocument.uri, relative_import) | ||
return await readFile(file, 'utf-8') | ||
} catch (e) { | ||
connection.console.log(`Error reading file ${inspect(e)}`) | ||
return undefined | ||
} | ||
}) | ||
const files = (await Promise.all(filePromises)).filter((file) => !!file) | ||
const analysisArr = files.map((file) => { | ||
if (!file) { | ||
return undefined | ||
} | ||
const opts = { | ||
uri: textDocument.uri, | ||
file_content: file, | ||
ids_to_check: [], | ||
typescript_settings: { | ||
decorators: true | ||
} | ||
} satisfies InputData | ||
return parse_js(opts) as ParseResult | ||
}) | ||
// TODO - this is a bit of a mess, but it works for now. | ||
// The original analysis is the one that has the throw statements Map() | ||
// We get the get the throw_ids from the imported analysis and then | ||
// check the original analysis for existing throw_ids. | ||
// This allows to to get the diagnostics from the imported analysis (one level deep for now) | ||
for (const import_analysis of analysisArr) { | ||
if (!import_analysis) { | ||
return | ||
} | ||
if (import_analysis.throw_ids.length) { | ||
for (const throw_id of import_analysis.throw_ids) { | ||
const newDiagnostics = analysis.imported_identifiers_diagnostics.get(throw_id) | ||
if (newDiagnostics?.diagnostics?.length) { | ||
analysis.diagnostics.push(...newDiagnostics.diagnostics) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
connection.sendDiagnostics({ | ||
uri: textDocument.uri, | ||
diagnostics: analysis.diagnostics | ||
}) | ||
} catch (e) { | ||
console.log(e) | ||
connection.console.error(`Error parsing file ${textDocument.uri}`) | ||
connection.console.error(`settings are: ${JSON.stringify(settings)}`) | ||
connection.console.error(`Error: ${e instanceof Error ? e.message : JSON.stringify(e)} error`) | ||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics: [] }) | ||
} | ||
} | ||
|
||
connection.onDidChangeWatchedFiles((_change) => { | ||
// Monitored files have change in VSCode | ||
connection.console.log(`We received an file change event ${_change}, not implemented yet`) | ||
}) | ||
|
||
// Make the text document manager listen on the connection | ||
// for open, change and close text document events | ||
documents.listen(connection) | ||
|
||
// Listen on the connection | ||
connection.listen() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "ES2021", | ||
"lib": [ | ||
"es2021", | ||
"DOM" | ||
], | ||
"module": "ESNext", | ||
"moduleResolution": "Bundler", | ||
"sourceMap": true, | ||
"strict": true, | ||
"outDir": "out", | ||
"rootDir": "src" | ||
}, | ||
"include": [ | ||
"src" | ||
], | ||
"exclude": [ | ||
"node_modules", | ||
".vscode-test" | ||
] | ||
} |
Oops, something went wrong.