eslint-plugin-yml 0.12.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -146,6 +146,18 @@ Example **.vscode/settings.json**:
146
146
  }
147
147
  ```
148
148
 
149
+ ### JetBrains WebStorm IDEs
150
+
151
+ In any of the JetBrains IDEs you can [configure the linting scope](https://www.jetbrains.com/help/webstorm/eslint.html#ws_eslint_configure_scope).
152
+ Following the steps in their help document, you can add YAML files to the scope like so:
153
+
154
+ 1. Open the **Settings/Preferences** dialog, go to **Languages and Frameworks** | **JavaScript** | **Code Quality Tools** | **ESLint**, and select **Automatic ESLint configuration** or **Manual ESLint configuration**.
155
+ 2. In the **Run for files** field, update the pattern that defines the set of files to be linted to include YAML files as well:
156
+ ```
157
+ {**/*,*}.{js,ts,jsx,tsx,html,vue,yaml,yml}
158
+ ^^^^ ^^^
159
+ ```
160
+
149
161
  <!--USAGE_GUIDE_END-->
150
162
  <!--USAGE_SECTION_END-->
151
163
 
@@ -176,6 +188,8 @@ The rules with the following star :star: are included in the config.
176
188
  | [yml/plain-scalar](https://ota-meshi.github.io/eslint-plugin-yml/rules/plain-scalar.html) | require or disallow plain style scalar. | :wrench: | | :star: |
177
189
  | [yml/quotes](https://ota-meshi.github.io/eslint-plugin-yml/rules/quotes.html) | enforce the consistent use of either double, or single quotes | :wrench: | | :star: |
178
190
  | [yml/require-string-key](https://ota-meshi.github.io/eslint-plugin-yml/rules/require-string-key.html) | disallow mapping keys other than strings | | | |
191
+ | [yml/sort-keys](https://ota-meshi.github.io/eslint-plugin-yml/rules/sort-keys.html) | require mapping keys to be sorted | :wrench: | | |
192
+ | [yml/sort-sequence-values](https://ota-meshi.github.io/eslint-plugin-yml/rules/sort-sequence-values.html) | require sequence values to be sorted | :wrench: | | |
179
193
  | [yml/vue-custom-block/no-parsing-error](https://ota-meshi.github.io/eslint-plugin-yml/rules/vue-custom-block/no-parsing-error.html) | disallow parsing errors in Vue custom blocks | | :star: | :star: |
180
194
 
181
195
  ### Extension Rules
@@ -189,7 +203,6 @@ The rules with the following star :star: are included in the config.
189
203
  | [yml/key-spacing](https://ota-meshi.github.io/eslint-plugin-yml/rules/key-spacing.html) | enforce consistent spacing between keys and values in mapping pairs | :wrench: | | :star: |
190
204
  | [yml/no-irregular-whitespace](https://ota-meshi.github.io/eslint-plugin-yml/rules/no-irregular-whitespace.html) | disallow irregular whitespace | | :star: | :star: |
191
205
  | [yml/no-multiple-empty-lines](https://ota-meshi.github.io/eslint-plugin-yml/rules/no-multiple-empty-lines.html) | disallow multiple empty lines | :wrench: | | |
192
- | [yml/sort-keys](https://ota-meshi.github.io/eslint-plugin-yml/rules/sort-keys.html) | require mapping keys to be sorted | :wrench: | | |
193
206
  | [yml/spaced-comment](https://ota-meshi.github.io/eslint-plugin-yml/rules/spaced-comment.html) | enforce consistent spacing after the `#` in a comment | :wrench: | | :star: |
194
207
 
195
208
  <!--RULES_TABLE_END-->
@@ -37,6 +37,7 @@ exports.default = (0, utils_1.createRule)("indent", {
37
37
  const sourceCode = context.getSourceCode();
38
38
  const offsets = new Map();
39
39
  const marks = new Set();
40
+ const blockLiteralMarks = new Set();
40
41
  const scalars = new Map();
41
42
  function setOffset(token, offset, baseToken, options) {
42
43
  var _a;
@@ -224,6 +225,7 @@ exports.default = (0, utils_1.createRule)("indent", {
224
225
  const literal = sourceCode.getLastToken(node);
225
226
  setOffset(literal, 1, mark);
226
227
  scalars.set(literal, node);
228
+ blockLiteralMarks.add(mark);
227
229
  }
228
230
  else {
229
231
  scalars.set(sourceCode.getFirstToken(node), node);
@@ -283,6 +285,10 @@ exports.default = (0, utils_1.createRule)("indent", {
283
285
  1,
284
286
  actualOffset: nextToken.range[0] - token.range[1],
285
287
  });
288
+ if (blockLiteralMarks.has(nextToken)) {
289
+ lineTokens.unshift(nextToken);
290
+ break;
291
+ }
286
292
  token = nextToken;
287
293
  expectedIndent = nextExpectedIndent;
288
294
  cacheExpectedIndent = expectedIndent;
@@ -23,6 +23,35 @@ function getPropertyName(node, sourceCode) {
23
23
  }
24
24
  return sourceCode.text.slice(...target.range);
25
25
  }
26
+ class YAMLPairData {
27
+ constructor(mapping, node, index, anchorAlias) {
28
+ this.cachedName = null;
29
+ this.mapping = mapping;
30
+ this.node = node;
31
+ this.index = index;
32
+ this.anchorAlias = anchorAlias;
33
+ }
34
+ get reportLoc() {
35
+ var _a, _b;
36
+ return (_b = (_a = this.node.key) === null || _a === void 0 ? void 0 : _a.loc) !== null && _b !== void 0 ? _b : this.node.loc;
37
+ }
38
+ get name() {
39
+ var _a;
40
+ return ((_a = this.cachedName) !== null && _a !== void 0 ? _a : (this.cachedName = getPropertyName(this.node, this.mapping.sourceCode)));
41
+ }
42
+ }
43
+ class YAMLMappingData {
44
+ constructor(node, sourceCode, anchorAliasMap) {
45
+ this.cachedProperties = null;
46
+ this.node = node;
47
+ this.sourceCode = sourceCode;
48
+ this.anchorAliasMap = anchorAliasMap;
49
+ }
50
+ get pairs() {
51
+ var _a;
52
+ return ((_a = this.cachedProperties) !== null && _a !== void 0 ? _a : (this.cachedProperties = this.node.pairs.map((e, index) => new YAMLPairData(this, e, index, this.anchorAliasMap.get(e)))));
53
+ }
54
+ }
26
55
  function isCompatibleWithESLintOptions(options) {
27
56
  if (options.length === 0) {
28
57
  return true;
@@ -44,7 +73,7 @@ function buildValidatorFromType(order, insensitive, natural) {
44
73
  const baseCompare = compare;
45
74
  compare = (args) => baseCompare(args.reverse());
46
75
  }
47
- return (a, b) => compare([a, b]);
76
+ return (a, b) => compare([a.name, b.name]);
48
77
  }
49
78
  function parseOptions(options, sourceCode) {
50
79
  var _a, _b, _c;
@@ -56,16 +85,15 @@ function parseOptions(options, sourceCode) {
56
85
  const minKeys = (_c = obj.minKeys) !== null && _c !== void 0 ? _c : 2;
57
86
  return [
58
87
  {
59
- isTargetMapping: () => true,
88
+ isTargetMapping: (data) => data.node.pairs.length >= minKeys,
60
89
  ignore: () => false,
61
90
  isValidOrder: buildValidatorFromType(type, insensitive, natural),
62
- minKeys,
63
91
  orderText: `${natural ? "natural " : ""}${insensitive ? "insensitive " : ""}${type}ending`,
64
92
  },
65
93
  ];
66
94
  }
67
95
  return options.map((opt) => {
68
- var _a, _b, _c;
96
+ var _a, _b, _c, _d, _e;
69
97
  const order = opt.order;
70
98
  const pathPattern = new RegExp(opt.pathPattern);
71
99
  const hasProperties = (_a = opt.hasProperties) !== null && _a !== void 0 ? _a : [];
@@ -78,30 +106,65 @@ function parseOptions(options, sourceCode) {
78
106
  isTargetMapping,
79
107
  ignore: () => false,
80
108
  isValidOrder: buildValidatorFromType(type, insensitive, natural),
81
- minKeys,
82
109
  orderText: `${natural ? "natural " : ""}${insensitive ? "insensitive " : ""}${type}ending`,
83
110
  };
84
111
  }
112
+ const parsedOrder = [];
113
+ for (const o of order) {
114
+ if (typeof o === "string") {
115
+ parsedOrder.push({
116
+ test: (data) => data.name === o,
117
+ isValidNestOrder: () => true,
118
+ });
119
+ }
120
+ else {
121
+ const keyPattern = o.keyPattern
122
+ ? new RegExp(o.keyPattern)
123
+ : null;
124
+ const nestOrder = (_d = o.order) !== null && _d !== void 0 ? _d : {};
125
+ const type = (_e = nestOrder.type) !== null && _e !== void 0 ? _e : "asc";
126
+ const insensitive = nestOrder.caseSensitive === false;
127
+ const natural = Boolean(nestOrder.natural);
128
+ parsedOrder.push({
129
+ test: (data) => keyPattern ? keyPattern.test(data.name) : true,
130
+ isValidNestOrder: buildValidatorFromType(type, insensitive, natural),
131
+ });
132
+ }
133
+ }
85
134
  return {
86
135
  isTargetMapping,
87
- ignore: (s) => !order.includes(s),
136
+ ignore: (data) => parsedOrder.every((p) => !p.test(data)),
88
137
  isValidOrder(a, b) {
89
- const aIndex = order.indexOf(a);
90
- const bIndex = order.indexOf(b);
91
- return aIndex <= bIndex;
138
+ for (const p of parsedOrder) {
139
+ const matchA = p.test(a);
140
+ const matchB = p.test(b);
141
+ if (!matchA || !matchB) {
142
+ if (matchA) {
143
+ return true;
144
+ }
145
+ if (matchB) {
146
+ return false;
147
+ }
148
+ continue;
149
+ }
150
+ return p.isValidNestOrder(a, b);
151
+ }
152
+ return false;
92
153
  },
93
- minKeys,
94
154
  orderText: "specified",
95
155
  };
96
- function isTargetMapping(node) {
156
+ function isTargetMapping(data) {
157
+ if (data.node.pairs.length < minKeys) {
158
+ return false;
159
+ }
97
160
  if (hasProperties.length > 0) {
98
- const names = new Set(node.pairs.map((p) => getPropertyName(p, sourceCode)));
161
+ const names = new Set(data.pairs.map((p) => p.name));
99
162
  if (!hasProperties.every((name) => names.has(name))) {
100
163
  return false;
101
164
  }
102
165
  }
103
166
  let path = "";
104
- let curr = node;
167
+ let curr = data.node;
105
168
  let p = curr.parent;
106
169
  while (p) {
107
170
  if (p.type === "YAMLPair") {
@@ -127,13 +190,28 @@ function parseOptions(options, sourceCode) {
127
190
  }
128
191
  });
129
192
  }
130
- const allowOrderTypes = ["asc", "desc"];
193
+ const ALLOW_ORDER_TYPES = ["asc", "desc"];
194
+ const ORDER_OBJECT_SCHEMA = {
195
+ type: "object",
196
+ properties: {
197
+ type: {
198
+ enum: ALLOW_ORDER_TYPES,
199
+ },
200
+ caseSensitive: {
201
+ type: "boolean",
202
+ },
203
+ natural: {
204
+ type: "boolean",
205
+ },
206
+ },
207
+ additionalProperties: false,
208
+ };
131
209
  exports.default = (0, utils_1.createRule)("sort-keys", {
132
210
  meta: {
133
211
  docs: {
134
212
  description: "require mapping keys to be sorted",
135
213
  categories: null,
136
- extensionRule: "sort-keys",
214
+ extensionRule: false,
137
215
  layout: false,
138
216
  },
139
217
  fixable: "code",
@@ -153,24 +231,24 @@ exports.default = (0, utils_1.createRule)("sort-keys", {
153
231
  oneOf: [
154
232
  {
155
233
  type: "array",
156
- items: { type: "string" },
157
- uniqueItems: true,
158
- },
159
- {
160
- type: "object",
161
- properties: {
162
- type: {
163
- enum: allowOrderTypes,
164
- },
165
- caseSensitive: {
166
- type: "boolean",
167
- },
168
- natural: {
169
- type: "boolean",
170
- },
234
+ items: {
235
+ anyOf: [
236
+ { type: "string" },
237
+ {
238
+ type: "object",
239
+ properties: {
240
+ keyPattern: {
241
+ type: "string",
242
+ },
243
+ order: ORDER_OBJECT_SCHEMA,
244
+ },
245
+ additionalProperties: false,
246
+ },
247
+ ],
171
248
  },
172
- additionalProperties: false,
249
+ uniqueItems: true,
173
250
  },
251
+ ORDER_OBJECT_SCHEMA,
174
252
  ],
175
253
  },
176
254
  minKeys: {
@@ -187,7 +265,7 @@ exports.default = (0, utils_1.createRule)("sort-keys", {
187
265
  type: "array",
188
266
  items: [
189
267
  {
190
- enum: allowOrderTypes,
268
+ enum: ALLOW_ORDER_TYPES,
191
269
  },
192
270
  {
193
271
  type: "object",
@@ -216,51 +294,81 @@ exports.default = (0, utils_1.createRule)("sort-keys", {
216
294
  type: "suggestion",
217
295
  },
218
296
  create(context) {
219
- const sourceCode = context.getSourceCode();
220
297
  if (!context.parserServices.isYAML) {
221
298
  return {};
222
299
  }
300
+ const sourceCode = context.getSourceCode();
223
301
  const parsedOptions = parseOptions(context.options, sourceCode);
224
- let mappingStack = {
225
- upper: null,
226
- prevList: [],
227
- numKeys: 0,
228
- option: null,
229
- };
230
- let pairStack = {
231
- upper: null,
232
- anchors: new Set(),
233
- aliases: new Set(),
234
- };
235
302
  function isValidOrder(prevData, thisData, option) {
236
- if (option.isValidOrder(prevData.name, thisData.name)) {
303
+ if (option.isValidOrder(prevData, thisData)) {
237
304
  return true;
238
305
  }
239
- for (const aliasName of thisData.aliases) {
240
- if (prevData.anchors.has(aliasName)) {
306
+ for (const aliasName of thisData.anchorAlias.aliases) {
307
+ if (prevData.anchorAlias.anchors.has(aliasName)) {
241
308
  return true;
242
309
  }
243
310
  }
244
- for (const anchorName of thisData.anchors) {
245
- if (prevData.aliases.has(anchorName)) {
311
+ for (const anchorName of thisData.anchorAlias.anchors) {
312
+ if (prevData.anchorAlias.aliases.has(anchorName)) {
246
313
  return true;
247
314
  }
248
315
  }
249
316
  return false;
250
317
  }
318
+ function ignore(data, option) {
319
+ if (!data.node.key && !data.node.value) {
320
+ return true;
321
+ }
322
+ return option.ignore(data);
323
+ }
324
+ function verifyPair(data, option) {
325
+ if (ignore(data, option)) {
326
+ return;
327
+ }
328
+ const prevList = data.mapping.pairs
329
+ .slice(0, data.index)
330
+ .reverse()
331
+ .filter((d) => !ignore(d, option));
332
+ if (prevList.length === 0) {
333
+ return;
334
+ }
335
+ const prev = prevList[0];
336
+ if (!isValidOrder(prev, data, option)) {
337
+ context.report({
338
+ loc: data.reportLoc,
339
+ messageId: "sortKeys",
340
+ data: {
341
+ thisName: data.name,
342
+ prevName: prev.name,
343
+ orderText: option.orderText,
344
+ },
345
+ *fix(fixer) {
346
+ let moveTarget = prevList[0];
347
+ for (const prev of prevList) {
348
+ if (isValidOrder(prev, data, option)) {
349
+ break;
350
+ }
351
+ else {
352
+ moveTarget = prev;
353
+ }
354
+ }
355
+ if (data.mapping.node.style === "flow") {
356
+ yield* fixForFlow(fixer, data, moveTarget);
357
+ }
358
+ else {
359
+ yield* fixForBlock(fixer, data, moveTarget);
360
+ }
361
+ },
362
+ });
363
+ }
364
+ }
365
+ let pairStack = {
366
+ upper: null,
367
+ anchors: new Set(),
368
+ aliases: new Set(),
369
+ };
370
+ const anchorAliasMap = new Map();
251
371
  return {
252
- YAMLMapping(node) {
253
- mappingStack = {
254
- upper: mappingStack,
255
- prevList: [],
256
- numKeys: node.pairs.length,
257
- option: parsedOptions.find((o) => o.isTargetMapping(node)) ||
258
- null,
259
- };
260
- },
261
- "YAMLMapping:exit"() {
262
- mappingStack = mappingStack.upper;
263
- },
264
372
  YAMLPair() {
265
373
  pairStack = {
266
374
  upper: pairStack,
@@ -279,69 +387,28 @@ exports.default = (0, utils_1.createRule)("sort-keys", {
279
387
  }
280
388
  },
281
389
  "YAMLPair:exit"(node) {
282
- var _a, _b;
390
+ anchorAliasMap.set(node, pairStack);
283
391
  const { anchors, aliases } = pairStack;
284
392
  pairStack = pairStack.upper;
285
393
  pairStack.anchors = new Set([...pairStack.anchors, ...anchors]);
286
394
  pairStack.aliases = new Set([...pairStack.aliases, ...aliases]);
287
- if (!node.key && !node.value) {
288
- return;
289
- }
290
- const option = mappingStack.option;
395
+ },
396
+ "YAMLMapping:exit"(node) {
397
+ const data = new YAMLMappingData(node, sourceCode, anchorAliasMap);
398
+ const option = parsedOptions.find((o) => o.isTargetMapping(data));
291
399
  if (!option) {
292
400
  return;
293
401
  }
294
- const thisName = getPropertyName(node, sourceCode);
295
- if (option.ignore(thisName)) {
296
- return;
297
- }
298
- const prevList = mappingStack.prevList;
299
- const numKeys = mappingStack.numKeys;
300
- const thisData = {
301
- name: thisName,
302
- node,
303
- anchors,
304
- aliases,
305
- };
306
- mappingStack.prevList = [thisData, ...prevList];
307
- if (prevList.length === 0 || numKeys < option.minKeys) {
308
- return;
309
- }
310
- if (!isValidOrder(prevList[0], thisData, option)) {
311
- context.report({
312
- loc: (_b = (_a = node.key) === null || _a === void 0 ? void 0 : _a.loc) !== null && _b !== void 0 ? _b : node.loc,
313
- messageId: "sortKeys",
314
- data: {
315
- thisName,
316
- prevName: prevList[0].name,
317
- orderText: option.orderText,
318
- },
319
- *fix(fixer) {
320
- let moveTarget = prevList[0].node;
321
- for (const prev of prevList) {
322
- if (isValidOrder(prev, thisData, option)) {
323
- break;
324
- }
325
- else {
326
- moveTarget = prev.node;
327
- }
328
- }
329
- if (node.parent.style === "flow") {
330
- yield* fixForFlow(fixer, node, moveTarget);
331
- }
332
- else {
333
- yield* fixForBlock(fixer, node, moveTarget);
334
- }
335
- },
336
- });
402
+ for (const pair of data.pairs) {
403
+ verifyPair(pair, option);
337
404
  }
338
405
  },
339
406
  };
340
- function* fixForFlow(fixer, node, moveTarget) {
341
- const beforeCommaToken = sourceCode.getTokenBefore(node);
407
+ function* fixForFlow(fixer, data, moveTarget) {
408
+ const beforeCommaToken = sourceCode.getTokenBefore(data.node);
342
409
  let insertCode, removeRange, insertTargetToken;
343
- const afterCommaToken = sourceCode.getTokenAfter(node);
344
- const moveTargetBeforeToken = sourceCode.getTokenBefore(moveTarget);
410
+ const afterCommaToken = sourceCode.getTokenAfter(data.node);
411
+ const moveTargetBeforeToken = sourceCode.getTokenBefore(moveTarget.node);
345
412
  if ((0, ast_utils_1.isComma)(afterCommaToken)) {
346
413
  removeRange = [
347
414
  beforeCommaToken.range[1],
@@ -351,22 +418,22 @@ exports.default = (0, utils_1.createRule)("sort-keys", {
351
418
  insertTargetToken = moveTargetBeforeToken;
352
419
  }
353
420
  else {
354
- removeRange = [beforeCommaToken.range[0], node.range[1]];
421
+ removeRange = [beforeCommaToken.range[0], data.node.range[1]];
355
422
  if ((0, ast_utils_1.isComma)(moveTargetBeforeToken)) {
356
423
  insertCode = sourceCode.text.slice(...removeRange);
357
424
  insertTargetToken = sourceCode.getTokenBefore(moveTargetBeforeToken);
358
425
  }
359
426
  else {
360
- insertCode = `${sourceCode.text.slice(beforeCommaToken.range[1], node.range[1])},`;
427
+ insertCode = `${sourceCode.text.slice(beforeCommaToken.range[1], data.node.range[1])},`;
361
428
  insertTargetToken = moveTargetBeforeToken;
362
429
  }
363
430
  }
364
431
  yield fixer.insertTextAfterRange(insertTargetToken.range, insertCode);
365
432
  yield fixer.removeRange(removeRange);
366
433
  }
367
- function* fixForBlock(fixer, node, moveTarget) {
368
- const nodeLocs = getPairRangeForBlock(node);
369
- const moveTargetLocs = getPairRangeForBlock(moveTarget);
434
+ function* fixForBlock(fixer, data, moveTarget) {
435
+ const nodeLocs = getPairRangeForBlock(data.node);
436
+ const moveTargetLocs = getPairRangeForBlock(moveTarget.node);
370
437
  if (moveTargetLocs.loc.start.column === 0) {
371
438
  const removeRange = [
372
439
  getNewlineStartIndex(nodeLocs.range[0]),
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../types").RuleModule;
2
+ export default _default;
@@ -0,0 +1,512 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const natural_compare_1 = __importDefault(require("natural-compare"));
7
+ const utils_1 = require("../utils");
8
+ const ast_utils_1 = require("../utils/ast-utils");
9
+ const yaml_eslint_parser_1 = require("yaml-eslint-parser");
10
+ class YAMLEntryData {
11
+ constructor(sequence, node, index, anchorAlias) {
12
+ this.cached = null;
13
+ this.cachedRange = null;
14
+ this.cachedAroundTokens = null;
15
+ this.sequence = sequence;
16
+ this.node = node;
17
+ this.index = index;
18
+ this.anchorAlias = anchorAlias;
19
+ }
20
+ get reportLoc() {
21
+ if (this.node) {
22
+ return this.node.loc;
23
+ }
24
+ const aroundTokens = this.aroundTokens;
25
+ return {
26
+ start: aroundTokens.before.loc.end,
27
+ end: aroundTokens.after.loc.start,
28
+ };
29
+ }
30
+ get range() {
31
+ if (this.node) {
32
+ return this.node.range;
33
+ }
34
+ if (this.cachedRange) {
35
+ return this.cachedRange;
36
+ }
37
+ const aroundTokens = this.aroundTokens;
38
+ return (this.cachedRange = [
39
+ aroundTokens.before.range[1],
40
+ aroundTokens.after.range[0],
41
+ ]);
42
+ }
43
+ get aroundTokens() {
44
+ if (this.cachedAroundTokens) {
45
+ return this.cachedAroundTokens;
46
+ }
47
+ const sourceCode = this.sequence.sourceCode;
48
+ if (this.node) {
49
+ return (this.cachedAroundTokens = {
50
+ before: sourceCode.getTokenBefore(this.node),
51
+ after: sourceCode.getTokenAfter(this.node),
52
+ });
53
+ }
54
+ const before = this.index > 0
55
+ ? this.sequence.entries[this.index - 1].aroundTokens.after
56
+ : sourceCode.getFirstToken(this.sequence.node);
57
+ const after = sourceCode.getTokenAfter(before);
58
+ return (this.cachedAroundTokens = { before, after });
59
+ }
60
+ get value() {
61
+ var _a;
62
+ return ((_a = this.cached) !== null && _a !== void 0 ? _a : (this.cached = {
63
+ value: this.node == null ? null : (0, yaml_eslint_parser_1.getStaticYAMLValue)(this.node),
64
+ })).value;
65
+ }
66
+ }
67
+ class YAMLSequenceData {
68
+ constructor(node, sourceCode, anchorAliasMap) {
69
+ this.cachedEntries = null;
70
+ this.node = node;
71
+ this.sourceCode = sourceCode;
72
+ this.anchorAliasMap = anchorAliasMap;
73
+ }
74
+ get entries() {
75
+ var _a;
76
+ return ((_a = this.cachedEntries) !== null && _a !== void 0 ? _a : (this.cachedEntries = this.node.entries.map((e, index) => new YAMLEntryData(this, e, index, this.anchorAliasMap.get(e)))));
77
+ }
78
+ }
79
+ function buildValidatorFromType(order, insensitive, natural) {
80
+ let compareValue = ([a, b]) => a <= b;
81
+ let compareText = compareValue;
82
+ if (natural) {
83
+ compareText = ([a, b]) => (0, natural_compare_1.default)(a, b) <= 0;
84
+ }
85
+ if (insensitive) {
86
+ const baseCompareText = compareText;
87
+ compareText = ([a, b]) => baseCompareText([a.toLowerCase(), b.toLowerCase()]);
88
+ }
89
+ if (order === "desc") {
90
+ const baseCompareText = compareText;
91
+ compareText = (args) => baseCompareText(args.reverse());
92
+ const baseCompareValue = compareValue;
93
+ compareValue = (args) => baseCompareValue(args.reverse());
94
+ }
95
+ return (a, b) => {
96
+ if (typeof a.value === "string" && typeof b.value === "string") {
97
+ return compareText([a.value, b.value]);
98
+ }
99
+ const type = getYAMLPrimitiveType(a.value);
100
+ if (type && type === getYAMLPrimitiveType(b.value)) {
101
+ return compareValue([a.value, b.value]);
102
+ }
103
+ return true;
104
+ };
105
+ }
106
+ function parseOptions(options, sourceCode) {
107
+ return options.map((opt) => {
108
+ var _a, _b, _c, _d;
109
+ const order = opt.order;
110
+ const pathPattern = new RegExp(opt.pathPattern);
111
+ const minValues = (_a = opt.minValues) !== null && _a !== void 0 ? _a : 2;
112
+ if (!Array.isArray(order)) {
113
+ const type = (_b = order.type) !== null && _b !== void 0 ? _b : "asc";
114
+ const insensitive = order.caseSensitive === false;
115
+ const natural = Boolean(order.natural);
116
+ return {
117
+ isTargetArray,
118
+ ignore: () => false,
119
+ isValidOrder: buildValidatorFromType(type, insensitive, natural),
120
+ orderText(data) {
121
+ if (typeof data.value === "string") {
122
+ return `${natural ? "natural " : ""}${insensitive ? "insensitive " : ""}${type}ending`;
123
+ }
124
+ return `${type}ending`;
125
+ },
126
+ };
127
+ }
128
+ const parsedOrder = [];
129
+ for (const o of order) {
130
+ if (typeof o === "string") {
131
+ parsedOrder.push({
132
+ test: (v) => v.value === o,
133
+ isValidNestOrder: () => true,
134
+ });
135
+ }
136
+ else {
137
+ const valuePattern = o.valuePattern
138
+ ? new RegExp(o.valuePattern)
139
+ : null;
140
+ const nestOrder = (_c = o.order) !== null && _c !== void 0 ? _c : {};
141
+ const type = (_d = nestOrder.type) !== null && _d !== void 0 ? _d : "asc";
142
+ const insensitive = nestOrder.caseSensitive === false;
143
+ const natural = Boolean(nestOrder.natural);
144
+ parsedOrder.push({
145
+ test: (v) => valuePattern
146
+ ? Boolean(getYAMLPrimitiveType(v.value)) &&
147
+ valuePattern.test(String(v.value))
148
+ : true,
149
+ isValidNestOrder: buildValidatorFromType(type, insensitive, natural),
150
+ });
151
+ }
152
+ }
153
+ return {
154
+ isTargetArray,
155
+ ignore: (v) => parsedOrder.every((p) => !p.test(v)),
156
+ isValidOrder(a, b) {
157
+ for (const p of parsedOrder) {
158
+ const matchA = p.test(a);
159
+ const matchB = p.test(b);
160
+ if (!matchA || !matchB) {
161
+ if (matchA) {
162
+ return true;
163
+ }
164
+ if (matchB) {
165
+ return false;
166
+ }
167
+ continue;
168
+ }
169
+ return p.isValidNestOrder(a, b);
170
+ }
171
+ return false;
172
+ },
173
+ orderText: () => "specified",
174
+ };
175
+ function isTargetArray(data) {
176
+ if (data.node.entries.length < minValues) {
177
+ return false;
178
+ }
179
+ let path = "";
180
+ let curr = data.node;
181
+ let p = curr.parent;
182
+ while (p) {
183
+ if (p.type === "YAMLPair") {
184
+ const name = getPropertyName(p);
185
+ if (/^[$_a-z][\w$]*$/iu.test(name)) {
186
+ path = `.${name}${path}`;
187
+ }
188
+ else {
189
+ path = `[${JSON.stringify(name)}]${path}`;
190
+ }
191
+ }
192
+ else if (p.type === "YAMLSequence") {
193
+ const index = p.entries.indexOf(curr);
194
+ path = `[${index}]${path}`;
195
+ }
196
+ curr = p;
197
+ p = curr.parent;
198
+ }
199
+ if (path.startsWith(".")) {
200
+ path = path.slice(1);
201
+ }
202
+ return pathPattern.test(path);
203
+ }
204
+ });
205
+ function getPropertyName(node) {
206
+ const prop = node.key;
207
+ if (prop == null) {
208
+ return "";
209
+ }
210
+ const target = prop.type === "YAMLWithMeta" ? prop.value : prop;
211
+ if (target == null) {
212
+ return "";
213
+ }
214
+ if (target.type === "YAMLScalar" && typeof target.value === "string") {
215
+ return target.value;
216
+ }
217
+ return sourceCode.text.slice(...target.range);
218
+ }
219
+ }
220
+ function getYAMLPrimitiveType(val) {
221
+ const t = typeof val;
222
+ if (t === "string" || t === "number" || t === "boolean" || t === "bigint") {
223
+ return t;
224
+ }
225
+ if (val === null) {
226
+ return "null";
227
+ }
228
+ if (val === undefined) {
229
+ return "undefined";
230
+ }
231
+ if (val instanceof RegExp) {
232
+ return "regexp";
233
+ }
234
+ return null;
235
+ }
236
+ const ALLOW_ORDER_TYPES = ["asc", "desc"];
237
+ const ORDER_OBJECT_SCHEMA = {
238
+ type: "object",
239
+ properties: {
240
+ type: {
241
+ enum: ALLOW_ORDER_TYPES,
242
+ },
243
+ caseSensitive: {
244
+ type: "boolean",
245
+ },
246
+ natural: {
247
+ type: "boolean",
248
+ },
249
+ },
250
+ additionalProperties: false,
251
+ };
252
+ exports.default = (0, utils_1.createRule)("sort-sequence-values", {
253
+ meta: {
254
+ docs: {
255
+ description: "require sequence values to be sorted",
256
+ categories: null,
257
+ extensionRule: false,
258
+ layout: false,
259
+ },
260
+ fixable: "code",
261
+ schema: {
262
+ type: "array",
263
+ items: {
264
+ type: "object",
265
+ properties: {
266
+ pathPattern: { type: "string" },
267
+ order: {
268
+ oneOf: [
269
+ {
270
+ type: "array",
271
+ items: {
272
+ anyOf: [
273
+ { type: "string" },
274
+ {
275
+ type: "object",
276
+ properties: {
277
+ valuePattern: {
278
+ type: "string",
279
+ },
280
+ order: ORDER_OBJECT_SCHEMA,
281
+ },
282
+ additionalProperties: false,
283
+ },
284
+ ],
285
+ },
286
+ uniqueItems: true,
287
+ },
288
+ ORDER_OBJECT_SCHEMA,
289
+ ],
290
+ },
291
+ minValues: {
292
+ type: "integer",
293
+ minimum: 2,
294
+ },
295
+ },
296
+ required: ["pathPattern", "order"],
297
+ additionalProperties: false,
298
+ },
299
+ minItems: 1,
300
+ },
301
+ messages: {
302
+ sortValues: "Expected sequence values to be in {{orderText}} order. '{{thisValue}}' should be before '{{prevValue}}'.",
303
+ },
304
+ type: "suggestion",
305
+ },
306
+ create(context) {
307
+ if (!context.parserServices.isYAML) {
308
+ return {};
309
+ }
310
+ const sourceCode = context.getSourceCode();
311
+ const parsedOptions = parseOptions(context.options, sourceCode);
312
+ function isValidOrder(prevData, thisData, option) {
313
+ if (option.isValidOrder(prevData, thisData)) {
314
+ return true;
315
+ }
316
+ for (const aliasName of thisData.anchorAlias.aliases) {
317
+ if (prevData.anchorAlias.anchors.has(aliasName)) {
318
+ return true;
319
+ }
320
+ }
321
+ for (const anchorName of thisData.anchorAlias.anchors) {
322
+ if (prevData.anchorAlias.aliases.has(anchorName)) {
323
+ return true;
324
+ }
325
+ }
326
+ return false;
327
+ }
328
+ function verifyArrayElement(data, option) {
329
+ if (option.ignore(data)) {
330
+ return;
331
+ }
332
+ const prevList = data.sequence.entries
333
+ .slice(0, data.index)
334
+ .reverse()
335
+ .filter((d) => !option.ignore(d));
336
+ if (prevList.length === 0) {
337
+ return;
338
+ }
339
+ const prev = prevList[0];
340
+ if (!isValidOrder(prev, data, option)) {
341
+ const reportLoc = data.reportLoc;
342
+ context.report({
343
+ loc: reportLoc,
344
+ messageId: "sortValues",
345
+ data: {
346
+ thisValue: toText(data),
347
+ prevValue: toText(prev),
348
+ orderText: option.orderText(data),
349
+ },
350
+ *fix(fixer) {
351
+ let moveTarget = prevList[0];
352
+ for (const prev of prevList) {
353
+ if (isValidOrder(prev, data, option)) {
354
+ break;
355
+ }
356
+ else {
357
+ moveTarget = prev;
358
+ }
359
+ }
360
+ if (data.sequence.node.style === "flow") {
361
+ yield* fixForFlow(fixer, data, moveTarget);
362
+ }
363
+ else {
364
+ yield* fixForBlock(fixer, data, moveTarget);
365
+ }
366
+ },
367
+ });
368
+ }
369
+ }
370
+ function toText(data) {
371
+ if (getYAMLPrimitiveType(data.value)) {
372
+ return String(data.value);
373
+ }
374
+ return sourceCode.getText(data.node);
375
+ }
376
+ let entryStack = {
377
+ upper: null,
378
+ anchors: new Set(),
379
+ aliases: new Set(),
380
+ };
381
+ const anchorAliasMap = new Map();
382
+ return {
383
+ "YAMLSequence > *"(node) {
384
+ if (!node.parent.entries.includes(node)) {
385
+ return;
386
+ }
387
+ entryStack = {
388
+ upper: entryStack,
389
+ anchors: new Set(),
390
+ aliases: new Set(),
391
+ };
392
+ if (node.type === "YAMLAlias") {
393
+ entryStack.aliases.add(node.name);
394
+ }
395
+ },
396
+ YAMLAnchor(node) {
397
+ if (entryStack) {
398
+ entryStack.anchors.add(node.name);
399
+ }
400
+ },
401
+ YAMLAlias(node) {
402
+ if (entryStack) {
403
+ entryStack.aliases.add(node.name);
404
+ }
405
+ },
406
+ "YAMLSequence > *:exit"(node) {
407
+ if (!node.parent.entries.includes(node)) {
408
+ return;
409
+ }
410
+ anchorAliasMap.set(node, entryStack);
411
+ const { anchors, aliases } = entryStack;
412
+ entryStack = entryStack.upper;
413
+ entryStack.anchors = new Set([
414
+ ...entryStack.anchors,
415
+ ...anchors,
416
+ ]);
417
+ entryStack.aliases = new Set([
418
+ ...entryStack.aliases,
419
+ ...aliases,
420
+ ]);
421
+ },
422
+ "YAMLSequence:exit"(node) {
423
+ const data = new YAMLSequenceData(node, sourceCode, anchorAliasMap);
424
+ const option = parsedOptions.find((o) => o.isTargetArray(data));
425
+ if (!option) {
426
+ return;
427
+ }
428
+ for (const element of data.entries) {
429
+ verifyArrayElement(element, option);
430
+ }
431
+ },
432
+ };
433
+ function* fixForFlow(fixer, data, moveTarget) {
434
+ const beforeToken = data.aroundTokens.before;
435
+ const afterToken = data.aroundTokens.after;
436
+ let insertCode, removeRange, insertTargetToken;
437
+ if ((0, ast_utils_1.isComma)(afterToken)) {
438
+ removeRange = [beforeToken.range[1], afterToken.range[1]];
439
+ insertCode = sourceCode.text.slice(...removeRange);
440
+ insertTargetToken = moveTarget.aroundTokens.before;
441
+ }
442
+ else {
443
+ removeRange = [beforeToken.range[0], data.range[1]];
444
+ if ((0, ast_utils_1.isComma)(moveTarget.aroundTokens.before)) {
445
+ insertCode = sourceCode.text.slice(...removeRange);
446
+ insertTargetToken = sourceCode.getTokenBefore(moveTarget.aroundTokens.before);
447
+ }
448
+ else {
449
+ insertCode = `${sourceCode.text.slice(beforeToken.range[1], data.range[1])},`;
450
+ insertTargetToken = moveTarget.aroundTokens.before;
451
+ }
452
+ }
453
+ yield fixer.insertTextAfterRange(insertTargetToken.range, insertCode);
454
+ yield fixer.removeRange(removeRange);
455
+ }
456
+ function* fixForBlock(fixer, data, moveTarget) {
457
+ const moveDataList = data.sequence.entries.slice(moveTarget.index, data.index + 1);
458
+ let replacementCodeRange = getBlockEntryRange(data);
459
+ for (const target of moveDataList) {
460
+ const range = getBlockEntryRange(target);
461
+ yield fixer.replaceTextRange(range, sourceCode.text.slice(...replacementCodeRange));
462
+ replacementCodeRange = range;
463
+ }
464
+ }
465
+ function getBlockEntryRange(data) {
466
+ return [
467
+ getBlockEntryStartOffset(data),
468
+ getBlockEntryEndOffset(data),
469
+ ];
470
+ }
471
+ function getBlockEntryStartOffset(data) {
472
+ const beforeHyphenToken = sourceCode.getTokenBefore(data.aroundTokens.before);
473
+ if (!beforeHyphenToken) {
474
+ const comment = sourceCode.getTokenBefore(data.aroundTokens.before, {
475
+ includeComments: true,
476
+ });
477
+ if (comment &&
478
+ data.aroundTokens.before.loc.start.column <=
479
+ comment.loc.start.column) {
480
+ return comment.range[0];
481
+ }
482
+ return data.aroundTokens.before.range[0];
483
+ }
484
+ let next = sourceCode.getTokenAfter(beforeHyphenToken, {
485
+ includeComments: true,
486
+ });
487
+ while (beforeHyphenToken.loc.end.line === next.loc.start.line &&
488
+ next.range[1] < data.aroundTokens.before.range[0]) {
489
+ next = sourceCode.getTokenAfter(next, {
490
+ includeComments: true,
491
+ });
492
+ }
493
+ return next.range[0];
494
+ }
495
+ function getBlockEntryEndOffset(data) {
496
+ var _a;
497
+ const valueEndToken = (_a = data.node) !== null && _a !== void 0 ? _a : data.aroundTokens.before;
498
+ let last = valueEndToken;
499
+ let afterToken = sourceCode.getTokenAfter(last, {
500
+ includeComments: true,
501
+ });
502
+ while (afterToken &&
503
+ valueEndToken.loc.end.line === afterToken.loc.start.line) {
504
+ last = afterToken;
505
+ afterToken = sourceCode.getTokenAfter(last, {
506
+ includeComments: true,
507
+ });
508
+ }
509
+ return last.range[1];
510
+ }
511
+ },
512
+ });
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
3
  if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
5
9
  }) : (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
7
11
  o[k2] = m[k];
@@ -26,6 +26,7 @@ const plain_scalar_1 = __importDefault(require("../rules/plain-scalar"));
26
26
  const quotes_1 = __importDefault(require("../rules/quotes"));
27
27
  const require_string_key_1 = __importDefault(require("../rules/require-string-key"));
28
28
  const sort_keys_1 = __importDefault(require("../rules/sort-keys"));
29
+ const sort_sequence_values_1 = __importDefault(require("../rules/sort-sequence-values"));
29
30
  const spaced_comment_1 = __importDefault(require("../rules/spaced-comment"));
30
31
  const no_parsing_error_1 = __importDefault(require("../rules/vue-custom-block/no-parsing-error"));
31
32
  exports.rules = [
@@ -51,6 +52,7 @@ exports.rules = [
51
52
  quotes_1.default,
52
53
  require_string_key_1.default,
53
54
  sort_keys_1.default,
55
+ sort_sequence_values_1.default,
54
56
  spaced_comment_1.default,
55
57
  no_parsing_error_1.default,
56
58
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-yml",
3
- "version": "0.12.0",
3
+ "version": "0.15.0",
4
4
  "description": "This ESLint plugin provides linting rules for YAML.",
5
5
  "main": "lib/index.js",
6
6
  "files": [
@@ -42,6 +42,7 @@
42
42
  "yml"
43
43
  ],
44
44
  "author": "Yosuke Ota",
45
+ "funding": "https://github.com/sponsors/ota-meshi",
45
46
  "license": "MIT",
46
47
  "bugs": {
47
48
  "url": "https://github.com/ota-meshi/eslint-plugin-yml/issues"
@@ -62,7 +63,7 @@
62
63
  "@types/eslint": "^8.0.0",
63
64
  "@types/eslint-scope": "^3.7.0",
64
65
  "@types/eslint-visitor-keys": "^1.0.0",
65
- "@types/estree": "^0.0.50",
66
+ "@types/estree": "^0.0.51",
66
67
  "@types/lodash": "^4.14.158",
67
68
  "@types/mocha": "^9.0.0",
68
69
  "@types/natural-compare": "^1.4.0",
@@ -80,27 +81,27 @@
80
81
  "eslint-plugin-jsonc": "^2.0.0",
81
82
  "eslint-plugin-markdown": "^2.0.0-0",
82
83
  "eslint-plugin-node": "^11.1.0",
83
- "eslint-plugin-node-dependencies": "^0.6.0",
84
+ "eslint-plugin-node-dependencies": "^0.8.0",
84
85
  "eslint-plugin-prettier": "^4.0.0",
85
86
  "eslint-plugin-regexp": "^1.0.0",
86
87
  "eslint-plugin-vue": "^8.0.0",
87
- "eslint-plugin-yml": "^0.11.0",
88
+ "eslint-plugin-yml": "^0.14.0",
88
89
  "eslint4b": "^7.3.1",
89
90
  "espree": "^9.0.0",
90
- "mocha": "^9.0.0",
91
- "monaco-editor": "^0.30.0",
91
+ "mocha": "^10.0.0",
92
+ "monaco-editor": "^0.33.0",
92
93
  "nyc": "^15.1.0",
93
94
  "prettier": "^2.2.1",
94
95
  "raw-loader": "^4.0.1",
95
96
  "semver": "^7.3.2",
96
97
  "stylelint": "^14.0.0",
97
98
  "stylelint-config-recommended-vue": "^1.0.0",
98
- "stylelint-config-standard": "^24.0.0",
99
+ "stylelint-config-standard": "^25.0.0",
99
100
  "stylelint-plugin-stylus": "^0.13.0",
100
101
  "ts-node": "^10.0.0",
101
- "typescript": "~4.5.0",
102
+ "typescript": "~4.6.0",
102
103
  "vue-eslint-editor": "^1.1.0",
103
- "vue-eslint-parser": "^8.0.0",
104
+ "vue-eslint-parser": "^9.0.0",
104
105
  "vuepress": "^1.5.2"
105
106
  }
106
107
  }