redlint 6.5.1 → 6.6.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.
@@ -0,0 +1,181 @@
1
+ # Contributing to RedLint
2
+
3
+ ## Quick Reference
4
+
5
+ - **No unused variables or imports** — clean them up before committing.
6
+ - **No `t.pass('everything ok')` in tests** — every assertion must test something meaningful.
7
+ - **100% coverage required** — checked via [`.nycrc.json`](.nycrc.json). Run `redrun coverage` to verify.
8
+ - **Tests use [supertape](https://github.com/coderaiser/supertape)** with `stub()` for mocking.
9
+ - **TDD is encouraged** — write a failing test first, then implement, then `redrun fix:lint test`.
10
+ - **Run `redrun fix:lint test` before every commit** — linter must be clean, all tests green.
11
+
12
+ ## Table of Contents
13
+
14
+ - [Do / Don't](#do--dont)
15
+ - [Workflow](#workflow)
16
+ - [Architecture](#architecture)
17
+ - [Import Map](#import-map)
18
+ - [File Tree](#file-tree)
19
+ - [Overrides Pattern](#overrides-pattern)
20
+ - [Tools we use](#tools-we-use)
21
+
22
+ ## Do / Don't
23
+
24
+ | Do | Don't |
25
+ |--------------------------------------------------|------------------------------------------------|
26
+ | Write one assertion per test | Use `t.pass('ok')` or `t.comment()` |
27
+ | Use descriptive test names | Use vague names like `'test1'` |
28
+ | Inject deps via `overrides` | Import real modules inline (hard to stub) |
29
+ | Use early returns to flatten logic | Nest deeply with `if/else` chains |
30
+ | Keep modules small, one concern per file | Put multiple operations in one file |
31
+ | Use `async` for tests that `await` | Forget `await` in async test functions |
32
+ | Run `redrun fix:lint coverage` before commit | Commit red linter output |
33
+
34
+ ## Workflow
35
+
36
+ Install [`redrun`](https://github.com/coderaiser/redrun) (faster than `npm run`):
37
+
38
+ ```sh
39
+ bun i redrun -g
40
+ ```
41
+
42
+ Every commit should pass:
43
+
44
+ ```sh
45
+ redrun fix:lint # runs putout . --fix
46
+ redrun test # runs all .spec.js tests
47
+ redrun coverage # run coverage (aim for 100%):
48
+ ```
49
+
50
+ ## Architecture
51
+
52
+ ### Worker / Direct Pattern
53
+
54
+ Each filesystem operation (`scan`, `fix`, `pack`, `extract`, `convert`) has two paths:
55
+
56
+ | Mode | Function | Thread |
57
+ |------------|-----------------------------------------|----------------|
58
+ | **Normal** | `master*()` + `slave.js` | Web Worker |
59
+ | **Debug** | Direct import (e.g. `lint()`, `convert()`) | Main thread |
60
+
61
+ The debug mode is an escape hatch when workers misbehave.
62
+
63
+ ### Module layout
64
+
65
+ - `lib/<operation>/master.js` — spawns a worker, returns a promise.
66
+ - `lib/<operation>/slave.js` — runs inside the worker, calls the real logic.
67
+ - `lib/<operation>/<operation>.js` — the actual implementation (imported by slave and tests).
68
+ - `lib/<operation>/<operation>.spec.js` — tests.
69
+ - `lib/<operation>/fixture/` — test fixtures.
70
+
71
+ ### Testing
72
+
73
+ - Framework: [supertape](https://github.com/coderaiser/supertape)
74
+ - Mocking: `stub()` from supertape (no external mocking library)
75
+ - Fixtures: plain `.js` files in `fixture/` directories
76
+
77
+ ## Import Map
78
+
79
+ The project uses Node.js [`imports`](package.json) for internal aliases:
80
+
81
+ ```json
82
+ {
83
+ "imports": {
84
+ "#edit": "./lib/edit/edit.js",
85
+ "#test": "./test/create-test.js"
86
+ }
87
+ }
88
+ ```
89
+
90
+ Used in `bin/redlint.js`:
91
+
92
+ ```js
93
+ import {edit, editHelp} from '#edit';
94
+ ```
95
+
96
+ ## File Tree
97
+
98
+ ```
99
+ redlint/
100
+ ├── bin/redlint.js # CLI entry point
101
+ ├── lib/
102
+ │ ├── convert/ # convert json↔js, rc→flat
103
+ │ ├── edit/ # interactive filesystem editing
104
+ │ ├── extract/ # unpack filesystem.red
105
+ │ ├── help/ # --help output
106
+ │ ├── lint/ # scan & fix filesystem
107
+ │ ├── pack/ # pack to filesystem.red
108
+ │ ├── rename/ # rename js↔jsx
109
+ │ ├── test/ # test runner & plugins
110
+ │ ├── view/ # view file contents
111
+ │ ├── cli/ # version
112
+ │ ├── choose.js # interactive menu
113
+ │ ├── debug.js # debug menu
114
+ │ ├── dialog.js # ask for filename
115
+ │ ├── menu.js # menu constants & predicates
116
+ │ ├── redlint.js # buildTree
117
+ │ ├── run.js # worker runner
118
+ │ ├── simple.js # simple filesystem format
119
+ │ ├── slave.js # worker helper
120
+ │ └── spinner.js # CLI spinner
121
+ ├── test/ # integration tests
122
+ ├── .nycrc.json # coverage config (100% required)
123
+ ├── .madrun.js # task runner scripts
124
+ ├── .putout.json # putout rules
125
+ └── package.json
126
+ ```
127
+
128
+ ## Overrides Pattern
129
+
130
+ When a module calls external dependencies, accept them as optional overrides so tests can inject stubs:
131
+
132
+ ```js
133
+ // run-convert.js
134
+ export const runConvert = async (arg, filesystem, overrides = {}) => {
135
+ const {
136
+ askFilename: getFilename = askFilename,
137
+ convert = masterConvert,
138
+ isRCToFlat = isConvertRCToFlat,
139
+ } = overrides;
140
+
141
+ let filename = '.eslintrc.json';
142
+
143
+ if (!isRCToFlat(arg))
144
+ filename = await getFilename();
145
+
146
+ if (filename)
147
+ return await convert(filename, arg, filesystem);
148
+ };
149
+ ```
150
+
151
+ Test injects stubs to verify behavior without side effects:
152
+
153
+ ```js
154
+ // run-convert.spec.js
155
+ test('redlint: run-convert: json to js: called with filename', async (t) => {
156
+ const convert = stub();
157
+ const askFilename = stub().returns('package.json');
158
+ const filesystem = '[]';
159
+
160
+ await runConvert(CONVERT_JSON_TO_JS, filesystem, {
161
+ askFilename,
162
+ convert,
163
+ });
164
+
165
+ t.calledWith(convert, ['package.json', CONVERT_JSON_TO_JS, filesystem]);
166
+ t.end();
167
+ });
168
+ ```
169
+
170
+ This pattern keeps functions pure, easy to unit test, and doesn't require any mocking framework.
171
+
172
+ ## Tools we use
173
+
174
+ | Tool | Purpose | Link |
175
+ |-------------------------------------------------------|------------------------------------------------------|----------------------|
176
+ | [redrun](https://github.com/coderaiser/redrun) | Fast task runner (replaces `npm run`) | `bun i redrun -g` |
177
+ | [madrun](https://github.com/coderaiser/madrun) | Define tasks in `.madrun.js` | `redrun` |
178
+ | [putout](https://github.com/coderaiser/putout) | JavaScript code transformer & linter | `redrun fix:lint` |
179
+ | [supertape](https://github.com/coderaiser/supertape) | Test framework with built-in `stub()` | `redrun test` |
180
+ | [superc8](https://github.com/coderaiser/superc8) | Enhanced c8 wrapper | `redrun coverage` |
181
+ | [nodemon](https://github.com/remy/nodemon) | Watch mode for tests | `redrun watch:test` |
package/ChangeLog CHANGED
@@ -1,3 +1,14 @@
1
+ 2026.06.06, v6.6.0
2
+
3
+ feature:
4
+ - 842206b convert: move out
5
+ - eb2885e convert yaml to json: add
6
+
7
+ 2026.06.05, v6.5.2
8
+
9
+ feature:
10
+ - fa9d692 redlint: convert: move out
11
+
1
12
  2026.06.05, v6.5.1
2
13
 
3
14
  fix:
@@ -490,7 +501,7 @@ feature:
490
501
  2024.02.02, v3.11.1
491
502
 
492
503
  fix:
493
- - 779bf51 isConvertToJson
504
+ - 779bf51 isConvertJSToJson
494
505
 
495
506
  feature:
496
507
  - 6edb2f7 redlint: @putout/plugin-filesystem v4.0.0
package/README.md CHANGED
@@ -45,6 +45,7 @@ To scan your files use `redlint scan`:
45
45
  - ✅ [`package-json/find-file`](https://github.com/coderaiser/putout/tree/master/packages/plugin-package-json#find-file);
46
46
  - ✅ [`package-json/remove-exports-with-missing-files`](https://github.com/coderaiser/putout/tree/master/packages/plugin-package-json#remove-exports-with-missing-files);
47
47
  - ✅ [`esm/resolve-imported-file`](https://github.com/coderaiser/putout/tree/master/packages/plugin-esm#resolve-imported-file);
48
+ - ✅ [`esm/resolve-imported-file-with-extension`](https://github.com/coderaiser/putout/tree/master/packages/plugin-esm#resolve-imported-file-with-extension);
48
49
  - ✅ [`esm/shorten-imported-file`](https://github.com/coderaiser/putout/tree/master/packages/plugin-esm#shorten-imported-file);
49
50
  - ✅ [`esm/apply-name-to-imported-file`](https://github.com/coderaiser/putout/tree/master/packages/plugin-esm#apply-name-to-imported-file);
50
51
  - ✅ [`esm/apply-namespace-to-imported-file`](https://github.com/coderaiser/putout/tree/master/packages/plugin-esm#apply-name-to-imported-file);
package/bin/redlint.js CHANGED
@@ -27,7 +27,7 @@ import {version} from '../lib/cli/version.js';
27
27
  import {chooseConvert} from '../lib/convert/index.js';
28
28
  import {chooseRename} from '../lib/rename/index.js';
29
29
  import {convert} from '../lib/convert/convert.js';
30
- import {masterConvert} from '../lib/convert/master.js';
30
+ import {runConvert} from '../lib/convert/run-convert.js';
31
31
  import {askFilename} from '../lib/dialog.js';
32
32
  import {masterRename} from '../lib/rename/master.js';
33
33
  import {view} from '../lib/view/view.js';
@@ -55,7 +55,6 @@ import {
55
55
  isBack,
56
56
  isExit,
57
57
  isBundleDebug,
58
- isConvertRCToFlat,
59
58
  isEdit,
60
59
  isView,
61
60
  VIEW,
@@ -184,14 +183,14 @@ async function uiLoop(arg) {
184
183
  }
185
184
 
186
185
  if (isConvertChosen(arg)) {
187
- let filename = '.eslintrc.json';
188
-
189
- if (!isConvertRCToFlat(arg))
190
- filename = await askFilename();
191
-
192
- if (filename)
193
- await masterConvert(filename, arg, filesystem);
194
-
186
+ await runConvert(arg, filesystem);
187
+ return;
188
+ }
189
+
190
+ if (isConvertChosenDebug(arg)) {
191
+ await runConvert(arg, filesystem, {
192
+ convert,
193
+ });
195
194
  return;
196
195
  }
197
196
 
@@ -205,15 +204,6 @@ async function uiLoop(arg) {
205
204
  return;
206
205
  }
207
206
 
208
- if (isConvertChosenDebug(arg)) {
209
- const filename = await askFilename();
210
-
211
- if (filename)
212
- await convert(filename, arg, filesystem);
213
-
214
- return;
215
- }
216
-
217
207
  if (isScan(arg)) {
218
208
  const places = await masterLint(filesystem, {
219
209
  fix: false,
@@ -0,0 +1,14 @@
1
+ import * as pluginFilesystem from '@putout/plugin-filesystem';
2
+
3
+ const [, pluginConvertJsToJson] = pluginFilesystem.rules['convert-js-to-json'];
4
+
5
+ export const convertJSToJson = (filename) => ({
6
+ rules: {
7
+ 'filesystem/convert-js-to-json': ['on', {
8
+ filename,
9
+ }],
10
+ },
11
+ plugins: [
12
+ ['filesystem/convert-js-to-json', pluginConvertJsToJson],
13
+ ],
14
+ });
@@ -0,0 +1,14 @@
1
+ import * as pluginFilesystem from '@putout/plugin-filesystem';
2
+
3
+ const [, pluginConvertJsonToJs] = pluginFilesystem.rules['convert-json-to-js'];
4
+
5
+ export const convertJsonToJs = (filename) => ({
6
+ rules: {
7
+ 'filesystem/convert-json-to-js': ['on', {
8
+ filename,
9
+ }],
10
+ },
11
+ plugins: [
12
+ ['filesystem/convert-json-to-js', pluginConvertJsonToJs],
13
+ ],
14
+ });
@@ -0,0 +1,12 @@
1
+ import * as pluginESLint from '@putout/plugin-eslint';
2
+
3
+ const [, pluginConvertRCToFlat] = pluginESLint.rules['convert-rc-to-flat'];
4
+
5
+ export const convertRCToFlat = () => ({
6
+ rules: {
7
+ 'eslint/convert-rc-to-flat': 'on',
8
+ },
9
+ plugins: [
10
+ ['eslint/convert-rc-to-flat', pluginConvertRCToFlat],
11
+ ],
12
+ });
@@ -0,0 +1,14 @@
1
+ import * as pluginFilesystem from '@putout/plugin-filesystem';
2
+
3
+ const [, pluginConvertYamlToJson] = pluginFilesystem.rules['convert-yaml-to-json'];
4
+
5
+ export const convertYamlToJson = (filename) => ({
6
+ rules: {
7
+ 'filesystem/convert-yaml-to-json': ['on', {
8
+ filename,
9
+ }],
10
+ },
11
+ plugins: [
12
+ ['filesystem/convert-yaml-to-json', pluginConvertYamlToJson],
13
+ ],
14
+ });
@@ -1,49 +1,32 @@
1
- import * as pluginFilesystem from '@putout/plugin-filesystem';
2
- import * as pluginESLint from '@putout/plugin-eslint';
3
1
  import {
4
- isConvertToJs,
5
- isConvertToJson,
6
- isConvertRCToFlat,
2
+ CONVERT_JS_TO_JSON,
3
+ CONVERT_JSON_TO_JS,
4
+ CONVERT_JSON_TO_JS_DEBUG,
5
+ CONVERT_JS_TO_JSON_DEBUG,
6
+ CONVERT_YAML_TO_JSON,
7
+ CONVERT_YAML_TO_JSON_DEBUG,
8
+ CONVERT_RC_TO_FLAT,
7
9
  } from '../menu.js';
10
+ import {convertYamlToJson} from './converters/convert-yaml-to-json.js';
11
+ import {convertJsonToJs} from './converters/convert-json-to-js.js';
12
+ import {convertJSToJson} from './converters/convert-js-to-json.js';
13
+ import {convertRCToFlat} from './converters/convert-rc-to-flat.js';
8
14
 
9
- const [, pluginConvertJsonToJs] = pluginFilesystem.rules['convert-json-to-js'];
10
- const [, pluginConvertJsToJson] = pluginFilesystem.rules['convert-js-to-json'];
11
- const [, convertRCToFlat] = pluginESLint.rules['convert-rc-to-flat'];
15
+ const CONVERTERS = {
16
+ [CONVERT_JSON_TO_JS]: convertJsonToJs,
17
+ [CONVERT_JSON_TO_JS_DEBUG]: convertJsonToJs,
18
+ [CONVERT_JS_TO_JSON]: convertJSToJson,
19
+ [CONVERT_JS_TO_JSON_DEBUG]: convertJSToJson,
20
+ [CONVERT_YAML_TO_JSON]: convertYamlToJson,
21
+ [CONVERT_YAML_TO_JSON_DEBUG]: convertYamlToJson,
22
+ [CONVERT_RC_TO_FLAT]: convertRCToFlat,
23
+ };
12
24
 
13
25
  export const createOptions = (filename, type) => {
14
- if (isConvertToJs(type))
15
- return {
16
- rules: {
17
- 'filesystem/convert-json-to-js': ['on', {
18
- filename,
19
- }],
20
- },
21
- plugins: [
22
- ['filesystem/convert-json-to-js', pluginConvertJsonToJs],
23
- ],
24
- };
25
-
26
- if (isConvertToJson(type))
27
- return {
28
- rules: {
29
- 'filesystem/convert-js-to-json': ['on', {
30
- filename,
31
- }],
32
- },
33
- plugins: [
34
- ['filesystem/convert-js-to-json', pluginConvertJsToJson],
35
- ],
36
- };
26
+ const converter = CONVERTERS[type];
37
27
 
38
- if (isConvertRCToFlat(type))
39
- return {
40
- rules: {
41
- 'eslint/convert-rc-to-flat': 'on',
42
- },
43
- plugins: [
44
- ['eslint/convert-rc-to-flat', convertRCToFlat],
45
- ],
46
- };
28
+ if (!converter)
29
+ return {};
47
30
 
48
- return {};
31
+ return converter(filename);
49
32
  };
@@ -5,6 +5,7 @@ import {
5
5
  BACK,
6
6
  EXIT,
7
7
  CONVERT_RC_TO_FLAT,
8
+ CONVERT_YAML_TO_JSON,
8
9
  } from '../menu.js';
9
10
 
10
11
  export * from './convert.js';
@@ -12,6 +13,7 @@ export const chooseConvert = async () => {
12
13
  const command = await chooseDialog('Convert:', [
13
14
  CONVERT_JS_TO_JSON,
14
15
  CONVERT_JSON_TO_JS,
16
+ CONVERT_YAML_TO_JSON,
15
17
  CONVERT_RC_TO_FLAT,
16
18
  BACK,
17
19
  EXIT,
@@ -0,0 +1,19 @@
1
+ import {askFilename} from '../dialog.js';
2
+ import {isConvertRCToFlat} from '../menu.js';
3
+ import {masterConvert} from './master.js';
4
+
5
+ export const runConvert = async (arg, filesystem, overrides = {}) => {
6
+ const {
7
+ askFilename: getFilename = askFilename,
8
+ convert = masterConvert,
9
+ isRCToFlat = isConvertRCToFlat,
10
+ } = overrides;
11
+
12
+ let filename = '.eslintrc.json';
13
+
14
+ if (!isRCToFlat(arg))
15
+ filename = await getFilename();
16
+
17
+ if (filename)
18
+ return await convert(filename, arg, filesystem);
19
+ };
package/lib/debug.js CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  FIX_DEBUG,
7
7
  CONVERT_JS_TO_JSON_DEBUG,
8
8
  CONVERT_JSON_TO_JS_DEBUG,
9
+ CONVERT_YAML_TO_JSON_DEBUG,
9
10
  RENAME_JS_TO_JSX_DEBUG,
10
11
  BACK,
11
12
  EXIT,
@@ -21,6 +22,7 @@ export const debug = async () => {
21
22
  PACK_DEBUG,
22
23
  CONVERT_JS_TO_JSON_DEBUG,
23
24
  CONVERT_JSON_TO_JS_DEBUG,
25
+ CONVERT_YAML_TO_JSON_DEBUG,
24
26
  RENAME_JS_TO_JSX_DEBUG,
25
27
  RENAME_JS_TO_JSX_DEBUG,
26
28
  BUNDLE_DEBUG,
package/lib/menu.js CHANGED
@@ -26,6 +26,9 @@ export const CONVERT_JS_TO_JSON_DEBUG = '🦏 convert js to json: debug';
26
26
  export const CONVERT_JSON_TO_JS = '🦏 convert json to js';
27
27
  export const CONVERT_JSON_TO_JS_DEBUG = '🦏 convert json to js: debug';
28
28
 
29
+ export const CONVERT_YAML_TO_JSON = '🦏 convert yaml to json';
30
+ export const CONVERT_YAML_TO_JSON_DEBUG = '🦏 convert yaml to json: debug';
31
+
29
32
  export const RENAME_JS_TO_JSX = '🦏 rename js to jsx';
30
33
  export const RENAME_JS_TO_JSX_DEBUG = '🦏 rename js to jsx: debug';
31
34
 
@@ -56,15 +59,18 @@ export const isConvertChosen = (a) => {
56
59
  return [
57
60
  CONVERT_JSON_TO_JS,
58
61
  CONVERT_JS_TO_JSON,
62
+ CONVERT_YAML_TO_JSON,
59
63
  CONVERT_RC_TO_FLAT,
60
64
  ].includes(a);
61
65
  };
62
66
  export const isRenameToJsxChosen = (a) => a === RENAME_JS_TO_JSX;
63
67
  export const isRenameToJsChosen = (a) => a === RENAME_JSX_TO_JS;
64
- export const isConvertChosenDebug = (a) => a === CONVERT_JS_TO_JSON_DEBUG || a === CONVERT_JSON_TO_JS_DEBUG;
68
+ export const isConvertChosenDebug = (a) => [
69
+ CONVERT_JS_TO_JSON_DEBUG,
70
+ CONVERT_JSON_TO_JS_DEBUG,
71
+ CONVERT_YAML_TO_JSON_DEBUG,
72
+ ].includes(a);
65
73
 
66
- export const isConvertToJson = (a) => a === CONVERT_JS_TO_JSON || a === CONVERT_JS_TO_JSON_DEBUG;
67
- export const isConvertToJs = (a) => a === CONVERT_JSON_TO_JS || a === CONVERT_JSON_TO_JS_DEBUG;
68
74
  export const isConvertRCToFlat = (a) => a === CONVERT_RC_TO_FLAT;
69
75
 
70
76
  export const isRenameToJs = (a) => a === RENAME_JS_TO_JSX || a === RENAME_JS_TO_JSX_DEBUG;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redlint",
3
- "version": "6.5.1",
3
+ "version": "6.6.0",
4
4
  "type": "module",
5
5
  "author": "coderaiser <mnemonic.enemy@gmail.com> (https://github.com/coderaiser)",
6
6
  "description": "Lint Filesystem with 🐊Putout",