Permalink
Cannot retrieve contributors at this time
277 lines (246 sloc)
9.71 KB
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
codeql-action/node_modules/eslint-plugin-prettier/eslint-plugin-prettier.js
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
/** | |
* @fileoverview Runs `prettier` as an ESLint rule. | |
* @author Andres Suarez | |
*/ | |
'use strict'; | |
// ------------------------------------------------------------------------------ | |
// Requirements | |
// ------------------------------------------------------------------------------ | |
const fs = require('fs'); | |
const path = require('path'); | |
const { | |
showInvisibles, | |
generateDifferences | |
} = require('prettier-linter-helpers'); | |
// ------------------------------------------------------------------------------ | |
// Constants | |
// ------------------------------------------------------------------------------ | |
const { INSERT, DELETE, REPLACE } = generateDifferences; | |
// ------------------------------------------------------------------------------ | |
// Privates | |
// ------------------------------------------------------------------------------ | |
// Lazily-loaded Prettier. | |
/** | |
* @type {import('prettier')} | |
*/ | |
let prettier; | |
// ------------------------------------------------------------------------------ | |
// 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 = [offset, offset + deleteText.length]; | |
const [start, end] = range.map(index => | |
context.getSourceCode().getLocFromIndex(index) | |
); | |
context.report({ | |
messageId: operation, | |
data: { | |
deleteText: showInvisibles(deleteText), | |
insertText: showInvisibles(insertText) | |
}, | |
loc: { start, end }, | |
fix: fixer => fixer.replaceTextRange(range, insertText) | |
}); | |
} | |
/** | |
* Given a filepath, get the nearest path that is a regular file. | |
* The filepath provided by eslint may be a virtual filepath rather than a file | |
* on disk. This attempts to transform a virtual path into an on-disk path | |
* @param {string} filepath | |
* @returns {string} | |
*/ | |
function getOnDiskFilepath(filepath) { | |
try { | |
if (fs.statSync(filepath).isFile()) { | |
return filepath; | |
} | |
} catch (err) { | |
// https://github.com/eslint/eslint/issues/11989 | |
if (err.code === 'ENOTDIR') { | |
return getOnDiskFilepath(path.dirname(filepath)); | |
} | |
} | |
return filepath; | |
} | |
// ------------------------------------------------------------------------------ | |
// Module Definition | |
// ------------------------------------------------------------------------------ | |
module.exports = { | |
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 | |
}, | |
{ | |
type: 'object', | |
properties: { | |
usePrettierrc: { type: 'boolean' }, | |
fileInfoOptions: { | |
type: 'object', | |
properties: {}, | |
additionalProperties: true | |
} | |
}, | |
additionalProperties: true | |
} | |
], | |
messages: { | |
[INSERT]: 'Insert `{{ insertText }}`', | |
[DELETE]: 'Delete `{{ deleteText }}`', | |
[REPLACE]: 'Replace `{{ deleteText }}` with `{{ insertText }}`' | |
} | |
}, | |
create(context) { | |
const usePrettierrc = | |
!context.options[1] || context.options[1].usePrettierrc !== false; | |
const eslintFileInfoOptions = | |
(context.options[1] && context.options[1].fileInfoOptions) || {}; | |
const sourceCode = context.getSourceCode(); | |
const filepath = 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. | |
// See https://github.com/eslint/eslint/issues/11989 for ideas around | |
// being able to get this value directly from eslint in the future. | |
const onDiskFilepath = getOnDiskFilepath(filepath); | |
const source = sourceCode.text; | |
return { | |
Program() { | |
if (!prettier) { | |
// Prettier is expensive to load, so only load it if needed. | |
prettier = require('prettier'); | |
} | |
const eslintPrettierOptions = context.options[0] || {}; | |
const prettierRcOptions = usePrettierrc | |
? prettier.resolveConfig.sync(onDiskFilepath, { | |
editorconfig: true | |
}) | |
: null; | |
const prettierFileInfo = prettier.getFileInfo.sync( | |
onDiskFilepath, | |
Object.assign( | |
{}, | |
{ resolveConfig: true, ignorePath: '.prettierignore' }, | |
eslintFileInfoOptions | |
) | |
); | |
// Skip if file is ignored using a .prettierignore file | |
if (prettierFileInfo.ignored) { | |
return; | |
} | |
const initialOptions = {}; | |
// ESLint supports processors that let you extract and lint JS | |
// fragments within a non-JS language. In the cases where prettier | |
// supports the same language as a processor, we want to process | |
// the provided source code as javascript (as ESLint provides the | |
// rules with fragments of JS) instead of guessing the parser | |
// based off the filename. Otherwise, for instance, on a .md file we | |
// end up trying to run prettier over a fragment of JS using the | |
// markdown parser, which throws an error. | |
// Processors may set virtual filenames for these extracted blocks. | |
// If they do so then we want to trust the file extension they | |
// provide, and no override is needed. | |
// If the processor does not set any virtual filename (signified by | |
// `filepath` and `onDiskFilepath` being equal) AND we can't | |
// infer the parser from the filename, either because no filename | |
// was provided or because there is no parser found for the | |
// filename, use javascript. | |
// This is added to the options first, so that | |
// prettierRcOptions and eslintPrettierOptions can still override | |
// the parser. | |
// | |
// `parserBlocklist` should contain the list of prettier parser | |
// names for file types where: | |
// * Prettier supports parsing the file type | |
// * There is an ESLint processor that extracts JavaScript snippets | |
// from the file type. | |
const parserBlocklist = [null, 'graphql', 'markdown', 'html']; | |
if ( | |
filepath === onDiskFilepath && | |
parserBlocklist.indexOf(prettierFileInfo.inferredParser) !== -1 | |
) { | |
// Prettier v1.16.0 renamed the `babylon` parser to `babel` | |
// Use the modern name if available | |
const supportBabelParser = prettier | |
.getSupportInfo() | |
.languages.some(language => language.parsers.includes('babel')); | |
initialOptions.parser = supportBabelParser ? 'babel' : 'babylon'; | |
} | |
const prettierOptions = Object.assign( | |
{}, | |
initialOptions, | |
prettierRcOptions, | |
eslintPrettierOptions, | |
{ filepath } | |
); | |
// 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. | |
let prettierSource; | |
try { | |
prettierSource = prettier.format(source, prettierOptions); | |
} catch (err) { | |
if (!(err instanceof SyntaxError)) { | |
throw err; | |
} | |
let message = 'Parsing error: ' + err.message; | |
// 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 (err.codeFrame) { | |
message = message.replace(`\n${err.codeFrame}`, ''); | |
} | |
if (err.loc) { | |
message = message.replace(/ \(\d+:\d+\)$/, ''); | |
} | |
context.report({ message, loc: err.loc }); | |
return; | |
} | |
if (source !== prettierSource) { | |
const differences = generateDifferences(source, prettierSource); | |
for (const difference of differences) { | |
reportDifference(context, difference); | |
} | |
} | |
} | |
}; | |
} | |
} | |
} | |
}; |