Skip to content

Commit 037af02

Browse files
authored
feat: support package.json linting (#7)
This adds support for using `jsonc-eslint-parser` against your `package.json` to the ban-dependencies rule.
1 parent 0d8e31e commit 037af02

File tree

5 files changed

+141
-0
lines changed

5 files changed

+141
-0
lines changed

README.md

+26
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,32 @@ export default [
3939
];
4040
```
4141

42+
### With `package.json`
43+
44+
Some rules (e.g. `ban-dependencies`) can be used against your `package.json`.
45+
46+
You can achieve this by using `jsonc-eslint-parser`.
47+
48+
For example, in your `.eslintrc.json`:
49+
50+
```json
51+
{
52+
"overrides": [
53+
{
54+
"files": ["package.json"],
55+
"parser": "jsonc-eslint-parser",
56+
"plugins": ["depend"],
57+
"rules": {
58+
"depend/ban-dependencies": "error"
59+
}
60+
}
61+
]
62+
}
63+
```
64+
65+
Read more at the
66+
[`jsonc-eslint-parser` docs](https://github.com/ota-meshi/jsonc-eslint-parser).
67+
4268
## Rules
4369

4470
- [`depend/ban-dependencies`](./docs/rules/ban-dependencies.md)

package-lock.json

+19
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"eslint": "^8.56.0",
4747
"eslint-config-google": "^0.14.0",
4848
"eslint-plugin-eslint-plugin": "^5.3.0",
49+
"jsonc-eslint-parser": "^2.4.0",
4950
"prettier": "^3.2.5",
5051
"rimraf": "^5.0.5",
5152
"typescript": "^5.3.3",

src/test/rules/ban-dependencies_test.ts

+48
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const ruleTester = new RuleTester({
1010
});
1111

1212
const tseslintParser = require.resolve('@typescript-eslint/parser');
13+
const jsonParser = require.resolve('jsonc-eslint-parser');
1314

1415
ruleTester.run('ban-dependencies', rule, {
1516
valid: [
@@ -43,6 +44,33 @@ ruleTester.run('ban-dependencies', rule, {
4344
presets: []
4445
}
4546
]
47+
},
48+
{
49+
code: `{
50+
"dependencies": {
51+
"unknown-module": "^1.0.0"
52+
}
53+
}`,
54+
filename: 'package.json',
55+
parser: jsonParser
56+
},
57+
{
58+
code: `{
59+
"dependencies": {
60+
"npm-run-all": "^1.0.0"
61+
}
62+
}`,
63+
filename: 'not-a-package.json',
64+
parser: jsonParser
65+
},
66+
{
67+
code: `{
68+
"not-dependencies": {
69+
"some-other-nonsense": 123
70+
}
71+
}`,
72+
filename: 'package.json',
73+
parser: jsonParser
4674
}
4775
],
4876

@@ -150,6 +178,26 @@ ruleTester.run('ban-dependencies', rule, {
150178
}
151179
}
152180
]
181+
},
182+
{
183+
code: `{
184+
"dependencies": {
185+
"npm-run-all": "^1.0.0"
186+
}
187+
}`,
188+
filename: 'package.json',
189+
parser: jsonParser,
190+
errors: [
191+
{
192+
line: 3,
193+
column: 11,
194+
messageId: 'documentedReplacement',
195+
data: {
196+
name: 'npm-run-all',
197+
url: getReplacementsDocUrl('npm-run-all')
198+
}
199+
}
200+
]
153201
}
154202
]
155203
});

src/util/imports.ts

+47
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {TSESTree} from '@typescript-eslint/typescript-estree';
33
import {Replacement} from '../replacements.js';
44
import {closestPackageSatisfiesNodeVersion} from './package-json.js';
55
import {getMdnUrl, getReplacementsDocUrl} from './rule-meta.js';
6+
import type {AST as JsonESTree} from 'jsonc-eslint-parser';
67

78
export type ImportListenerCallback = (
89
context: Rule.RuleContext,
@@ -139,6 +140,46 @@ function replacementListenerCallback(
139140
}
140141
}
141142

143+
const dependencyKeys = ['dependencies', 'devDependencies'];
144+
145+
/**
146+
* Creates a rule listener for detecting dependencies in a `package.json`
147+
* file
148+
* @param {Rule.RuleContext} context ESLint context
149+
* @param {ImportListenerCallback} callback Listener callback
150+
* @return {Rule.RuleListener}
151+
*/
152+
export function createPackageJsonListener(
153+
context: Rule.RuleContext,
154+
callback: ImportListenerCallback
155+
): Rule.RuleListener {
156+
return {
157+
'Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty': (
158+
astNode: Rule.Node
159+
) => {
160+
const node = astNode as unknown as JsonESTree.JSONProperty;
161+
162+
if (
163+
node.key.type === 'JSONLiteral' &&
164+
typeof node.key.value === 'string' &&
165+
dependencyKeys.includes(node.key.value) &&
166+
node.value.type === 'JSONObjectExpression'
167+
) {
168+
for (const prop of node.value.properties) {
169+
if (
170+
prop.key.type === 'JSONLiteral' &&
171+
typeof prop.key.value === 'string'
172+
) {
173+
callback(context, prop as unknown as Rule.Node, prop.key.value);
174+
}
175+
}
176+
}
177+
}
178+
};
179+
}
180+
181+
const packageJsonLikePath = /(^|[/\\])package.json$/;
182+
142183
/**
143184
* Creates a rule listener which finds replacements in imports/requires
144185
* @param {Rule.RuleContext} context ESLint context
@@ -149,6 +190,12 @@ export function createReplacementListener(
149190
context: Rule.RuleContext,
150191
replacements: Replacement[]
151192
): Rule.RuleListener {
193+
if (packageJsonLikePath.test(context.filename)) {
194+
return createPackageJsonListener(context, (context, node, name) =>
195+
replacementListenerCallback(context, replacements, node, name)
196+
);
197+
}
198+
152199
return createImportListener(context, (context, node, source) =>
153200
replacementListenerCallback(context, replacements, node, source)
154201
);

0 commit comments

Comments
 (0)