| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 | /** * @file Runs `prettier` as an ESLint rule. * @author Andres Suarez */'use strict';// ------------------------------------------------------------------------------//  Requirements// ------------------------------------------------------------------------------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),  });}// ------------------------------------------------------------------------------//  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.        const onDiskFilepath = context.getPhysicalFilename();        const source = sourceCode.text;        return {          // eslint-disable-next-line sonarjs/cognitive-complexity          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 { ignored, inferredParser } = prettier.getFileInfo.sync(              onDiskFilepath,              {                resolveConfig: false,                withNodeModules: false,                ignorePath: '.prettierignore',                plugins: prettierRcOptions ? prettierRcOptions.plugins : null,                ...eslintFileInfoOptions,              },            );            // Skip if file is ignored using a .prettierignore file            if (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.            if (filepath === onDiskFilepath) {              // The following list means the plugin process source into js content              // but with same filename, so we need to change the parser to `babel`              // by default.              // Related ESLint plugins are:              // 1. `eslint-plugin-graphql` (replacement: `@graphql-eslint/eslint-plugin`)              // 2. `eslint-plugin-html`              // 3. `eslint-plugin-markdown@1` (replacement: `eslint-plugin-markdown@2+`)              // 4. `eslint-plugin-svelte3` (replacement: `eslint-plugin-svelte@2+`)              const parserBlocklist = [null, 'markdown', 'html'];              let inferParserToBabel = parserBlocklist.includes(inferredParser);              switch (inferredParser) {                // it could be processed by `@graphql-eslint/eslint-plugin` or `eslint-plugin-graphql`                case 'graphql': {                  if (                    // for `eslint-plugin-graphql`, see https://github.com/apollographql/eslint-plugin-graphql/blob/master/src/index.js#L416                    source.startsWith('ESLintPluginGraphQLFile`')                  ) {                    inferParserToBabel = true;                  }                  break;                }                // it could be processed by `@ota-meshi/eslint-plugin-svelte`, `eslint-plugin-svelte` or `eslint-plugin-svelte3`                case 'svelte': {                  // The `source` would be modified by `eslint-plugin-svelte3`                  if (!context.parserPath.includes('svelte-eslint-parser')) {                    // We do not support `eslint-plugin-svelte3`,                    // the users should run `prettier` on `.svelte` files manually                    return;                  }                }              }              if (inferParserToBabel) {                initialOptions.parser = 'babel';              }            } else {              // Similar to https://github.com/prettier/stylelint-prettier/pull/22              // In all of the following cases ESLint extracts a part of a file to              // be formatted and there exists a prettier parser for the whole file.              // If you're interested in prettier you'll want a fully formatted file so              // you're about to run prettier over the whole file anyway.              // Therefore running prettier over just the style section is wasteful, so              // skip it.              const parserBlocklist = [                'babel',                'babylon',                'flow',                'typescript',                'vue',                'markdown',                'html',                'mdx',                'angular',                'svelte',              ];              if (parserBlocklist.includes(inferredParser)) {                return;              }            }            const prettierOptions = {              ...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);              }            }          },        };      },    },  },};
 |