Skip to content

Commit badb09b

Browse files
authored
feat: huge performance boost, better json fixing, sorting presets (#1)
1 parent 6bda52d commit badb09b

File tree

11 files changed

+3781
-166
lines changed

11 files changed

+3781
-166
lines changed

.eslintignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
!.*
22
test/fixtures
3-
3+
**/node_modules
44
**/package-lock.json

README.md

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
<a href="https://github.com/bkucera/eslint-plugin-json-format/blob/master/LICENSE"><img src="https://img.shields.io/github/license/bkucera/eslint-plugin-json-format.svg"></a>
88
</div>
99

10-
Lint and auto-fix your `json` with `eslint`
10+
Lint, format, and auto-fix your `json` with `eslint`
1111

1212
## Features
1313

14-
- lint and auto-fix `json` files (files ending with `.json` or `rc`)
14+
- lint, format, and auto-fix `json` files (files ending with `.json` or `rc`)
1515
- auto-sort `package.json` files (default `true`, can be disabled and sorting configured)
1616
- ignores `json-with-comments` files (default `["**/.tsconfig.json", ".vscode/**"]`)
1717
- ignores certain files by default (default `["**/package-lock.json"]`)
@@ -55,43 +55,33 @@ eslint --ext .js,.json,.eslintrc,.babelrc --fix .
5555
!.*
5656
```
5757

58-
## Configuration
58+
## Settings
5959

60-
### default configuration** (`.eslintrc`):
60+
### default settings (`.eslintrc`):
6161
```json
6262
"settings": {
6363
"json/sort-package-json": true,
6464
"json/ignore-files": ["**/package-lock.json"],
6565
"json/json-with-comments-files": ["**/tsconfig.json", ".vscode/**"],
66-
"json/package-json-sort-order": [
67-
"name",
68-
"version",
69-
"description",
70-
"private",
71-
"main",
72-
"browser",
73-
"scripts",
74-
"husky",
75-
"dependencies",
76-
"devDependencies",
77-
"peerDependencies",
78-
"files",
79-
"bin",
80-
"engines",
81-
"types",
82-
"typings",
83-
"productName",
84-
"license",
85-
"repository",
86-
"homepage",
87-
"author",
88-
"bugs",
89-
]
66+
"json/package-json-sort-order": "pro"
9067
}
9168
```
9269
> Note: glob patterns use [`minimatch`](https://github.com/isaacs/minimatch/) against pathnames relative to the project root (cwd)
9370
94-
### examples:
71+
### `package-json-sort-order`
72+
You can configure the exact sort order of your `package.json` files (or turn it off entirely with the `sort-package-json` setting)
73+
74+
By default the sort order is `"pro"` (will change to "standard" in next major version)
75+
76+
#### Available sorting options
77+
78+
**"pro"**: places scripts and depenedencies at the top, reducing need to scroll down to view them. Pros only.
79+
80+
**"standard"**: default from [`sort-package-json`](https://github.com/keithamus/sort-package-json). This is a sane, standard order.
81+
82+
**["your", "custom", "order", "here"]**: provide an array to manually set the sort order.
83+
84+
### Settings examples
9585

9686
to turn off `sort-package-json` for example, in your `.eslintrc`:
9787
```json

lib/index.js

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
const _ = require('lodash')
22
const debug = require('debug')('json')
3-
const Diff = require('diff')
3+
const diffMatchPatch = require('diff-match-patch')
44
const lineColumn = require('line-column')
55
const path = require('path')
66
const { getSettings, SETTINGS } = require('./settings')
77
const { parseJSON, formatJSON, sortPkgJSON, initializeEslintPlugin, includesFile } = require('./utils')
88
const { stripIndent } = require('common-tags')
99

10+
const dmp = new diffMatchPatch()
11+
12+
dmp.Diff_Timeout = 0.1
13+
1014
const pluginName = 'json'
1115

1216
initializeEslintPlugin({
@@ -49,33 +53,35 @@ initializeEslintPlugin({
4953
saveState
5054
)
5155

52-
debug('verify:', ret)
53-
5456
return ret
5557
}
5658

5759
const source = textOrSourceCode
5860

59-
debug({ source })
61+
// debug({ source })
6062

6163
let parsed
6264

65+
const startTime = new Date()
66+
6367
try {
6468
parsed = parseJSON(source)
6569
} catch (e) {
6670
debug('parseJSON error:', e.message)
6771

68-
const res = /JSON5: (.*?) at (\d+):(\d+)/.exec(e.message)
72+
const res = /(.*)\sin JSON at position (\d+)/.exec(e.message)
6973

7074
let line = 1
7175
let col = 1
7276
let message = e.message
7377

7478
if (res) {
75-
debug(res[1], res[2])
76-
line = res[2]
77-
col = res[3]
79+
debug('error parsed as:', res)
7880
message = res[1]
81+
const lineCol = lineColumn(source, +res[2])
82+
83+
line = lineCol.line
84+
col = lineCol.col
7985
}
8086

8187
const ret = {
@@ -92,15 +98,21 @@ initializeEslintPlugin({
9298
return [ret]
9399

94100
}
95-
debug({ parsed })
101+
const endTime = new Date()
102+
103+
debug('parsed:', endTime - startTime)
104+
// debug({ parsed })
96105

97106
const formatted = formatJSON(parsed)
98107

99-
debug({ formatted })
108+
debug('formatted')
109+
110+
// debug({ formatted })
100111

101112
let fixes = getFixes(source, formatted)
102113

103114
if (mode === 'package-json') {
115+
debug('sorting JSON')
104116
const sorted = formatJSON(sortPkgJSON(parsed, pluginSettings['package-json-sort-order']))
105117

106118
if (sorted !== formatted) {
@@ -121,7 +133,9 @@ initializeEslintPlugin({
121133
}
122134
}
123135

124-
debug({ fixes })
136+
// debug({ fixes })
137+
138+
debug('fixes count:', fixes.length)
125139

126140
messages = messages.concat(fixes)
127141

@@ -144,7 +158,7 @@ function getMode (pluginSettings, filenameOrOptions) {
144158

145159
debug({ extension })
146160

147-
debug({ pluginSettings })
161+
// debug({ pluginSettings })
148162

149163
const basename = path.basename(filename)
150164

@@ -192,15 +206,31 @@ function getMode (pluginSettings, filenameOrOptions) {
192206

193207
const getFixes = (source, formatted) => {
194208

195-
const diff = Diff.diffChars(source, formatted)
209+
const startTime = new Date()
210+
// old, slow diff algo
211+
// const diff = Diff.diffChars(source, formatted)
212+
const diff = dmp.diff_main(source, formatted)
213+
const endTime = new Date()
196214

197-
debug({ diff })
215+
debug('diff time:', endTime - startTime)
216+
// debug({ diff })
217+
debug('diff length:', diff.length)
198218

199219
let index = 0
200220

201221
let fixes = []
202222

203-
_.each(diff, (d) => {
223+
_.each(diff, (_d) => {
224+
225+
// const d = _d
226+
const d = {
227+
added: _d[0] === 1,
228+
removed: _d[0] === -1,
229+
value: _d[1],
230+
count: _d[1].length,
231+
}
232+
233+
// debug({ d })
204234

205235
const valEscaped = d.value ? JSON.stringify(d.value) : ''
206236

lib/settings.js

Lines changed: 94 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -26,31 +26,35 @@ const schema = {
2626
],
2727
},
2828
'package-json-sort-order': {
29-
type: 'array',
30-
default: [
31-
'name',
32-
'version',
33-
'description',
34-
'private',
35-
'main',
36-
'browser',
37-
'scripts',
38-
'husky',
39-
'dependencies',
40-
'devDependencies',
41-
'peerDependencies',
42-
'files',
43-
'bin',
44-
'engines',
45-
'types',
46-
'typings',
47-
'productName',
48-
'license',
49-
'repository',
50-
'homepage',
51-
'author',
52-
'bugs',
53-
],
29+
type: ['array'],
30+
default: 'pro',
31+
presets: {
32+
'standard': [],
33+
'pro': [
34+
'name',
35+
'version',
36+
'description',
37+
'private',
38+
'main',
39+
'browser',
40+
'scripts',
41+
'husky',
42+
'dependencies',
43+
'devDependencies',
44+
'peerDependencies',
45+
'files',
46+
'bin',
47+
'engines',
48+
'types',
49+
'typings',
50+
'productName',
51+
'license',
52+
'repository',
53+
'homepage',
54+
'author',
55+
'bugs',
56+
],
57+
},
5458
},
5559
}
5660

@@ -65,51 +69,88 @@ function getSetting (settings, name) {
6569
function getSettings (settings) {
6670
const errors = []
6771
const warnings = []
68-
const pluginSettings = _.mapValues(schema, (val, ruleName) => {
72+
const pluginSettings = _.mapValues(schema, (setting, ruleName) => {
6973
let settingValue = getSetting(settings, ruleName)
7074

7175
if (settingValue == null) {
72-
settingValue = val.default
76+
settingValue = setting.default
77+
} else {
78+
debug('user supplied setting:', ruleName)
7379
}
7480

75-
if (val.deprecated) {
76-
if (settingValue != null) {
77-
if (val.renamed) {
78-
errors.push({
79-
message: stripIndent`
80-
Eslint Settings Error: [${namespace}]:
81-
Using deprecated settings key: "${namespace}/${ruleName}"
82-
Please rename this settings key to: "${namespace}/${val.renamed}"
83-
`,
84-
})
85-
}
86-
}
81+
try {
82+
settingValue = getFinalSettingValue(settingValue, setting, ruleName)
83+
} catch (e) {
84+
errors.push({
85+
message: e.message,
86+
})
8787

8888
return
8989
}
9090

91-
const type = typeOf(settingValue)
92-
93-
debug('setting value', { ruleName, settingValue, type })
94-
if (type !== val.type) {
95-
throw new Error(stripIndent`
96-
ESLint Settings Error [${namespace}]:
97-
invalid property value ${namespace}/${ruleName}
98-
expected type of ${val.type}, but got ${type}`)
99-
}
100-
101-
{ return settingValue }
91+
return settingValue
10292
})
10393

104-
debug({ pluginSettings })
105-
10694
return { pluginSettings, warnings, errors }
10795
}
10896

10997
module.exports = {
11098
getSettings,
11199
SETTINGS: _.mapValues(schema, (__, key) => key),
112100
schema,
101+
getDefault: (key) => getFinalSettingValue(schema[key].default, schema[key], key),
113102
}
114103

115-
const typeOf = (obj) => Object.prototype.toString.call(obj).replace(/\[\w+ (\w+)\]/, '$1').toLowerCase()
104+
const typeOf = (obj) => {
105+
return Object.prototype.toString.call(obj).replace(/\[\w+ (\w+)\]/, '$1').toLowerCase()
106+
}
107+
108+
const getFinalSettingValue = (val, setting, ruleName) => {
109+
const type = typeOf(val)
110+
const allowedTypes = _.isArray(setting.type) ? setting.type : [setting.type]
111+
112+
// debug({ ruleName, allowedTypes, type })
113+
if (setting.deprecated) {
114+
if (val != null) {
115+
throwDeprecatedError(setting, ruleName)
116+
}
117+
118+
return
119+
}
120+
121+
if (setting.presets && type === 'string') {
122+
if (!setting.presets[val]) {
123+
throwInvalidTypeError(type, setting, allowedTypes, ruleName)
124+
}
125+
126+
return setting.presets[val]
127+
}
128+
129+
if (!_.includes(allowedTypes, type)) {
130+
throwInvalidTypeError(type, setting, allowedTypes, ruleName)
131+
}
132+
133+
return val
134+
135+
}
136+
137+
const throwInvalidTypeError = (type, setting, allowedTypes, ruleName) => {
138+
throw new Error(stripIndent`
139+
ESLint Settings Error [${namespace}]:
140+
invalid property value ${namespace}/${ruleName}
141+
expected type of ${allowedTypes.join(', ')}, but got ${type}
142+
${setting.presets ? `
143+
You may also use one of the following preset values via string:
144+
${_.keys(setting.presets).map((v) => `'${v}'`).join(', ')}
145+
` : ''}
146+
`)
147+
148+
}
149+
150+
const throwDeprecatedError = (setting, ruleName) => {
151+
throw new Error(stripIndent`
152+
Eslint Settings Error: [${namespace}]:
153+
Using deprecated settings key: "${namespace}/${ruleName}"
154+
${setting.renamed ? `Please rename this settings key to: "${namespace}/${setting.renamed}"` : ''}
155+
`)
156+
}

0 commit comments

Comments
 (0)