flatlint 4.13.0 → 5.0.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/ChangeLog CHANGED
@@ -1,3 +1,25 @@
1
+ 2026.02.17, v5.0.0
2
+
3
+ feature:
4
+ - e97e93a compare: for-of
5
+ - 39351ad feature: compare: match-token -> match
6
+ - aa77d10 cursor: getIndex: add
7
+ - d582e38 flatlint: compare: move out cursor
8
+ - 8c238c4 flatlint: compare: move out match-token
9
+ - 49281ca flatlint: compare: matchers
10
+ - 0ddcf54 compare: simplify
11
+
12
+ 2026.02.17, v4.13.1
13
+
14
+ fix:
15
+ - a3a40b5 flatlint: squire -> square
16
+ - 376082e flatlint: add-missing-square-brace: inner
17
+
18
+ feature:
19
+ - 5dcdd09 flatlint: madrun v13.0.0
20
+ - 8de4017 flatlint: putout v42.0.3
21
+ - 67e23ea flatlint: eslint-plugin-putout v31.0.0
22
+
1
23
  2026.02.17, v4.13.0
2
24
 
3
25
  feature:
@@ -1,120 +1,59 @@
1
1
  import {prepare} from '#parser';
2
- import {
3
- isTemplateArgsToken,
4
- isTemplateArrayToken,
5
- isTemplateExpressionToken,
6
- OK,
7
- NOT_OK,
8
- } from '#types';
9
- import {collectArray} from './collect-array.js';
10
- import {collectExpression} from './collect-expression.js';
11
- import {collectArgs} from './collect-args.js';
12
- import {equalTemplate} from './equal.js';
2
+ import {OK, NOT_OK} from '#types';
3
+ import {match} from './match.js';
4
+ import {createCursor} from './cursor.js';
13
5
 
14
6
  export const compare = (source, template, {index = 0} = {}) => {
15
- const templateTokens = prepare(template);
16
7
  const tokens = prepare(source);
8
+ const templateTokens = prepare(template);
17
9
 
18
- const n = tokens.length - 1;
19
- const templateTokensLength = templateTokens.length;
20
-
21
- let isEqual = false;
22
- let start = 0;
23
- let end = 0;
24
- let delta = 0;
10
+ const lastTokenIndex = tokens.length - 1;
11
+ const lastTemplateIndex = templateTokens.length - 1;
25
12
 
26
- for (; index < n; index++) {
27
- const indexCheck = index;
28
- let skip = 0;
13
+ for (; index <= lastTokenIndex; index++) {
14
+ const cursor = createCursor({
15
+ index,
16
+ tokens,
17
+ templateTokens,
18
+ lastTokenIndex,
19
+ });
29
20
 
30
- for (const [templateIndex] of templateTokens.entries()) {
31
- let currentTokenIndex = index + templateIndex - skip;
21
+ for (const [templateIndex, templateToken] of templateTokens.entries()) {
22
+ const nextTemplateToken = templateTokens[templateIndex + 1];
23
+ const isLast = templateIndex === lastTemplateIndex;
32
24
 
33
- checkIndexes(index, indexCheck);
25
+ const {
26
+ matched,
27
+ skip,
28
+ end,
29
+ } = match(cursor, {
30
+ templateToken,
31
+ nextTemplateToken,
32
+ templateIndex,
33
+ isLast,
34
+ });
34
35
 
35
- if (currentTokenIndex > n)
36
- return [NOT_OK];
37
-
38
- const templateToken = templateTokens[templateIndex];
39
- const currentToken = tokens[currentTokenIndex];
40
-
41
- if (isTemplateArgsToken(templateToken)) {
42
- const [ok, end] = collectArgs({
43
- currentTokenIndex,
44
- tokens,
45
- templateToken,
46
- nextTemplateToken: templateTokens[templateIndex + 1],
47
- });
48
-
49
- delta = 0;
50
-
51
- if (!ok) {
52
- ++skip;
53
- } else if (templateIndex === templateTokensLength - 1) {
54
- currentTokenIndex = end;
55
- } else if (currentTokenIndex < end) {
56
- delta = end - currentTokenIndex;
57
- index = end - templateIndex;
58
- }
59
- } else if (isTemplateExpressionToken(templateToken)) {
60
- const indexOfExpressionEnd = collectExpression({
61
- currentTokenIndex,
62
- tokens,
63
- templateToken,
64
- nextTemplateToken: templateTokens[templateIndex + 1],
65
- });
66
-
67
- const sameTokensIndex = templateIndex === templateTokensLength - 1;
68
- const outOfBound = indexOfExpressionEnd >= n;
69
-
70
- if (outOfBound || sameTokensIndex) {
71
- end = indexOfExpressionEnd;
72
- currentTokenIndex = end;
73
- } else {
74
- delta = indexOfExpressionEnd - currentTokenIndex;
75
- index = indexOfExpressionEnd - templateIndex;
36
+ if (!matched) {
37
+ if (skip) {
38
+ cursor.skip();
39
+ continue;
76
40
  }
77
- } else if (isTemplateArrayToken(templateToken)) {
78
- const [ok, end] = collectArray({
79
- currentTokenIndex,
80
- tokens,
81
- templateToken,
82
- nextTemplateToken: templateTokens[templateIndex + 1],
83
- });
84
41
 
85
- if (!ok) {
86
- ++skip;
87
- } else if (currentTokenIndex < end) {
88
- delta = end - currentTokenIndex;
89
- index = end - templateIndex;
90
- }
91
- } else if (!equalTemplate(currentToken, templateToken)) {
92
- isEqual = false;
42
+ cursor.setUnmatched();
93
43
  break;
94
44
  }
95
45
 
96
- isEqual = true;
97
- start = index - delta;
98
- end = currentTokenIndex;
46
+ cursor.setMatched(end);
99
47
  }
100
48
 
101
- if (isEqual)
49
+ if (cursor.isMatched())
102
50
  return [
103
51
  OK,
104
- start,
105
- ++end,
52
+ cursor.state.start,
53
+ cursor.state.end + 1,
106
54
  ];
107
55
  }
108
56
 
109
57
  return [NOT_OK];
110
58
  };
111
59
 
112
- function checkIndexes(index, indexCheck) {
113
- /* c8 ignore start */
114
- if (indexCheck > index + 1)
115
- throw Error(`index should never decrease more then on one: ${index} > ${indexCheck}`);
116
-
117
- if (index < 0)
118
- throw Error(`index should never be < zero: ${index}`);
119
- /* c8 ignore end */
120
- }
@@ -0,0 +1,51 @@
1
+ export function createCursor({index, tokens, templateTokens, lastTokenIndex}) {
2
+ const state = {
3
+ index,
4
+ delta: 0,
5
+ skip: 0,
6
+ start: 0,
7
+ end: 0,
8
+ matched: false,
9
+ };
10
+
11
+ return {
12
+ tokens,
13
+ templateTokens,
14
+ lastTokenIndex,
15
+ state,
16
+
17
+ getIndex(templateIndex) {
18
+ return this.state.index + templateIndex - this.state.skip;
19
+ },
20
+
21
+ getToken(templateIndex) {
22
+ const index = this.getIndex(templateIndex);
23
+ return this.tokens[index];
24
+ },
25
+
26
+ skip() {
27
+ state.skip++;
28
+ },
29
+
30
+ setMatched(end) {
31
+ state.matched = true;
32
+ state.start = state.index - state.delta;
33
+ state.end = end;
34
+ },
35
+
36
+ setUnmatched() {
37
+ state.matched = false;
38
+ },
39
+
40
+ isMatched() {
41
+ return state.matched;
42
+ },
43
+
44
+ updateDelta(currentTokenIndex, end, templateIndex) {
45
+ if (currentTokenIndex < end) {
46
+ state.delta = end - currentTokenIndex;
47
+ state.index = end - templateIndex;
48
+ }
49
+ },
50
+ };
51
+ }
@@ -0,0 +1,54 @@
1
+ import {equalTemplate} from './equal.js';
2
+ import {matchers} from './matchers/index.js';
3
+
4
+ const SKIP = {
5
+ skip: true,
6
+ matched: false,
7
+ };
8
+
9
+ const NOT_MATCHED = {
10
+ matched: false,
11
+ };
12
+
13
+ const createMatched = (a) => ({
14
+ ...a,
15
+ matched: true,
16
+ });
17
+
18
+ export function match(cursor, {templateToken, templateIndex, nextTemplateToken, isLast}) {
19
+ const {tokens} = cursor;
20
+ const currentTokenIndex = cursor.getIndex(templateIndex);
21
+
22
+ if (currentTokenIndex > cursor.lastTokenIndex)
23
+ return NOT_MATCHED;
24
+
25
+ const currentToken = cursor.getToken(templateIndex);
26
+
27
+ for (const {testToken, collect} of matchers) {
28
+ if (!testToken(templateToken))
29
+ continue;
30
+
31
+ const [ok, end] = collect({
32
+ currentTokenIndex,
33
+ tokens,
34
+ templateToken,
35
+ nextTemplateToken,
36
+ });
37
+
38
+ if (!ok)
39
+ return SKIP;
40
+
41
+ cursor.updateDelta(currentTokenIndex, end, templateIndex);
42
+
43
+ return createMatched({
44
+ end: isLast ? end : currentTokenIndex,
45
+ });
46
+ }
47
+
48
+ if (!equalTemplate(currentToken, templateToken))
49
+ return NOT_MATCHED;
50
+
51
+ return createMatched({
52
+ end: currentTokenIndex,
53
+ });
54
+ }
@@ -3,13 +3,14 @@ import {
3
3
  closeRoundBrace,
4
4
  isNewLine,
5
5
  isPunctuator,
6
+ isTemplateArgs,
6
7
  NOT_OK,
7
8
  OK,
8
9
  openCurlyBrace,
9
10
  openRoundBrace,
10
11
  semicolon,
11
12
  } from '#types';
12
- import {equal} from './equal.js';
13
+ import {equal} from '../equal.js';
13
14
 
14
15
  const NOT_OK_PUNCTUATORS = [
15
16
  semicolon,
@@ -17,7 +18,9 @@ const NOT_OK_PUNCTUATORS = [
17
18
  closeRoundBrace,
18
19
  ];
19
20
 
20
- export const collectArgs = ({currentTokenIndex, tokens}) => {
21
+ export const test = isTemplateArgs;
22
+
23
+ export const collect = ({currentTokenIndex, tokens}) => {
21
24
  let index = currentTokenIndex;
22
25
 
23
26
  const n = tokens.length;
@@ -4,10 +4,13 @@ import {
4
4
  semicolon,
5
5
  OK,
6
6
  NOT_OK,
7
+ isTemplateArray,
7
8
  } from '#types';
8
- import {equal} from './equal.js';
9
+ import {equal} from '../equal.js';
9
10
 
10
- export const collectArray = ({currentTokenIndex, tokens, nextTemplateToken = semicolon}) => {
11
+ export const test = isTemplateArray;
12
+
13
+ export const collect = ({currentTokenIndex, tokens, nextTemplateToken = semicolon}) => {
11
14
  const n = tokens.length;
12
15
  let index = currentTokenIndex;
13
16
 
@@ -3,13 +3,16 @@ import {
3
3
  closeRoundBrace,
4
4
  comma,
5
5
  isNewLine,
6
+ isTemplateExpression,
6
7
  openCurlyBrace,
7
8
  openRoundBrace,
8
9
  semicolon,
9
10
  } from '#types';
10
- import {equal} from './equal.js';
11
+ import {equal} from '../equal.js';
11
12
 
12
- export const collectExpression = ({currentTokenIndex, tokens, nextTemplateToken = semicolon}) => {
13
+ export const test = isTemplateExpression;
14
+
15
+ export const collect = ({currentTokenIndex, tokens, nextTemplateToken = semicolon}) => {
13
16
  const n = tokens.length;
14
17
  let index = currentTokenIndex;
15
18
  let roundBracesBalance = 0;
@@ -52,7 +55,10 @@ export const collectExpression = ({currentTokenIndex, tokens, nextTemplateToken
52
55
  --index;
53
56
 
54
57
  if (currentTokenIndex === index)
55
- return currentTokenIndex;
58
+ return [true, currentTokenIndex];
56
59
 
57
- return --index;
60
+ return [
61
+ true,
62
+ --index,
63
+ ];
58
64
  };
@@ -0,0 +1,25 @@
1
+ import {isIdentifier} from '#types';
2
+ import * as argsMatcher from './args.js';
3
+ import * as arrayMatcher from './array.js';
4
+ import * as expressionMatcher from './expression.js';
5
+
6
+ export const matchers = [
7
+ argsMatcher,
8
+ arrayMatcher,
9
+ expressionMatcher,
10
+ ].map(createMatcher);
11
+
12
+ function createMatcher({test, collect, testToken}) {
13
+ return {
14
+ collect,
15
+ test,
16
+ testToken: testToken || ((token) => {
17
+ const {value} = token;
18
+
19
+ if (!isIdentifier(token))
20
+ return false;
21
+
22
+ return test(value);
23
+ }),
24
+ };
25
+ }
@@ -1,14 +1,9 @@
1
1
  import {traverse} from '#traverser';
2
2
  import {prepare} from '#parser';
3
- import {
4
- is,
5
- isTemplateArgs,
6
- isTemplateArray,
7
- isTemplateExpression,
8
- } from '#types';
9
- import {collectArray} from './collect-array.js';
10
- import {collectExpression} from './collect-expression.js';
11
- import {collectArgs} from './collect-args.js';
3
+ import {is} from '#types';
4
+ import * as arrayMatcher from './matchers/array.js';
5
+ import * as expressionMatcher from './matchers/expression.js';
6
+ import * as argsMatcher from './matchers/args.js';
12
7
 
13
8
  const {isArray} = Array;
14
9
  const maybeArray = (a) => isArray(a) ? a : [a];
@@ -41,18 +36,18 @@ export function getValues(tokens, waysFrom) {
41
36
  let end = index;
42
37
  let ok = true;
43
38
 
44
- if (isTemplateArray(name)) {
45
- [ok, end] = collectArray({
39
+ if (arrayMatcher.test(name)) {
40
+ [ok, end] = arrayMatcher.collect({
46
41
  currentTokenIndex: index,
47
42
  tokens,
48
43
  });
49
- } else if (isTemplateExpression(name)) {
50
- end = collectExpression({
44
+ } else if (expressionMatcher.test(name)) {
45
+ [ok, end] = expressionMatcher.collect({
51
46
  currentTokenIndex: index,
52
47
  tokens,
53
48
  });
54
- } else if (isTemplateArgs(name)) {
55
- [ok, end] = collectArgs({
49
+ } else if (argsMatcher.test(name)) {
50
+ [ok, end] = argsMatcher.collect({
56
51
  currentTokenIndex: index,
57
52
  tokens,
58
53
  });
@@ -1,41 +1,18 @@
1
1
  import {
2
2
  closeCurlyBrace,
3
- comma,
4
3
  isPunctuator,
5
- openSquireBrace,
6
4
  } from '#types';
7
5
 
8
6
  export const report = () => 'Add missing square brace';
9
7
 
10
- const addIndex = (a, i) => [i, a];
11
-
12
8
  export const match = () => ({
13
9
  '["__a"': (vars, path) => !path.isNext(),
14
10
  '[__array;': ({__array}) => {
15
11
  const last = __array.at(-1);
16
12
  return !isPunctuator(last, closeCurlyBrace);
17
13
  },
18
- '"__a",\n}': (vars, path) => {
19
- const allPrevs = path
20
- .getAllPrev()
21
- .map(addIndex);
22
-
23
- let is = false;
24
-
25
- for (const [i, prev] of allPrevs) {
26
- const COMMA_POSITION = '\n",'.length;
27
-
28
- if (i === COMMA_POSITION) {
29
- is = isPunctuator(prev, [comma, openSquireBrace]);
30
- break;
31
- }
32
- }
33
-
34
- return is;
35
- },
36
14
  });
37
15
  export const replace = () => ({
38
- '"__a",\n}': '"__a",\n]}',
39
16
  '[__array;': '[__array];',
40
17
  '["__a"': '["__a"];',
41
18
  '[;': '[];',
@@ -44,4 +21,3 @@ export const replace = () => ({
44
21
  '(__a, [__b)': '(__a, [__b])',
45
22
  '(__a, [__b, __c)': '(__a, [__b, __c])',
46
23
  });
47
-
package/lib/plugins.js CHANGED
@@ -37,7 +37,7 @@ export const plugins = [
37
37
  ['add-missing-assign', addMissingAssign],
38
38
  ['add-missing-curly-brace', addMissingCurlyBrace],
39
39
  ['add-missing-round-brace', addMissingRoundBrace],
40
- ['add-missing-squire-brace', addMissingSquireBrace],
40
+ ['add-missing-square-brace', addMissingSquireBrace],
41
41
  ['apply-import-from', applyImportFrom],
42
42
  ['apply-import-order', applyImportOrder],
43
43
  ['convert-assert-to-with', convertAssertToWith],
@@ -139,9 +139,6 @@ export const isQuote = (a) => QUOTE.test(a);
139
139
  export const isTemplateArray = (a) => a === ARRAY;
140
140
  export const isTemplateExpression = (a) => a === EXPR;
141
141
  export const isTemplateArgs = (a) => a === ARGS;
142
- export const isTemplateArrayToken = (a) => isIdentifier(a) && isTemplateArray(a.value);
143
- export const isTemplateExpressionToken = (a) => isIdentifier(a) && isTemplateExpression(a.value);
144
- export const isTemplateArgsToken = (a) => isIdentifier(a) && isTemplateArgs(a.value);
145
142
 
146
143
  export const bitwiseAnd = Punctuator('&');
147
144
  export const arrow = Punctuator('=>');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flatlint",
3
- "version": "4.13.0",
3
+ "version": "5.0.0",
4
4
  "description": "JavaScript tokens-based linter",
5
5
  "main": "lib/flatlint.js",
6
6
  "type": "module",
@@ -68,12 +68,12 @@
68
68
  "@putout/test": "^15.0.0",
69
69
  "c8": "^10.1.2",
70
70
  "eslint": "^10.0.0",
71
- "eslint-plugin-putout": "^30.0.1",
72
- "madrun": "^12.1.0",
71
+ "eslint-plugin-putout": "^31.0.0",
72
+ "madrun": "^13.0.0",
73
73
  "mock-require": "^3.0.3",
74
74
  "montag": "^1.0.0",
75
75
  "nodemon": "^3.0.1",
76
- "putout": "^41.0.2",
76
+ "putout": "^42.0.3",
77
77
  "supertape": "^12.0.0"
78
78
  },
79
79
  "engines": {