flatlint 4.13.1 → 5.1.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,19 @@
1
+ 2026.02.18, v5.1.0
2
+
3
+ feature:
4
+ - 5262747 flatlint: add-missing-square-brace: inner
5
+
6
+ 2026.02.17, v5.0.0
7
+
8
+ feature:
9
+ - e97e93a compare: for-of
10
+ - 39351ad feature: compare: match-token -> match
11
+ - aa77d10 cursor: getIndex: add
12
+ - d582e38 flatlint: compare: move out cursor
13
+ - 8c238c4 flatlint: compare: move out match-token
14
+ - 49281ca flatlint: compare: matchers
15
+ - 0ddcf54 compare: simplify
16
+
1
17
  2026.02.17, v4.13.1
2
18
 
3
19
  fix:
@@ -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,14 @@ import {
4
4
  semicolon,
5
5
  OK,
6
6
  NOT_OK,
7
+ isTemplateArray,
8
+ closeCurlyBrace,
7
9
  } from '#types';
8
- import {equal} from './equal.js';
10
+ import {equal} from '../equal.js';
9
11
 
10
- export const collectArray = ({currentTokenIndex, tokens, nextTemplateToken = semicolon}) => {
12
+ export const test = isTemplateArray;
13
+
14
+ export const collect = ({currentTokenIndex, tokens, nextTemplateToken = semicolon}) => {
11
15
  const n = tokens.length;
12
16
  let index = currentTokenIndex;
13
17
 
@@ -23,6 +27,9 @@ export const collectArray = ({currentTokenIndex, tokens, nextTemplateToken = sem
23
27
  if (equal(token, nextTemplateToken))
24
28
  break;
25
29
 
30
+ if (equal(token, closeCurlyBrace))
31
+ break;
32
+
26
33
  if (equal(token, closeSquareBrace))
27
34
  break;
28
35
  }
@@ -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
  });
@@ -14,6 +14,7 @@ export const match = () => ({
14
14
  });
15
15
  export const replace = () => ({
16
16
  '[__array;': '[__array];',
17
+ '[__array};': '[__array]};',
17
18
  '["__a"': '["__a"];',
18
19
  '[;': '[];',
19
20
  '(__a, ["__b")': '(__a, ["__b"])',
@@ -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.1",
3
+ "version": "5.1.0",
4
4
  "description": "JavaScript tokens-based linter",
5
5
  "main": "lib/flatlint.js",
6
6
  "type": "module",