Skip to content

Commit 1e1320e

Browse files
committed
feat: engines and files rules
1 parent ca76f9b commit 1e1320e

File tree

17 files changed

+425
-17
lines changed

17 files changed

+425
-17
lines changed

README.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,38 @@ $ npx prettier --write package.json
4848

4949
This plugin enforces its own set of opinionated rules:
5050

51+
### Engines
52+
53+
Keys in `engines` are ordered alphabetically.
54+
55+
#### Options
56+
57+
`enginesNode`<br>
58+
Type: `string`
59+
60+
Asserts an `engines.node` property value. e.g. `{ enginesNode: '>= 10.0.0' }`. If this option is set and no `node` key exists in `engines`, it will be created.
61+
62+
`enginesNpm`<br>
63+
Type: `string`
64+
65+
Asserts an `engines.npm` property value. e.g. `{ enginesNpm: '>= 10.0.0' }`. If this option is set and no `npm` key exists in `engines`, it will be created.
66+
67+
### Files
68+
69+
Keys in `files` are ordered alphabetically. Additionally, `LICENSE` and `README.md` are added to `files` if they are missing. This plugin prefers implicit declaration of those files, even though they are included in a package automatically by `npm`.
70+
71+
#### Options
72+
73+
`filesLicense`<br>
74+
Type: `boolean`
75+
76+
To prevent `LICENSE` from being added to `files`, set this option to `false`.
77+
78+
`filesReadme`<br>
79+
Type: `boolean`
80+
81+
To prevent `README.md` from being added to `files`, set this option to `false`.
82+
5183
### Scripts
5284

5385
Keys in `scripts` are ordered alphabetically. Use prefixes wisely to properly order child scripts. e.g. `lint`, `lint:ts`.
@@ -102,8 +134,6 @@ Unknown keys, or keys not part of the list above, will be alphabetically sorted
102134
Forthcoming rules include:
103135

104136
- [ ] Author format
105-
- [ ] Engines format
106-
- [ ] Files order and content
107137
- [ ] Repository format
108138

109139
## Meta

lib/index.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,28 @@
1010
*/
1111
const { parsers } = require('prettier/parser-babylon');
1212

13-
const { scripts } = require('./scripts');
14-
const { sort } = require('./sort');
13+
const { engines } = require('./rules/engines');
14+
const { files } = require('./rules/files');
15+
const { options: pluginOptions } = require('./options');
16+
const { scripts } = require('./rules/scripts');
17+
const { sort } = require('./rules/sort');
1518

1619
const { 'json-stringify': parser } = parsers;
1720
const { parse } = parser;
1821
const rePkg = /package\.json$/;
1922

20-
const format = (properties) => {
23+
const format = (properties, options) => {
2124
let props = sort(properties);
25+
props = engines(props, options);
26+
props = files(props, options);
2227
props = scripts(props);
2328

2429
return props;
2530
};
2631

2732
module.exports = {
2833
name: 'prettier-plugin-package',
34+
options: pluginOptions,
2935
parsers: {
3036
'json-stringify': {
3137
...parser,
@@ -36,7 +42,7 @@ module.exports = {
3642

3743
if (rePkg.test(filepath)) {
3844
const { properties } = ast;
39-
ast.properties = format(properties);
45+
ast.properties = format(properties, options);
4046
}
4147

4248
return ast;

lib/options.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
Copyright © 2019 Andrew Powell
3+
4+
This Source Code Form is subject to the terms of the Mozilla Public
5+
License, v. 2.0. If a copy of the MPL was not distributed with this
6+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
7+
8+
The above copyright notice and this permission notice shall be
9+
included in all copies or substantial portions of this Source Code Form.
10+
*/
11+
const options = {
12+
enginesNode: {
13+
type: 'path',
14+
category: 'Prettier Package',
15+
default: '',
16+
description: 'Asserts an `engines.node` property value for package.json.'
17+
},
18+
enginesNpm: {
19+
type: 'path',
20+
category: 'Prettier Package',
21+
default: '',
22+
description: 'Asserts an `engines.npm` property value for package.json.'
23+
},
24+
filesLicense: {
25+
type: 'boolean',
26+
category: 'Prettier Package',
27+
default: true,
28+
description:
29+
'If true, asserts `LICENSE` as an entry in the `files` property value for package.json.'
30+
},
31+
filesReadme: {
32+
type: 'boolean',
33+
category: 'Prettier Package',
34+
default: true,
35+
description:
36+
'If true, asserts `README.md` as an entry in the `files` property value for package.json.'
37+
},
38+
repository: {
39+
type: 'path',
40+
category: 'Prettier Package',
41+
default: '',
42+
description: 'Enforce a `repository` property value for package.json.'
43+
}
44+
};
45+
46+
module.exports = { options };

lib/rules/engines.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
Copyright © 2019 Andrew Powell
3+
4+
This Source Code Form is subject to the terms of the Mozilla Public
5+
License, v. 2.0. If a copy of the MPL was not distributed with this
6+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
7+
8+
The above copyright notice and this permission notice shall be
9+
included in all copies or substantial portions of this Source Code Form.
10+
*/
11+
const process = (props, { enginesNode, enginesNpm }) => {
12+
const enginesIndex = props.findIndex((prop) => prop.key.value === 'engines');
13+
14+
if (enginesIndex >= 0) {
15+
const [engines] = props.splice(enginesIndex, 1);
16+
const { properties } = engines.value;
17+
18+
properties.sort((a, b) => (a.key.value > b.key.value ? 1 : a.key.value < b.key.value ? -1 : 0));
19+
20+
if (enginesNode) {
21+
const nodeIndex = properties.findIndex((prop) => prop.key.value === 'node');
22+
23+
if (nodeIndex >= 0) {
24+
properties[nodeIndex].value.value = enginesNode;
25+
} else {
26+
properties.push({
27+
type: 'ObjectProperty',
28+
key: { type: 'StringLiteral', value: 'node' },
29+
value: { type: 'StringLiteral', value: enginesNode }
30+
});
31+
}
32+
}
33+
34+
if (enginesNpm) {
35+
const npmIndex = properties.findIndex((prop) => prop.key.value === 'npm');
36+
37+
if (npmIndex >= 0) {
38+
properties[npmIndex].value.value = enginesNpm;
39+
} else {
40+
properties.push({
41+
type: 'ObjectProperty',
42+
key: { type: 'StringLiteral', value: 'npm' },
43+
value: { type: 'StringLiteral', value: enginesNpm }
44+
});
45+
}
46+
}
47+
48+
engines.value.properties = properties;
49+
50+
props.splice(enginesIndex, 0, engines);
51+
}
52+
53+
return props;
54+
};
55+
56+
module.exports = { engines: process };

lib/rules/files.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
Copyright © 2019 Andrew Powell
3+
4+
This Source Code Form is subject to the terms of the Mozilla Public
5+
License, v. 2.0. If a copy of the MPL was not distributed with this
6+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
7+
8+
The above copyright notice and this permission notice shall be
9+
included in all copies or substantial portions of this Source Code Form.
10+
*/
11+
const files = (props, { filesReadme, filesLicense }) => {
12+
const filesIndex = props.findIndex((prop) => prop.key.value === 'files');
13+
14+
if (filesIndex >= 0) {
15+
const [filesNode] = props.splice(filesIndex, 1);
16+
17+
let { elements } = filesNode.value;
18+
19+
elements = elements
20+
.filter((node) => {
21+
const value = node.value.toLowerCase();
22+
return value !== 'license' && value !== 'readme.md';
23+
})
24+
.sort((a, b) => (a.value > b.value ? 1 : a.value < b.value ? -1 : 0));
25+
26+
if (filesLicense) {
27+
elements.push({ type: 'StringLiteral', value: 'LICENSE' });
28+
}
29+
30+
if (filesReadme) {
31+
elements.push({ type: 'StringLiteral', value: 'README.md' });
32+
}
33+
34+
filesNode.value.elements = elements;
35+
36+
props.splice(filesIndex, 0, filesNode);
37+
}
38+
39+
return props;
40+
};
41+
42+
module.exports = { files };

lib/scripts.js renamed to lib/rules/scripts.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
const sort = (props) => {
1212
const scriptsIndex = props.findIndex((prop) => prop.key.value === 'scripts');
1313

14-
if (scriptsIndex) {
14+
if (scriptsIndex >= 0) {
1515
const scripts = props[scriptsIndex];
1616
scripts.value.properties.sort((a, b) =>
1717
a.key.value > b.key.value ? 1 : a.key.value < b.key.value ? -1 : 0
File renamed without changes.

test/engines.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
const test = require('ava');
2+
const prettier = require('prettier');
3+
4+
test('default', (t) => {
5+
const options = {
6+
filepath: 'package.json',
7+
parser: 'json-stringify',
8+
plugins: ['.']
9+
};
10+
const fixture = {
11+
engines: {
12+
npm: 'joker',
13+
node: 'batman'
14+
}
15+
};
16+
17+
const input = JSON.stringify(fixture, null, 2);
18+
const output = prettier.format(input, options);
19+
20+
t.snapshot(output);
21+
});
22+
23+
test('enginesNode', (t) => {
24+
const options = {
25+
enginesNode: '>= batcave',
26+
filepath: 'package.json',
27+
parser: 'json-stringify',
28+
plugins: ['.']
29+
};
30+
const fixture = {
31+
engines: {
32+
npm: 'joker',
33+
node: 'batman'
34+
}
35+
};
36+
37+
const input = JSON.stringify(fixture, null, 2);
38+
const output = prettier.format(input, options);
39+
40+
t.snapshot(output);
41+
});
42+
43+
test('enginesNpm', (t) => {
44+
const options = {
45+
enginesNpm: '>= harley',
46+
filepath: 'package.json',
47+
parser: 'json-stringify',
48+
plugins: ['.']
49+
};
50+
const fixture = {
51+
engines: {
52+
npm: 'joker',
53+
node: 'batman'
54+
}
55+
};
56+
57+
const input = JSON.stringify(fixture, null, 2);
58+
const output = prettier.format(input, options);
59+
60+
t.snapshot(output);
61+
});
62+
63+
test('missing', (t) => {
64+
const options = {
65+
enginesNode: '>= batcave',
66+
enginesNpm: '>= harley',
67+
filepath: 'package.json',
68+
parser: 'json-stringify',
69+
plugins: ['.']
70+
};
71+
const fixture = {
72+
engines: {}
73+
};
74+
75+
const input = JSON.stringify(fixture, null, 2);
76+
const output = prettier.format(input, options);
77+
78+
t.snapshot(output);
79+
});

test/files.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const test = require('ava');
2+
const prettier = require('prettier');
3+
4+
test('default', (t) => {
5+
const options = {
6+
filepath: 'package.json',
7+
parser: 'json-stringify',
8+
plugins: ['.']
9+
};
10+
const fixture = {
11+
files: ['lib/']
12+
};
13+
14+
const input = JSON.stringify(fixture, null, 2);
15+
const output = prettier.format(input, options);
16+
17+
t.snapshot(output);
18+
});
19+
20+
test('filesLicense', (t) => {
21+
const options = {
22+
filepath: 'package.json',
23+
filesLicense: false,
24+
parser: 'json-stringify',
25+
plugins: ['.']
26+
};
27+
const fixture = {
28+
files: ['lib/']
29+
};
30+
31+
const input = JSON.stringify(fixture, null, 2);
32+
const output = prettier.format(input, options);
33+
34+
t.snapshot(output);
35+
});
36+
37+
test('filesReadme', (t) => {
38+
const options = {
39+
filepath: 'package.json',
40+
filesReadme: false,
41+
parser: 'json-stringify',
42+
plugins: ['.']
43+
};
44+
const fixture = {
45+
files: ['lib/']
46+
};
47+
48+
const input = JSON.stringify(fixture, null, 2);
49+
const output = prettier.format(input, options);
50+
51+
t.snapshot(output);
52+
});

test/fixtures/fixture.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@
2525
"lint-staged": "lint-staged"
2626
},
2727
"files": [
28-
"lib/",
2928
"README.md",
30-
"LICENSE"
29+
"lib/",
30+
"LICENSE",
31+
"index.js"
3132
],
3233
"keywords": [
3334
"package",

0 commit comments

Comments
 (0)