flatlint 1.26.0 → 1.28.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,14 @@
1
+ 2025.01.08, v1.28.0
2
+
3
+ feature:
4
+ - b1e6b43 remove-useless-comma: improve
5
+ - 3ebb93d add-missing-semicolon: improve
6
+
7
+ 2025.01.08, v1.27.0
8
+
9
+ feature:
10
+ - a6b1991 flatlint: multiple fixes in one source
11
+
1
12
  2025.01.07, v1.26.0
2
13
 
3
14
  feature:
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  closeRoundBrace,
3
+ comma,
3
4
  openRoundBrace,
4
5
  semicolon,
5
6
  } from '#types';
@@ -15,6 +16,9 @@ export const collectExpression = ({currentTokenIndex, tokens, nextTemplateToken
15
16
  if (equal(token, semicolon))
16
17
  break;
17
18
 
19
+ if (equal(token, comma))
20
+ break;
21
+
18
22
  if (equal(token, nextTemplateToken))
19
23
  break;
20
24
 
@@ -3,6 +3,8 @@ import {
3
3
  isTemplateArgsToken,
4
4
  isTemplateArrayToken,
5
5
  isTemplateExpressionToken,
6
+ OK,
7
+ NOT_OK,
6
8
  } from '#types';
7
9
  import {collectArray} from './collect-array.js';
8
10
  import {collectExpression} from './collect-expression.js';
@@ -15,7 +17,7 @@ import {
15
17
  equalStr,
16
18
  } from './equal.js';
17
19
 
18
- export const compare = (source, template) => {
20
+ export const compare = (source, template, {index = 0} = {}) => {
19
21
  const templateTokens = prepare(template);
20
22
  const tokens = prepare(source);
21
23
 
@@ -25,9 +27,10 @@ export const compare = (source, template) => {
25
27
  let start = 0;
26
28
  let end = 0;
27
29
  let delta = 0;
28
- let skip = 0;
29
30
 
30
- for (let index = 0; index < n; index++) {
31
+ for (; index < n; index++) {
32
+ let skip = 0;
33
+
31
34
  for (let templateIndex = 0; templateIndex < templateTokensLength; templateIndex++) {
32
35
  let currentTokenIndex = index + templateIndex - skip;
33
36
 
@@ -58,7 +61,7 @@ export const compare = (source, template) => {
58
61
  nextTemplateToken: templateTokens[templateIndex + 1],
59
62
  });
60
63
 
61
- if (n === indexOfExpressionEnd) {
64
+ if (indexOfExpressionEnd >= n - 1) {
62
65
  end = indexOfExpressionEnd;
63
66
  continue;
64
67
  }
@@ -90,10 +93,14 @@ export const compare = (source, template) => {
90
93
  }
91
94
 
92
95
  if (isEqual)
93
- return [true, start, ++end];
96
+ return [
97
+ OK,
98
+ start,
99
+ ++end,
100
+ ];
94
101
  }
95
102
 
96
- return [false];
103
+ return [NOT_OK];
97
104
  };
98
105
 
99
106
  const comparators = [
@@ -0,0 +1,77 @@
1
+ import {traverse} from '#traverser';
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';
12
+
13
+ const {entries} = Object;
14
+
15
+ export function findVarsWays(tokens) {
16
+ const ways = {};
17
+
18
+ traverse(tokens, {
19
+ Identifier({value, index}) {
20
+ if (is(value))
21
+ ways[value] = index;
22
+ },
23
+ StringLiteral({value, index}) {
24
+ if (is(value))
25
+ ways[value] = index;
26
+ },
27
+ });
28
+
29
+ return ways;
30
+ }
31
+
32
+ export function getValues(tokens, waysFrom) {
33
+ const values = {};
34
+
35
+ for (const [name, index] of entries(waysFrom)) {
36
+ let end = index;
37
+ let ok = true;
38
+
39
+ if (isTemplateArray(name))
40
+ [ok, end] = collectArray({
41
+ currentTokenIndex: index,
42
+ tokens,
43
+ });
44
+ else if (isTemplateExpression(name))
45
+ end = collectExpression({
46
+ currentTokenIndex: index,
47
+ tokens,
48
+ });
49
+ else if (isTemplateArgs(name))
50
+ [ok, end] = collectArgs({
51
+ currentTokenIndex: index,
52
+ tokens,
53
+ });
54
+
55
+ if (!ok) {
56
+ values[name] = [];
57
+ continue;
58
+ }
59
+
60
+ values[name] = tokens.slice(index, end + 1);
61
+ }
62
+
63
+ return values;
64
+ }
65
+
66
+ export function setValues({to, waysTo, values}) {
67
+ for (const [name, index] of entries(waysTo)) {
68
+ to.splice(index, 1, ...values[name]);
69
+ }
70
+ }
71
+
72
+ export function getCurrentValues({from, start, end, tokens}) {
73
+ const current = tokens.slice(start, end);
74
+ const waysFrom = findVarsWays(prepare(from));
75
+
76
+ return getValues(current, waysFrom);
77
+ }
package/lib/flatlint.js CHANGED
@@ -1,11 +1,7 @@
1
1
  import {loadPlugins} from '@putout/engine-loader';
2
2
  import {parse} from '#parser';
3
3
  import {run} from '#runner';
4
-
5
- const getValue = (token) => token.value;
6
- const print = (tokens) => tokens
7
- .map(getValue)
8
- .join('');
4
+ import {print} from '#printer';
9
5
 
10
6
  export function lint(source, overrides = {}) {
11
7
  const {fix = true, plugins: pluginNames = []} = overrides;
@@ -1,9 +1,22 @@
1
- import {closeRoundBrace} from '#types';
1
+ import {
2
+ closeRoundBrace,
3
+ isPunctuator,
4
+ } from '#types';
2
5
 
3
6
  export const report = () => 'Add missing round braces';
4
7
 
5
8
  export const match = () => ({
6
- '__a(__args': (vars, path) => !path.isNextPunctuator(closeRoundBrace),
9
+ '__a(__args': (vars, path) => {
10
+ if (path.isCurrentPunctuator(closeRoundBrace))
11
+ return false;
12
+
13
+ for (const token of path.getAllNext()) {
14
+ if (isPunctuator(token, closeRoundBrace))
15
+ return false;
16
+ }
17
+
18
+ return true;
19
+ },
7
20
  });
8
21
 
9
22
  export const replace = () => ({
@@ -17,7 +17,13 @@ export const replace = () => ({
17
17
  });
18
18
 
19
19
  function check(vars, path) {
20
- if (path.isEndsWithPunctuator(comma))
20
+ if (path.isNextPunctuator(comma))
21
+ return false;
22
+
23
+ if (path.isNextPunctuator(semicolon))
24
+ return false;
25
+
26
+ if (path.isPrevIdentifier('function'))
21
27
  return false;
22
28
 
23
29
  for (const token of path.getAllNext()) {
@@ -1,4 +1,18 @@
1
+ import {colon, isPunctuator} from '#types';
2
+
1
3
  export const report = () => 'Use semicolon instead of trailing comma';
4
+ export const match = () => ({
5
+ '__a(__args),': (vars, path) => {
6
+ for (const token of path.getAllPrev()) {
7
+ if (isPunctuator(token, colon))
8
+ return false;
9
+ }
10
+
11
+ return true;
12
+ },
13
+ });
14
+
2
15
  export const replace = () => ({
3
16
  'const __a = __b,': 'const __a = __b;',
17
+ '__a(__args),': '__a(__args);',
4
18
  });
@@ -15,4 +15,5 @@ export const match = () => ({
15
15
 
16
16
  export const replace = () => ({
17
17
  '__a(__args) {},': '__a(__args) {}',
18
+ '__a(),': '__a()',
18
19
  });
package/lib/plugins.js CHANGED
@@ -2,20 +2,22 @@ import * as wrapAssignmentInParens from './plugins/wrap-assignment-in-parens/ind
2
2
  import * as addMissingRoundBraces from './plugins/add-missing-round-braces/index.js';
3
3
  import * as addMissingSquireBrace from './plugins/add-missing-square-brace/index.js';
4
4
  import * as addMissingQuote from './plugins/add-missing-quote/index.js';
5
+ import * as addMissingSemicolon from './plugins/add-missing-semicolon/index.js';
5
6
  import * as convertCommaToSemicolon from './plugins/convert-comma-to-semicolon/index.js';
6
7
  import * as convertFromToRequire from './plugins/convert-from-to-require/index.js';
7
8
  import * as removeUselessRoundBrace from './plugins/remove-useless-round-brace/index.js';
9
+ import * as removeUselessComma from './plugins/remove-useless-comma/index.js';
8
10
  import * as addConstToExport from './plugins/add-const-to-export/index.js';
9
- import * as addMissingSemicolon from './plugins/add-missing-semicolon/index.js';
10
11
 
11
12
  export const plugins = [
12
13
  ['wrap-assignment-in-parens', wrapAssignmentInParens],
13
14
  ['add-missing-round-braces', addMissingRoundBraces],
14
15
  ['add-missing-squire-brace', addMissingSquireBrace],
15
- ['add-missing-quote', addMissingQuote],
16
16
  ['add-missing-semicolon', addMissingSemicolon],
17
+ ['add-missing-quote', addMissingQuote],
17
18
  ['add-const-to-export', addConstToExport],
18
19
  ['convert-comma-to-semicolon', convertCommaToSemicolon],
19
20
  ['convert-from-to-require', convertFromToRequire],
20
21
  ['remove-useless-round-brace', removeUselessRoundBrace],
22
+ ['remove-useless-comma', removeUselessComma],
21
23
  ];
@@ -0,0 +1,7 @@
1
+ const getValue = (token) => token.value;
2
+
3
+ export const print = (tokens) => {
4
+ return tokens
5
+ .map(getValue)
6
+ .join('');
7
+ };
@@ -1,6 +1,13 @@
1
- import {isNewLine, isPunctuator} from '#types';
1
+ import {
2
+ isIdentifier,
3
+ isNewLine,
4
+ isPunctuator,
5
+ } from '#types';
2
6
 
3
7
  export const createPath = ({tokens, start, end}) => ({
8
+ tokens,
9
+ start,
10
+ end,
4
11
  getAllPrev: createGetAllPrev({
5
12
  tokens,
6
13
  start,
@@ -13,29 +20,34 @@ export const createPath = ({tokens, start, end}) => ({
13
20
  tokens,
14
21
  end,
15
22
  }),
16
- isEndsWithPunctuator: createIsCurrentPunctuator({
23
+ isPrevIdentifier: createIsPrevIdentifier({
17
24
  tokens,
18
- end,
25
+ start,
19
26
  }),
20
- isCurrentPunctuator: createIsCurrentPunctuator({
27
+ isCurrentPunctuator: createIsNextPunctuator({
21
28
  tokens,
22
- end,
29
+ end: end - 1,
23
30
  }),
24
31
  });
25
32
 
26
- const createIsCurrentPunctuator = ({tokens, end}) => (punctuator) => {
27
- const current = tokens[end - 1];
33
+ const createIsNextPunctuator = ({tokens, end}) => (punctuator) => {
34
+ const current = tokens[end];
35
+
36
+ if (!current)
37
+ return false;
28
38
 
29
39
  return isPunctuator(current, punctuator);
30
40
  };
31
41
 
32
- const createIsNextPunctuator = ({tokens, end}) => (punctuator) => {
33
- const current = tokens[end];
42
+ const createIsPrevIdentifier = ({tokens, start}) => (value) => {
43
+ const SPACE = 1;
44
+ const FUNCTION = 1;
45
+ const current = tokens[start - (FUNCTION + SPACE)];
34
46
 
35
47
  if (!current)
36
48
  return false;
37
49
 
38
- return isPunctuator(current, punctuator);
50
+ return isIdentifier(current, value);
39
51
  };
40
52
 
41
53
  const createGetAllPrev = ({tokens, start}) => function*() {
@@ -1,17 +1,12 @@
1
1
  import debug from 'debug';
2
2
  import {compare} from '#compare';
3
3
  import {prepare} from '#parser';
4
- import {traverse} from '#traverser';
5
4
  import {
6
- is,
7
- isTemplateArgs,
8
- isTemplateArray,
9
- isTemplateExpression,
10
- } from '#types';
11
- import {collectArray} from '../compare/collect-array.js';
12
- import {collectExpression} from '../compare/collect-expression.js';
5
+ findVarsWays,
6
+ getCurrentValues,
7
+ setValues,
8
+ } from '#compare/values';
13
9
  import {createPath} from './path.js';
14
- import {collectArgs} from '../compare/collect-args.js';
15
10
 
16
11
  const returns = (a) => () => a;
17
12
  const {entries} = Object;
@@ -20,125 +15,75 @@ const log = debug('flatlint');
20
15
  export const replace = (tokens, {fix, rule, plugin}) => {
21
16
  const places = [];
22
17
  let isFixed = false;
18
+
23
19
  const match = plugin.match?.() ?? returns({});
24
20
 
25
- for (let [from, to] of entries(plugin.replace())) {
26
- const [ok, start, end] = compare(tokens, from);
27
-
28
- if (!ok)
29
- continue;
30
-
31
- const values = getCurrentValues({
32
- from,
33
- start,
34
- end,
35
- tokens,
36
- });
21
+ for (const [from, to] of entries(plugin.replace())) {
22
+ let index = 0;
37
23
 
38
- const matchFn = match[from];
39
-
40
- if (matchFn) {
41
- const is = matchFn(values, createPath({
42
- tokens,
24
+ while (index < tokens.length - 1) {
25
+ const [ok, start, end] = compare(tokens, from, {
26
+ index,
27
+ });
28
+
29
+ if (!ok)
30
+ break;
31
+
32
+ const values = getCurrentValues({
33
+ from,
43
34
  start,
44
35
  end,
45
- }));
36
+ tokens,
37
+ });
46
38
 
47
- if (!is)
39
+ const matchFn = match[from];
40
+
41
+ if (matchFn) {
42
+ const is = matchFn(values, createPath({
43
+ tokens,
44
+ start,
45
+ end,
46
+ }));
47
+
48
+ if (!is) {
49
+ index = end;
50
+ continue;
51
+ }
52
+ }
53
+
54
+ if (fix) {
55
+ log(`${rule}: ${from} -> ${to}`);
56
+
57
+ const preparedTo = prepare(to);
58
+ const waysTo = findVarsWays(preparedTo);
59
+
60
+ setValues({
61
+ values,
62
+ waysTo,
63
+ to: preparedTo,
64
+ });
65
+
66
+ tokens.splice(start, end - start, ...preparedTo);
67
+ isFixed = true;
68
+ index = end;
69
+
48
70
  continue;
49
- }
50
-
51
- if (fix) {
52
- log(`${rule}: ${from} -> ${to}`);
53
- to = prepare(to);
54
- const waysTo = findVarsWays(to);
71
+ }
72
+
73
+ const {line, column} = tokens[start];
74
+ const message = plugin.report();
55
75
 
56
- setValues({
57
- values,
58
- waysTo,
59
- to,
76
+ places.push({
77
+ rule,
78
+ message,
79
+ line,
80
+ column,
60
81
  });
61
82
 
62
- tokens.splice(start, end - start, ...to);
63
- isFixed = true;
64
- continue;
83
+ index = end;
84
+ isFixed = false;
65
85
  }
66
-
67
- const {line, column} = tokens[start];
68
- const message = plugin.report();
69
-
70
- places.push({
71
- rule,
72
- message,
73
- line,
74
- column,
75
- });
76
86
  }
77
87
 
78
88
  return [isFixed, places];
79
89
  };
80
-
81
- function findVarsWays(tokens) {
82
- const ways = {};
83
-
84
- traverse(tokens, {
85
- Identifier({value, index}) {
86
- if (is(value))
87
- ways[value] = index;
88
- },
89
- StringLiteral({value, index}) {
90
- if (is(value))
91
- ways[value] = index;
92
- },
93
- });
94
-
95
- return ways;
96
- }
97
-
98
- function getValues(tokens, waysFrom) {
99
- const values = {};
100
-
101
- for (const [name, index] of entries(waysFrom)) {
102
- let end = index;
103
- let ok = true;
104
-
105
- if (isTemplateArray(name))
106
- [ok, end] = collectArray({
107
- currentTokenIndex: index,
108
- tokens,
109
- });
110
- else if (isTemplateExpression(name))
111
- end = collectExpression({
112
- currentTokenIndex: index,
113
- tokens,
114
- });
115
- else if (isTemplateArgs(name))
116
- [ok, end] = collectArgs({
117
- currentTokenIndex: index,
118
- tokens,
119
- });
120
-
121
- if (!ok) {
122
- values[name] = [];
123
- continue;
124
- }
125
-
126
- values[name] = tokens.slice(index, end + 1);
127
- }
128
-
129
- return values;
130
- }
131
-
132
- function setValues({to, waysTo, values}) {
133
- for (const [name, index] of entries(waysTo)) {
134
- to.splice(index, 1, ...values[name]);
135
- }
136
- }
137
-
138
- function getCurrentValues({from, start, end, tokens}) {
139
- const current = tokens.slice(start, end);
140
-
141
- const waysFrom = findVarsWays(prepare(from));
142
-
143
- return getValues(current, waysFrom);
144
- }
@@ -82,6 +82,7 @@ export const closeRoundBrace = Punctuator(')');
82
82
  export const closeSquareBrace = Punctuator(']');
83
83
  export const semicolon = Punctuator(';');
84
84
  export const comma = Punctuator(',');
85
+ export const colon = Punctuator(':');
85
86
 
86
87
  export const OK = true;
87
88
  export const NOT_OK = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flatlint",
3
- "version": "1.26.0",
3
+ "version": "1.28.0",
4
4
  "description": "JavaScript tokens-based linter",
5
5
  "main": "lib/flatlint.js",
6
6
  "type": "module",
@@ -17,12 +17,18 @@
17
17
  "#parser": {
18
18
  "default": "./lib/parser/parser.js"
19
19
  },
20
+ "#printer": {
21
+ "default": "./lib/printer/printer.js"
22
+ },
20
23
  "#traverser": {
21
24
  "default": "./lib/traverser/traverser.js"
22
25
  },
23
26
  "#compare": {
24
27
  "default": "./lib/compare/compare.js"
25
28
  },
29
+ "#compare/values": {
30
+ "default": "./lib/compare/values.js"
31
+ },
26
32
  "#runner": {
27
33
  "default": "./lib/runner/runner.js"
28
34
  },