@rslint/core 0.6.3 → 0.6.5

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 "./cli.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,6 +236,7 @@ const promise_recommended = {
347
236
  'promise/no-return-wrap': 'error',
348
237
  'promise/param-names': 'error',
349
238
  'promise/catch-or-return': 'error',
239
+ 'promise/no-promise-in-callback': 'warn',
350
240
  'promise/avoid-new': 'off',
351
241
  'promise/valid-params': 'warn'
352
242
  }
@@ -359,6 +249,7 @@ const jest_recommended = {
359
249
  'jest/expect-expect': 'warn',
360
250
  'jest/no-alias-methods': 'error',
361
251
  'jest/no-commented-out-tests': 'warn',
252
+ 'jest/no-conditional-expect': 'error',
362
253
  'jest/no-deprecated-functions': 'error',
363
254
  'jest/no-disabled-tests': 'warn',
364
255
  'jest/no-done-callback': 'error',
@@ -562,7 +453,7 @@ const jsx_a11y_recommended = {
562
453
  };
563
454
  const ts = {
564
455
  configs: {
565
- base: base,
456
+ base: typescript_base,
566
457
  recommended: recommended
567
458
  }
568
459
  };
@@ -607,26 +498,258 @@ const jsxA11yPlugin = {
607
498
  recommended: jsx_a11y_recommended
608
499
  }
609
500
  };
610
- async function lint(options) {
611
- const service = new RSLintService(new NodeRslintService({
612
- workingDirectory: options.workingDirectory
613
- }));
614
- const result = await service.lint(options);
615
- await service.close();
616
- return result;
501
+ function _to_primitive(input, hint) {
502
+ if ("object" !== _type_of(input) || null === input) return input;
503
+ var prim = input[Symbol.toPrimitive];
504
+ if (void 0 !== prim) {
505
+ var res = prim.call(input, hint || "default");
506
+ if ("object" !== _type_of(res)) return res;
507
+ throw new TypeError("@@toPrimitive must return a primitive value.");
508
+ }
509
+ return ("string" === hint ? String : Number)(input);
510
+ }
511
+ function _to_property_key(arg) {
512
+ var key = _to_primitive(arg, "string");
513
+ return "symbol" === _type_of(key) ? key : String(key);
514
+ }
515
+ function _type_of(obj) {
516
+ return obj && "u" > typeof Symbol && obj.constructor === Symbol ? "symbol" : typeof obj;
517
+ }
518
+ let _computedKey;
519
+ _computedKey = _to_property_key(Symbol.asyncDispose);
520
+ class Rslint {
521
+ #service;
522
+ #cwd;
523
+ #overrideConfig;
524
+ #overrideConfigFile;
525
+ #fix;
526
+ #virtualFiles;
527
+ constructor(options = {}){
528
+ this.#cwd = options.cwd ? node_path.resolve(options.cwd) : process.cwd();
529
+ this.#overrideConfig = options.overrideConfig;
530
+ this.#overrideConfigFile = options.overrideConfigFile;
531
+ this.#fix = options.fix ?? false;
532
+ this.#virtualFiles = options.virtualFiles;
533
+ this.#service = new RSLintService(new NodeRslintService());
534
+ }
535
+ async lintText(code, options = {}) {
536
+ const filePath = node_path.resolve(this.#cwd, options.filePath ?? '__text__.ts');
537
+ const { config, configDirectory } = await this.#resolveConfig(node_path.dirname(filePath));
538
+ const response = await this.#service.lint({
539
+ config,
540
+ configDirectory,
541
+ workingDirectory: this.#cwd,
542
+ files: [
543
+ filePath
544
+ ],
545
+ fileContents: {
546
+ ...this.#resolveOverlay(),
547
+ [filePath]: code
548
+ },
549
+ fix: this.#fix
550
+ });
551
+ const results = this.#toLintResults(response, configDirectory, [
552
+ filePath
553
+ ], {
554
+ [filePath]: code
555
+ });
556
+ const primary = results.filter((r)=>r.filePath === filePath);
557
+ if (null == options.filePath) {
558
+ for (const r of primary)if (r.filePath === filePath) r.filePath = '<text>';
559
+ }
560
+ return primary;
561
+ }
562
+ async lintFiles(patterns) {
563
+ const globs = Array.isArray(patterns) ? patterns : [
564
+ patterns
565
+ ];
566
+ const matched = await glob(globs, {
567
+ cwd: this.#cwd,
568
+ absolute: true,
569
+ onlyFiles: true
570
+ });
571
+ const files = matched.map((f)=>node_path.normalize(f));
572
+ if (0 === files.length) return [];
573
+ const { config, configDirectory } = await this.#resolveConfig(this.#cwd);
574
+ const response = await this.#service.lint({
575
+ config,
576
+ configDirectory,
577
+ workingDirectory: this.#cwd,
578
+ files,
579
+ fileContents: this.#resolveOverlay(),
580
+ fix: this.#fix
581
+ });
582
+ const contents = {};
583
+ const bomFiles = new Set();
584
+ for (const d of response.diagnostics ?? []){
585
+ const abs = node_path.isAbsolute(d.filePath) ? node_path.normalize(d.filePath) : node_path.resolve(configDirectory, d.filePath);
586
+ if (!(abs in contents)) try {
587
+ const raw = await readFile(abs, 'utf8');
588
+ if (0xfeff === raw.charCodeAt(0)) {
589
+ bomFiles.add(abs);
590
+ contents[abs] = raw.slice(1);
591
+ } else contents[abs] = raw;
592
+ } catch {}
593
+ }
594
+ const linted = response.lintedFiles ? response.lintedFiles.map((f)=>node_path.isAbsolute(f) ? node_path.normalize(f) : node_path.resolve(configDirectory, f)) : files;
595
+ return this.#toLintResults(response, configDirectory, linted, contents, bomFiles);
596
+ }
597
+ static async outputFixes(results) {
598
+ await Promise.all(results.map(async (r)=>{
599
+ if ('string' == typeof r.output && node_path.isAbsolute(r.filePath)) await writeFile(r.filePath, r.output);
600
+ }));
601
+ }
602
+ async close() {
603
+ await this.#service.close();
604
+ }
605
+ async [_computedKey]() {
606
+ await this.close();
607
+ }
608
+ #resolveOverlay() {
609
+ if (!this.#virtualFiles) return;
610
+ const resolved = {};
611
+ for (const [p, content] of Object.entries(this.#virtualFiles))resolved[node_path.resolve(this.#cwd, p)] = content;
612
+ return resolved;
613
+ }
614
+ async #resolveConfig(fromDir) {
615
+ let base = [];
616
+ let configDirectory = this.#cwd;
617
+ if (true === this.#overrideConfigFile) ;
618
+ else if ('string' == typeof this.#overrideConfigFile) {
619
+ const configPath = node_path.resolve(this.#cwd, this.#overrideConfigFile);
620
+ base = normalizeConfig(await loadConfigFile(configPath));
621
+ configDirectory = node_path.dirname(configPath);
622
+ } else {
623
+ const configPath = findJSConfigUp(fromDir);
624
+ if (configPath) {
625
+ base = normalizeConfig(await loadConfigFile(configPath));
626
+ configDirectory = node_path.dirname(configPath);
627
+ }
628
+ }
629
+ if (null != this.#overrideConfig) {
630
+ const override = Array.isArray(this.#overrideConfig) ? this.#overrideConfig : [
631
+ this.#overrideConfig
632
+ ];
633
+ base = [
634
+ ...base,
635
+ ...normalizeConfig(override)
636
+ ];
637
+ }
638
+ return {
639
+ config: base,
640
+ configDirectory
641
+ };
642
+ }
643
+ #toLintResults(response, configDirectory, files, contents, bomFiles) {
644
+ const toAbs = (p)=>node_path.isAbsolute(p) ? node_path.normalize(p) : node_path.resolve(configDirectory, p);
645
+ const byFile = new Map();
646
+ for (const f of files)byFile.set(node_path.normalize(f), []);
647
+ for (const d of response.diagnostics ?? []){
648
+ const abs = toAbs(d.filePath);
649
+ let messages = byFile.get(abs);
650
+ if (!messages) {
651
+ messages = [];
652
+ byFile.set(abs, messages);
653
+ }
654
+ messages.push(toLintMessage(d, contents?.[abs]));
655
+ }
656
+ const outputByAbs = new Map();
657
+ for (const [rel, fixed] of Object.entries(response.output ?? {}))outputByAbs.set(toAbs(rel), fixed);
658
+ const results = [];
659
+ for (const [filePath, messages] of byFile){
660
+ let errorCount = 0;
661
+ let warningCount = 0;
662
+ let fixableErrorCount = 0;
663
+ let fixableWarningCount = 0;
664
+ for (const m of messages)if (2 === m.severity) {
665
+ errorCount++;
666
+ if (m.fix) fixableErrorCount++;
667
+ } else {
668
+ warningCount++;
669
+ if (m.fix) fixableWarningCount++;
670
+ }
671
+ const result = {
672
+ filePath,
673
+ messages,
674
+ errorCount,
675
+ warningCount,
676
+ fixableErrorCount,
677
+ fixableWarningCount
678
+ };
679
+ const output = outputByAbs.get(filePath);
680
+ if (void 0 !== output) result.output = bomFiles?.has(filePath) ? '\uFEFF' + output : output;
681
+ results.push(result);
682
+ }
683
+ return results;
684
+ }
617
685
  }
618
- async function applyFixes(options) {
619
- const service = new RSLintService(new NodeRslintService());
620
- const result = await service.applyFixes(options);
621
- await service.close();
622
- return result;
686
+ function toLintMessage(d, sourceText) {
687
+ const message = {
688
+ ruleId: d.ruleName || null,
689
+ severity: 'error' === d.severity ? 2 : 1,
690
+ message: d.message,
691
+ line: d.range.start.line,
692
+ column: d.range.start.column,
693
+ endLine: d.range.end.line,
694
+ endColumn: d.range.end.column
695
+ };
696
+ if (d.messageId) message.messageId = d.messageId;
697
+ const fix = mergeFixes(d.fixes, sourceText);
698
+ if (fix) message.fix = fix;
699
+ if (d.suggestions && d.suggestions.length > 0) message.suggestions = d.suggestions.map((s)=>{
700
+ const sFix = mergeFixes(s.fixes, sourceText);
701
+ return {
702
+ messageId: s.messageId,
703
+ ...s.data ? {
704
+ data: s.data
705
+ } : {},
706
+ desc: s.message,
707
+ fix: sFix ?? {
708
+ range: [
709
+ 0,
710
+ 0
711
+ ],
712
+ text: ''
713
+ }
714
+ };
715
+ });
716
+ return message;
623
717
  }
624
- async function getAstInfo(options) {
625
- const service = new RSLintService(new NodeRslintService());
626
- const result = await service.getAstInfo(options);
627
- await service.close();
628
- return result;
718
+ function mergeFixes(fixes, sourceText) {
719
+ if (!fixes || 0 === fixes.length) return;
720
+ if (1 === fixes.length) return {
721
+ range: [
722
+ fixes[0].startPos,
723
+ fixes[0].endPos
724
+ ],
725
+ text: fixes[0].text
726
+ };
727
+ const sorted = [
728
+ ...fixes
729
+ ].sort((a, b)=>a.startPos - b.startPos || a.endPos - b.endPos);
730
+ const start = sorted[0].startPos;
731
+ const end = sorted[sorted.length - 1].endPos;
732
+ if (void 0 === sourceText) return {
733
+ range: [
734
+ sorted[0].startPos,
735
+ sorted[0].endPos
736
+ ],
737
+ text: sorted[0].text
738
+ };
739
+ let text = '';
740
+ let lastPos = start;
741
+ for (const f of sorted)if (!(f.startPos < lastPos)) {
742
+ text += sourceText.slice(lastPos, f.startPos) + f.text;
743
+ lastPos = f.endPos;
744
+ }
745
+ return {
746
+ range: [
747
+ start,
748
+ end
749
+ ],
750
+ text
751
+ };
629
752
  }
630
- export { RSLintService } from "./service.js";
631
- export { defineConfig, globalIgnores } from "./34.js";
632
- export { NodeRslintService, applyFixes, getAstInfo, importPlugin, jestPlugin, js, jsxA11yPlugin, lint, promisePlugin, reactHooksPlugin, reactPlugin, ts, unicornPlugin };
753
+ export { defineConfig, globalIgnores } from "./config-loader.js";
754
+ export { runCLI } from "./cli.js";
755
+ 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 { }