@swissquote/eslint-plugin-swissquote
Version:
- Provide a style guide for your JavaScript - Warns on common mistakes in JavaScript - Works with EcmaScript 2015+
240 lines (209 loc) • 8.16 kB
JavaScript
/**
* This was extracted from https://github.com/prettier/eslint-plugin-prettier/releases/tag/v4.2.1
*
* This is a clone because the version of prettier isn't configurable and
* we need to be able to select which implementation to load
*/
/**
* @file Runs `prettier` as an ESLint rule.
* @author Andres Suarez
*/
// @ts-check
/**
* @typedef {import('eslint').AST.Range} Range
* @typedef {import('eslint').AST.SourceLocation} SourceLocation
* @typedef {import('eslint').ESLint.Plugin} Plugin
* @typedef {import('prettier').FileInfoOptions} FileInfoOptions
* @typedef {import('prettier').Options & {
* onDiskFilepath: string,
* parserPath: string,
* mode: string,
* }} Options
*/
// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------
const {
showInvisibles,
generateDifferences
} = require("../../dist/prettier-linter-helpers/index.js");
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
const { INSERT, DELETE, REPLACE } = generateDifferences;
// ------------------------------------------------------------------------------
// Privates
// ------------------------------------------------------------------------------
// Lazily-loaded Prettier.
/**
* @type {(source: string, options: Options, fileInfoOptions: FileInfoOptions) => string}
*/
let prettierFormat;
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
/**
* Reports a difference.
*
* @param {import('eslint').Rule.RuleContext} context - The ESLint rule context.
* @param {import('prettier-linter-helpers').Difference} difference - The difference object.
* @returns {void}
*/
function reportDifference(context, difference) {
const { operation, offset, deleteText = "", insertText = "" } = difference;
const range = /** @type {Range} */ ([offset, offset + deleteText.length]);
// `context.getSourceCode()` was deprecated in ESLint v8.40.0 and replaced
// with the `sourceCode` property.
// TODO: Only use property when our eslint peerDependency is >=8.40.0.
const [start, end] = range.map(index =>
(context.sourceCode ?? context.getSourceCode()).getLocFromIndex(index)
);
context.report({
messageId: operation,
data: {
deleteText: showInvisibles(deleteText),
insertText: showInvisibles(insertText)
},
loc: { start, end },
fix: fixer => fixer.replaceTextRange(range, insertText)
});
}
// ------------------------------------------------------------------------------
// Module Definition
// ------------------------------------------------------------------------------
/**
* @type {Plugin}
*/
const eslintPluginPrettier = {
configs: {
recommended: {
extends: ["prettier"],
plugins: ["prettier"],
rules: {
"prettier/prettier": "error",
"arrow-body-style": "off",
"prefer-arrow-callback": "off"
}
}
},
rules: {
prettier: {
meta: {
docs: {
url: "https://github.com/prettier/eslint-plugin-prettier#options"
},
type: "layout",
fixable: "code",
schema: [
// Prettier options:
{
type: "object",
properties: {},
additionalProperties: true
}
],
messages: {
[INSERT]: "Insert `{{ insertText }}`",
[DELETE]: "Delete `{{ deleteText }}`",
[REPLACE]: "Replace `{{ deleteText }}` with `{{ insertText }}`"
}
},
create(context) {
/**
* @type {FileInfoOptions}
*/
const fileInfoOptions =
(context.options[1] && context.options[1].fileInfoOptions) || {};
// `context.getSourceCode()` was deprecated in ESLint v8.40.0 and replaced
// with the `sourceCode` property.
// TODO: Only use property when our eslint peerDependency is >=8.40.0.
const sourceCode = context.sourceCode ?? context.getSourceCode();
// `context.getFilename()` was deprecated in ESLint v8.40.0 and replaced
// with the `filename` property.
// TODO: Only use property when our eslint peerDependency is >=8.40.0.
const filepath = context.filename ?? context.getFilename();
// Processors that extract content from a file, such as the markdown
// plugin extracting fenced code blocks may choose to specify virtual
// file paths. If this is the case then we need to resolve prettier
// config and file info using the on-disk path instead of the virtual
// path.
// `context.getPhysicalFilename()` was deprecated in ESLint v8.40.0 and replaced
// with the `physicalFilename` property.
// TODO: Only use property when our eslint peerDependency is >=8.40.0.
const onDiskFilepath =
context.physicalFilename ?? context.getPhysicalFilename();
const source = sourceCode.text;
return {
Program() {
/**
* @type {{}}
*/
const eslintPrettierOptions = context.options[0] || {};
const mode =
(context.settings && context.settings["formatting/mode"]) ||
"prettier:1";
if (!prettierFormat) {
// Prettier is expensive to load, so only load it if needed.
prettierFormat = require("../../packages/synckit.js").createSyncFn(
require.resolve("./worker.js")
);
}
// prettier.format() may throw a SyntaxError if it cannot parse the
// source code it is given. Usually for JS files this isn't a
// problem as ESLint will report invalid syntax before trying to
// pass it to the prettier plugin. However this might be a problem
// for non-JS languages that are handled by a plugin. Notably Vue
// files throw an error if they contain unclosed elements, such as
// `<template><div></template>. In this case report an error at the
// point at which parsing failed.
/**
* @type {string}
*/
let prettierSource;
try {
prettierSource = prettierFormat(
source,
{
...eslintPrettierOptions,
mode,
filepath,
onDiskFilepath,
parserPath: context.parserPath
},
fileInfoOptions
);
} catch (err) {
if (!(err instanceof SyntaxError)) {
throw err;
}
let message = `Parsing error: ${err.message}`;
const error = /** @type {SyntaxError & {codeFrame: string; loc: SourceLocation}} */ (err);
// Prettier's message contains a codeframe style preview of the
// invalid code and the line/column at which the error occurred.
// ESLint shows those pieces of information elsewhere already so
// remove them from the message
if (error.codeFrame) {
message = message.replace(`\n${error.codeFrame}`, "");
}
if (error.loc) {
message = message.replace(/ \(\d+:\d+\)$/, "");
}
context.report({ message, loc: error.loc });
return;
}
if (prettierSource == null) {
return;
}
if (source !== prettierSource) {
const differences = generateDifferences(source, prettierSource);
for (const difference of differences) {
reportDifference(context, difference);
}
}
}
};
}
}
}
};
module.exports = eslintPluginPrettier;