lintcn 0.7.0 → 0.8.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.md +67 -0
- package/README.md +16 -12
- package/dist/cache.d.ts +1 -1
- package/dist/cache.js +4 -4
- package/dist/cli.js +9 -12
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +14 -0
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +6 -2
- package/package.json +1 -1
- package/src/cache.ts +4 -4
- package/src/cli.ts +9 -12
- package/src/commands/add.ts +25 -1
- package/src/commands/lint.ts +7 -2
- package/src/hash.ts +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,70 @@
|
|
|
1
|
+
## 0.8.0
|
|
2
|
+
|
|
3
|
+
1. **Diagnostic output now shows `warning` / `error` prefix** — each diagnostic header now includes an explicit severity label so warnings are clear in non-color terminals and CI logs:
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
warning prefer-object-params — function `createUser` has 3 positional parameters. Use one object parameter instead.
|
|
7
|
+
error no-unhandled-error — Error-typed return value is not handled.
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
2. **New rule: `no-unsafe-unknown` (warn)** — warns when `unknown` appears in function parameter types or type assertions. Agents often reach for `unknown` when hitting TypeScript friction rather than reading the real types:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
function run(input: unknown) {} // warned — use the real type
|
|
14
|
+
const v = input as unknown as User // warned — narrow or validate instead
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
3. **New rule: `no-tiny-wrapper-function` (warn)** — warns on functions and methods whose entire body is a direct forwarding call. Keeps the codebase direct instead of building throwaway delegation layers:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const save = (file: string) => write(file) // warned — inline at use site
|
|
21
|
+
function persist(v: string) { return db.save(v) } // warned
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Computed arguments (`write(normalize(value))`) are allowed — those add real behavior.
|
|
25
|
+
|
|
26
|
+
4. **New rule: `no-single-use-top-level-function` (warn)** — warns on short top-level functions that are only called once. Suggests inlining at the call site to reduce indirection:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
function buildQuery(id: string) {
|
|
30
|
+
return `SELECT * FROM users WHERE id = '${id}'`
|
|
31
|
+
}
|
|
32
|
+
// called only once — inline it
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
5. **New rule: `no-single-use-top-level-type` (warn)** — warns on short root-level interfaces and type aliases that are only referenced once. The type just adds an extra name; inlining it at the use site is cleaner:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
type User = { name: string } // warned — only used once below
|
|
39
|
+
const user: User = { name: 'a' }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Exported types, recursive types, and longer declarations are allowed.
|
|
43
|
+
|
|
44
|
+
6. **New rule: `no-unused-top-level-function` (warn)** — warns on top-level functions with zero callers in the program. Either inline, delete, or promote to a proper exported API.
|
|
45
|
+
|
|
46
|
+
7. **New rule: `prefer-object-params` (warn)** — warns on reusable function definitions with 3 or more positional parameters. Object parameters are easier to read, easier to evolve, and harder to call in the wrong order:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// warned
|
|
50
|
+
function createUser(name: string, email: string, role: string) {}
|
|
51
|
+
|
|
52
|
+
// preferred
|
|
53
|
+
function createUser(options: { name: string; email: string; role: string }) {}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Inline callbacks passed directly at the call site are exempt.
|
|
57
|
+
|
|
58
|
+
8. **Clearer error when no rules found** — the "no rules" error message now shows the resolved absolute path to the `.lintcn/` directory it searched, making misconfigured project roots easy to spot.
|
|
59
|
+
|
|
60
|
+
9. **Fixed UTF-16 code frame rendering** — diagnostic code frames now render correctly for files containing non-ASCII characters or emoji.
|
|
61
|
+
|
|
62
|
+
## 0.7.1
|
|
63
|
+
|
|
64
|
+
1. **`lintcn lint` / `lintcn build` exit 0 when `.lintcn/` not found** — instead of throwing and exiting 1, they now print a helpful message and exit cleanly. Useful when running lintcn in CI on repos that haven't set up rules yet.
|
|
65
|
+
|
|
66
|
+
2. **Snapshot files land in `.lintcn/<rule>/__snapshots__/`** — bumped tsgolint to `518fa0d`. Rule tests now write snapshots relative to the test package directory instead of the cached tsgolint source, so they're committed alongside your rule and survive `lintcn clean`. Set `TSGOLINT_SNAPSHOT_CWD=true` when running `go test` to get this behavior.
|
|
67
|
+
|
|
1
68
|
## 0.7.0
|
|
2
69
|
|
|
3
70
|
1. **`lintcn lint --fix`** — automatically apply fixes in-place. Collects diagnostics per file, applies fixes via the Go runner, and only reports what couldn't be auto-fixed:
|
package/README.md
CHANGED
|
@@ -22,6 +22,9 @@ npx lintcn add https://github.com/oxc-project/tsgolint/blob/main/internal/rules/
|
|
|
22
22
|
# Lint your project
|
|
23
23
|
npx lintcn lint
|
|
24
24
|
|
|
25
|
+
# Show warning rules for all files, not just updated/added files
|
|
26
|
+
npx lintcn lint --all-warnings
|
|
27
|
+
|
|
25
28
|
# Lint with a specific tsconfig
|
|
26
29
|
npx lintcn lint --tsconfig tsconfig.build.json
|
|
27
30
|
|
|
@@ -156,9 +159,9 @@ void getUser("id");
|
|
|
156
159
|
Rules can be configured as **warnings** instead of errors:
|
|
157
160
|
|
|
158
161
|
- **Don't fail CI** — warnings produce exit code 0
|
|
159
|
-
- **Only shown for
|
|
162
|
+
- **Only shown for updated/added files by default** — warning rules are limited to files in `git diff` plus untracked files, so unchanged files are silently skipped
|
|
160
163
|
|
|
161
|
-
This lets you adopt new rules gradually. In a large codebase, enabling a rule as an error means hundreds of violations at once. As a warning, you only see violations in files you're actively changing — fixing issues in new code without blocking the build.
|
|
164
|
+
This lets you adopt new rules gradually. In a large codebase, enabling a rule as an error means hundreds of violations at once. As a warning, you only see violations in files you're actively changing or adding — fixing issues in new code without blocking the build.
|
|
162
165
|
|
|
163
166
|
### Configuring a rule as a warning
|
|
164
167
|
|
|
@@ -174,29 +177,30 @@ Rules without `// lintcn:severity` default to `error`.
|
|
|
174
177
|
|
|
175
178
|
### When warnings are shown
|
|
176
179
|
|
|
177
|
-
By default, `lintcn lint` runs `git diff` to find
|
|
180
|
+
By default, `lintcn lint` runs `git diff` to find updated files and also includes untracked files you just added. Warning rules are only printed for files in that set:
|
|
178
181
|
|
|
179
182
|
```bash
|
|
180
|
-
# Warnings only for files
|
|
183
|
+
# Warnings only for updated files plus newly added untracked files (default)
|
|
181
184
|
npx lintcn lint
|
|
182
185
|
|
|
183
186
|
# Warnings for ALL files, ignoring git diff
|
|
184
187
|
npx lintcn lint --all-warnings
|
|
185
188
|
```
|
|
186
189
|
|
|
187
|
-
| Scenario
|
|
188
|
-
|
|
|
189
|
-
| File is in `git diff`
|
|
190
|
-
| File is
|
|
191
|
-
|
|
|
192
|
-
|
|
|
193
|
-
|
|
|
190
|
+
| Scenario | Warnings shown? |
|
|
191
|
+
| ----------------------------------------- | ----------------- |
|
|
192
|
+
| File is updated in `git diff` | Yes |
|
|
193
|
+
| File is newly added and untracked | Yes |
|
|
194
|
+
| File is committed and unchanged | No |
|
|
195
|
+
| `--all-warnings` flag is passed | Yes, all files |
|
|
196
|
+
| Git is not installed or not a repo | No warnings shown |
|
|
197
|
+
| Clean git tree (no changes, no new files) | No warnings shown |
|
|
194
198
|
|
|
195
199
|
### Workflow
|
|
196
200
|
|
|
197
201
|
1. Add a new rule with `lintcn add`
|
|
198
202
|
2. Set it to `// lintcn:severity warn` in the Go source
|
|
199
|
-
3. Run `lintcn lint` — only see warnings in files you're currently editing
|
|
203
|
+
3. Run `lintcn lint` — only see warnings in files you're currently editing or adding
|
|
200
204
|
4. Fix warnings as you touch files naturally
|
|
201
205
|
5. Once the codebase is clean, change to `// lintcn:severity error` (or remove the directive) to enforce it
|
|
202
206
|
|
package/dist/cache.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const DEFAULT_TSGOLINT_VERSION = "
|
|
1
|
+
export declare const DEFAULT_TSGOLINT_VERSION = "4e4666c284d3b5cf7fa082523a369ef507e4360c";
|
|
2
2
|
/** Validate version string to prevent path traversal attacks.
|
|
3
3
|
* Only allows alphanumeric chars, dots, underscores, and hyphens. */
|
|
4
4
|
export declare function validateVersion(version: string): void;
|
package/dist/cache.js
CHANGED
|
@@ -14,13 +14,13 @@ import { pipeline } from 'node:stream/promises';
|
|
|
14
14
|
import { extract } from 'tar';
|
|
15
15
|
import { execAsync } from "./exec.js";
|
|
16
16
|
// Pinned tsgolint fork commit — updated with each lintcn release.
|
|
17
|
-
// Uses remorses/tsgolint fork which adds internal/runner.Run()
|
|
18
|
-
//
|
|
19
|
-
export const DEFAULT_TSGOLINT_VERSION = '
|
|
17
|
+
// Uses remorses/tsgolint fork which adds internal/runner.Run() and
|
|
18
|
+
// TSGOLINT_SNAPSHOT_CWD env var for cwd-relative test snapshots.
|
|
19
|
+
export const DEFAULT_TSGOLINT_VERSION = '4e4666c284d3b5cf7fa082523a369ef507e4360c';
|
|
20
20
|
// Pinned typescript-go base commit from microsoft/typescript-go (before patches).
|
|
21
21
|
// Patches from tsgolint/patches/ are applied on top during setup.
|
|
22
22
|
// Must be updated when DEFAULT_TSGOLINT_VERSION changes.
|
|
23
|
-
const TYPESCRIPT_GO_COMMIT = '
|
|
23
|
+
const TYPESCRIPT_GO_COMMIT = 'c0703e66b68b826eedadce353d63fe9f4ea21fb6';
|
|
24
24
|
// Strict pattern for version strings — prevents path traversal via ../
|
|
25
25
|
const VERSION_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
26
26
|
/** Validate version string to prevent path traversal attacks.
|
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import { listRules } from "./commands/list.js";
|
|
|
9
9
|
import { removeRule } from "./commands/remove.js";
|
|
10
10
|
import { clean } from "./commands/clean.js";
|
|
11
11
|
import { DEFAULT_TSGOLINT_VERSION } from "./cache.js";
|
|
12
|
+
import { findLintcnDir } from "./paths.js";
|
|
12
13
|
const require = createRequire(import.meta.url);
|
|
13
14
|
const packageJson = require('../package.json');
|
|
14
15
|
const cli = goke('lintcn');
|
|
@@ -20,20 +21,14 @@ cli
|
|
|
20
21
|
.example('lintcn add https://github.com/oxc-project/tsgolint/blob/main/internal/rules/await_thenable/await_thenable.go')
|
|
21
22
|
.example('# Add all rules from a repo (downloads .lintcn/ folder)')
|
|
22
23
|
.example('lintcn add https://github.com/someone/their-project')
|
|
23
|
-
.action(
|
|
24
|
-
await addRule(url);
|
|
25
|
-
});
|
|
24
|
+
.action(addRule);
|
|
26
25
|
cli
|
|
27
26
|
.command('remove <name>', 'Remove an installed rule from .lintcn/')
|
|
28
27
|
.example('lintcn remove no-floating-promises')
|
|
29
|
-
.action(
|
|
30
|
-
removeRule(name);
|
|
31
|
-
});
|
|
28
|
+
.action(removeRule);
|
|
32
29
|
cli
|
|
33
30
|
.command('list', 'List all installed rules')
|
|
34
|
-
.action(
|
|
35
|
-
listRules();
|
|
36
|
-
});
|
|
31
|
+
.action(listRules);
|
|
37
32
|
cli
|
|
38
33
|
.command('lint', 'Build custom tsgolint binary and run it against the project')
|
|
39
34
|
.option('--rebuild', 'Force rebuild even if cached binary exists')
|
|
@@ -72,15 +67,17 @@ cli
|
|
|
72
67
|
.option('--rebuild', 'Force rebuild even if cached binary exists')
|
|
73
68
|
.option('--tsgolint-version [version]', 'Override the pinned tsgolint version (tag or commit). For testing unreleased tsgolint versions.')
|
|
74
69
|
.action(async (options) => {
|
|
70
|
+
if (!findLintcnDir()) {
|
|
71
|
+
console.log('No .lintcn/ directory found. Run `lintcn add <url>` to add rules.');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
75
74
|
const tsgolintVersion = options.tsgolintVersion || DEFAULT_TSGOLINT_VERSION;
|
|
76
75
|
const binaryPath = await buildBinary({ rebuild: !!options.rebuild, tsgolintVersion });
|
|
77
76
|
console.log(binaryPath);
|
|
78
77
|
});
|
|
79
78
|
cli
|
|
80
79
|
.command('clean', 'Remove cached tsgolint source and compiled binaries to free disk space')
|
|
81
|
-
.action(
|
|
82
|
-
clean();
|
|
83
|
-
});
|
|
80
|
+
.action(clean);
|
|
84
81
|
cli.help();
|
|
85
82
|
cli.version(packageJson.version);
|
|
86
83
|
cli.parse();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AA6RA,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0ExD"}
|
package/dist/commands/add.js
CHANGED
|
@@ -53,6 +53,17 @@ function parseGitHubUrl(url) {
|
|
|
53
53
|
}
|
|
54
54
|
return null;
|
|
55
55
|
}
|
|
56
|
+
function isGitHubContentItem(value) {
|
|
57
|
+
if (!value) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
const name = value.name;
|
|
61
|
+
const downloadUrl = value.download_url;
|
|
62
|
+
const itemType = value.type;
|
|
63
|
+
return typeof name === 'string'
|
|
64
|
+
&& (downloadUrl === null || typeof downloadUrl === 'string')
|
|
65
|
+
&& (itemType === 'file' || itemType === 'dir');
|
|
66
|
+
}
|
|
56
67
|
/** Get a GitHub auth token from gh CLI, GITHUB_TOKEN env, or return undefined. */
|
|
57
68
|
function getGitHubToken() {
|
|
58
69
|
if (process.env.GITHUB_TOKEN) {
|
|
@@ -107,6 +118,9 @@ async function listGitHubFolder(owner, repo, dirPath, ref) {
|
|
|
107
118
|
if (!Array.isArray(data)) {
|
|
108
119
|
throw new Error(`Expected a directory listing from GitHub API but got a single file.\n ${apiUrl}`);
|
|
109
120
|
}
|
|
121
|
+
if (!data.every(isGitHubContentItem)) {
|
|
122
|
+
throw new Error(`Unexpected GitHub directory listing shape.\n ${apiUrl}`);
|
|
123
|
+
}
|
|
110
124
|
return data;
|
|
111
125
|
}
|
|
112
126
|
async function fetchFile(url) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/commands/lint.ts"],"names":[],"mappings":"AAwBA,wBAAsB,WAAW,CAAC,EAChC,OAAO,EACP,eAAe,GAChB,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkElB;AAED,wBAAsB,IAAI,CAAC,EACzB,OAAO,EACP,eAAe,EACf,eAAe,EACf,WAAW,GACZ,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,WAAW,EAAE,OAAO,CAAA;CACrB,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/commands/lint.ts"],"names":[],"mappings":"AAwBA,wBAAsB,WAAW,CAAC,EAChC,OAAO,EACP,eAAe,GAChB,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkElB;AAED,wBAAsB,IAAI,CAAC,EACzB,OAAO,EACP,eAAe,EACf,eAAe,EACf,WAAW,GACZ,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,WAAW,EAAE,OAAO,CAAA;CACrB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyClB"}
|
package/dist/commands/lint.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
|
-
import { requireLintcnDir } from "../paths.js";
|
|
6
|
+
import { requireLintcnDir, findLintcnDir } from "../paths.js";
|
|
7
7
|
import { discoverRules } from "../discover.js";
|
|
8
8
|
import { generateBuildWorkspace, generateEditorGoFiles } from "../codegen.js";
|
|
9
9
|
import { ensureTsgolintSource, validateVersion, cachedBinaryExists, getBinaryPath, getBuildDir, getBinDir } from "../cache.js";
|
|
@@ -24,7 +24,7 @@ export async function buildBinary({ rebuild, tsgolintVersion, }) {
|
|
|
24
24
|
const lintcnDir = requireLintcnDir();
|
|
25
25
|
const rules = discoverRules(lintcnDir);
|
|
26
26
|
if (rules.length === 0) {
|
|
27
|
-
throw new Error(
|
|
27
|
+
throw new Error(`No rules found in ${lintcnDir}. Run \`lintcn add <url>\` to add rules.`);
|
|
28
28
|
}
|
|
29
29
|
console.log(`Found ${rules.length} custom rule${rules.length === 1 ? '' : 's'} (tsgolint ${tsgolintVersion.slice(0, 8)})`);
|
|
30
30
|
// ensure tsgolint source
|
|
@@ -75,6 +75,10 @@ export async function buildBinary({ rebuild, tsgolintVersion, }) {
|
|
|
75
75
|
return binaryPath;
|
|
76
76
|
}
|
|
77
77
|
export async function lint({ rebuild, tsgolintVersion, passthroughArgs, allWarnings, }) {
|
|
78
|
+
if (!findLintcnDir()) {
|
|
79
|
+
console.log('No .lintcn/ directory found. Run `lintcn add <url>` to add rules.');
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
78
82
|
const binaryPath = await buildBinary({ rebuild, tsgolintVersion });
|
|
79
83
|
// Discover rules to inject --warn flags for warning-severity rules.
|
|
80
84
|
// buildBinary already discovered rules for compilation, but we need the
|
package/package.json
CHANGED
package/src/cache.ts
CHANGED
|
@@ -16,14 +16,14 @@ import { extract } from 'tar'
|
|
|
16
16
|
import { execAsync } from './exec.ts'
|
|
17
17
|
|
|
18
18
|
// Pinned tsgolint fork commit — updated with each lintcn release.
|
|
19
|
-
// Uses remorses/tsgolint fork which adds internal/runner.Run()
|
|
20
|
-
//
|
|
21
|
-
export const DEFAULT_TSGOLINT_VERSION = '
|
|
19
|
+
// Uses remorses/tsgolint fork which adds internal/runner.Run() and
|
|
20
|
+
// TSGOLINT_SNAPSHOT_CWD env var for cwd-relative test snapshots.
|
|
21
|
+
export const DEFAULT_TSGOLINT_VERSION = '4e4666c284d3b5cf7fa082523a369ef507e4360c'
|
|
22
22
|
|
|
23
23
|
// Pinned typescript-go base commit from microsoft/typescript-go (before patches).
|
|
24
24
|
// Patches from tsgolint/patches/ are applied on top during setup.
|
|
25
25
|
// Must be updated when DEFAULT_TSGOLINT_VERSION changes.
|
|
26
|
-
const TYPESCRIPT_GO_COMMIT = '
|
|
26
|
+
const TYPESCRIPT_GO_COMMIT = 'c0703e66b68b826eedadce353d63fe9f4ea21fb6'
|
|
27
27
|
|
|
28
28
|
// Strict pattern for version strings — prevents path traversal via ../
|
|
29
29
|
const VERSION_PATTERN = /^[a-zA-Z0-9._-]+$/
|
package/src/cli.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { listRules } from './commands/list.ts'
|
|
|
11
11
|
import { removeRule } from './commands/remove.ts'
|
|
12
12
|
import { clean } from './commands/clean.ts'
|
|
13
13
|
import { DEFAULT_TSGOLINT_VERSION } from './cache.ts'
|
|
14
|
+
import { findLintcnDir } from './paths.ts'
|
|
14
15
|
|
|
15
16
|
const require = createRequire(import.meta.url)
|
|
16
17
|
const packageJson = require('../package.json') as { version: string }
|
|
@@ -25,22 +26,16 @@ cli
|
|
|
25
26
|
.example('lintcn add https://github.com/oxc-project/tsgolint/blob/main/internal/rules/await_thenable/await_thenable.go')
|
|
26
27
|
.example('# Add all rules from a repo (downloads .lintcn/ folder)')
|
|
27
28
|
.example('lintcn add https://github.com/someone/their-project')
|
|
28
|
-
.action(
|
|
29
|
-
await addRule(url)
|
|
30
|
-
})
|
|
29
|
+
.action(addRule)
|
|
31
30
|
|
|
32
31
|
cli
|
|
33
32
|
.command('remove <name>', 'Remove an installed rule from .lintcn/')
|
|
34
33
|
.example('lintcn remove no-floating-promises')
|
|
35
|
-
.action(
|
|
36
|
-
removeRule(name)
|
|
37
|
-
})
|
|
34
|
+
.action(removeRule)
|
|
38
35
|
|
|
39
36
|
cli
|
|
40
37
|
.command('list', 'List all installed rules')
|
|
41
|
-
.action(
|
|
42
|
-
listRules()
|
|
43
|
-
})
|
|
38
|
+
.action(listRules)
|
|
44
39
|
|
|
45
40
|
cli
|
|
46
41
|
.command('lint', 'Build custom tsgolint binary and run it against the project')
|
|
@@ -81,6 +76,10 @@ cli
|
|
|
81
76
|
.option('--rebuild', 'Force rebuild even if cached binary exists')
|
|
82
77
|
.option('--tsgolint-version [version]', 'Override the pinned tsgolint version (tag or commit). For testing unreleased tsgolint versions.')
|
|
83
78
|
.action(async (options) => {
|
|
79
|
+
if (!findLintcnDir()) {
|
|
80
|
+
console.log('No .lintcn/ directory found. Run `lintcn add <url>` to add rules.')
|
|
81
|
+
return
|
|
82
|
+
}
|
|
84
83
|
const tsgolintVersion = (options.tsgolintVersion as string) || DEFAULT_TSGOLINT_VERSION
|
|
85
84
|
const binaryPath = await buildBinary({ rebuild: !!options.rebuild, tsgolintVersion })
|
|
86
85
|
console.log(binaryPath)
|
|
@@ -88,9 +87,7 @@ cli
|
|
|
88
87
|
|
|
89
88
|
cli
|
|
90
89
|
.command('clean', 'Remove cached tsgolint source and compiled binaries to free disk space')
|
|
91
|
-
.action(
|
|
92
|
-
clean()
|
|
93
|
-
})
|
|
90
|
+
.action(clean)
|
|
94
91
|
|
|
95
92
|
cli.help()
|
|
96
93
|
cli.version(packageJson.version)
|
package/src/commands/add.ts
CHANGED
|
@@ -75,6 +75,26 @@ interface GitHubContentItem {
|
|
|
75
75
|
type: 'file' | 'dir'
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
type GitHubContentItemCandidate = {
|
|
79
|
+
name?: string | null
|
|
80
|
+
download_url?: string | null
|
|
81
|
+
type?: string | null
|
|
82
|
+
} | null
|
|
83
|
+
|
|
84
|
+
function isGitHubContentItem(value: GitHubContentItemCandidate): value is GitHubContentItem {
|
|
85
|
+
if (!value) {
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const name = value.name
|
|
90
|
+
const downloadUrl = value.download_url
|
|
91
|
+
const itemType = value.type
|
|
92
|
+
|
|
93
|
+
return typeof name === 'string'
|
|
94
|
+
&& (downloadUrl === null || typeof downloadUrl === 'string')
|
|
95
|
+
&& (itemType === 'file' || itemType === 'dir')
|
|
96
|
+
}
|
|
97
|
+
|
|
78
98
|
/** Get a GitHub auth token from gh CLI, GITHUB_TOKEN env, or return undefined. */
|
|
79
99
|
function getGitHubToken(): string | undefined {
|
|
80
100
|
if (process.env.GITHUB_TOKEN) {
|
|
@@ -134,7 +154,11 @@ async function listGitHubFolder(owner: string, repo: string, dirPath: string, re
|
|
|
134
154
|
throw new Error(`Expected a directory listing from GitHub API but got a single file.\n ${apiUrl}`)
|
|
135
155
|
}
|
|
136
156
|
|
|
137
|
-
|
|
157
|
+
if (!data.every(isGitHubContentItem)) {
|
|
158
|
+
throw new Error(`Unexpected GitHub directory listing shape.\n ${apiUrl}`)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return data
|
|
138
162
|
}
|
|
139
163
|
|
|
140
164
|
async function fetchFile(url: string): Promise<string> {
|
package/src/commands/lint.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import fs from 'node:fs'
|
|
5
5
|
import path from 'node:path'
|
|
6
6
|
import { spawn } from 'node:child_process'
|
|
7
|
-
import { requireLintcnDir } from '../paths.ts'
|
|
7
|
+
import { requireLintcnDir, findLintcnDir } from '../paths.ts'
|
|
8
8
|
import { discoverRules, type RuleMetadata } from '../discover.ts'
|
|
9
9
|
import { generateBuildWorkspace, generateEditorGoFiles } from '../codegen.ts'
|
|
10
10
|
import { ensureTsgolintSource, validateVersion, cachedBinaryExists, getBinaryPath, getBuildDir, getBinDir } from '../cache.ts'
|
|
@@ -36,7 +36,7 @@ export async function buildBinary({
|
|
|
36
36
|
|
|
37
37
|
const rules = discoverRules(lintcnDir)
|
|
38
38
|
if (rules.length === 0) {
|
|
39
|
-
throw new Error(
|
|
39
|
+
throw new Error(`No rules found in ${lintcnDir}. Run \`lintcn add <url>\` to add rules.`)
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
console.log(`Found ${rules.length} custom rule${rules.length === 1 ? '' : 's'} (tsgolint ${tsgolintVersion.slice(0, 8)})`)
|
|
@@ -107,6 +107,11 @@ export async function lint({
|
|
|
107
107
|
passthroughArgs: string[]
|
|
108
108
|
allWarnings: boolean
|
|
109
109
|
}): Promise<number> {
|
|
110
|
+
if (!findLintcnDir()) {
|
|
111
|
+
console.log('No .lintcn/ directory found. Run `lintcn add <url>` to add rules.')
|
|
112
|
+
return 0
|
|
113
|
+
}
|
|
114
|
+
|
|
110
115
|
const binaryPath = await buildBinary({ rebuild, tsgolintVersion })
|
|
111
116
|
|
|
112
117
|
// Discover rules to inject --warn flags for warning-severity rules.
|