Skip to content

Instantly share code, notes, and snippets.

@prescience-data
Last active August 27, 2022 01:52
Show Gist options
  • Save prescience-data/fa82f9b26e00cd74be8ea262b3048f08 to your computer and use it in GitHub Desktop.
Save prescience-data/fa82f9b26e00cd74be8ea262b3048f08 to your computer and use it in GitHub Desktop.
WebStorm ESLint 8 Plugin
export function containsString(src: string | null | undefined, toFind: string): boolean {
return src != null && src.indexOf(toFind) >= 0
}
export function normalizePath(eslintPackagePath: string | undefined): string | undefined {
if (eslintPackagePath === undefined) return undefined
if (eslintPackagePath.charAt(eslintPackagePath.length - 1) !== '/' &&
eslintPackagePath.charAt(eslintPackagePath.length - 1) !== '\\') {
eslintPackagePath = eslintPackagePath + '/';
}
return toUnixPathSeparators(eslintPackagePath);
}
export function toUnixPathSeparators(path: string) {
return path.split("\\").join("/");
}
export function requireInContext(modulePathToRequire: string, contextPath?: string): any {
const contextRequire = getContextRequire(contextPath);
return contextRequire(modulePathToRequire);
}
export function requireResolveInContext(modulePathToRequire: string, contextPath?: string): string {
const contextRequire = getContextRequire(contextPath);
return contextRequire.resolve(modulePathToRequire);
}
function getContextRequire(contextPath?: string): NodeRequire {
if (contextPath != null) {
const module = require('module')
if (typeof module.createRequire === 'function') {
// https://nodejs.org/api/module.html#module_module_createrequire_filename
// Implemented in Yarn PnP: https://next.yarnpkg.com/advanced/pnpapi/#requiremodule
return module.createRequire(contextPath);
}
// noinspection JSDeprecatedSymbols
if (typeof module.createRequireFromPath === 'function') {
// Use createRequireFromPath (a deprecated version of createRequire) to support Node.js 10.x
// noinspection JSDeprecatedSymbols
return module.createRequireFromPath(contextPath);
}
throw Error('Function module.createRequire is unavailable in Node.js ' + process.version +
', Node.js >= 12.2.0 is required')
}
return require;
}
import {EslintPluginState, ESLintRequest, ESLintResponse, FileKind, RequestArguments} from "./eslint-api"
import {containsString, normalizePath, requireInContext, requireResolveInContext} from "./eslint-common"
export class ESLint8Plugin implements LanguagePlugin {
private static readonly GetErrors: string = "GetErrors";
private static readonly FixErrors: string = "FixErrors";
private readonly includeSourceText: boolean | null;
private readonly additionalRulesDirectory?: string;
private readonly ESLint: any;
private readonly libOptions: any;
constructor(state: EslintPluginState) {
this.includeSourceText = state.includeSourceText;
this.additionalRulesDirectory = state.additionalRootDirectory;
let eslintPackagePath = normalizePath(state.eslintPackagePath);
this.ESLint = requireInContext(eslintPackagePath, state.packageJsonPath).ESLint;
try {
const apiJsPath = requireResolveInContext(eslintPackagePath, state.packageJsonPath);
this.libOptions = requireInContext("../lib/options" /* path relative to eslint/lib/api.js */, apiJsPath);
}
catch (e) {
this.libOptions = null;
}
}
async onMessage(p: string, writer: MessageWriter) {
const request: ESLintRequest = JSON.parse(p);
let response: ESLintResponse = new ESLintResponse(request.seq, request.command);
try {
if (request.command === ESLint8Plugin.GetErrors) {
let lintResults: ESLint.LintResult[] = await this.getErrors(request.arguments);
response.body = {results: this.filterSourceIfNeeded(lintResults)};
}
else if (request.command === ESLint8Plugin.FixErrors) {
let lintResults: ESLint.LintResult[] = await this.fixErrors(request.arguments);
response.body = {results: this.filterSourceIfNeeded(lintResults)};
}
else {
response.error = `Unknown command: ${request.command}`
}
}
catch (e) {
response.isNoConfigFile = "no-config-found" === e.messageTemplate
|| (e.message && containsString(e.message.toString(), "No ESLint configuration found"));
response.error = e.toString() + "\n\n" + e.stack;
}
writer.write(JSON.stringify(response));
}
private filterSourceIfNeeded(results: ESLint.LintResult[]): ESLint.LintResult[] {
if (!this.includeSourceText) {
results.forEach(value => {
delete value.source
value.messages.forEach(msg => delete msg.source)
})
}
return results
}
private async getErrors(getErrorsArguments: RequestArguments): Promise<ESLint.LintResult[]> {
return this.invokeESLint(getErrorsArguments)
}
private async fixErrors(fixErrorsArguments: RequestArguments): Promise<ESLint.LintResult[]> {
return this.invokeESLint(fixErrorsArguments, {fix: true})
}
private async invokeESLint(requestArguments: RequestArguments, additionalOptions: ESLint.Options = {}): Promise<ESLint.LintResult[]> {
const parsedCommandLineOptions =
this.libOptions != null
? translateOptions(this.libOptions.parse(requestArguments.extraOptions || ""))
: {};
const options: ESLint.Options = {...parsedCommandLineOptions, ...additionalOptions};
options.ignorePath = requestArguments.ignoreFilePath;
if (requestArguments.configPath != null) {
options.overrideConfigFile = requestArguments.configPath;
}
if (this.additionalRulesDirectory != null && this.additionalRulesDirectory.length > 0) {
if (options.rulePaths == null) {
options.rulePaths = [this.additionalRulesDirectory]
}
else {
options.rulePaths.push(this.additionalRulesDirectory);
}
}
const eslint = new this.ESLint(options);
if (requestArguments.fileKind === FileKind.html) {
const config: any = await eslint.calculateConfigForFile(requestArguments.fileName);
const plugins: string[] | null | undefined = config.plugins;
if (!Array.isArray(plugins) || !plugins.includes("html")) {
return [];
}
}
if (await eslint.isPathIgnored(requestArguments.fileName)) {
return [];
}
return eslint.lintText(requestArguments.content, {filePath: requestArguments.fileName});
}
}
// See https://github.com/eslint/eslint/blob/f1b7499a5162d3be918328ce496eb80692353a5a/lib/cli.js#L62
/**
* Translates the CLI options into the options expected by the CLIEngine.
* @param {ParsedCLIOptions} cliOptions The CLI options to translate.
* @returns {ESLintOptions} The options object for the CLIEngine.
* @private
*/
function translateOptions({
cache,
cacheFile,
cacheLocation,
cacheStrategy,
config,
env,
errorOnUnmatchedPattern,
eslintrc,
ext,
fix,
fixDryRun,
fixType,
global,
ignore,
ignorePath,
ignorePattern,
inlineConfig,
parser,
parserOptions,
plugin,
quiet,
reportUnusedDisableDirectives,
resolvePluginsRelativeTo,
rule,
rulesdir
}) {
return {
allowInlineConfig: inlineConfig,
cache,
cacheLocation: cacheLocation || cacheFile,
cacheStrategy,
errorOnUnmatchedPattern,
extensions: ext,
// fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true),
fixTypes: fixType,
ignore,
ignorePath,
overrideConfig: {
env: env && env.reduce((obj, name) => {
obj[name] = true;
return obj;
}, {}),
globals: global && global.reduce((obj, name) => {
if (name.endsWith(":true")) {
obj[name.slice(0, -5)] = "writable";
} else {
obj[name] = "readonly";
}
return obj;
}, {}),
ignorePatterns: ignorePattern,
parser,
parserOptions,
plugins: plugin,
rules: rule
},
overrideConfigFile: config,
reportUnusedDisableDirectives: reportUnusedDisableDirectives ? "error" : void 0,
resolvePluginsRelativeTo,
rulePaths: rulesdir,
useEslintrc: eslintrc
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment