Skip to content

Commit 92f2c9c

Browse files
dotcarmenJounQin
andauthored
feat: support non-js languages (#743)
Co-authored-by: JounQin <[email protected]>
1 parent aa4935c commit 92f2c9c

File tree

7 files changed

+145
-6
lines changed

7 files changed

+145
-6
lines changed

.changeset/weak-lamps-refuse.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-prettier": minor
3+
---
4+
5+
feat: support non-js languages like `css` for `@eslint/css` and `json` for `@eslint/json`

eslint-plugin-prettier.js

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
/**
99
* @import {AST, ESLint, Linter, Rule, SourceCode} from 'eslint'
10+
* @import {Position} from 'estree'
1011
* @import {FileInfoOptions, Options as PrettierOptions} from 'prettier'
1112
* @import {Difference} from 'prettier-linter-helpers'
1213
*/
@@ -57,6 +58,42 @@ let prettierFormat;
5758
// Rule Definition
5859
// ------------------------------------------------------------------------------
5960

61+
/** @type {WeakMap<SourceCode, number[]>} */
62+
const lineIndexesCache = new WeakMap();
63+
64+
/**
65+
* Ponyfill `sourceCode.getLocFromIndex` when it's unavailable.
66+
*
67+
* See also `getLocFromIndex` in `@eslint/js`.
68+
*
69+
* @param {SourceCode} sourceCode
70+
* @param {number} index
71+
* @returns {Position}
72+
*/
73+
function getLocFromIndex(sourceCode, index) {
74+
if (typeof sourceCode.getLocFromIndex === 'function') {
75+
return sourceCode.getLocFromIndex(index);
76+
}
77+
78+
let lineIndexes = lineIndexesCache.get(sourceCode);
79+
if (!lineIndexes) {
80+
lineIndexes = [...sourceCode.text.matchAll(/\r?\n/g)].map(
81+
match => match.index,
82+
);
83+
// first line in the file starts at byte offset 0
84+
lineIndexes.unshift(0);
85+
lineIndexesCache.set(sourceCode, lineIndexes);
86+
}
87+
88+
let line = 0;
89+
while (line + 1 < lineIndexes.length && lineIndexes[line + 1] < index) {
90+
line += 1;
91+
}
92+
const column = index - lineIndexes[line];
93+
94+
return { line: line + 1, column };
95+
}
96+
6097
/**
6198
* Reports a difference.
6299
*
@@ -71,9 +108,9 @@ function reportDifference(context, difference) {
71108
// `context.getSourceCode()` was deprecated in ESLint v8.40.0 and replaced
72109
// with the `sourceCode` property.
73110
// TODO: Only use property when our eslint peerDependency is >=8.40.0.
74-
const [start, end] = range.map(index =>
75-
(context.sourceCode ?? context.getSourceCode()).getLocFromIndex(index),
76-
);
111+
const sourceCode = context.sourceCode ?? context.getSourceCode();
112+
113+
const [start, end] = range.map(index => getLocFromIndex(sourceCode, index));
77114

78115
context.report({
79116
messageId: operation,
@@ -168,7 +205,8 @@ const eslintPluginPrettier = {
168205
const source = sourceCode.text;
169206

170207
return {
171-
Program(node) {
208+
/** @param {unknown} node */
209+
[sourceCode.ast.type](node) {
172210
if (!prettierFormat) {
173211
// Prettier is expensive to load, so only load it if needed.
174212
prettierFormat = /** @type {PrettierFormat} */ (

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"@commitlint/config-conventional": "^19.8.0",
7575
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
7676
"@eslint/js": "^9.23.0",
77+
"@eslint/json": "^0.12.0",
7778
"@graphql-eslint/eslint-plugin": "^4.3.0",
7879
"@html-eslint/parser": "^0.41.0",
7980
"@prettier/plugin-pug": "^3.2.1",

pnpm-lock.yaml

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/empty.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

test/invalid/json.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
CODE:
2+
{
3+
"a": [
4+
"b",
5+
{"c":
6+
"d"}
7+
] }
8+
9+
OUTPUT:
10+
{
11+
"a": ["b", { "c": "d" }]
12+
}
13+
14+
OPTIONS:
15+
[]
16+
17+
ERRORS:
18+
[
19+
{
20+
message: 'Replace `"a":·[⏎"b",⏎{"c":⏎"d"}⏎]·` with `··"a":·["b",·{·"c":·"d"·}]⏎`',
21+
},
22+
]

test/prettier.mjs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@ import eslintPluginPug from 'eslint-plugin-pug';
2525
import vueEslintParser from 'vue-eslint-parser';
2626
import * as eslintPluginGraphql from '@graphql-eslint/eslint-plugin';
2727
import eslintMdx from 'eslint-mdx';
28+
import eslintPluginJson from '@eslint/json';
2829

2930
const rule = eslintPluginPrettier.rules.prettier;
3031
const RuleTester =
3132
eslintUnsupportedApi.FlatRuleTester ?? eslintPackage.RuleTester;
3233
const ESLint = eslintUnsupportedApi.FlatESLint ?? eslintPackage.ESLint;
3334

35+
const isESLint9 = !eslintUnsupportedApi.FlatRuleTester;
36+
3437
// ------------------------------------------------------------------------------
3538
// Tests
3639
// ------------------------------------------------------------------------------
@@ -380,6 +383,53 @@ runFixture('invalid-prettierrc/*', [
380383
],
381384
]);
382385

386+
runFixture('*.json', [
387+
[
388+
{
389+
column: 1,
390+
endColumn: 1,
391+
endLine: 2,
392+
fix: {
393+
range: [0, 1],
394+
text: '',
395+
},
396+
line: 1,
397+
message: 'Delete `⏎`',
398+
messageId: 'delete',
399+
nodeType: null,
400+
ruleId: 'prettier/prettier',
401+
severity: 2,
402+
},
403+
],
404+
]);
405+
406+
if (isESLint9) {
407+
const jsonRuleTester = new RuleTester({
408+
plugins: {
409+
json: eslintPluginJson,
410+
},
411+
language: 'json/json',
412+
});
413+
414+
jsonRuleTester.run('@eslint/json', rule, {
415+
valid: [
416+
{
417+
code: '{}\n',
418+
filename: 'empty.json',
419+
},
420+
{
421+
code: '{ "foo": 1 }\n',
422+
filename: 'simple.json',
423+
},
424+
],
425+
invalid: [
426+
Object.assign(loadInvalidFixture('json'), {
427+
filename: 'invalid.json',
428+
}),
429+
],
430+
});
431+
}
432+
383433
// ------------------------------------------------------------------------------
384434
// Helpers
385435
// ------------------------------------------------------------------------------
@@ -436,7 +486,7 @@ function getPrettierRcJsFilename(dir, file = 'dummy.js') {
436486
* @type {ESLint}
437487
* @import {ESLint} from 'eslint'
438488
*/
439-
let eslint;
489+
var eslint; // bad mocha: `ReferenceError: Cannot access 'eslint' before initialization`
440490

441491
/**
442492
* @param {string} pattern
@@ -504,11 +554,16 @@ async function runFixture(pattern, asserts, skip) {
504554
pug: eslintPluginPug,
505555
},
506556
},
557+
{
558+
files: ['**/*.json'],
559+
plugins: {
560+
json: eslintPluginJson,
561+
},
562+
},
507563
],
508564
ignore: false,
509565
});
510566
}
511-
512567
if (skip) {
513568
return;
514569
}

0 commit comments

Comments
 (0)