@rslint/core 0.6.2 → 0.6.4

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/dist/index.js CHANGED
@@ -1,121 +1,10 @@
1
- import { spawn } from "child_process";
2
- import { createRequire } from "node:module";
1
+ import node_path from "node:path";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import { NodeRslintService } from "./internal.js";
4
+ import { loadConfigFile, normalizeConfig } from "./config-loader.js";
5
+ import { findJSConfigUp, glob } from "./207.js";
3
6
  import { RSLintService } from "./service.js";
4
- const node_require = createRequire(import.meta.url);
5
- function resolveRslintBinary() {
6
- const arch = process.arch;
7
- const tuples = 'linux' === process.platform ? [
8
- `linux-${arch}-gnu`,
9
- `linux-${arch}-musl`
10
- ] : 'win32' === process.platform ? [
11
- `win32-${arch}-msvc`
12
- ] : [
13
- `${process.platform}-${arch}`
14
- ];
15
- for (const tuple of tuples)try {
16
- return node_require.resolve(`@rslint/native-${tuple}/bin`);
17
- } catch {}
18
- throw new Error(`rslint: no native binary for ${process.platform}-${arch} (looked for @rslint/native-{${tuples.join(',')}})`);
19
- }
20
- class NodeRslintService {
21
- nextMessageId;
22
- pendingMessages;
23
- rslintPath;
24
- process;
25
- chunks;
26
- chunkSize;
27
- expectedSize;
28
- constructor(options = {}){
29
- this.nextMessageId = 1;
30
- this.pendingMessages = new Map();
31
- this.rslintPath = options.rslintPath || resolveRslintBinary();
32
- this.process = spawn(this.rslintPath, [
33
- '--api'
34
- ], {
35
- stdio: [
36
- 'pipe',
37
- 'pipe',
38
- 'inherit'
39
- ],
40
- cwd: options.workingDirectory || process.cwd(),
41
- env: {
42
- ...process.env
43
- }
44
- });
45
- this.process.stdout.on('data', (data)=>{
46
- this.handleChunk(data);
47
- });
48
- this.chunks = [];
49
- this.chunkSize = 0;
50
- this.expectedSize = null;
51
- }
52
- async sendMessage(kind, data) {
53
- return new Promise((resolve, reject)=>{
54
- const id = this.nextMessageId++;
55
- const message = {
56
- id,
57
- kind,
58
- data
59
- };
60
- this.pendingMessages.set(id, {
61
- resolve,
62
- reject
63
- });
64
- const json = JSON.stringify(message);
65
- const jsonBuffer = Buffer.from(json, 'utf8');
66
- const length = Buffer.alloc(4);
67
- length.writeUInt32LE(jsonBuffer.length, 0);
68
- this.process.stdin.write(Buffer.concat([
69
- length,
70
- jsonBuffer
71
- ]));
72
- });
73
- }
74
- handleChunk(chunk) {
75
- this.chunks.push(chunk);
76
- this.chunkSize += chunk.length;
77
- while(true){
78
- if (null === this.expectedSize) {
79
- if (this.chunkSize < 4) return;
80
- const combined = Buffer.concat(this.chunks);
81
- this.expectedSize = combined.readUInt32LE(0);
82
- this.chunks = [
83
- combined.subarray(4)
84
- ];
85
- this.chunkSize -= 4;
86
- }
87
- if (this.chunkSize < this.expectedSize) return;
88
- const combined = Buffer.concat(this.chunks);
89
- const message = combined.subarray(0, this.expectedSize).toString('utf8');
90
- try {
91
- const parsed = JSON.parse(message);
92
- this.handleMessage(parsed);
93
- } catch (err) {
94
- console.error('Error parsing message:', err);
95
- }
96
- this.chunks = [
97
- combined.subarray(this.expectedSize)
98
- ];
99
- this.chunkSize = this.chunks[0].length;
100
- this.expectedSize = null;
101
- }
102
- }
103
- handleMessage(message) {
104
- const { id, kind, data } = message;
105
- const pending = this.pendingMessages.get(id);
106
- if (!pending) return;
107
- this.pendingMessages.delete(id);
108
- if ('error' === kind) pending.reject(new Error(data.message));
109
- else pending.resolve(data);
110
- }
111
- terminate() {
112
- if (this.process && !this.process.killed) {
113
- this.process.stdin.end();
114
- this.process.kill();
115
- }
116
- }
117
- }
118
- const base = {
7
+ const typescript_base = {
119
8
  files: [
120
9
  '**/*.ts',
121
10
  '**/*.tsx',
@@ -127,7 +16,7 @@ const base = {
127
16
  ]
128
17
  };
129
18
  const recommended = {
130
- ...base,
19
+ ...typescript_base,
131
20
  languageOptions: {
132
21
  parserOptions: {
133
22
  projectService: true
@@ -347,7 +236,8 @@ const promise_recommended = {
347
236
  'promise/no-return-wrap': 'error',
348
237
  'promise/param-names': 'error',
349
238
  'promise/catch-or-return': 'error',
350
- 'promise/avoid-new': 'off'
239
+ 'promise/avoid-new': 'off',
240
+ 'promise/valid-params': 'warn'
351
241
  }
352
242
  };
353
243
  const jest_recommended = {
@@ -361,6 +251,7 @@ const jest_recommended = {
361
251
  'jest/no-deprecated-functions': 'error',
362
252
  'jest/no-disabled-tests': 'warn',
363
253
  'jest/no-done-callback': 'error',
254
+ 'jest/no-export': 'error',
364
255
  'jest/no-focused-tests': 'error',
365
256
  'jest/no-identical-title': 'error',
366
257
  'jest/no-jasmine-globals': 'error',
@@ -560,7 +451,7 @@ const jsx_a11y_recommended = {
560
451
  };
561
452
  const ts = {
562
453
  configs: {
563
- base: base,
454
+ base: typescript_base,
564
455
  recommended: recommended
565
456
  }
566
457
  };
@@ -605,26 +496,257 @@ const jsxA11yPlugin = {
605
496
  recommended: jsx_a11y_recommended
606
497
  }
607
498
  };
608
- async function lint(options) {
609
- const service = new RSLintService(new NodeRslintService({
610
- workingDirectory: options.workingDirectory
611
- }));
612
- const result = await service.lint(options);
613
- await service.close();
614
- return result;
499
+ function _to_primitive(input, hint) {
500
+ if ("object" !== _type_of(input) || null === input) return input;
501
+ var prim = input[Symbol.toPrimitive];
502
+ if (void 0 !== prim) {
503
+ var res = prim.call(input, hint || "default");
504
+ if ("object" !== _type_of(res)) return res;
505
+ throw new TypeError("@@toPrimitive must return a primitive value.");
506
+ }
507
+ return ("string" === hint ? String : Number)(input);
508
+ }
509
+ function _to_property_key(arg) {
510
+ var key = _to_primitive(arg, "string");
511
+ return "symbol" === _type_of(key) ? key : String(key);
512
+ }
513
+ function _type_of(obj) {
514
+ return obj && "u" > typeof Symbol && obj.constructor === Symbol ? "symbol" : typeof obj;
515
+ }
516
+ let _computedKey;
517
+ _computedKey = _to_property_key(Symbol.asyncDispose);
518
+ class Rslint {
519
+ #service;
520
+ #cwd;
521
+ #overrideConfig;
522
+ #overrideConfigFile;
523
+ #fix;
524
+ #virtualFiles;
525
+ constructor(options = {}){
526
+ this.#cwd = options.cwd ? node_path.resolve(options.cwd) : process.cwd();
527
+ this.#overrideConfig = options.overrideConfig;
528
+ this.#overrideConfigFile = options.overrideConfigFile;
529
+ this.#fix = options.fix ?? false;
530
+ this.#virtualFiles = options.virtualFiles;
531
+ this.#service = new RSLintService(new NodeRslintService());
532
+ }
533
+ async lintText(code, options = {}) {
534
+ const filePath = node_path.resolve(this.#cwd, options.filePath ?? '__text__.ts');
535
+ const { config, configDirectory } = await this.#resolveConfig(node_path.dirname(filePath));
536
+ const response = await this.#service.lint({
537
+ config,
538
+ configDirectory,
539
+ workingDirectory: this.#cwd,
540
+ files: [
541
+ filePath
542
+ ],
543
+ fileContents: {
544
+ ...this.#resolveOverlay(),
545
+ [filePath]: code
546
+ },
547
+ fix: this.#fix
548
+ });
549
+ const results = this.#toLintResults(response, configDirectory, [
550
+ filePath
551
+ ], {
552
+ [filePath]: code
553
+ });
554
+ const primary = results.filter((r)=>r.filePath === filePath);
555
+ if (null == options.filePath) {
556
+ for (const r of primary)if (r.filePath === filePath) r.filePath = '<text>';
557
+ }
558
+ return primary;
559
+ }
560
+ async lintFiles(patterns) {
561
+ const globs = Array.isArray(patterns) ? patterns : [
562
+ patterns
563
+ ];
564
+ const matched = await glob(globs, {
565
+ cwd: this.#cwd,
566
+ absolute: true,
567
+ onlyFiles: true
568
+ });
569
+ const files = matched.map((f)=>node_path.normalize(f));
570
+ if (0 === files.length) return [];
571
+ const { config, configDirectory } = await this.#resolveConfig(this.#cwd);
572
+ const response = await this.#service.lint({
573
+ config,
574
+ configDirectory,
575
+ workingDirectory: this.#cwd,
576
+ files,
577
+ fileContents: this.#resolveOverlay(),
578
+ fix: this.#fix
579
+ });
580
+ const contents = {};
581
+ const bomFiles = new Set();
582
+ for (const d of response.diagnostics ?? []){
583
+ const abs = node_path.isAbsolute(d.filePath) ? node_path.normalize(d.filePath) : node_path.resolve(configDirectory, d.filePath);
584
+ if (!(abs in contents)) try {
585
+ const raw = await readFile(abs, 'utf8');
586
+ if (0xfeff === raw.charCodeAt(0)) {
587
+ bomFiles.add(abs);
588
+ contents[abs] = raw.slice(1);
589
+ } else contents[abs] = raw;
590
+ } catch {}
591
+ }
592
+ const linted = response.lintedFiles ? response.lintedFiles.map((f)=>node_path.isAbsolute(f) ? node_path.normalize(f) : node_path.resolve(configDirectory, f)) : files;
593
+ return this.#toLintResults(response, configDirectory, linted, contents, bomFiles);
594
+ }
595
+ static async outputFixes(results) {
596
+ await Promise.all(results.map(async (r)=>{
597
+ if ('string' == typeof r.output && node_path.isAbsolute(r.filePath)) await writeFile(r.filePath, r.output);
598
+ }));
599
+ }
600
+ async close() {
601
+ await this.#service.close();
602
+ }
603
+ async [_computedKey]() {
604
+ await this.close();
605
+ }
606
+ #resolveOverlay() {
607
+ if (!this.#virtualFiles) return;
608
+ const resolved = {};
609
+ for (const [p, content] of Object.entries(this.#virtualFiles))resolved[node_path.resolve(this.#cwd, p)] = content;
610
+ return resolved;
611
+ }
612
+ async #resolveConfig(fromDir) {
613
+ let base = [];
614
+ let configDirectory = this.#cwd;
615
+ if (true === this.#overrideConfigFile) ;
616
+ else if ('string' == typeof this.#overrideConfigFile) {
617
+ const configPath = node_path.resolve(this.#cwd, this.#overrideConfigFile);
618
+ base = normalizeConfig(await loadConfigFile(configPath));
619
+ configDirectory = node_path.dirname(configPath);
620
+ } else {
621
+ const configPath = findJSConfigUp(fromDir);
622
+ if (configPath) {
623
+ base = normalizeConfig(await loadConfigFile(configPath));
624
+ configDirectory = node_path.dirname(configPath);
625
+ }
626
+ }
627
+ if (null != this.#overrideConfig) {
628
+ const override = Array.isArray(this.#overrideConfig) ? this.#overrideConfig : [
629
+ this.#overrideConfig
630
+ ];
631
+ base = [
632
+ ...base,
633
+ ...normalizeConfig(override)
634
+ ];
635
+ }
636
+ return {
637
+ config: base,
638
+ configDirectory
639
+ };
640
+ }
641
+ #toLintResults(response, configDirectory, files, contents, bomFiles) {
642
+ const toAbs = (p)=>node_path.isAbsolute(p) ? node_path.normalize(p) : node_path.resolve(configDirectory, p);
643
+ const byFile = new Map();
644
+ for (const f of files)byFile.set(node_path.normalize(f), []);
645
+ for (const d of response.diagnostics ?? []){
646
+ const abs = toAbs(d.filePath);
647
+ let messages = byFile.get(abs);
648
+ if (!messages) {
649
+ messages = [];
650
+ byFile.set(abs, messages);
651
+ }
652
+ messages.push(toLintMessage(d, contents?.[abs]));
653
+ }
654
+ const outputByAbs = new Map();
655
+ for (const [rel, fixed] of Object.entries(response.output ?? {}))outputByAbs.set(toAbs(rel), fixed);
656
+ const results = [];
657
+ for (const [filePath, messages] of byFile){
658
+ let errorCount = 0;
659
+ let warningCount = 0;
660
+ let fixableErrorCount = 0;
661
+ let fixableWarningCount = 0;
662
+ for (const m of messages)if (2 === m.severity) {
663
+ errorCount++;
664
+ if (m.fix) fixableErrorCount++;
665
+ } else {
666
+ warningCount++;
667
+ if (m.fix) fixableWarningCount++;
668
+ }
669
+ const result = {
670
+ filePath,
671
+ messages,
672
+ errorCount,
673
+ warningCount,
674
+ fixableErrorCount,
675
+ fixableWarningCount
676
+ };
677
+ const output = outputByAbs.get(filePath);
678
+ if (void 0 !== output) result.output = bomFiles?.has(filePath) ? '\uFEFF' + output : output;
679
+ results.push(result);
680
+ }
681
+ return results;
682
+ }
615
683
  }
616
- async function applyFixes(options) {
617
- const service = new RSLintService(new NodeRslintService());
618
- const result = await service.applyFixes(options);
619
- await service.close();
620
- return result;
684
+ function toLintMessage(d, sourceText) {
685
+ const message = {
686
+ ruleId: d.ruleName || null,
687
+ severity: 'error' === d.severity ? 2 : 1,
688
+ message: d.message,
689
+ line: d.range.start.line,
690
+ column: d.range.start.column,
691
+ endLine: d.range.end.line,
692
+ endColumn: d.range.end.column
693
+ };
694
+ if (d.messageId) message.messageId = d.messageId;
695
+ const fix = mergeFixes(d.fixes, sourceText);
696
+ if (fix) message.fix = fix;
697
+ if (d.suggestions && d.suggestions.length > 0) message.suggestions = d.suggestions.map((s)=>{
698
+ const sFix = mergeFixes(s.fixes, sourceText);
699
+ return {
700
+ messageId: s.messageId,
701
+ ...s.data ? {
702
+ data: s.data
703
+ } : {},
704
+ desc: s.message,
705
+ fix: sFix ?? {
706
+ range: [
707
+ 0,
708
+ 0
709
+ ],
710
+ text: ''
711
+ }
712
+ };
713
+ });
714
+ return message;
621
715
  }
622
- async function getAstInfo(options) {
623
- const service = new RSLintService(new NodeRslintService());
624
- const result = await service.getAstInfo(options);
625
- await service.close();
626
- return result;
716
+ function mergeFixes(fixes, sourceText) {
717
+ if (!fixes || 0 === fixes.length) return;
718
+ if (1 === fixes.length) return {
719
+ range: [
720
+ fixes[0].startPos,
721
+ fixes[0].endPos
722
+ ],
723
+ text: fixes[0].text
724
+ };
725
+ const sorted = [
726
+ ...fixes
727
+ ].sort((a, b)=>a.startPos - b.startPos || a.endPos - b.endPos);
728
+ const start = sorted[0].startPos;
729
+ const end = sorted[sorted.length - 1].endPos;
730
+ if (void 0 === sourceText) return {
731
+ range: [
732
+ sorted[0].startPos,
733
+ sorted[0].endPos
734
+ ],
735
+ text: sorted[0].text
736
+ };
737
+ let text = '';
738
+ let lastPos = start;
739
+ for (const f of sorted)if (!(f.startPos < lastPos)) {
740
+ text += sourceText.slice(lastPos, f.startPos) + f.text;
741
+ lastPos = f.endPos;
742
+ }
743
+ return {
744
+ range: [
745
+ start,
746
+ end
747
+ ],
748
+ text
749
+ };
627
750
  }
628
- export { RSLintService } from "./service.js";
629
- export { defineConfig, globalIgnores } from "./34.js";
630
- export { NodeRslintService, applyFixes, getAstInfo, importPlugin, jestPlugin, js, jsxA11yPlugin, lint, promisePlugin, reactHooksPlugin, reactPlugin, ts, unicornPlugin };
751
+ export { defineConfig, globalIgnores } from "./config-loader.js";
752
+ export { Rslint, importPlugin, jestPlugin, js, jsxA11yPlugin, promisePlugin, reactHooksPlugin, reactPlugin, ts, unicornPlugin };
@@ -0,0 +1,135 @@
1
+ export declare interface Diagnostic {
2
+ ruleName: string;
3
+ message: string;
4
+ messageId: string;
5
+ filePath: string;
6
+ range: Range;
7
+ severity?: string;
8
+ fixes?: Fix[];
9
+ suggestions?: Suggestion[];
10
+ }
11
+
12
+ declare interface Fix {
13
+ text: string;
14
+ startPos: number;
15
+ endPos: number;
16
+ }
17
+
18
+ /**
19
+ * One-shot convenience: spin up a Node-backed service, run a single lint
20
+ * request, then tear it down. This is an internal/tooling surface (the
21
+ * rule-tester and the ESLint-plugin conformance harnesses) reached via the
22
+ * `@rslint/core/internal` subpath — the package root deliberately exposes only
23
+ * the high-level `Rslint` class as its linting surface, not this low-level engine.
24
+ */
25
+ export declare function lint(options: LintOptions): Promise<LintResponse>;
26
+
27
+ export declare interface LintOptions {
28
+ files?: string[];
29
+ config?: Record<string, unknown>[];
30
+ configDirectory?: string;
31
+ workingDirectory?: string;
32
+ fileContents?: Record<string, string>;
33
+ includeEncodedSourceFiles?: boolean;
34
+ fix?: boolean;
35
+ }
36
+
37
+ export declare interface LintResponse {
38
+ diagnostics: Diagnostic[];
39
+ errorCount: number;
40
+ warningCount: number;
41
+ fixableErrorCount: number;
42
+ fixableWarningCount: number;
43
+ fileCount: number;
44
+ ruleCount: number;
45
+ lintedFiles?: string[];
46
+ output?: Record<string, string>;
47
+ encodedSourceFiles?: Record<string, string>;
48
+ }
49
+
50
+ /**
51
+ * Node.js implementation of RslintService using child processes
52
+ */
53
+ export declare class NodeRslintService implements RslintServiceInterface {
54
+ private nextMessageId;
55
+ private readonly pendingMessages;
56
+ private readonly rslintPath;
57
+ private readonly process;
58
+ private chunks;
59
+ private chunkSize;
60
+ private expectedSize;
61
+ private dead;
62
+ private closing;
63
+ constructor(options?: RSlintOptions);
64
+ /**
65
+ * Keep the Node event loop alive only while a request is in flight. The
66
+ * resident child and its stdio pipes are unref'd while idle so a caller that
67
+ * never calls close() still lets the process exit; they are ref'd around an
68
+ * in-flight request because a pending promise alone does NOT keep the loop
69
+ * alive — without the ref the loop could drain before the response arrives,
70
+ * leaving the await unsettled (Node would exit with code 13). The piped stdio
71
+ * streams are net.Socket at runtime (with ref/unref); child_process widens
72
+ * them to Readable/Writable, so narrow via `instanceof Socket` to reach those.
73
+ */
74
+ private setLoopActive;
75
+ /**
76
+ * Send a message to the rslint process
77
+ */
78
+ sendMessage(kind: string, data: any): Promise<any>;
79
+ /**
80
+ * Handle incoming binary data chunks
81
+ */
82
+ private handleChunk;
83
+ /**
84
+ * Handle a complete message from rslint
85
+ */
86
+ private handleMessage;
87
+ /**
88
+ * Reject every in-flight request and clear the queue. Called when the process
89
+ * dies (exit/error) or is terminated, so callers never hang on a process that
90
+ * can no longer reply. No-op when nothing is pending (the normal close path).
91
+ */
92
+ private rejectAllPending;
93
+ /**
94
+ * Resolve every in-flight request (no payload) and clear the queue. Used on
95
+ * the expected close() shutdown path so the 'exit' request settles cleanly
96
+ * instead of rejecting. No-op when nothing is pending.
97
+ */
98
+ private resolveAllPending;
99
+ /**
100
+ * Terminate the rslint process
101
+ */
102
+ terminate(): void;
103
+ }
104
+
105
+ /**
106
+ * Shared types for rslint IPC protocol across all environments
107
+ */
108
+ declare interface Position {
109
+ line: number;
110
+ column: number;
111
+ }
112
+
113
+ declare interface Range {
114
+ start: Position;
115
+ end: Position;
116
+ }
117
+
118
+ declare interface RSlintOptions {
119
+ rslintPath?: string;
120
+ workingDirectory?: string;
121
+ }
122
+
123
+ declare interface RslintServiceInterface {
124
+ sendMessage(kind: string, data: any): Promise<any>;
125
+ terminate(): void;
126
+ }
127
+
128
+ declare interface Suggestion {
129
+ messageId: string;
130
+ message: string;
131
+ data?: Record<string, string>;
132
+ fixes?: Fix[];
133
+ }
134
+
135
+ export { }