vscode-eslint 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/vscode-eslint +24 -1
- package/client/out/client.d.ts +13 -0
- package/client/out/extension.d.ts +3 -0
- package/client/out/extension.js +2 -0
- package/client/out/extension.js.map +1 -0
- package/client/out/node-utils.d.ts +51 -0
- package/client/out/settings.d.ts +41 -0
- package/client/out/shared/customMessages.d.ts +89 -0
- package/client/out/shared/settings.d.ts +106 -0
- package/client/out/tasks.d.ts +31 -0
- package/client/out/tests/glob.test.d.ts +1 -0
- package/client/out/vscode-utils.d.ts +5 -0
- package/package.json +8 -1
- package/server/out/diff.d.ts +134 -0
- package/server/out/eslint.d.ts +298 -0
- package/server/out/eslintServer.d.ts +1 -0
- package/server/out/eslintServer.js +2 -0
- package/server/out/eslintServer.js.map +1 -0
- package/server/out/is.d.ts +3 -0
- package/server/out/languageDefaults.d.ts +6 -0
- package/server/out/linkedMap.d.ts +53 -0
- package/server/out/paths.d.ts +22 -0
- package/server/out/shared/customMessages.d.ts +89 -0
- package/server/out/shared/settings.d.ts +106 -0
- package/$shared/customMessages.ts +0 -113
- package/$shared/settings.ts +0 -189
- package/.CodeQL.yml +0 -5
- package/.azure-pipelines.yml +0 -27
- package/.gemini/agents/vscode-eslint.md +0 -23
- package/.github/commands.yml +0 -127
- package/.github/locker.yml +0 -6
- package/.github/needs_more_info.yml +0 -6
- package/.github/workflows/npm-publish.yml +0 -53
- package/.github/workflows/release-please.yml +0 -22
- package/.lsifrc.json +0 -4
- package/.vscode/launch.json +0 -20
- package/.vscode/settings.json +0 -52
- package/.vscode/spellright.dict +0 -8
- package/.vscode/tasks.json +0 -39
- package/.vscodeignore +0 -23
- package/CHANGELOG.md +0 -524
- package/SECURITY.md +0 -41
- package/agents.md +0 -36
- package/build/azure-pipelines/linux/build.yml +0 -14
- package/build/azure-pipelines/pre-release.yml +0 -42
- package/build/azure-pipelines/release.yml +0 -45
- package/build/azure-pipelines/win32/build.yml +0 -14
- package/build/bin/all.js +0 -29
- package/build/bin/linking.js +0 -102
- package/build/bin/symlink.js +0 -35
- package/client/.mocharc.json +0 -6
- package/client/agents.md +0 -5
- package/client/package-lock.json +0 -176
- package/client/package.json +0 -29
- package/client/src/client.ts +0 -992
- package/client/src/extension.ts +0 -180
- package/client/src/node-utils.ts +0 -393
- package/client/src/settings.ts +0 -379
- package/client/src/tasks.ts +0 -186
- package/client/src/tests/glob.test.ts +0 -31
- package/client/src/vscode-utils.ts +0 -28
- package/client/test/mocha.opts +0 -3
- package/client/tsconfig.json +0 -20
- package/client/webpack.config.js +0 -25
- package/contributing.md +0 -19
- package/esbuild.js +0 -62
- package/eslint.config.js +0 -129
- package/history/settings_1_9_x.md +0 -110
- package/images/2_1_10/eslint-dialog.png +0 -0
- package/images/2_1_10/eslint-status.png +0 -0
- package/package-json-schema.json +0 -9
- package/playgrounds/7.0/.eslintignore +0 -1
- package/playgrounds/7.0/.eslintrc.json +0 -71
- package/playgrounds/7.0/.vscode/settings.json +0 -85
- package/playgrounds/7.0/app.js +0 -12
- package/playgrounds/7.0/build/.eslintignore +0 -1
- package/playgrounds/7.0/build/.eslintrc.json +0 -30
- package/playgrounds/7.0/build/build.js +0 -11
- package/playgrounds/7.0/jsconfig.json +0 -5
- package/playgrounds/7.0/package-lock.json +0 -2133
- package/playgrounds/7.0/package.json +0 -10
- package/playgrounds/7.0/readme.md +0 -0
- package/playgrounds/7.0/subDir/sub.js +0 -11
- package/playgrounds/7.0/subDir/test.jsx +0 -10
- package/playgrounds/7.0/test.js +0 -11
- package/playgrounds/7.0/test.sh +0 -1
- package/playgrounds/7.0/test.vue +0 -33
- package/playgrounds/7.0/test2.html +0 -8
- package/playgrounds/8.0/.eslintignore +0 -1
- package/playgrounds/8.0/.eslintrc.json +0 -71
- package/playgrounds/8.0/.vscode/settings.json +0 -91
- package/playgrounds/8.0/app.js +0 -12
- package/playgrounds/8.0/build/.eslintignore +0 -1
- package/playgrounds/8.0/build/.eslintrc.json +0 -30
- package/playgrounds/8.0/build/build.js +0 -11
- package/playgrounds/8.0/jsconfig.json +0 -5
- package/playgrounds/8.0/package-lock.json +0 -2321
- package/playgrounds/8.0/package.json +0 -10
- package/playgrounds/8.0/readme.md +0 -17
- package/playgrounds/8.0/subDir/sub.js +0 -11
- package/playgrounds/8.0/subDir/test.jsx +0 -10
- package/playgrounds/8.0/test.ipynb +0 -49
- package/playgrounds/8.0/test.js +0 -3
- package/playgrounds/8.0/test.sh +0 -1
- package/playgrounds/8.0/test.vue +0 -33
- package/playgrounds/8.0/test2.html +0 -8
- package/playgrounds/9.0/flat/.vscode/settings.json +0 -3
- package/playgrounds/9.0/flat/app.js +0 -12
- package/playgrounds/9.0/flat/dist/ignore.js +0 -12
- package/playgrounds/9.0/flat/eslint.config.js +0 -61
- package/playgrounds/9.0/flat/package-lock.json +0 -1053
- package/playgrounds/9.0/flat/package.json +0 -9
- package/playgrounds/9.0/rc/.eslintrc.json +0 -57
- package/playgrounds/9.0/rc/.vscode/settings.json +0 -3
- package/playgrounds/9.0/rc/app.js +0 -12
- package/playgrounds/9.0/rc/package-lock.json +0 -1345
- package/playgrounds/9.0/rc/package.json +0 -9
- package/playgrounds/flat-config/.vscode/settings.json +0 -22
- package/playgrounds/flat-config/app.js +0 -12
- package/playgrounds/flat-config/eslint.config.js +0 -51
- package/playgrounds/flat-config/package-lock.json +0 -2733
- package/playgrounds/flat-config/package.json +0 -12
- package/playgrounds/flat-config/sub/sub.js +0 -2
- package/playgrounds/flat-config/test.ts +0 -7
- package/playgrounds/flat-config/tsconfig.json +0 -11
- package/playgrounds/flat-config-fail/f1/app.js +0 -12
- package/playgrounds/flat-config-fail/f1/eslint.config.js +0 -51
- package/playgrounds/flat-config-fail/package-lock.json +0 -1683
- package/playgrounds/flat-config-fail/package.json +0 -11
- package/playgrounds/flat-config-mjs/.vscode/settings.json +0 -21
- package/playgrounds/flat-config-mjs/app.js +0 -12
- package/playgrounds/flat-config-mjs/eslint.config.mjs +0 -53
- package/playgrounds/flat-config-mjs/package-lock.json +0 -2860
- package/playgrounds/flat-config-mjs/package.json +0 -11
- package/playgrounds/flat-config-mjs/sub/sub.js +0 -2
- package/playgrounds/flat-config-mjs/test.ts +0 -7
- package/playgrounds/flat-config-mjs/tsconfig.json +0 -11
- package/playgrounds/load-eslint/.vscode/settings.json +0 -21
- package/playgrounds/load-eslint/app.js +0 -12
- package/playgrounds/load-eslint/eslint.config.js +0 -51
- package/playgrounds/load-eslint/package-lock.json +0 -2860
- package/playgrounds/load-eslint/package.json +0 -11
- package/playgrounds/load-eslint/sub/sub.js +0 -2
- package/playgrounds/load-eslint/test.ts +0 -7
- package/playgrounds/load-eslint/tsconfig.json +0 -11
- package/playgrounds/noLib/test.js +0 -22
- package/playgrounds/noWD/.vscode/settings.json +0 -2
- package/playgrounds/noWD/src/.eslintrc.json +0 -18
- package/playgrounds/noWD/src/package-lock.json +0 -2812
- package/playgrounds/noWD/src/package.json +0 -12
- package/playgrounds/noWD/src/test.js +0 -3
- package/playgrounds/notebooks/notebook.ipynb +0 -7072
- package/playgrounds/notebooks/notebook2.ipynb +0 -20
- package/playgrounds/testing.code-workspace +0 -28
- package/playgrounds/ts/.eslintrc.base.json +0 -23
- package/playgrounds/ts/.eslintrc.json +0 -191
- package/playgrounds/ts/.vscode/settings.json +0 -12
- package/playgrounds/ts/package-lock.json +0 -2687
- package/playgrounds/ts/package.json +0 -11
- package/playgrounds/ts/test copy.ts +0 -4
- package/playgrounds/ts/test.ipynb +0 -49
- package/playgrounds/ts/test.ts +0 -4
- package/playgrounds/ts/test.tsx +0 -14
- package/playgrounds/ts/tsconfig.json +0 -100
- package/server/agents.md +0 -9
- package/server/package-lock.json +0 -93
- package/server/package.json +0 -32
- package/server/src/diff.ts +0 -1079
- package/server/src/eslint.ts +0 -1471
- package/server/src/eslintServer.ts +0 -865
- package/server/src/is.ts +0 -18
- package/server/src/languageDefaults.ts +0 -40
- package/server/src/linkedMap.ts +0 -448
- package/server/src/paths.ts +0 -128
- package/server/src/thenable.d.ts +0 -5
- package/server/tsconfig.json +0 -21
- package/server/webpack.config.js +0 -25
- package/shared.webpack.config.js +0 -59
- package/tsconfig.base.json +0 -9
- package/tsconfig.json +0 -21
package/server/src/eslint.ts
DELETED
|
@@ -1,1471 +0,0 @@
|
|
|
1
|
-
/* --------------------------------------------------------------------------------------------
|
|
2
|
-
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
|
-
* ------------------------------------------------------------------------------------------ */
|
|
5
|
-
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import * as crypto from 'crypto';
|
|
8
|
-
import { execSync } from 'child_process';
|
|
9
|
-
|
|
10
|
-
import semverParse = require('semver/functions/parse');
|
|
11
|
-
import semverGte = require('semver/functions/gte');
|
|
12
|
-
|
|
13
|
-
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
14
|
-
import {
|
|
15
|
-
Diagnostic, DiagnosticSeverity, DiagnosticTag, ProposedFeatures, Range, TextEdit, Files, DocumentFilter, DocumentFormattingRegistrationOptions,
|
|
16
|
-
Disposable, DocumentFormattingRequest, TextDocuments, uinteger
|
|
17
|
-
} from 'vscode-languageserver/node';
|
|
18
|
-
import { URI } from 'vscode-uri';
|
|
19
|
-
|
|
20
|
-
import { ProbeFailedParams, ProbeFailedRequest, NoESLintLibraryRequest, Status, NoConfigRequest, StatusNotification } from './shared/customMessages';
|
|
21
|
-
import { ConfigurationSettings, DirectoryItem, ESLintOptions, ESLintSeverity, ModeEnum, ModeItem, PackageManagers, RuleCustomization, RuleSeverity, Validate } from './shared/settings';
|
|
22
|
-
|
|
23
|
-
import * as Is from './is';
|
|
24
|
-
import { LRUCache } from './linkedMap';
|
|
25
|
-
import { isUNC, normalizeDriveLetter, normalizePath } from './paths';
|
|
26
|
-
import LanguageDefaults from './languageDefaults';
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* ESLint specific settings for a text document.
|
|
31
|
-
*/
|
|
32
|
-
export type TextDocumentSettings = Omit<ConfigurationSettings, 'workingDirectory'> & {
|
|
33
|
-
silent: boolean;
|
|
34
|
-
workingDirectory: DirectoryItem | undefined;
|
|
35
|
-
library: ESLintModule | undefined;
|
|
36
|
-
resolvedGlobalPackageManagerPath: string | undefined;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export namespace TextDocumentSettings {
|
|
40
|
-
export function hasLibrary(settings: TextDocumentSettings): settings is (TextDocumentSettings & { library: ESLintModule }) {
|
|
41
|
-
return settings.library !== undefined;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* A special error thrown by the ESLint library
|
|
47
|
-
*/
|
|
48
|
-
export interface ESLintError extends Error {
|
|
49
|
-
messageTemplate?: string;
|
|
50
|
-
messageData?: {
|
|
51
|
-
pluginName?: string;
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export namespace ESLintError {
|
|
56
|
-
export function isNoConfigFound(error: any): boolean {
|
|
57
|
-
const candidate = error as ESLintError;
|
|
58
|
-
return candidate.messageTemplate === 'no-config-found' || candidate.message === 'No ESLint configuration found.';
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
type ESLintAutoFixEdit = {
|
|
63
|
-
range: [number, number];
|
|
64
|
-
text: string;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
type ESLintSuggestionResult = {
|
|
68
|
-
desc: string;
|
|
69
|
-
fix: ESLintAutoFixEdit;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
type ESLintProblem = {
|
|
73
|
-
line: number;
|
|
74
|
-
column: number;
|
|
75
|
-
endLine?: number;
|
|
76
|
-
endColumn?: number;
|
|
77
|
-
severity: number;
|
|
78
|
-
ruleId: string;
|
|
79
|
-
message: string;
|
|
80
|
-
fix?: ESLintAutoFixEdit;
|
|
81
|
-
suggestions?: ESLintSuggestionResult[];
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
type ESLintDocumentReport = {
|
|
85
|
-
filePath: string;
|
|
86
|
-
errorCount: number;
|
|
87
|
-
warningCount: number;
|
|
88
|
-
messages: ESLintProblem[];
|
|
89
|
-
output?: string;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
type ESLintReport = {
|
|
93
|
-
errorCount: number;
|
|
94
|
-
warningCount: number;
|
|
95
|
-
results: ESLintDocumentReport[];
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
export type CLIOptions = {
|
|
99
|
-
cwd?: string;
|
|
100
|
-
fixTypes?: string[];
|
|
101
|
-
fix?: boolean;
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
export type SeverityConf = 0 | 1 | 2 | 'off' | 'warn' | 'error';
|
|
105
|
-
|
|
106
|
-
export type RuleConf = SeverityConf | [SeverityConf, ...any[]];
|
|
107
|
-
|
|
108
|
-
export type ConfigData = {
|
|
109
|
-
rules?: Record<string, RuleConf>;
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
export type ESLintClassOptions = {
|
|
113
|
-
cwd?: string;
|
|
114
|
-
fixTypes?: string[];
|
|
115
|
-
fix?: boolean;
|
|
116
|
-
overrideConfig?: ConfigData;
|
|
117
|
-
overrideConfigFile?: string | null;
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
export type RuleMetaData = {
|
|
121
|
-
docs?: {
|
|
122
|
-
url?: string;
|
|
123
|
-
};
|
|
124
|
-
type?: string;
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
export namespace RuleMetaData {
|
|
128
|
-
// For unused eslint-disable comments, ESLint does not include a rule ID
|
|
129
|
-
// nor any other metadata (although they do provide a fix). In order to
|
|
130
|
-
// provide code actions for these, we create a fake rule ID and metadata.
|
|
131
|
-
export const unusedDisableDirectiveId = 'unused-disable-directive';
|
|
132
|
-
const unusedDisableDirectiveMeta: RuleMetaData = {
|
|
133
|
-
docs: {
|
|
134
|
-
url: 'https://eslint.org/docs/latest/use/configure/rules#report-unused-eslint-disable-comments'
|
|
135
|
-
},
|
|
136
|
-
type: 'directive'
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
const handled: Set<string> = new Set();
|
|
140
|
-
const ruleId2Meta: Map<string, RuleMetaData> = new Map([[unusedDisableDirectiveId, unusedDisableDirectiveMeta]]);
|
|
141
|
-
|
|
142
|
-
export function capture(eslint: ESLintClass, reports: ESLintDocumentReport[]): void {
|
|
143
|
-
let rulesMetaData: Record<string, RuleMetaData> | undefined;
|
|
144
|
-
if (eslint.isCLIEngine) {
|
|
145
|
-
const toHandle = reports.filter(report => !handled.has(report.filePath));
|
|
146
|
-
if (toHandle.length === 0) {
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
rulesMetaData = typeof eslint.getRulesMetaForResults === 'function' ? eslint.getRulesMetaForResults(toHandle) : undefined;
|
|
150
|
-
toHandle.forEach(report => handled.add(report.filePath));
|
|
151
|
-
} else {
|
|
152
|
-
rulesMetaData = typeof eslint.getRulesMetaForResults === 'function' ? eslint.getRulesMetaForResults(reports) : undefined;
|
|
153
|
-
}
|
|
154
|
-
if (rulesMetaData === undefined) {
|
|
155
|
-
return undefined;
|
|
156
|
-
}
|
|
157
|
-
Object.entries(rulesMetaData).forEach(([key, meta]) => {
|
|
158
|
-
if (ruleId2Meta.has(key)) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
if (meta && meta.docs && Is.string(meta.docs.url)) {
|
|
162
|
-
ruleId2Meta.set(key, meta);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export function clear(): void {
|
|
168
|
-
handled.clear();
|
|
169
|
-
ruleId2Meta.clear();
|
|
170
|
-
ruleId2Meta.set(unusedDisableDirectiveId, unusedDisableDirectiveMeta);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export function getUrl(ruleId: string): string | undefined {
|
|
174
|
-
return ruleId2Meta.get(ruleId)?.docs?.url;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export function getType(ruleId: string): string | undefined {
|
|
178
|
-
return ruleId2Meta.get(ruleId)?.type;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function hasRuleId(ruleId: string): boolean {
|
|
182
|
-
return ruleId2Meta.has(ruleId);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export function isUnusedDisableDirectiveProblem(problem: ESLintProblem): boolean {
|
|
186
|
-
return problem.ruleId === null && problem.message.startsWith('Unused eslint-disable directive');
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
type ParserOptions = {
|
|
191
|
-
parser?: string;
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
type ESLintRcConfig = {
|
|
195
|
-
env: Record<string, boolean>;
|
|
196
|
-
extends: string | string[];
|
|
197
|
-
// globals: Record<string, GlobalConf>;
|
|
198
|
-
ignorePatterns: string | string[];
|
|
199
|
-
noInlineConfig: boolean;
|
|
200
|
-
// overrides: OverrideConfigData[];
|
|
201
|
-
parser: string | null;
|
|
202
|
-
parserOptions?: ParserOptions;
|
|
203
|
-
plugins: string[];
|
|
204
|
-
processor: string;
|
|
205
|
-
reportUnusedDisableDirectives: boolean | undefined;
|
|
206
|
-
root: boolean;
|
|
207
|
-
rules: Record<string, RuleConf>;
|
|
208
|
-
settings: object;
|
|
209
|
-
};
|
|
210
|
-
type ESLintConfig = ESLintRcConfig;
|
|
211
|
-
|
|
212
|
-
export type Problem = {
|
|
213
|
-
label: string;
|
|
214
|
-
documentVersion: number;
|
|
215
|
-
ruleId: string;
|
|
216
|
-
line: number;
|
|
217
|
-
diagnostic: Diagnostic;
|
|
218
|
-
edit?: ESLintAutoFixEdit;
|
|
219
|
-
suggestions?: ESLintSuggestionResult[];
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
export namespace Problem {
|
|
223
|
-
export function isFixable(problem: Problem): problem is FixableProblem {
|
|
224
|
-
return problem.edit !== undefined;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export function hasSuggestions(problem: Problem): problem is SuggestionsProblem {
|
|
228
|
-
return problem.suggestions !== undefined;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
export type FixableProblem = Problem & {
|
|
233
|
-
edit: ESLintAutoFixEdit;
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
export namespace FixableProblem {
|
|
237
|
-
export function createTextEdit(document: TextDocument, editInfo: FixableProblem): TextEdit {
|
|
238
|
-
return TextEdit.replace(Range.create(document.positionAt(editInfo.edit.range[0]), document.positionAt(editInfo.edit.range[1])), editInfo.edit.text || '');
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export type SuggestionsProblem = Problem & {
|
|
243
|
-
suggestions: ESLintSuggestionResult[];
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
export namespace SuggestionsProblem {
|
|
247
|
-
export function createTextEdit(document: TextDocument, suggestion: ESLintSuggestionResult): TextEdit {
|
|
248
|
-
return TextEdit.replace(Range.create(document.positionAt(suggestion.fix.range[0]), document.positionAt(suggestion.fix.range[1])), suggestion.fix.text || '');
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
interface ESLintClass extends Object {
|
|
253
|
-
// https://eslint.org/docs/developer-guide/nodejs-api#-eslintlinttextcode-options
|
|
254
|
-
lintText(content: string, options: {filePath?: string; warnIgnored?: boolean}): Promise<ESLintDocumentReport[]>;
|
|
255
|
-
// https://eslint.org/docs/developer-guide/nodejs-api#-eslintispathignoredfilepath
|
|
256
|
-
isPathIgnored(path: string): Promise<boolean>;
|
|
257
|
-
// https://eslint.org/docs/developer-guide/nodejs-api#-eslintgetrulesmetaforresultsresults
|
|
258
|
-
getRulesMetaForResults?(results: ESLintDocumentReport[]): Record<string, RuleMetaData> | undefined /* for ESLintClassEmulator */;
|
|
259
|
-
// https://eslint.org/docs/developer-guide/nodejs-api#-eslintcalculateconfigforfilefilepath
|
|
260
|
-
calculateConfigForFile(path: string): Promise<ESLintConfig | undefined /* for ESLintClassEmulator */>;
|
|
261
|
-
// Whether it is the old CLI Engine
|
|
262
|
-
isCLIEngine?: boolean;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
namespace ESLintClass {
|
|
266
|
-
export function getConfigType(eslint: ESLintClass): 'eslintrc' | 'flat' {
|
|
267
|
-
if (eslint.isCLIEngine === true) {
|
|
268
|
-
return 'eslintrc';
|
|
269
|
-
}
|
|
270
|
-
const configType = (eslint.constructor as ESLintClassConstructor).configType;
|
|
271
|
-
return configType ?? 'eslintrc';
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
interface ESLintClassConstructor {
|
|
276
|
-
configType?: 'eslintrc' | 'flat';
|
|
277
|
-
version?: string;
|
|
278
|
-
new(options: ESLintClassOptions): ESLintClass;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
interface CLIEngineConstructor {
|
|
282
|
-
new(options: CLIOptions): CLIEngine;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* A loaded ESLint npm module.
|
|
287
|
-
*/
|
|
288
|
-
export type ESLintModule =
|
|
289
|
-
{
|
|
290
|
-
// version < 7.0
|
|
291
|
-
ESLint: undefined;
|
|
292
|
-
CLIEngine: CLIEngineConstructor;
|
|
293
|
-
loadESLint?: undefined;
|
|
294
|
-
} | {
|
|
295
|
-
// 7.0 <= version < 8.0
|
|
296
|
-
ESLint: ESLintClassConstructor;
|
|
297
|
-
CLIEngine: CLIEngineConstructor;
|
|
298
|
-
loadESLint?: undefined;
|
|
299
|
-
} | {
|
|
300
|
-
// 8.0 <= version.
|
|
301
|
-
ESLint: ESLintClassConstructor;
|
|
302
|
-
isFlatConfig?: boolean;
|
|
303
|
-
CLIEngine: undefined;
|
|
304
|
-
loadESLint?: (options?: { cwd?: string; useFlatConfig?: boolean }) => Promise<ESLintClassConstructor>;
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
export namespace ESLintModule {
|
|
308
|
-
export function hasLoadESLint(value: ESLintModule): value is { ESLint: ESLintClassConstructor; CLIEngine: undefined; loadESLint: (options?: { cwd?: string; useFlatConfig?: boolean }) => Promise<ESLintClassConstructor> } {
|
|
309
|
-
return value.loadESLint !== undefined;
|
|
310
|
-
}
|
|
311
|
-
export function hasESLintClass(value: ESLintModule): value is { ESLint: ESLintClassConstructor; CLIEngine: undefined } {
|
|
312
|
-
return value.ESLint !== undefined;
|
|
313
|
-
}
|
|
314
|
-
export function hasCLIEngine(value: ESLintModule): value is { ESLint: undefined; CLIEngine: CLIEngineConstructor } {
|
|
315
|
-
return value.CLIEngine !== undefined;
|
|
316
|
-
}
|
|
317
|
-
export function isFlatConfig(value: ESLintModule): value is { ESLint: ESLintClassConstructor; CLIEngine: undefined; isFlatConfig: true } {
|
|
318
|
-
const candidate: { ESLint: ESLintClassConstructor; isFlatConfig?: boolean } = value as any;
|
|
319
|
-
return candidate.ESLint !== undefined && candidate.isFlatConfig === true;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// { meta: { docs: [Object], schema: [Array] }, create: [Function: create] }
|
|
324
|
-
type RuleData = {
|
|
325
|
-
meta?: RuleMetaData;
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
namespace RuleData {
|
|
329
|
-
export function hasMetaType(value: RuleMetaData | undefined): value is RuleMetaData & { type: string } {
|
|
330
|
-
return value !== undefined && value.type !== undefined;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
interface CLIEngine {
|
|
335
|
-
executeOnText(content: string, file?: string, warn?: boolean): ESLintReport;
|
|
336
|
-
isPathIgnored(path: string): boolean;
|
|
337
|
-
// This is only available from v4.15.0 forward
|
|
338
|
-
getRules?(): Map<string, RuleData>;
|
|
339
|
-
getConfigForFile?(path: string): ESLintConfig;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
namespace CLIEngine {
|
|
343
|
-
export function hasRule(value: CLIEngine): value is CLIEngine & { getRules(): Map<string, RuleData> } {
|
|
344
|
-
return value.getRules !== undefined;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* ESLint class emulator using CLI Engine.
|
|
350
|
-
*/
|
|
351
|
-
class ESLintClassEmulator implements ESLintClass {
|
|
352
|
-
|
|
353
|
-
private cli: CLIEngine;
|
|
354
|
-
|
|
355
|
-
constructor(cli: CLIEngine) {
|
|
356
|
-
this.cli = cli;
|
|
357
|
-
}
|
|
358
|
-
get isCLIEngine(): boolean {
|
|
359
|
-
return true;
|
|
360
|
-
}
|
|
361
|
-
async lintText(content: string, options: { filePath?: string | undefined; warnIgnored?: boolean | undefined }): Promise<ESLintDocumentReport[]> {
|
|
362
|
-
return this.cli.executeOnText(content, options.filePath, options.warnIgnored).results;
|
|
363
|
-
}
|
|
364
|
-
async isPathIgnored(path: string): Promise<boolean> {
|
|
365
|
-
return this.cli.isPathIgnored(path);
|
|
366
|
-
}
|
|
367
|
-
getRulesMetaForResults(_results: ESLintDocumentReport[]): Record<string, RuleMetaData> | undefined {
|
|
368
|
-
if (!CLIEngine.hasRule(this.cli)) {
|
|
369
|
-
return undefined;
|
|
370
|
-
}
|
|
371
|
-
const rules: Record<string, RuleMetaData> = {};
|
|
372
|
-
for (const [name, rule] of this.cli.getRules()) {
|
|
373
|
-
if (rule.meta !== undefined) {
|
|
374
|
-
rules[name] = rule.meta;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return rules;
|
|
378
|
-
}
|
|
379
|
-
async calculateConfigForFile(path: string): Promise<ESLintConfig | undefined> {
|
|
380
|
-
return typeof this.cli.getConfigForFile === 'function' ? this.cli.getConfigForFile(path) : undefined;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* Class for dealing with Fixes.
|
|
387
|
-
*/
|
|
388
|
-
export class Fixes {
|
|
389
|
-
constructor(private edits: Map<string, Problem>) {
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
public static overlaps(a: FixableProblem | undefined, b: FixableProblem): boolean {
|
|
393
|
-
return a !== undefined && a.edit.range[1] > b.edit.range[0];
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
public static sameRange(a: FixableProblem, b: FixableProblem): boolean {
|
|
397
|
-
return a.edit.range[0] === b.edit.range[0] && a.edit.range[1] === b.edit.range[1];
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
public isEmpty(): boolean {
|
|
401
|
-
return this.edits.size === 0;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
public getDocumentVersion(): number {
|
|
405
|
-
if (this.isEmpty()) {
|
|
406
|
-
throw new Error('No edits recorded.');
|
|
407
|
-
}
|
|
408
|
-
return this.edits.values().next().value!.documentVersion;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
public getScoped(diagnostics: Diagnostic[]): Problem[] {
|
|
412
|
-
const result: Problem[] = [];
|
|
413
|
-
for (const diagnostic of diagnostics) {
|
|
414
|
-
const key = Diagnostics.computeKey(diagnostic);
|
|
415
|
-
const editInfo = this.edits.get(key);
|
|
416
|
-
if (editInfo) {
|
|
417
|
-
result.push(editInfo);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
return result;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
public getAllSorted(): FixableProblem[] {
|
|
424
|
-
const result: FixableProblem[] = [];
|
|
425
|
-
for (const value of this.edits.values()) {
|
|
426
|
-
if (Problem.isFixable(value)) {
|
|
427
|
-
result.push(value);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
return result.sort((a, b) => {
|
|
431
|
-
const d0 = a.edit.range[0] - b.edit.range[0];
|
|
432
|
-
if (d0 !== 0) {
|
|
433
|
-
return d0;
|
|
434
|
-
}
|
|
435
|
-
// Both edits have now the same start offset.
|
|
436
|
-
|
|
437
|
-
// Length of a and length of b
|
|
438
|
-
const al = a.edit.range[1] - a.edit.range[0];
|
|
439
|
-
const bl = b.edit.range[1] - b.edit.range[0];
|
|
440
|
-
// Both has the same start offset and length.
|
|
441
|
-
if (al === bl) {
|
|
442
|
-
return 0;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
if (al === 0) {
|
|
446
|
-
return -1;
|
|
447
|
-
}
|
|
448
|
-
if (bl === 0) {
|
|
449
|
-
return 1;
|
|
450
|
-
}
|
|
451
|
-
return al - bl;
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
public getApplicable(): FixableProblem[] {
|
|
456
|
-
const sorted = this.getAllSorted();
|
|
457
|
-
if (sorted.length <= 1) {
|
|
458
|
-
return sorted;
|
|
459
|
-
}
|
|
460
|
-
const result: FixableProblem[] = [];
|
|
461
|
-
let last: FixableProblem = sorted[0];
|
|
462
|
-
result.push(last);
|
|
463
|
-
for (let i = 1; i < sorted.length; i++) {
|
|
464
|
-
const current = sorted[i];
|
|
465
|
-
if (!Fixes.overlaps(last, current) && !Fixes.sameRange(last, current)) {
|
|
466
|
-
result.push(current);
|
|
467
|
-
last = current;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
return result;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
export type SaveRuleConfigItem = { offRules: Set<string>; onRules: Set<string>; options: ESLintOptions | undefined};
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Manages the special save rule configurations done in the VS Code settings.
|
|
478
|
-
*/
|
|
479
|
-
export namespace SaveRuleConfigs {
|
|
480
|
-
|
|
481
|
-
export let inferFilePath: (documentOrUri: string | TextDocument | URI | undefined, useRealpaths: boolean) => string | undefined;
|
|
482
|
-
|
|
483
|
-
const saveRuleConfigCache = new LRUCache<string, SaveRuleConfigItem | null>(128);
|
|
484
|
-
export async function get(uri: string, settings: TextDocumentSettings & { library: ESLintModule }): Promise<SaveRuleConfigItem | undefined> {
|
|
485
|
-
const filePath = inferFilePath(uri, settings.useRealpaths);
|
|
486
|
-
let result = saveRuleConfigCache.get(uri);
|
|
487
|
-
if (filePath === undefined || result === null) {
|
|
488
|
-
return undefined;
|
|
489
|
-
}
|
|
490
|
-
if (result !== undefined) {
|
|
491
|
-
return result;
|
|
492
|
-
}
|
|
493
|
-
const rules = settings.codeActionOnSave.rules;
|
|
494
|
-
const options = settings.codeActionOnSave.options;
|
|
495
|
-
result = await ESLint.withClass(async (eslint) => {
|
|
496
|
-
if ((rules === undefined && options === undefined) || eslint.isCLIEngine) {
|
|
497
|
-
return undefined;
|
|
498
|
-
}
|
|
499
|
-
const config = await eslint.calculateConfigForFile(filePath);
|
|
500
|
-
if (config === undefined || config.rules === undefined || config.rules.length === 0) {
|
|
501
|
-
return undefined;
|
|
502
|
-
}
|
|
503
|
-
const offRules: Set<string> = new Set();
|
|
504
|
-
const onRules: Set<string> = new Set();
|
|
505
|
-
if (rules !== undefined) {
|
|
506
|
-
if (rules.length === 0) {
|
|
507
|
-
Object.keys(config.rules).forEach(ruleId => offRules.add(ruleId));
|
|
508
|
-
} else {
|
|
509
|
-
for (const ruleId of Object.keys(config.rules)) {
|
|
510
|
-
if (isOff(ruleId, rules)) {
|
|
511
|
-
offRules.add(ruleId);
|
|
512
|
-
} else {
|
|
513
|
-
onRules.add(ruleId);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
return (offRules.size > 0 || options) ? { offRules, onRules, options } : undefined;
|
|
519
|
-
}, settings);
|
|
520
|
-
if (result === undefined || result === null) {
|
|
521
|
-
saveRuleConfigCache.set(uri, null);
|
|
522
|
-
return undefined;
|
|
523
|
-
} else {
|
|
524
|
-
saveRuleConfigCache.set(uri, result);
|
|
525
|
-
return result;
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
export function remove(key: string): boolean {
|
|
529
|
-
return saveRuleConfigCache.delete(key);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
export function clear(): void {
|
|
533
|
-
saveRuleConfigCache.clear();
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function isOff(ruleId: string, matchers: string[]): boolean {
|
|
537
|
-
for (const matcher of matchers) {
|
|
538
|
-
if (matcher.startsWith('!') && new RegExp(`^${matcher.slice(1).replace(/\*/g, '.*')}$`, 'g').test(ruleId)) {
|
|
539
|
-
return true;
|
|
540
|
-
} else if (new RegExp(`^${matcher.replace(/\*/g, '.*')}$`, 'g').test(ruleId)) {
|
|
541
|
-
return false;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
return true;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
/**
|
|
549
|
-
* Manages rule severity overrides done using VS Code settings.
|
|
550
|
-
*/
|
|
551
|
-
export namespace RuleSeverities {
|
|
552
|
-
|
|
553
|
-
const ruleSeverityCache = new LRUCache<string, RuleSeverity | null>(1024);
|
|
554
|
-
|
|
555
|
-
export function getOverride(ruleId: string, customizations: RuleCustomization[], isFixable?: boolean): RuleSeverity | undefined {
|
|
556
|
-
let result: RuleSeverity | undefined | null = ruleSeverityCache.get(ruleId);
|
|
557
|
-
if (result === null) {
|
|
558
|
-
return undefined;
|
|
559
|
-
}
|
|
560
|
-
if (result !== undefined) {
|
|
561
|
-
return result;
|
|
562
|
-
}
|
|
563
|
-
for (const customization of customizations) {
|
|
564
|
-
if (
|
|
565
|
-
// Rule name should match
|
|
566
|
-
asteriskMatches(customization.rule, ruleId) &&
|
|
567
|
-
// Fixable flag should match the fixability of the rule if it's defined
|
|
568
|
-
(customization.fixable === undefined || customization.fixable === isFixable)
|
|
569
|
-
) {
|
|
570
|
-
result = customization.severity;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
if (result === undefined) {
|
|
574
|
-
ruleSeverityCache.set(ruleId, null);
|
|
575
|
-
return undefined;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
ruleSeverityCache.set(ruleId, result);
|
|
579
|
-
return result;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
export function clear(): void {
|
|
583
|
-
ruleSeverityCache.clear();
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
function asteriskMatches(matcher: string, ruleId: string): boolean {
|
|
587
|
-
return matcher.startsWith('!')
|
|
588
|
-
? !(new RegExp(`^${matcher.slice(1).replace(/\*/g, '.*')}$`, 'g').test(ruleId))
|
|
589
|
-
: new RegExp(`^${matcher.replace(/\*/g, '.*')}$`, 'g').test(ruleId);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
/**
|
|
595
|
-
* Creates LSP Diagnostics and captures code action information.
|
|
596
|
-
*/
|
|
597
|
-
namespace Diagnostics {
|
|
598
|
-
|
|
599
|
-
export function computeKey(diagnostic: Diagnostic): string {
|
|
600
|
-
const range = diagnostic.range;
|
|
601
|
-
let message: string | undefined;
|
|
602
|
-
if (diagnostic.message) {
|
|
603
|
-
const hash = crypto.createHash('sha256');
|
|
604
|
-
hash.update(diagnostic.message);
|
|
605
|
-
message = hash.digest('base64');
|
|
606
|
-
}
|
|
607
|
-
return `[${range.start.line},${range.start.character},${range.end.line},${range.end.character}]-${diagnostic.code}-${message ?? ''}`;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
export function create(settings: TextDocumentSettings, problem: ESLintProblem, document: TextDocument): [Diagnostic, RuleSeverity | undefined] {
|
|
611
|
-
const message = problem.message;
|
|
612
|
-
const startLine = typeof problem.line !== 'number' || Number.isNaN(problem.line) ? 0 : Math.max(0, problem.line - 1);
|
|
613
|
-
const startChar = typeof problem.column !== 'number' || Number.isNaN(problem.column) ? 0 : Math.max(0, problem.column - 1);
|
|
614
|
-
let endLine = typeof problem.endLine !== 'number' || Number.isNaN(problem.endLine) ? startLine : Math.max(0, problem.endLine - 1);
|
|
615
|
-
let endChar = typeof problem.endColumn !== 'number' || Number.isNaN(problem.endColumn) ? startChar : Math.max(0, problem.endColumn - 1);
|
|
616
|
-
if (settings.problems.shortenToSingleLine && endLine !== startLine) {
|
|
617
|
-
const startLineText = document.getText({
|
|
618
|
-
start: {
|
|
619
|
-
line: startLine,
|
|
620
|
-
character: 0,
|
|
621
|
-
},
|
|
622
|
-
end: {
|
|
623
|
-
line: startLine,
|
|
624
|
-
character: uinteger.MAX_VALUE,
|
|
625
|
-
}
|
|
626
|
-
});
|
|
627
|
-
endLine = startLine;
|
|
628
|
-
endChar = startLineText.length;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
const override = RuleSeverities.getOverride(problem.ruleId, settings.rulesCustomizations, problem.fix !== undefined);
|
|
632
|
-
const result: Diagnostic = {
|
|
633
|
-
message: message,
|
|
634
|
-
severity: convertSeverityToDiagnosticWithOverride(problem.severity, override),
|
|
635
|
-
source: 'eslint',
|
|
636
|
-
range: {
|
|
637
|
-
start: { line: startLine, character: startChar },
|
|
638
|
-
end: { line: endLine, character: endChar }
|
|
639
|
-
}
|
|
640
|
-
};
|
|
641
|
-
if (problem.ruleId) {
|
|
642
|
-
const url = RuleMetaData.getUrl(problem.ruleId);
|
|
643
|
-
result.code = problem.ruleId;
|
|
644
|
-
if (url !== undefined) {
|
|
645
|
-
result.codeDescription = {
|
|
646
|
-
href: url
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
if (problem.ruleId === 'no-unused-vars') {
|
|
650
|
-
result.tags = [DiagnosticTag.Unnecessary];
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
return [result, override];
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
function adjustSeverityForOverride(severity: number | RuleSeverity, severityOverride?: RuleSeverity) {
|
|
658
|
-
switch (severityOverride) {
|
|
659
|
-
case RuleSeverity.off:
|
|
660
|
-
case RuleSeverity.info:
|
|
661
|
-
case RuleSeverity.warn:
|
|
662
|
-
case RuleSeverity.error:
|
|
663
|
-
return severityOverride;
|
|
664
|
-
|
|
665
|
-
case RuleSeverity.downgrade:
|
|
666
|
-
switch (convertSeverityToDiagnostic(severity)) {
|
|
667
|
-
case DiagnosticSeverity.Error:
|
|
668
|
-
return RuleSeverity.warn;
|
|
669
|
-
case DiagnosticSeverity.Warning:
|
|
670
|
-
case DiagnosticSeverity.Information:
|
|
671
|
-
return RuleSeverity.info;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
case RuleSeverity.upgrade:
|
|
675
|
-
switch (convertSeverityToDiagnostic(severity)) {
|
|
676
|
-
case DiagnosticSeverity.Information:
|
|
677
|
-
return RuleSeverity.warn;
|
|
678
|
-
case DiagnosticSeverity.Warning:
|
|
679
|
-
case DiagnosticSeverity.Error:
|
|
680
|
-
return RuleSeverity.error;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
default:
|
|
684
|
-
return severity;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
function convertSeverityToDiagnostic(severity: number | RuleSeverity) {
|
|
689
|
-
// RuleSeverity concerns an overridden rule. A number is direct from ESLint.
|
|
690
|
-
switch (severity) {
|
|
691
|
-
// Eslint 1 is warning
|
|
692
|
-
case 1:
|
|
693
|
-
case RuleSeverity.warn:
|
|
694
|
-
return DiagnosticSeverity.Warning;
|
|
695
|
-
case 2:
|
|
696
|
-
case RuleSeverity.error:
|
|
697
|
-
return DiagnosticSeverity.Error;
|
|
698
|
-
case RuleSeverity.info:
|
|
699
|
-
return DiagnosticSeverity.Information;
|
|
700
|
-
default:
|
|
701
|
-
return DiagnosticSeverity.Error;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
function convertSeverityToDiagnosticWithOverride(severity: number | RuleSeverity, severityOverride: RuleSeverity | undefined): DiagnosticSeverity {
|
|
706
|
-
return convertSeverityToDiagnostic(adjustSeverityForOverride(severity, severityOverride));
|
|
707
|
-
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
/**
|
|
712
|
-
* Capture information necessary to compute code actions.
|
|
713
|
-
*/
|
|
714
|
-
export namespace CodeActions {
|
|
715
|
-
const codeActions: Map<string, Map<string, Problem>> = new Map<string, Map<string, Problem>>();
|
|
716
|
-
|
|
717
|
-
export function get(uri: string): Map<string, Problem> | undefined {
|
|
718
|
-
return codeActions.get(uri);
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
export function set(uri: string, value: Map<string, Problem>): void {
|
|
722
|
-
codeActions.set(uri, value);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
export function remove(uri: string): boolean {
|
|
726
|
-
return codeActions.delete(uri);
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
export function record(document: TextDocument, diagnostic: Diagnostic, problem: ESLintProblem): void {
|
|
730
|
-
if (!problem.ruleId) {
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
const uri = document.uri;
|
|
734
|
-
let edits: Map<string, Problem> | undefined = CodeActions.get(uri);
|
|
735
|
-
if (edits === undefined) {
|
|
736
|
-
edits = new Map<string, Problem>();
|
|
737
|
-
CodeActions.set(uri, edits);
|
|
738
|
-
}
|
|
739
|
-
edits.set(Diagnostics.computeKey(diagnostic), {
|
|
740
|
-
label: `Fix this ${problem.ruleId} problem`,
|
|
741
|
-
documentVersion: document.version,
|
|
742
|
-
ruleId: problem.ruleId,
|
|
743
|
-
line: problem.line,
|
|
744
|
-
diagnostic: diagnostic,
|
|
745
|
-
edit: problem.fix,
|
|
746
|
-
suggestions: problem.suggestions
|
|
747
|
-
});
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
/**
|
|
752
|
-
* Wrapper round the ESLint npm module.
|
|
753
|
-
*/
|
|
754
|
-
export namespace ESLint {
|
|
755
|
-
|
|
756
|
-
let connection: ProposedFeatures.Connection;
|
|
757
|
-
let documents: TextDocuments<TextDocument>;
|
|
758
|
-
let inferFilePath: (documentOrUri: string | TextDocument | URI | undefined, useRealpaths: boolean) => string | undefined;
|
|
759
|
-
let loadNodeModule: <T>(moduleName: string) => T | undefined;
|
|
760
|
-
|
|
761
|
-
const languageId2ParserRegExp: Map<string, RegExp[]> = function createLanguageId2ParserRegExp() {
|
|
762
|
-
const result = new Map<string, RegExp[]>();
|
|
763
|
-
const typescript = /\/@typescript-eslint\/parser\//;
|
|
764
|
-
const babelESLint = /\/babel-eslint\/lib\/index.js$/;
|
|
765
|
-
const vueESLint = /\/vue-eslint-parser\/index.js$/;
|
|
766
|
-
result.set('typescript', [typescript, babelESLint, vueESLint]);
|
|
767
|
-
result.set('typescriptreact', [typescript, babelESLint, vueESLint]);
|
|
768
|
-
|
|
769
|
-
const angular = /\/@angular-eslint\/template-parser\//;
|
|
770
|
-
result.set('html', [angular]);
|
|
771
|
-
|
|
772
|
-
return result;
|
|
773
|
-
}();
|
|
774
|
-
|
|
775
|
-
const languageId2ParserOptions: Map<string, { regExps: RegExp[]; parsers: Set<string>; parserRegExps?: RegExp[] }> = function createLanguageId2ParserOptionsRegExp() {
|
|
776
|
-
const result = new Map<string, { regExps: RegExp[]; parsers: Set<string>; parserRegExps?: RegExp[] }>();
|
|
777
|
-
const vue = /vue-eslint-parser\/.*\.js$/;
|
|
778
|
-
const typescriptEslintParser = /@typescript-eslint\/parser\/.*\.js$/;
|
|
779
|
-
result.set('typescript', { regExps: [vue], parsers: new Set<string>(['@typescript-eslint/parser']), parserRegExps: [typescriptEslintParser] });
|
|
780
|
-
return result;
|
|
781
|
-
}();
|
|
782
|
-
|
|
783
|
-
const languageId2PluginName: Map<string, string> = new Map([
|
|
784
|
-
['astro', 'astro'],
|
|
785
|
-
['civet', 'civet'],
|
|
786
|
-
['html', 'html'],
|
|
787
|
-
['json', 'jsonc'],
|
|
788
|
-
['json5', 'jsonc'],
|
|
789
|
-
['jsonc', 'jsonc'],
|
|
790
|
-
['mdx', 'mdx'],
|
|
791
|
-
['vue', 'vue'],
|
|
792
|
-
['markdown', 'markdown'],
|
|
793
|
-
['css', 'css'],
|
|
794
|
-
['glimmer-js', 'ember'],
|
|
795
|
-
['glimmer-ts', 'ember'],
|
|
796
|
-
['svelte', 'svelte'],
|
|
797
|
-
]);
|
|
798
|
-
|
|
799
|
-
const defaultLanguageIds: Set<string> = new Set([
|
|
800
|
-
'javascript', 'javascriptreact'
|
|
801
|
-
]);
|
|
802
|
-
|
|
803
|
-
const projectFolderIndicators: {
|
|
804
|
-
fileName: string;
|
|
805
|
-
isRoot: boolean;
|
|
806
|
-
isFlatConfig: boolean;
|
|
807
|
-
}[] = [
|
|
808
|
-
{ fileName: 'eslint.config.js', isRoot: true, isFlatConfig: true },
|
|
809
|
-
{ fileName: 'eslint.config.cjs', isRoot: true, isFlatConfig: true },
|
|
810
|
-
{ fileName: 'eslint.config.mjs', isRoot: true, isFlatConfig: true },
|
|
811
|
-
{ fileName: 'eslint.config.ts', isRoot: true, isFlatConfig: true },
|
|
812
|
-
{ fileName: 'eslint.config.cts', isRoot: true, isFlatConfig: true },
|
|
813
|
-
{ fileName: 'eslint.config.mts', isRoot: true, isFlatConfig: true },
|
|
814
|
-
{ fileName: 'package.json', isRoot: true, isFlatConfig: false },
|
|
815
|
-
{ fileName: '.eslintignore', isRoot: true, isFlatConfig: false },
|
|
816
|
-
{ fileName: '.eslintrc', isRoot: false, isFlatConfig: false },
|
|
817
|
-
{ fileName: '.eslintrc.json', isRoot: false, isFlatConfig: false },
|
|
818
|
-
{ fileName: '.eslintrc.js', isRoot: false, isFlatConfig: false },
|
|
819
|
-
{ fileName: '.eslintrc.yaml', isRoot: false, isFlatConfig: false },
|
|
820
|
-
{ fileName: '.eslintrc.yml', isRoot: false, isFlatConfig: false }
|
|
821
|
-
];
|
|
822
|
-
|
|
823
|
-
const path2Library: Map<string, ESLintModule> = new Map<string, ESLintModule>();
|
|
824
|
-
const document2Settings: Map<string, Promise<TextDocumentSettings>> = new Map<string, Promise<TextDocumentSettings>>();
|
|
825
|
-
const formatterRegistrations: Map<string, Promise<Disposable>> = new Map();
|
|
826
|
-
|
|
827
|
-
export function initialize($connection: ProposedFeatures.Connection, $documents: TextDocuments<TextDocument>, $inferFilePath: (documentOrUri: string | TextDocument | URI | undefined, useRealpaths: boolean) => string | undefined, $loadNodeModule: <T>(moduleName: string) => T | undefined) {
|
|
828
|
-
connection = $connection;
|
|
829
|
-
documents = $documents;
|
|
830
|
-
inferFilePath = $inferFilePath;
|
|
831
|
-
loadNodeModule = $loadNodeModule;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
export function removeSettings(key: string): boolean {
|
|
835
|
-
return document2Settings.delete(key);
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
export function clearSettings(): void {
|
|
839
|
-
document2Settings.clear();
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
export function unregisterAsFormatter(document: TextDocument): void {
|
|
843
|
-
const unregister = formatterRegistrations.get(document.uri);
|
|
844
|
-
if (unregister !== undefined) {
|
|
845
|
-
void unregister.then(disposable => disposable.dispose());
|
|
846
|
-
formatterRegistrations.delete(document.uri);
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
export function clearFormatters(): void {
|
|
851
|
-
for (const unregistration of formatterRegistrations.values()) {
|
|
852
|
-
void unregistration.then(disposable => disposable.dispose());
|
|
853
|
-
}
|
|
854
|
-
formatterRegistrations.clear();
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
export function resolveSettings(document: TextDocument): Promise<TextDocumentSettings> {
|
|
858
|
-
const uri = document.uri;
|
|
859
|
-
let resultPromise = document2Settings.get(uri);
|
|
860
|
-
if (resultPromise) {
|
|
861
|
-
return resultPromise;
|
|
862
|
-
}
|
|
863
|
-
resultPromise = connection.workspace.getConfiguration({ scopeUri: uri, section: '' }).then((configuration: ConfigurationSettings) => {
|
|
864
|
-
const settings: TextDocumentSettings = Object.assign(
|
|
865
|
-
{},
|
|
866
|
-
configuration,
|
|
867
|
-
{ silent: false, library: undefined, resolvedGlobalPackageManagerPath: undefined },
|
|
868
|
-
{ workingDirectory: undefined}
|
|
869
|
-
);
|
|
870
|
-
if (settings.validate === Validate.off) {
|
|
871
|
-
return settings;
|
|
872
|
-
}
|
|
873
|
-
settings.resolvedGlobalPackageManagerPath = GlobalPaths.get(settings.packageManager);
|
|
874
|
-
const filePath = inferFilePath(document, settings.useRealpaths);
|
|
875
|
-
const workspaceFolderPath = settings.workspaceFolder !== undefined ? inferFilePath(settings.workspaceFolder.uri, settings.useRealpaths) : undefined;
|
|
876
|
-
let assumeFlatConfig:boolean = false;
|
|
877
|
-
const hasUserDefinedWorkingDirectories: boolean = configuration.workingDirectory !== undefined;
|
|
878
|
-
const workingDirectoryConfig = configuration.workingDirectory ?? { mode: ModeEnum.location };
|
|
879
|
-
if (ModeItem.is(workingDirectoryConfig)) {
|
|
880
|
-
let candidate: string | undefined;
|
|
881
|
-
if (workingDirectoryConfig.mode === ModeEnum.location) {
|
|
882
|
-
if (workspaceFolderPath !== undefined) {
|
|
883
|
-
const [configLocation, isFlatConfig] = findWorkingDirectory(workspaceFolderPath, filePath);
|
|
884
|
-
if (isFlatConfig && settings.useFlatConfig !== false) {
|
|
885
|
-
candidate = configLocation;
|
|
886
|
-
assumeFlatConfig = true;
|
|
887
|
-
} else {
|
|
888
|
-
candidate = workspaceFolderPath;
|
|
889
|
-
}
|
|
890
|
-
} else if (filePath !== undefined && !isUNC(filePath)) {
|
|
891
|
-
candidate = path.dirname(filePath);
|
|
892
|
-
}
|
|
893
|
-
} else if (workingDirectoryConfig.mode === ModeEnum.auto) {
|
|
894
|
-
if (workspaceFolderPath !== undefined) {
|
|
895
|
-
candidate = findWorkingDirectory(workspaceFolderPath, filePath)[0];
|
|
896
|
-
} else if (filePath !== undefined && !isUNC(filePath)) {
|
|
897
|
-
candidate = path.dirname(filePath);
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
if (candidate !== undefined && fs.existsSync(candidate)) {
|
|
901
|
-
settings.workingDirectory = { directory: candidate };
|
|
902
|
-
}
|
|
903
|
-
} else {
|
|
904
|
-
settings.workingDirectory = workingDirectoryConfig;
|
|
905
|
-
}
|
|
906
|
-
let nodePath: string | undefined;
|
|
907
|
-
if (settings.nodePath !== null) {
|
|
908
|
-
nodePath = settings.nodePath;
|
|
909
|
-
if (!path.isAbsolute(nodePath) && workspaceFolderPath !== undefined) {
|
|
910
|
-
nodePath = path.join(workspaceFolderPath, nodePath);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
let moduleResolveWorkingDirectory: string | undefined;
|
|
914
|
-
if (!hasUserDefinedWorkingDirectories && filePath !== undefined) {
|
|
915
|
-
moduleResolveWorkingDirectory = path.dirname(filePath);
|
|
916
|
-
}
|
|
917
|
-
if (moduleResolveWorkingDirectory === undefined && settings.workingDirectory !== undefined && !settings.workingDirectory['!cwd']) {
|
|
918
|
-
moduleResolveWorkingDirectory = settings.workingDirectory.directory;
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
let promise: Promise<string>;
|
|
922
|
-
// During Flat Config is considered experimental,
|
|
923
|
-
// we need to import FlatESLint from 'eslint/use-at-your-own-risk'.
|
|
924
|
-
// See: https://eslint.org/blog/2022/08/new-config-system-part-3/
|
|
925
|
-
const eslintPath = settings.experimental?.useFlatConfig ? 'eslint/use-at-your-own-risk' : 'eslint';
|
|
926
|
-
if (nodePath !== undefined) {
|
|
927
|
-
promise = Files.resolve(eslintPath, nodePath, nodePath, trace).then<string, string>(undefined, () => {
|
|
928
|
-
return Files.resolve(eslintPath, settings.resolvedGlobalPackageManagerPath, moduleResolveWorkingDirectory, trace);
|
|
929
|
-
});
|
|
930
|
-
} else {
|
|
931
|
-
promise = Files.resolve(eslintPath, settings.resolvedGlobalPackageManagerPath, moduleResolveWorkingDirectory, trace);
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
settings.silent = settings.validate === Validate.probe;
|
|
935
|
-
return promise.then(async (libraryPath) => {
|
|
936
|
-
let library = path2Library.get(libraryPath);
|
|
937
|
-
if (library === undefined) {
|
|
938
|
-
if (settings.experimental?.useFlatConfig === true) {
|
|
939
|
-
const lib = loadNodeModule<{ FlatESLint?: ESLintClassConstructor }>(libraryPath);
|
|
940
|
-
if (lib === undefined) {
|
|
941
|
-
settings.validate = Validate.off;
|
|
942
|
-
if (!settings.silent) {
|
|
943
|
-
connection.console.error(`Failed to load eslint library from ${libraryPath}. If you are using ESLint v8.21 or earlier, try upgrading it. For newer versions, try disabling the 'eslint.experimental.useFlatConfig' setting. See the output panel for more information.`);
|
|
944
|
-
}
|
|
945
|
-
} else if (lib.FlatESLint === undefined) {
|
|
946
|
-
settings.validate = Validate.off;
|
|
947
|
-
connection.console.error(`The eslint library loaded from ${libraryPath} doesn\'t export a FlatESLint class.`);
|
|
948
|
-
} else {
|
|
949
|
-
connection.console.info(`ESLint library loaded from: ${libraryPath}`);
|
|
950
|
-
// pretend to be a regular eslint endpoint
|
|
951
|
-
library = {
|
|
952
|
-
ESLint: lib.FlatESLint,
|
|
953
|
-
isFlatConfig: true,
|
|
954
|
-
CLIEngine: undefined,
|
|
955
|
-
};
|
|
956
|
-
settings.library = library;
|
|
957
|
-
path2Library.set(libraryPath, library);
|
|
958
|
-
}
|
|
959
|
-
} else {
|
|
960
|
-
library = loadNodeModule(libraryPath);
|
|
961
|
-
if (library === undefined) {
|
|
962
|
-
settings.validate = Validate.off;
|
|
963
|
-
if (!settings.silent) {
|
|
964
|
-
connection.console.error(`Failed to load eslint library from ${libraryPath}. See output panel for more information.`);
|
|
965
|
-
}
|
|
966
|
-
} else if (library.CLIEngine === undefined && library.ESLint === undefined) {
|
|
967
|
-
settings.validate = Validate.off;
|
|
968
|
-
connection.console.error(`The eslint library loaded from ${libraryPath} doesn\'t export neither a CLIEngine nor an ESLint class. You need at least eslint@1.0.0`);
|
|
969
|
-
} else {
|
|
970
|
-
connection.console.info(`ESLint library loaded from: ${libraryPath}`);
|
|
971
|
-
settings.library = library;
|
|
972
|
-
path2Library.set(libraryPath, library);
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
if (library !== undefined && ESLintModule.hasESLintClass(library) && typeof library.ESLint.version === 'string') {
|
|
976
|
-
const esLintVersion = semverParse(library.ESLint.version);
|
|
977
|
-
if (esLintVersion !== null) {
|
|
978
|
-
if (semverGte(esLintVersion, '8.57.0') && settings.experimental?.useFlatConfig === true) {
|
|
979
|
-
connection.console.info(`ESLint version ${library.ESLint.version} supports flat config without experimental opt-in. The 'eslint.experimental.useFlatConfig' setting can be removed.`);
|
|
980
|
-
} else if (semverGte(esLintVersion, '10.0.0') && (settings.experimental?.useFlatConfig === false || settings.useFlatConfig === false)) {
|
|
981
|
-
connection.console.info(`ESLint version ${library.ESLint.version} only supports flat configs. Setting is ignored.`);
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
} else {
|
|
986
|
-
settings.library = library;
|
|
987
|
-
}
|
|
988
|
-
if (settings.validate === Validate.probe && TextDocumentSettings.hasLibrary(settings)) {
|
|
989
|
-
settings.validate = Validate.off;
|
|
990
|
-
const filePath = ESLint.getFilePath(document, settings);
|
|
991
|
-
if (filePath !== undefined) {
|
|
992
|
-
const parserRegExps = languageId2ParserRegExp.get(document.languageId);
|
|
993
|
-
const pluginName = languageId2PluginName.get(document.languageId);
|
|
994
|
-
const parserOptions = languageId2ParserOptions.get(document.languageId);
|
|
995
|
-
if (defaultLanguageIds.has(document.languageId)) {
|
|
996
|
-
try {
|
|
997
|
-
const [isIgnored, configType] = await ESLint.withClass(async (eslintClass) => {
|
|
998
|
-
return [await eslintClass.isPathIgnored(filePath), ESLintClass.getConfigType(eslintClass)];
|
|
999
|
-
}, settings);
|
|
1000
|
-
if (isIgnored === false || (isIgnored === true && settings.onIgnoredFiles !== ESLintSeverity.off)) {
|
|
1001
|
-
settings.validate = Validate.on;
|
|
1002
|
-
if (assumeFlatConfig && configType === 'eslintrc') {
|
|
1003
|
-
connection.console.info(`Expected to use flat configuration from directory ${settings.workingDirectory?.directory} but loaded eslintrc config.`);
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
} catch (error: any) {
|
|
1007
|
-
settings.validate = Validate.off;
|
|
1008
|
-
await connection.sendNotification(StatusNotification.type, { uri, state: Status.error });
|
|
1009
|
-
connection.console.error(`Calculating config file for ${uri}) failed.\n${error instanceof Error ? error.stack : ''}`);
|
|
1010
|
-
}
|
|
1011
|
-
} else if (parserRegExps !== undefined || pluginName !== undefined || parserOptions !== undefined) {
|
|
1012
|
-
const [eslintConfig, configType] = await ESLint.withClass(async (eslintClass) => {
|
|
1013
|
-
try {
|
|
1014
|
-
if (await eslintClass.isPathIgnored(filePath)) {
|
|
1015
|
-
return [undefined, undefined];
|
|
1016
|
-
} else {
|
|
1017
|
-
return [await eslintClass.calculateConfigForFile(filePath), ESLintClass.getConfigType(eslintClass)];
|
|
1018
|
-
}
|
|
1019
|
-
} catch (err) {
|
|
1020
|
-
try {
|
|
1021
|
-
await connection.sendNotification(StatusNotification.type, { uri, state: Status.error });
|
|
1022
|
-
connection.console.error(`Calculating config file for ${uri}) failed.\n${err instanceof Error ? err.stack : ''}`);
|
|
1023
|
-
} catch {
|
|
1024
|
-
// little we can do here
|
|
1025
|
-
}
|
|
1026
|
-
return [undefined, undefined];
|
|
1027
|
-
}
|
|
1028
|
-
}, settings);
|
|
1029
|
-
if (eslintConfig !== undefined) {
|
|
1030
|
-
if (assumeFlatConfig && configType === 'eslintrc') {
|
|
1031
|
-
connection.console.info(`Expected to use flat configuration from directory ${settings.workingDirectory?.directory} but loaded eslintrc config.`);
|
|
1032
|
-
}
|
|
1033
|
-
if (configType === 'flat' || ESLintModule.isFlatConfig(settings.library)) {
|
|
1034
|
-
// We have a flat configuration. This means that the config file needs to
|
|
1035
|
-
// have a section per file extension we want to validate. If there is none than
|
|
1036
|
-
// `calculateConfigForFile` will return no config since the config options without
|
|
1037
|
-
// a `files` property only applies to `**/*.js, **/*.cjs, and **/*.mjs` by default
|
|
1038
|
-
// See https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new#specifying-files-and-ignores
|
|
1039
|
-
|
|
1040
|
-
// This means since we have found a configuration for the given file we assume that
|
|
1041
|
-
// that configuration is correctly pointing to a parser.
|
|
1042
|
-
settings.validate = Validate.on;
|
|
1043
|
-
} else {
|
|
1044
|
-
const parser: string | undefined = eslintConfig.parser !== null
|
|
1045
|
-
? normalizePath(eslintConfig.parser)
|
|
1046
|
-
: undefined;
|
|
1047
|
-
if (parser !== undefined) {
|
|
1048
|
-
if (parserRegExps !== undefined) {
|
|
1049
|
-
for (const regExp of parserRegExps) {
|
|
1050
|
-
if (regExp.test(parser)) {
|
|
1051
|
-
settings.validate = Validate.on;
|
|
1052
|
-
break;
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
if (settings.validate !== Validate.on && parserOptions !== undefined && typeof eslintConfig.parserOptions?.parser === 'string') {
|
|
1057
|
-
const eslintConfigParserOptionsParser = normalizePath(eslintConfig.parserOptions.parser);
|
|
1058
|
-
for (const regExp of parserOptions.regExps) {
|
|
1059
|
-
if (regExp.test(parser) && (
|
|
1060
|
-
parserOptions.parsers.has(eslintConfig.parserOptions.parser) ||
|
|
1061
|
-
parserOptions.parserRegExps !== undefined && parserOptions.parserRegExps.some(parserRegExp => parserRegExp.test(eslintConfigParserOptionsParser))
|
|
1062
|
-
)) {
|
|
1063
|
-
settings.validate = Validate.on;
|
|
1064
|
-
break;
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
if (settings.validate !== Validate.on && Array.isArray(eslintConfig.plugins) && eslintConfig.plugins.length > 0 && pluginName !== undefined) {
|
|
1070
|
-
for (const name of eslintConfig.plugins) {
|
|
1071
|
-
if (name === pluginName) {
|
|
1072
|
-
settings.validate = Validate.on;
|
|
1073
|
-
break;
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
if (settings.validate === Validate.off) {
|
|
1082
|
-
const params: ProbeFailedParams = { textDocument: { uri: document.uri } };
|
|
1083
|
-
void connection.sendRequest(ProbeFailedRequest.type, params);
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
if (settings.validate === Validate.on) {
|
|
1087
|
-
settings.silent = false;
|
|
1088
|
-
if (settings.format && TextDocumentSettings.hasLibrary(settings) && !formatterRegistrations.has(uri)) {
|
|
1089
|
-
const Uri = URI.parse(uri);
|
|
1090
|
-
const isFile = Uri.scheme === 'file';
|
|
1091
|
-
let pattern: string = isFile
|
|
1092
|
-
? Uri.fsPath.replace(/\\/g, '/')
|
|
1093
|
-
: Uri.fsPath;
|
|
1094
|
-
pattern = pattern.replace(/[\[\]\{\}]/g, '?');
|
|
1095
|
-
|
|
1096
|
-
const filter: DocumentFilter = { scheme: Uri.scheme, pattern: pattern };
|
|
1097
|
-
const options: DocumentFormattingRegistrationOptions = { documentSelector: [filter] };
|
|
1098
|
-
if (!isFile) {
|
|
1099
|
-
formatterRegistrations.set(uri, connection.client.register(DocumentFormattingRequest.type, options));
|
|
1100
|
-
} else {
|
|
1101
|
-
const filePath = inferFilePath(uri, settings.useRealpaths)!;
|
|
1102
|
-
await ESLint.withClass(async (eslintClass) => {
|
|
1103
|
-
if (!await eslintClass.isPathIgnored(filePath)) {
|
|
1104
|
-
formatterRegistrations.set(uri, connection.client.register(DocumentFormattingRequest.type, options));
|
|
1105
|
-
}
|
|
1106
|
-
}, settings);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
return settings;
|
|
1111
|
-
}, () => {
|
|
1112
|
-
settings.validate = Validate.off;
|
|
1113
|
-
if (!settings.silent) {
|
|
1114
|
-
void connection.sendRequest(NoESLintLibraryRequest.type, { source: { uri: document.uri } });
|
|
1115
|
-
}
|
|
1116
|
-
return settings;
|
|
1117
|
-
});
|
|
1118
|
-
});
|
|
1119
|
-
document2Settings.set(uri, resultPromise);
|
|
1120
|
-
return resultPromise;
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
export async function newClass(library: ESLintModule, newOptions: ESLintClassOptions | CLIOptions, settings: TextDocumentSettings): Promise<ESLintClass> {
|
|
1124
|
-
// Since ESLint version 8.57 we have a dedicated loadESLint function
|
|
1125
|
-
// which takes care of loading the right ESLint class. We available
|
|
1126
|
-
// we use it.
|
|
1127
|
-
if (ESLintModule.hasLoadESLint(library)) {
|
|
1128
|
-
return new (await library.loadESLint({ useFlatConfig: settings.useFlatConfig }))(newOptions);
|
|
1129
|
-
}
|
|
1130
|
-
// If we have version 7 where we have both ESLint class and CLIEngine we only
|
|
1131
|
-
// use the ESLint class if a corresponding setting (useESLintClass) is set.
|
|
1132
|
-
if (ESLintModule.hasESLintClass(library) && settings.useESLintClass) {
|
|
1133
|
-
return new library.ESLint(newOptions);
|
|
1134
|
-
}
|
|
1135
|
-
if (ESLintModule.hasCLIEngine(library)) {
|
|
1136
|
-
return new ESLintClassEmulator(new library.CLIEngine(newOptions));
|
|
1137
|
-
}
|
|
1138
|
-
return new library.ESLint(newOptions);
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
export async function withClass<T>(func: (eslintClass: ESLintClass) => Promise<T>, settings: TextDocumentSettings & { library: ESLintModule }, options?: ESLintClassOptions | CLIOptions): Promise<T> {
|
|
1142
|
-
const newOptions: ESLintClassOptions | CLIOptions = options === undefined
|
|
1143
|
-
? Object.assign(Object.create(null), settings.options)
|
|
1144
|
-
: Object.assign(Object.create(null), settings.options, options);
|
|
1145
|
-
|
|
1146
|
-
const cwd = process.cwd();
|
|
1147
|
-
try {
|
|
1148
|
-
if (settings.workingDirectory) {
|
|
1149
|
-
// A lot of libs are sensitive to drive letter casing and assume a
|
|
1150
|
-
// upper case drive letter. Make sure we support that correctly.
|
|
1151
|
-
const newCWD = normalizeWorkingDirectory(settings.workingDirectory.directory);
|
|
1152
|
-
newOptions.cwd = newCWD;
|
|
1153
|
-
if (settings.workingDirectory['!cwd'] !== true && fs.existsSync(newCWD)) {
|
|
1154
|
-
process.chdir(newCWD);
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
const eslintClass = await newClass(settings.library, newOptions, settings);
|
|
1159
|
-
// We need to await the result to ensure proper execution of the
|
|
1160
|
-
// finally block.
|
|
1161
|
-
return await func(eslintClass);
|
|
1162
|
-
} finally {
|
|
1163
|
-
if (cwd !== process.cwd()) {
|
|
1164
|
-
process.chdir(cwd);
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
function normalizeWorkingDirectory(value: string): string {
|
|
1170
|
-
const result = normalizeDriveLetter(value);
|
|
1171
|
-
if (result.length === 0) {
|
|
1172
|
-
return result;
|
|
1173
|
-
}
|
|
1174
|
-
return result[result.length - 1] === path.sep
|
|
1175
|
-
? result.substring(0, result.length - 1)
|
|
1176
|
-
: result;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
export function getFilePath(document: TextDocument | undefined, settings: TextDocumentSettings): string | undefined {
|
|
1180
|
-
if (document === undefined) {
|
|
1181
|
-
return undefined;
|
|
1182
|
-
}
|
|
1183
|
-
const uri = URI.parse(document.uri);
|
|
1184
|
-
if (uri.scheme !== 'file') {
|
|
1185
|
-
if (settings.workspaceFolder !== undefined) {
|
|
1186
|
-
const ext = LanguageDefaults.getExtension(document.languageId);
|
|
1187
|
-
const workspacePath = inferFilePath(settings.workspaceFolder.uri, settings.useRealpaths);
|
|
1188
|
-
if (workspacePath !== undefined && ext !== undefined) {
|
|
1189
|
-
return path.join(workspacePath, `test.${ext}`);
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
return undefined;
|
|
1193
|
-
} else {
|
|
1194
|
-
return inferFilePath(uri, settings.useRealpaths);
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
const validFixTypes = new Set<string>(['problem', 'suggestion', 'layout', 'directive']);
|
|
1199
|
-
export async function validate(document: TextDocument, settings: TextDocumentSettings & { library: ESLintModule }): Promise<Diagnostic[]> {
|
|
1200
|
-
const newOptions: CLIOptions = Object.assign(Object.create(null), settings.options);
|
|
1201
|
-
let fixTypes: Set<string> | undefined = undefined;
|
|
1202
|
-
if (Array.isArray(newOptions.fixTypes) && newOptions.fixTypes.length > 0) {
|
|
1203
|
-
fixTypes = new Set();
|
|
1204
|
-
for (const item of newOptions.fixTypes) {
|
|
1205
|
-
if (validFixTypes.has(item)) {
|
|
1206
|
-
fixTypes.add(item);
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
if (fixTypes.size === 0) {
|
|
1210
|
-
fixTypes = undefined;
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
const content = document.getText();
|
|
1215
|
-
const uri = document.uri;
|
|
1216
|
-
const file = getFilePath(document, settings);
|
|
1217
|
-
|
|
1218
|
-
return withClass(async (eslintClass) => {
|
|
1219
|
-
CodeActions.remove(uri);
|
|
1220
|
-
const reportResults: ESLintDocumentReport[] = await eslintClass.lintText(content, { filePath: file, warnIgnored: settings.onIgnoredFiles !== ESLintSeverity.off });
|
|
1221
|
-
RuleMetaData.capture(eslintClass, reportResults);
|
|
1222
|
-
const diagnostics: Diagnostic[] = [];
|
|
1223
|
-
if (reportResults && Array.isArray(reportResults) && reportResults.length > 0) {
|
|
1224
|
-
const docReport = reportResults[0];
|
|
1225
|
-
if (docReport.messages && Array.isArray(docReport.messages)) {
|
|
1226
|
-
docReport.messages.forEach((problem) => {
|
|
1227
|
-
if (problem) {
|
|
1228
|
-
const [diagnostic, override] = Diagnostics.create(settings, problem, document);
|
|
1229
|
-
if (!(override === RuleSeverity.off || (settings.quiet && (diagnostic.severity === DiagnosticSeverity.Warning || diagnostic.severity === DiagnosticSeverity.Information)))) {
|
|
1230
|
-
diagnostics.push(diagnostic);
|
|
1231
|
-
}
|
|
1232
|
-
if (fixTypes !== undefined && problem.ruleId !== undefined && problem.fix !== undefined) {
|
|
1233
|
-
const type = RuleMetaData.getType(problem.ruleId);
|
|
1234
|
-
if (type !== undefined && fixTypes.has(type)) {
|
|
1235
|
-
CodeActions.record(document, diagnostic, problem);
|
|
1236
|
-
}
|
|
1237
|
-
} else {
|
|
1238
|
-
if (RuleMetaData.isUnusedDisableDirectiveProblem(problem)) {
|
|
1239
|
-
problem.ruleId = RuleMetaData.unusedDisableDirectiveId;
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
CodeActions.record(document, diagnostic, problem);
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
});
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
return diagnostics;
|
|
1249
|
-
}, settings);
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
function trace(message: string, verbose?: string): void {
|
|
1253
|
-
connection.tracer.log(message, verbose);
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
/**
|
|
1257
|
-
* Global paths for the different package managers
|
|
1258
|
-
*/
|
|
1259
|
-
namespace GlobalPaths {
|
|
1260
|
-
const globalPaths: Record<string, { cache: string | undefined; get(): string | undefined }> = {
|
|
1261
|
-
yarn: {
|
|
1262
|
-
cache: undefined,
|
|
1263
|
-
get(): string | undefined {
|
|
1264
|
-
return Files.resolveGlobalYarnPath(trace);
|
|
1265
|
-
}
|
|
1266
|
-
},
|
|
1267
|
-
npm: {
|
|
1268
|
-
cache: undefined,
|
|
1269
|
-
get(): string | undefined {
|
|
1270
|
-
return Files.resolveGlobalNodePath(trace);
|
|
1271
|
-
}
|
|
1272
|
-
},
|
|
1273
|
-
pnpm: {
|
|
1274
|
-
cache: undefined,
|
|
1275
|
-
get(): string {
|
|
1276
|
-
const pnpmPath = execSync('pnpm root -g').toString().trim();
|
|
1277
|
-
return pnpmPath;
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
};
|
|
1281
|
-
|
|
1282
|
-
export function get(packageManager: PackageManagers): string | undefined {
|
|
1283
|
-
const pm = globalPaths[packageManager];
|
|
1284
|
-
if (pm) {
|
|
1285
|
-
if (pm.cache === undefined) {
|
|
1286
|
-
pm.cache = pm.get();
|
|
1287
|
-
}
|
|
1288
|
-
return pm.cache;
|
|
1289
|
-
}
|
|
1290
|
-
return undefined;
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
export function findWorkingDirectory(workspaceFolder: string, file: string | undefined): [string, boolean] {
|
|
1295
|
-
if (file === undefined || isUNC(file)) {
|
|
1296
|
-
return [workspaceFolder, false];
|
|
1297
|
-
}
|
|
1298
|
-
// Don't probe for something in node modules folder.
|
|
1299
|
-
if (file.indexOf(`${path.sep}node_modules${path.sep}`) !== -1) {
|
|
1300
|
-
return [workspaceFolder, false];
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
let result: string = workspaceFolder;
|
|
1304
|
-
let flatConfig: boolean = false;
|
|
1305
|
-
let directory: string | undefined = path.dirname(file);
|
|
1306
|
-
outer: while (directory !== undefined && directory.startsWith(workspaceFolder)) {
|
|
1307
|
-
for (const { fileName, isRoot, isFlatConfig } of projectFolderIndicators) {
|
|
1308
|
-
if (fs.existsSync(path.join(directory, fileName))) {
|
|
1309
|
-
result = directory;
|
|
1310
|
-
flatConfig = isFlatConfig;
|
|
1311
|
-
if (isRoot) {
|
|
1312
|
-
break outer;
|
|
1313
|
-
} else {
|
|
1314
|
-
break;
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
const parent = path.dirname(directory);
|
|
1319
|
-
directory = parent !== directory ? parent : undefined;
|
|
1320
|
-
}
|
|
1321
|
-
return [result, flatConfig];
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
export namespace ErrorHandlers {
|
|
1325
|
-
|
|
1326
|
-
export const single: ((error: any, document: TextDocument, library: ESLintModule, settings: TextDocumentSettings) => Status | undefined)[] = [
|
|
1327
|
-
tryHandleNoConfig,
|
|
1328
|
-
tryHandleConfigError,
|
|
1329
|
-
tryHandleMissingModule,
|
|
1330
|
-
showErrorMessage
|
|
1331
|
-
];
|
|
1332
|
-
|
|
1333
|
-
export function getMessage(err: any, document: TextDocument): string {
|
|
1334
|
-
let result: string | undefined = undefined;
|
|
1335
|
-
if (typeof err.message === 'string' || err.message instanceof String) {
|
|
1336
|
-
result = <string>err.message;
|
|
1337
|
-
result = result.replace(/\r?\n/g, ' ');
|
|
1338
|
-
if (/^CLI: /.test(result)) {
|
|
1339
|
-
result = result.substr(5);
|
|
1340
|
-
}
|
|
1341
|
-
} else {
|
|
1342
|
-
result = `An unknown error occurred while validating document: ${document.uri}`;
|
|
1343
|
-
}
|
|
1344
|
-
return result;
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
const noConfigReported: Map<string, ESLintModule> = new Map<string, ESLintModule>();
|
|
1348
|
-
|
|
1349
|
-
export function clearNoConfigReported(): void {
|
|
1350
|
-
noConfigReported.clear();
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
function tryHandleNoConfig(error: any, document: TextDocument, library: ESLintModule): Status | undefined {
|
|
1354
|
-
if (!ESLintError.isNoConfigFound(error)) {
|
|
1355
|
-
return undefined;
|
|
1356
|
-
}
|
|
1357
|
-
if (!noConfigReported.has(document.uri)) {
|
|
1358
|
-
connection.sendRequest(
|
|
1359
|
-
NoConfigRequest.type,
|
|
1360
|
-
{
|
|
1361
|
-
message: getMessage(error, document),
|
|
1362
|
-
document: {
|
|
1363
|
-
uri: document.uri
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
).then(undefined, () => { });
|
|
1367
|
-
noConfigReported.set(document.uri, library);
|
|
1368
|
-
}
|
|
1369
|
-
return Status.warn;
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
const configErrorReported: Map<string, { library: ESLintModule; settings: TextDocumentSettings }> = new Map();
|
|
1373
|
-
|
|
1374
|
-
export function getConfigErrorReported(key: string): { library: ESLintModule; settings: TextDocumentSettings } | undefined {
|
|
1375
|
-
return configErrorReported.get(key);
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
export function removeConfigErrorReported(key: string): boolean {
|
|
1379
|
-
return configErrorReported.delete(key);
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
function tryHandleConfigError(error: any, document: TextDocument, library: ESLintModule, settings: TextDocumentSettings): Status | undefined {
|
|
1383
|
-
if (!error.message) {
|
|
1384
|
-
return undefined;
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
function handleFileName(filename: string): Status {
|
|
1388
|
-
if (!configErrorReported.has(filename)) {
|
|
1389
|
-
connection.console.error(getMessage(error, document));
|
|
1390
|
-
if (!documents.get(URI.file(filename).toString())) {
|
|
1391
|
-
connection.window.showInformationMessage(getMessage(error, document));
|
|
1392
|
-
}
|
|
1393
|
-
configErrorReported.set(filename, { library, settings });
|
|
1394
|
-
}
|
|
1395
|
-
return Status.warn;
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
let matches = /Cannot read config file:\s+(.*)\nError:\s+(.*)/.exec(error.message);
|
|
1399
|
-
if (matches && matches.length === 3) {
|
|
1400
|
-
return handleFileName(matches[1]);
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
matches = /(.*):\n\s*Configuration for rule \"(.*)\" is /.exec(error.message);
|
|
1404
|
-
if (matches && matches.length === 3) {
|
|
1405
|
-
return handleFileName(matches[1]);
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
matches = /Cannot find module '([^']*)'\nReferenced from:\s+(.*)/.exec(error.message);
|
|
1409
|
-
if (matches && matches.length === 3) {
|
|
1410
|
-
return handleFileName(matches[2]);
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
return undefined;
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
const missingModuleReported: Map<string, ESLintModule> = new Map<string, ESLintModule>();
|
|
1417
|
-
|
|
1418
|
-
export function clearMissingModuleReported(): void {
|
|
1419
|
-
missingModuleReported.clear();
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
function tryHandleMissingModule(error: any, document: TextDocument, library: ESLintModule, settings: TextDocumentSettings): Status | undefined {
|
|
1423
|
-
if (!error.message) {
|
|
1424
|
-
return undefined;
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
function handleMissingModule(plugin: string, module: string, error: ESLintError): Status {
|
|
1428
|
-
if (!missingModuleReported.has(plugin)) {
|
|
1429
|
-
const fsPath = inferFilePath(document, settings.useRealpaths);
|
|
1430
|
-
missingModuleReported.set(plugin, library);
|
|
1431
|
-
if (error.messageTemplate === 'plugin-missing') {
|
|
1432
|
-
connection.console.error([
|
|
1433
|
-
'',
|
|
1434
|
-
`${error.message.toString()}`,
|
|
1435
|
-
`Happened while validating ${fsPath ? fsPath : document.uri}`,
|
|
1436
|
-
`This can happen for a couple of reasons:`,
|
|
1437
|
-
`1. The plugin name is spelled incorrectly in an ESLint configuration file (e.g. .eslintrc).`,
|
|
1438
|
-
`2. If ESLint is installed globally, then make sure ${module} is installed globally as well.`,
|
|
1439
|
-
`3. If ESLint is installed locally, then ${module} isn't installed correctly.`,
|
|
1440
|
-
'',
|
|
1441
|
-
`Consider running eslint --debug ${fsPath ? fsPath : document.uri} from a terminal to obtain a trace about the configuration files used.`
|
|
1442
|
-
].join('\n'));
|
|
1443
|
-
} else {
|
|
1444
|
-
connection.console.error([
|
|
1445
|
-
`${error.message.toString()}`,
|
|
1446
|
-
`Happened while validating ${fsPath ? fsPath : document.uri}`
|
|
1447
|
-
].join('\n'));
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
return Status.warn;
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
const matches = /Failed to load plugin (.*): Cannot find module (.*)/.exec(error.message);
|
|
1454
|
-
if (matches && matches.length === 3) {
|
|
1455
|
-
return handleMissingModule(matches[1], matches[2], error);
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
return undefined;
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
function showErrorMessage(error: any, document: TextDocument): Status {
|
|
1462
|
-
if (Is.string(error.stack)) {
|
|
1463
|
-
connection.console.error('An unexpected error occurred:');
|
|
1464
|
-
connection.console.error(error.stack);
|
|
1465
|
-
} else {
|
|
1466
|
-
connection.console.error(`An unexpected error occurred: ${getMessage(error, document)}.`);
|
|
1467
|
-
}
|
|
1468
|
-
return Status.error;
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
}
|