eslint-plugin-yml 0.13.0 → 0.14.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
@@ -188,6 +188,8 @@ The rules with the following star :star: are included in the config.
188
188
  | [yml/plain-scalar](https://ota-meshi.github.io/eslint-plugin-yml/rules/plain-scalar.html) | require or disallow plain style scalar. | :wrench: | | :star: |
189
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: |
190
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: | | |
191
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: |
192
194
 
193
195
  ### Extension Rules
@@ -201,7 +203,6 @@ The rules with the following star :star: are included in the config.
201
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: |
202
204
  | [yml/no-irregular-whitespace](https://ota-meshi.github.io/eslint-plugin-yml/rules/no-irregular-whitespace.html) | disallow irregular whitespace | | :star: | :star: |
203
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: | | |
204
- | [yml/sort-keys](https://ota-meshi.github.io/eslint-plugin-yml/rules/sort-keys.html) | require mapping keys to be sorted | :wrench: | | |
205
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: |
206
207
 
207
208
  <!--RULES_TABLE_END-->
@@ -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,10 +85,9 @@ 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
  ];
@@ -78,7 +106,6 @@ 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
  }
@@ -86,7 +113,7 @@ function parseOptions(options, sourceCode) {
86
113
  for (const o of order) {
87
114
  if (typeof o === "string") {
88
115
  parsedOrder.push({
89
- test: (s) => s === o,
116
+ test: (data) => data.name === o,
90
117
  isValidNestOrder: () => true,
91
118
  });
92
119
  }
@@ -99,14 +126,14 @@ function parseOptions(options, sourceCode) {
99
126
  const insensitive = nestOrder.caseSensitive === false;
100
127
  const natural = Boolean(nestOrder.natural);
101
128
  parsedOrder.push({
102
- test: (s) => (keyPattern ? keyPattern.test(s) : true),
129
+ test: (data) => keyPattern ? keyPattern.test(data.name) : true,
103
130
  isValidNestOrder: buildValidatorFromType(type, insensitive, natural),
104
131
  });
105
132
  }
106
133
  }
107
134
  return {
108
135
  isTargetMapping,
109
- ignore: (s) => parsedOrder.every((p) => !p.test(s)),
136
+ ignore: (data) => parsedOrder.every((p) => !p.test(data)),
110
137
  isValidOrder(a, b) {
111
138
  for (const p of parsedOrder) {
112
139
  const matchA = p.test(a);
@@ -124,18 +151,20 @@ function parseOptions(options, sourceCode) {
124
151
  }
125
152
  return false;
126
153
  },
127
- minKeys,
128
154
  orderText: "specified",
129
155
  };
130
- function isTargetMapping(node) {
156
+ function isTargetMapping(data) {
157
+ if (data.node.pairs.length < minKeys) {
158
+ return false;
159
+ }
131
160
  if (hasProperties.length > 0) {
132
- const names = new Set(node.pairs.map((p) => getPropertyName(p, sourceCode)));
161
+ const names = new Set(data.pairs.map((p) => p.name));
133
162
  if (!hasProperties.every((name) => names.has(name))) {
134
163
  return false;
135
164
  }
136
165
  }
137
166
  let path = "";
138
- let curr = node;
167
+ let curr = data.node;
139
168
  let p = curr.parent;
140
169
  while (p) {
141
170
  if (p.type === "YAMLPair") {
@@ -182,7 +211,7 @@ exports.default = (0, utils_1.createRule)("sort-keys", {
182
211
  docs: {
183
212
  description: "require mapping keys to be sorted",
184
213
  categories: null,
185
- extensionRule: "sort-keys",
214
+ extensionRule: false,
186
215
  layout: false,
187
216
  },
188
217
  fixable: "code",
@@ -265,51 +294,81 @@ exports.default = (0, utils_1.createRule)("sort-keys", {
265
294
  type: "suggestion",
266
295
  },
267
296
  create(context) {
268
- const sourceCode = context.getSourceCode();
269
297
  if (!context.parserServices.isYAML) {
270
298
  return {};
271
299
  }
300
+ const sourceCode = context.getSourceCode();
272
301
  const parsedOptions = parseOptions(context.options, sourceCode);
273
- let mappingStack = {
274
- upper: null,
275
- prevList: [],
276
- numKeys: 0,
277
- option: null,
278
- };
279
- let pairStack = {
280
- upper: null,
281
- anchors: new Set(),
282
- aliases: new Set(),
283
- };
284
302
  function isValidOrder(prevData, thisData, option) {
285
- if (option.isValidOrder(prevData.name, thisData.name)) {
303
+ if (option.isValidOrder(prevData, thisData)) {
286
304
  return true;
287
305
  }
288
- for (const aliasName of thisData.aliases) {
289
- if (prevData.anchors.has(aliasName)) {
306
+ for (const aliasName of thisData.anchorAlias.aliases) {
307
+ if (prevData.anchorAlias.anchors.has(aliasName)) {
290
308
  return true;
291
309
  }
292
310
  }
293
- for (const anchorName of thisData.anchors) {
294
- if (prevData.aliases.has(anchorName)) {
311
+ for (const anchorName of thisData.anchorAlias.anchors) {
312
+ if (prevData.anchorAlias.aliases.has(anchorName)) {
295
313
  return true;
296
314
  }
297
315
  }
298
316
  return false;
299
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();
300
371
  return {
301
- YAMLMapping(node) {
302
- mappingStack = {
303
- upper: mappingStack,
304
- prevList: [],
305
- numKeys: node.pairs.length,
306
- option: parsedOptions.find((o) => o.isTargetMapping(node)) ||
307
- null,
308
- };
309
- },
310
- "YAMLMapping:exit"() {
311
- mappingStack = mappingStack.upper;
312
- },
313
372
  YAMLPair() {
314
373
  pairStack = {
315
374
  upper: pairStack,
@@ -328,69 +387,28 @@ exports.default = (0, utils_1.createRule)("sort-keys", {
328
387
  }
329
388
  },
330
389
  "YAMLPair:exit"(node) {
331
- var _a, _b;
390
+ anchorAliasMap.set(node, pairStack);
332
391
  const { anchors, aliases } = pairStack;
333
392
  pairStack = pairStack.upper;
334
393
  pairStack.anchors = new Set([...pairStack.anchors, ...anchors]);
335
394
  pairStack.aliases = new Set([...pairStack.aliases, ...aliases]);
336
- if (!node.key && !node.value) {
337
- return;
338
- }
339
- 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));
340
399
  if (!option) {
341
400
  return;
342
401
  }
343
- const thisName = getPropertyName(node, sourceCode);
344
- if (option.ignore(thisName)) {
345
- return;
346
- }
347
- const prevList = mappingStack.prevList;
348
- const numKeys = mappingStack.numKeys;
349
- const thisData = {
350
- name: thisName,
351
- node,
352
- anchors,
353
- aliases,
354
- };
355
- mappingStack.prevList = [thisData, ...prevList];
356
- if (prevList.length === 0 || numKeys < option.minKeys) {
357
- return;
358
- }
359
- if (!isValidOrder(prevList[0], thisData, option)) {
360
- context.report({
361
- loc: (_b = (_a = node.key) === null || _a === void 0 ? void 0 : _a.loc) !== null && _b !== void 0 ? _b : node.loc,
362
- messageId: "sortKeys",
363
- data: {
364
- thisName,
365
- prevName: prevList[0].name,
366
- orderText: option.orderText,
367
- },
368
- *fix(fixer) {
369
- let moveTarget = prevList[0].node;
370
- for (const prev of prevList) {
371
- if (isValidOrder(prev, thisData, option)) {
372
- break;
373
- }
374
- else {
375
- moveTarget = prev.node;
376
- }
377
- }
378
- if (node.parent.style === "flow") {
379
- yield* fixForFlow(fixer, node, moveTarget);
380
- }
381
- else {
382
- yield* fixForBlock(fixer, node, moveTarget);
383
- }
384
- },
385
- });
402
+ for (const pair of data.pairs) {
403
+ verifyPair(pair, option);
386
404
  }
387
405
  },
388
406
  };
389
- function* fixForFlow(fixer, node, moveTarget) {
390
- const beforeCommaToken = sourceCode.getTokenBefore(node);
407
+ function* fixForFlow(fixer, data, moveTarget) {
408
+ const beforeCommaToken = sourceCode.getTokenBefore(data.node);
391
409
  let insertCode, removeRange, insertTargetToken;
392
- const afterCommaToken = sourceCode.getTokenAfter(node);
393
- const moveTargetBeforeToken = sourceCode.getTokenBefore(moveTarget);
410
+ const afterCommaToken = sourceCode.getTokenAfter(data.node);
411
+ const moveTargetBeforeToken = sourceCode.getTokenBefore(moveTarget.node);
394
412
  if ((0, ast_utils_1.isComma)(afterCommaToken)) {
395
413
  removeRange = [
396
414
  beforeCommaToken.range[1],
@@ -400,22 +418,22 @@ exports.default = (0, utils_1.createRule)("sort-keys", {
400
418
  insertTargetToken = moveTargetBeforeToken;
401
419
  }
402
420
  else {
403
- removeRange = [beforeCommaToken.range[0], node.range[1]];
421
+ removeRange = [beforeCommaToken.range[0], data.node.range[1]];
404
422
  if ((0, ast_utils_1.isComma)(moveTargetBeforeToken)) {
405
423
  insertCode = sourceCode.text.slice(...removeRange);
406
424
  insertTargetToken = sourceCode.getTokenBefore(moveTargetBeforeToken);
407
425
  }
408
426
  else {
409
- insertCode = `${sourceCode.text.slice(beforeCommaToken.range[1], node.range[1])},`;
427
+ insertCode = `${sourceCode.text.slice(beforeCommaToken.range[1], data.node.range[1])},`;
410
428
  insertTargetToken = moveTargetBeforeToken;
411
429
  }
412
430
  }
413
431
  yield fixer.insertTextAfterRange(insertTargetToken.range, insertCode);
414
432
  yield fixer.removeRange(removeRange);
415
433
  }
416
- function* fixForBlock(fixer, node, moveTarget) {
417
- const nodeLocs = getPairRangeForBlock(node);
418
- const moveTargetLocs = getPairRangeForBlock(moveTarget);
434
+ function* fixForBlock(fixer, data, moveTarget) {
435
+ const nodeLocs = getPairRangeForBlock(data.node);
436
+ const moveTargetLocs = getPairRangeForBlock(moveTarget.node);
419
437
  if (moveTargetLocs.loc.start.column === 0) {
420
438
  const removeRange = [
421
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
+ });
@@ -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.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "This ESLint plugin provides linting rules for YAML.",
5
5
  "main": "lib/index.js",
6
6
  "files": [
@@ -63,7 +63,7 @@
63
63
  "@types/eslint": "^8.0.0",
64
64
  "@types/eslint-scope": "^3.7.0",
65
65
  "@types/eslint-visitor-keys": "^1.0.0",
66
- "@types/estree": "^0.0.50",
66
+ "@types/estree": "^0.0.51",
67
67
  "@types/lodash": "^4.14.158",
68
68
  "@types/mocha": "^9.0.0",
69
69
  "@types/natural-compare": "^1.4.0",
@@ -85,18 +85,18 @@
85
85
  "eslint-plugin-prettier": "^4.0.0",
86
86
  "eslint-plugin-regexp": "^1.0.0",
87
87
  "eslint-plugin-vue": "^8.0.0",
88
- "eslint-plugin-yml": "^0.12.0",
88
+ "eslint-plugin-yml": "^0.13.0",
89
89
  "eslint4b": "^7.3.1",
90
90
  "espree": "^9.0.0",
91
91
  "mocha": "^9.0.0",
92
- "monaco-editor": "^0.31.0",
92
+ "monaco-editor": "^0.32.0",
93
93
  "nyc": "^15.1.0",
94
94
  "prettier": "^2.2.1",
95
95
  "raw-loader": "^4.0.1",
96
96
  "semver": "^7.3.2",
97
97
  "stylelint": "^14.0.0",
98
98
  "stylelint-config-recommended-vue": "^1.0.0",
99
- "stylelint-config-standard": "^24.0.0",
99
+ "stylelint-config-standard": "^25.0.0",
100
100
  "stylelint-plugin-stylus": "^0.13.0",
101
101
  "ts-node": "^10.0.0",
102
102
  "typescript": "~4.5.0",