Skip to content

Commit

Permalink
feat!: bump eslint, node and prettier versions, add types support (#508)
Browse files Browse the repository at this point in the history
JounQin authored Sep 2, 2022
1 parent 21d87ab commit 910aeb6
Showing 18 changed files with 4,559 additions and 3,155 deletions.
2 changes: 1 addition & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://unpkg.com/@changesets/config@1.6.0/schema.json",
"$schema": "https://unpkg.com/@changesets/config/schema.json",
"changelog": [
"@changesets/changelog-github",
{
5 changes: 5 additions & 0 deletions .changeset/old-plants-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'eslint-plugin-prettier': major
---

feat!: bump peer eslint to ">=8.0.0" and node to "^14.18.0 || >=16.0.0"
5 changes: 5 additions & 0 deletions .changeset/orange-eyes-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-prettier": major
---

feat!: upgrade to prettier v3
8 changes: 8 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mode": "pre",
"tag": "alpha",
"initialVersions": {
"eslint-plugin-prettier": "4.2.2"
},
"changesets": []
}
5 changes: 5 additions & 0 deletions .changeset/quiet-cups-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-prettier": minor
---

feat: add typings support
6 changes: 5 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -7,6 +7,9 @@
"@1stg",
"plugin:eslint-plugin/recommended"
],
"parserOptions": {
"project": null
},
"rules": {
"eslint-plugin/report-message-format": [
"error",
@@ -28,7 +31,8 @@
{
"files": "test/*.js",
"rules": {
"no-magic-numbers": "off"
"no-magic-numbers": "off",
"unicorn/prefer-top-level-await": "off"
}
}
]
4 changes: 2 additions & 2 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
github:
- JounQin
- 1stG
- rxts
- unts
- rx-ts
- un-ts
patreon: 1stG
open_collective: prettier
custom:
12 changes: 3 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -8,17 +8,14 @@ on:

jobs:
ci:
name: 'Test: Node ${{ matrix.node-version }} - ESLint ${{ matrix.eslint-version }}'
name: Lint and Test on Node ${{ matrix.node-version }}
runs-on: ubuntu-latest
strategy:
matrix:
eslint-version:
- 7
- 8
node-version:
- 12
- 14
- 16
- 18

steps:
- uses: actions/checkout@v3
@@ -29,11 +26,8 @@ jobs:
node-version: ${{ matrix.node-version }}
cache: yarn

- name: Use ESLint ${{ matrix.eslint-version }}
run: yarn upgrade eslint@${{ matrix.eslint-version }} --ignore-engines

- name: Install
run: yarn --frozen-lockfile --ignore-engines
run: yarn --frozen-lockfile

- name: Test
run: yarn mocha
2 changes: 1 addition & 1 deletion .remarkrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"plugins": [
"@1stg/remark-config"
"@1stg/preset"
]
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -139,7 +139,7 @@ If you’re fixing large of amounts of previously unformatted code, consider tem

- An object with the following options

- `usePrettierrc`: Enables loading of the Prettier configuration file, (default: `true`). May be useful if you are using multiple tools that conflict with each other, or do not wish to mix your ESLint settings with your Prettier configuration.
- `usePrettierrc`: Enables loading of the Prettier configuration file, (default: `true`). May be useful if you are using multiple tools that conflict with each other, or do not wish to mix your ESLint settings with your Prettier configuration. And also, it is possible to run prettier without loading the prettierrc config file [via the CLI's --no-config option](https://prettier.io/docs/en/cli.html#--no-config) or through the API by [calling prettier.format() without passing through the options generated by calling resolveConfig](https://prettier.io/docs/en/api.html#prettierresolveconfigfilepath--options).

```json
{
5 changes: 5 additions & 0 deletions eslint-plugin-prettier.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ESLint } from 'eslint';

declare const eslintPluginPrettier: ESLint.Plugin;

export = eslintPluginPrettier;
189 changes: 57 additions & 132 deletions eslint-plugin-prettier.js
Original file line number Diff line number Diff line change
@@ -3,6 +3,16 @@
* @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, usePrettierrc?: boolean }} Options
*/

'use strict';

// ------------------------------------------------------------------------------
@@ -26,9 +36,9 @@ const { INSERT, DELETE, REPLACE } = generateDifferences;

// Lazily-loaded Prettier.
/**
* @type {import('prettier')}
* @type {(source: string, options: Options, fileInfoOptions: FileInfoOptions) => string}
*/
let prettier;
let prettierFormat;

// ------------------------------------------------------------------------------
// Rule Definition
@@ -43,7 +53,7 @@ let prettier;
*/
function reportDifference(context, difference) {
const { operation, offset, deleteText = '', insertText = '' } = difference;
const range = [offset, offset + deleteText.length];
const range = /** @type {Range} */ ([offset, offset + deleteText.length]);
const [start, end] = range.map(index =>
context.getSourceCode().getLocFromIndex(index),
);
@@ -63,7 +73,10 @@ function reportDifference(context, difference) {
// Module Definition
// ------------------------------------------------------------------------------

module.exports = {
/**
* @type {Plugin}
*/
const eslintPluginPrettier = {
configs: {
recommended: {
extends: ['prettier'],
@@ -112,7 +125,10 @@ module.exports = {
create(context) {
const usePrettierrc =
!context.options[1] || context.options[1].usePrettierrc !== false;
const eslintFileInfoOptions =
/**
* @type {FileInfoOptions}
*/
const fileInfoOptions =
(context.options[1] && context.options[1].fileInfoOptions) || {};
const sourceCode = context.getSourceCode();
const filepath = context.getFilename();
@@ -125,134 +141,19 @@ module.exports = {
const source = sourceCode.text;

return {
// eslint-disable-next-line sonarjs/cognitive-complexity
Program() {
if (!prettier) {
if (!prettierFormat) {
// Prettier is expensive to load, so only load it if needed.
prettier = require('prettier');
prettierFormat = require('synckit').createSyncFn(
require.resolve('./worker'),
);
}

/**
* @type {{}}
*/
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
@@ -261,29 +162,51 @@ module.exports = {
// 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 = prettier.format(source, prettierOptions);
prettierSource = prettierFormat(
source,
{
...eslintPrettierOptions,
filepath,
onDiskFilepath,
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
);

// 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 (error.codeFrame) {
message = message.replace(`\n${error.codeFrame}`, '');
}
if (err.loc) {
if (error.loc) {
message = message.replace(/ \(\d+:\d+\)$/, '');
}

context.report({ message, loc: err.loc });
context.report({ message, loc: error.loc });

return;
}

if (prettierSource == null) {
return;
}

@@ -300,3 +223,5 @@ module.exports = {
},
},
};

module.exports = eslintPluginPrettier;
86 changes: 31 additions & 55 deletions package.json
Original file line number Diff line number Diff line change
@@ -4,17 +4,20 @@
"description": "Runs prettier as an eslint rule",
"repository": "git+https://github.com/prettier/eslint-plugin-prettier.git",
"homepage": "https://github.com/prettier/eslint-plugin-prettier#readme",
"funding": "https://opencollective.com/prettier",
"author": "Teddy Katz",
"contributors": [
"JounQin (https://github.com/JounQin) <admin@1stg.me>"
],
"funding": "https://opencollective.com/prettier",
"license": "MIT",
"packageManager": "yarn@1.22.19",
"engines": {
"node": ">=12.0.0"
"node": "^14.18.0 || >=16.0.0"
},
"main": "eslint-plugin-prettier.js",
"types": "eslint-plugin-prettier.d.ts",
"files": [
"eslint-plugin-prettier.d.ts",
"eslint-plugin-prettier.js"
],
"keywords": [
@@ -26,16 +29,20 @@
"scripts": {
"format": "yarn prettier '**/*.{js,json,md,yml}' --write && yarn lint --fix",
"lint": "eslint . --cache -f friendly --max-warnings 10",
"prepare": "patch-package && simple-git-hooks && yarn-deduplicate --strategy fewer || exit 0",
"prepare": "simple-git-hooks && yarn-deduplicate --strategy fewer || exit 0",
"prerelease": "yarn format && yarn test",
"release": "changeset publish",
"test": "yarn lint && mocha"
},
"peerDependencies": {
"eslint": ">=7.28.0",
"prettier": ">=2.0.0"
"@types/eslint": ">=8.0.0",
"eslint": ">=8.0.0",
"prettier": ">=3.0.0"
},
"peerDependenciesMeta": {
"@types/eslint": {
"optional": true
},
"eslint-config-prettier": {
"optional": true
}
@@ -44,60 +51,29 @@
"prettier-linter-helpers": "^1.0.0"
},
"devDependencies": {
"@1stg/common-config": "~3.0.0",
"@1stg/eslint-config": "~3.0.0",
"@changesets/changelog-github": "^0.4.5",
"@changesets/cli": "^2.23.1",
"@graphql-eslint/eslint-plugin": "^2.5.0",
"@typescript-eslint/parser": "^5.30.5",
"@1stg/common-config": "^7.1.1",
"@changesets/changelog-github": "^0.4.6",
"@changesets/cli": "^2.24.4",
"@graphql-eslint/eslint-plugin": "^3.10.7",
"@types/eslint": "^8.4.6",
"@types/prettier": "^2.7.0",
"@types/prettier-linter-helpers": "^1.0.1",
"@typescript-eslint/parser": "^5.36.1",
"eslint-config-prettier": "^8.5.0",
"eslint-mdx": "^1.17.1",
"eslint-plugin-eslint-plugin": "^4.4.0",
"eslint-plugin-mdx": "^1.17.1",
"eslint-mdx": "^2.0.2",
"eslint-plugin-eslint-plugin": "^5.0.6",
"eslint-plugin-mdx": "^2.0.2",
"eslint-plugin-self": "^1.2.1",
"eslint-plugin-svelte": "~2.1.0",
"eslint-plugin-utils": "^0.1.0",
"graphql": "^16.5.0",
"mocha": "^9.2.2",
"patch-package": "^6.4.7",
"eslint-plugin-svelte": "^2.7.0",
"eslint-plugin-svelte3": "^4.0.0",
"graphql": "^16.6.0",
"mocha": "^10.0.0",
"svelte": "^3.49.0",
"vue-eslint-parser": "^8.3.0"
"vue-eslint-parser": "^9.0.3",
"yarn-deduplicate": "^6.0.0"
},
"resolutions": {
"@babel/traverse": "^7.18.5",
"eslint-plugin-prettier": "link:.",
"prettier": "^2.7.1"
},
"donate": {
"recipients": [
{
"name": "unts",
"platform": "opencollective",
"address": "https://opencollective.com/unts",
"weight": 60
},
{
"name": "rxts",
"platform": "opencollective",
"address": "https://opencollective.com/rxts",
"weight": 20
},
{
"name": "1stG",
"email": "i@1stg.me",
"weight": 20,
"platforms": [
{
"platform": "opencollective",
"address": "https://opencollective.com/1stG"
},
{
"platform": "patreon",
"address": "https://www.patreon.com/1stG"
}
]
}
]
},
"packageManager": "yarn@1.22.19"
"prettier": "^3.0.0-alpha.0"
}
}
13 changes: 0 additions & 13 deletions patches/@1stg+lint-staged+1.7.5.patch

This file was deleted.

21 changes: 0 additions & 21 deletions patches/@pkgr+utils+2.2.0.patch

This file was deleted.

21 changes: 12 additions & 9 deletions test/prettier.js
Original file line number Diff line number Diff line change
@@ -14,9 +14,9 @@
// Requirements
// ------------------------------------------------------------------------------

const assert = require('assert');
const fs = require('fs');
const path = require('path');
const assert = require('node:assert');
const fs = require('node:fs');
const path = require('node:path');

const { ESLint, RuleTester } = require('eslint');

@@ -31,7 +31,7 @@ const rule = eslintPluginPrettier.rules.prettier;
const eslint = new ESLint({
baseConfig: {
parserOptions: {
ecmaVersion: 2021,
ecmaVersion: 'latest',
ecmaFeatures: {
jsx: true,
},
@@ -184,7 +184,9 @@ atGraphqlEslintRuleTester.run('@graphql-eslint/eslint-plugin', rule, {
// `newRuleTester({processor: require('eslint-plugin-graphql').processor['.graphql']})
// and then pass in pure graphql into the code value.
const eslintPluginGraphqlRuleTester = new RuleTester({
parserOptions: { ecmaVersion: 2015 },
parserOptions: {
ecmaVersion: 'latest',
},
});

eslintPluginGraphqlRuleTester.run('eslint-plugin-graphql', rule, {
@@ -199,7 +201,10 @@ eslintPluginGraphqlRuleTester.run('eslint-plugin-graphql', rule, {

const mdxRuleTester = new RuleTester({
parser: require.resolve('eslint-mdx'),
parserOptions: require('eslint-mdx').DEFAULT_PARSER_OPTIONS,
parserOptions: {
sourceType: 'module',
ecmaVersion: 'latest',
},
});

mdxRuleTester.run('eslint-plugin-mdx', rule, {
@@ -309,9 +314,7 @@ runFixture('eslint-plugin-svelte3/*.svelte', [[], []]);
function loadInvalidFixture(name) {
const filename = path.join(__dirname, 'invalid', name + '.txt');
const src = fs.readFileSync(filename, 'utf8');
const sections = src
.split(/^[A-Z]+:\n/m)
.map(x => x.replace(/(?=\n)\n$/, ''));
const sections = src.split(/^[A-Z]+:\n/m).map(x => x.replace(/\n$/, ''));
const item = {
code: sections[1],
output: sections[2],
161 changes: 161 additions & 0 deletions worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// @ts-check

/**
* @typedef {import('prettier').FileInfoOptions} FileInfoOptions
* @typedef {import('prettier').Options & { onDiskFilepath: string, parserPath: string, usePrettierrc?: boolean }} Options
*/

const { runAsWorker } = require('synckit');

/**
* @type {typeof import('prettier')}
*/
let prettier;

runAsWorker(
/**
* @param {string} source - The source code to format.
* @param {Options} options - The prettier options.
* @param {FileInfoOptions} eslintFileInfoOptions - The file info options.
* @returns {Promise<string | undefined>} The formatted source code.
*/
async (
source,
{
filepath,
onDiskFilepath,
parserPath,
usePrettierrc,
...eslintPrettierOptions
},
eslintFileInfoOptions,
// eslint-disable-next-line sonarjs/cognitive-complexity
) => {
if (!prettier) {
prettier = await import('prettier');
}

const prettierRcOptions = usePrettierrc
? await prettier.resolveConfig(onDiskFilepath, {
editorconfig: true,
})
: null;

const { ignored, inferredParser } = await prettier.getFileInfo(
onDiskFilepath,
{
resolveConfig: false,
withNodeModules: false,
ignorePath: '.prettierignore',
plugins: /** @type {string[] | undefined} */ (
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 (!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(/** @type {string} */ (inferredParser))) {
return;
}
}

const prettierOptions = {
...initialOptions,
...prettierRcOptions,
...eslintPrettierOptions,
filepath,
};

return prettier.format(source, prettierOptions);
},
);
7,167 changes: 4,257 additions & 2,910 deletions yarn.lock

Large diffs are not rendered by default.

0 comments on commit 910aeb6

Please sign in to comment.