prettier与eslint

365 阅读2分钟

一,介绍

前端的自动化代码规范工具

  • JavaScript/TypeScript 规范。主流的 Lint 工具包括 Eslint、Prettier;
  • 样式开发规范。主流的 Lint 工具包括Stylelint、Prettier;
  • Git 提交规范。主流的 Lint 工具包括Commitlint。

prettier

用于代码格式校验,一般用于格式化

eslint

用于代码格式和质量的校验

二,配置

prettier

在scripts里面一般执配置脚本代码检查 "format":"prettier —write ."

// .prettierrc
{
  "singleQuote": true
}

eslint

在scripts里面一般执配置脚本格式化 "lint":"eslint --fix ."

// .eslintrc.js
{
  parser:'@typescript-elsint/parser',// ESLint底层默认使用ESpree来进行ast解析,espree是基于acorn实现的
  plugins:[],// 扩展规则
  extends:[],// 继承另一份配置
  rules: {   // 具体规则
    quotes: "off",
    "no-unused-vars": "error",
  }
}

三,原理

  • 1、 首先禁掉 ESLint/插件 中与 Prettier 冲突的规则,创建一个包 eslint-config-prettier,里面定义了被禁掉的 ESLint/插件 规则。

    没有创造新的规则,它只是关闭了 ESLint 中一些不必要的规则以及可能与 Prettier 冲突的规则

  • 2、 创建一个插件 eslint-plugin-prettier,定义一条规则 prettier/prettier,调用 Prettier,配合 ESLint 实现运行 eslint --fix 按 Prettier 规则自动格式化代码

    我们看下eslint-plugin-prettier的代码,以 ESLint 规则的方式运行 Prettier,通过 Prettier 找出格式化前后的差异,并以 ESLint 问题的方式报告差异,同时针对不同类型的差异提供不同的 ESLint fixer


const {
  showInvisibles,
  generateDifferences,
} = require('prettier-linter-helpers');
const { name, version } = require('./package.json');
const { INSERT, DELETE, REPLACE } = generateDifferences;

let prettierFormat;

function reportDifference(context, difference) {
  const { operation, offset, deleteText = '', insertText = '' } = difference;
  const range = /** @type {Range} */ ([offset, offset + deleteText.length]);
  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),
  });
}


const eslintPluginPrettier = {
  meta: { name, version },
  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 fileInfoOptions =
          (context.options[1] && context.options[1].fileInfoOptions) || {};
        const sourceCode = context.sourceCode ?? context.getSourceCode();
        const filepath = context.filename ?? context.getFilename();
        const onDiskFilepath =
          context.physicalFilename ?? context.getPhysicalFilename();
        const source = sourceCode.text;

        return {
          Program() {
            if (!prettierFormat) {
              // Prettier is expensive to load, so only load it if needed.
              prettierFormat = require('synckit').createSyncFn(
                require.resolve('./worker'),
              );
            }

            const eslintPrettierOptions = context.options[0] || {};

            const parser = context.languageOptions?.parser;
            let prettierSource;
            try {
              prettierSource = prettierFormat(
                source,
                {
                  ...eslintPrettierOptions,
                  filepath,
                  onDiskFilepath,
                  parserMeta:
                    parser &&
                    (parser.meta ?? {
                      name: parser.name,
                      version: parser.version,
                    }),
                  parserPath: context.parserPath,
                  usePrettierrc,
                },
                fileInfoOptions,
              );
            } catch (err) {
              if (!(err instanceof SyntaxError)) {
                throw err;
              }

              let message = 'Parsing error: ' + err.message;

              const error =
                /** @type {SyntaxError & {codeFrame: string; loc: SourceLocation}} */ (
                  err
                );
              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;

四,配置步骤

npm安装 prettier 、eslint-config-prettier、 eslint-plugin-prettier,配置 .eslintrc.js

eslint-plugin-prettier 提供了这样一个配置 plugin:prettier/recommended多了rules规则,分离出 pluginName,它就是 plugin: 和最后一个 / 的之间部分

{
  extends: [
    ..., // 其他
    "prettier", // eslint-config-prettier
  ]
  plugins: ["prettier"], // eslint-plugin-prettier
  rules: {
    "prettier/prettier": "error" // 开启规则
  }
}

推荐文章

欢迎关注我的前端自检清单,我和你一起成长