Skip to content

Commit

Permalink
feat!: ESLint 8 and jsonc-eslint-parser (#40)
Browse files Browse the repository at this point in the history
Fixes #39; fixes #43.

Moves to the external `jsonc-eslint-parser` because:

* #43: it's a good ecosystem standard
* ESLint's RuleTester doesn't support this usage of processors
(#40 (comment))

Also removes the `disparity` dependency, in favor of directly telling
users to run the auto-fixer. This is the strategy other plugins such as
[`eslint-plugin-simple-import-sort`](https://github.com/lydell/eslint-plugin-simple-import-sort)
take. It's simpler to implement, makes for easier-to-read error
messages, and reduces the size of `node_modules`.
  • Loading branch information
JoshuaKGoldberg committed Oct 17, 2023
1 parent a1c3bf7 commit d1a2843
Show file tree
Hide file tree
Showing 18 changed files with 6,786 additions and 7,123 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Expand Up @@ -5,7 +5,7 @@
},
"extends": ["eslint:recommended"],
"parserOptions": {
"ecmaVersion": 6,
"ecmaVersion": 2021,
"sourceType": "module"
}
}
59 changes: 29 additions & 30 deletions README.md
Expand Up @@ -4,47 +4,46 @@ Rules for valid, consistent, and readable package.json files

## Installation

You'll first need to install [ESLint](http://eslint.org):
You'll first need to install [ESLint](http://eslint.org) >=8 and `eslint-plugin-package-json`:

```
$ npm i eslint --save-dev
```

Next, install `eslint-plugin-package-json`:

```
$ npm install eslint-plugin-package-json --save-dev
```shell
$ npm install eslint eslint-plugin-package-json --save-dev
```

**Note:** If you installed ESLint globally (using the `-g` flag) then you must also install `eslint-plugin-package-json` globally.

## Usage

Add `package-json` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix:

```json
{
"plugins": ["package-json"]
}
```

Use the prepackaged config by adding an "extends" property, or appending to an existing "extends" property:

```json
{
"extends": ["eslint:recommended", "plugin:package-json/recommended"],
"plugins": ["package-json"]
}
Add an override to your ESLint configuration file that specifies this plugin, [`jsonc-eslint-parser`](https://github.com/ota-meshi/jsonc-eslint-parser) and its recommended rules for your `package.json` file:

```js
module.exports = {
overrides: [
{
extends: ['plugin:package-json/recommended'],
files: ['package.json'],
parser: 'jsonc-eslint-parser'
plugins: ['package-json']
}
]
};
```

Or, individually configure the rules you want to use under the rules section.

```json
{
"rules": {
"package-json/rule-name": 2
}
}
```js
module.exports = {
overrides: [
{
files: ['package.json'],
parser: 'jsonc-eslint-parser'
plugins: ['package-json'],
rules: {
'package-json/valid-package-def': 'error'
}
}
]
};
```

## Supported Rules
Expand Down
17 changes: 17 additions & 0 deletions lib/createRule.js
@@ -0,0 +1,17 @@
const isPackageJson = filePath =>
filePath.endsWith('/package.json') || filePath === 'package.json';

function createRule(rule) {
return {
...rule,
create(context) {
if (!isPackageJson(context.filename)) {
return {};
}

return rule.create(context);
}
};
}

module.exports.createRule = createRule;
41 changes: 13 additions & 28 deletions lib/index.js
@@ -1,34 +1,19 @@
'use strict';

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const orderProperties = require('./rules/order-properties');
const sortCollections = require('./rules/sort-collections');
const validLocalDependency = require('./rules/valid-local-dependency');
const validPackageDef = require('./rules/valid-package-def');

var requireIndex = require('requireindex');

//------------------------------------------------------------------------------
// Plugin Definition
//------------------------------------------------------------------------------

// import all rules in lib/rules
module.exports.rules = requireIndex(__dirname + '/rules');

// import processors
const PackageJsonProcessor = require('./processors/PackageJsonProcessor');
module.exports.processors = {
// adapted from https://github.com/godaddy/eslint-plugin-i18n-json
// thank you!
'.json': PackageJsonProcessor
// add your processors here
};

module.exports.configs = {
recommended: {
rules: {
'package-json/sort-collections': 'error',
'package-json/order-properties': 'warn',
'package-json/valid-package-def': 'error',
'package-json/valid-local-dependency': 'error'
module.exports = {
configs: {
recommended: {
rules: {
'order-properties': orderProperties,
'sort-collections': sortCollections,
'valid-local-dependency': validLocalDependency,
'valid-package-def': validPackageDef
}
}
}
};
63 changes: 0 additions & 63 deletions lib/processors/PackageJsonProcessor.js

This file was deleted.

72 changes: 31 additions & 41 deletions lib/rules/order-properties.js
@@ -1,16 +1,12 @@
'use strict';
const disparity = require('disparity');
const { createRule } = require('../createRule');
const sortPackageJson = require('sort-package-json');
const {
isPackageJson,
extractPackageObjectFromAST
} = require('../processors/PackageJsonProcessor');

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

const standardOrder = [
const standardOrderLegacy = [
'name',
'version',
'private',
Expand Down Expand Up @@ -41,15 +37,15 @@ const standardOrder = [
'cpu'
];

module.exports = {
module.exports = createRule({
meta: {
docs: {
description:
'Package properties must be declared in standard order',
category: 'Best Practices',
recommended: true
},
fixable: 'code', // or "code" or "whitespace"
fixable: 'code',
schema: [
{
type: 'object',
Expand All @@ -75,50 +71,44 @@ module.exports = {

create(context) {
return {
'Program:exit': node => {
'Program:exit'() {
const { ast, text } = context.sourceCode;

const options = context.options[0] || { order: 'legacy' };
if (!isPackageJson(context.getFilename())) {
return;
}
const sourceCode = context.getSourceCode();
const packageRoot = extractPackageObjectFromAST(node);
const original = JSON.parse(sourceCode.getText(packageRoot));
const requiredOrder =
options.order === 'legacy' ? standardOrder : options.order;

options.order === 'legacy'
? standardOrderLegacy
: options.order;
const orderedSource = sortPackageJson(
original,
JSON.parse(text),
requiredOrder === 'sort-package-json'
? undefined
: {
sortOrder: requiredOrder
}
);
const orderedKeys = Object.keys(orderedSource);

const diff = disparity.unified(
JSON.stringify(orderedSource, null, 2),
JSON.stringify(original, null, 2)
);
if (diff) {
context.report({
node: packageRoot,
message:
'Package top-level properties are not ordered in the NPM standard way:\n\n{{ diff }}',
data: {
diff: diff
.split('\n')
.slice(3)
.join('\n')
},
fix(fixer) {
return fixer.replaceText(
node,
JSON.stringify(orderedSource, null, 2) + `\n`
);
}
});
const { properties } = ast.body[0].expression;

for (let i = 0; i < properties.length; i += 1) {
if (properties[i].value !== orderedKeys[i]) {
context.report({
node: context.sourceCode.ast,
message:
'Package top-level properties are not ordered in the npm standard way. Run the ESLint auto-fixer to correct.',
fix(fixer) {
return fixer.replaceText(
context.sourceCode.ast,
JSON.stringify(orderedSource, null, 2) +
`\n`
);
}
});
}
break;
}
}
};
}
};
});
16 changes: 7 additions & 9 deletions lib/rules/sort-collections.js
@@ -1,5 +1,6 @@
'use strict';
const { isPackageJson } = require('../processors/PackageJsonProcessor');

const { createRule } = require('../createRule');

//------------------------------------------------------------------------------
// Rule Definition
Expand All @@ -11,7 +12,7 @@ const defaultCollections = [
'peerDependencies',
'config'
];
module.exports = {
module.exports = createRule({
meta: {
docs: {
description:
Expand All @@ -30,16 +31,13 @@ module.exports = {
]
},

create: function(context) {
create(context) {
const toSort = context.options[0] || defaultCollections;
return {
'Property:exit': node => {
if (!isPackageJson(context.getFilename())) {
return;
}
'JSONProperty:exit'(node) {
const collection = node.value;
if (
collection.type === 'ObjectExpression' &&
collection.type === 'JSONObjectExpression' &&
toSort.includes(node.key.value)
) {
const currentOrder = collection.properties;
Expand Down Expand Up @@ -85,4 +83,4 @@ module.exports = {
}
};
}
};
});

0 comments on commit d1a2843

Please sign in to comment.