lintcn 0.7.1 → 0.9.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 +101 -0
- package/README.md +16 -12
- package/dist/cache.d.ts +1 -1
- package/dist/cache.js +2 -2
- package/dist/cli.js +7 -16
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +14 -0
- package/dist/commands/lint.js +1 -1
- package/package.json +2 -2
- package/src/cache.ts +2 -2
- package/src/cli.ts +10 -19
- package/src/commands/add.ts +25 -1
- package/src/commands/lint.ts +1 -1
- package/src/hash.ts +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,104 @@
|
|
|
1
|
+
## 0.9.0
|
|
2
|
+
|
|
3
|
+
1. **New rule: `no-redundant-exported-return-type` (warn)** — warns when exported APIs keep spelling `ReturnType<typeof ...>` even though the function already returns a public named type. This keeps public types direct and easier to read:
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
export interface User {
|
|
7
|
+
id: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getUser(): User {
|
|
11
|
+
return { id: '1' }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type UserResult = ReturnType<typeof getUser> // warned
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Use the named type directly instead:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
export type UserResult = User
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
2. **New rule: `no-redundant-contextual-parameter-type` (error)** — errors when an inline callback repeats a parameter type that TypeScript already knows from the surrounding API. This removes annotation noise without losing type safety:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
declare function useRunner(fn: (ctx: { command: string }) => void): void
|
|
27
|
+
|
|
28
|
+
useRunner((ctx: { command: string }) => {
|
|
29
|
+
ctx.command
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Preferred:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
useRunner((ctx) => {
|
|
37
|
+
ctx.command
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 0.8.0
|
|
42
|
+
|
|
43
|
+
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:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
warning prefer-object-params — function `createUser` has 3 positional parameters. Use one object parameter instead.
|
|
47
|
+
error no-unhandled-error — Error-typed return value is not handled.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
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:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
function run(input: unknown) {} // warned — use the real type
|
|
54
|
+
const v = input as unknown as User // warned — narrow or validate instead
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
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:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const save = (file: string) => write(file) // warned — inline at use site
|
|
61
|
+
function persist(v: string) { return db.save(v) } // warned
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Computed arguments (`write(normalize(value))`) are allowed — those add real behavior.
|
|
65
|
+
|
|
66
|
+
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:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
function buildQuery(id: string) {
|
|
70
|
+
return `SELECT * FROM users WHERE id = '${id}'`
|
|
71
|
+
}
|
|
72
|
+
// called only once — inline it
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
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:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
type User = { name: string } // warned — only used once below
|
|
79
|
+
const user: User = { name: 'a' }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Exported types, recursive types, and longer declarations are allowed.
|
|
83
|
+
|
|
84
|
+
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.
|
|
85
|
+
|
|
86
|
+
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:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// warned
|
|
90
|
+
function createUser(name: string, email: string, role: string) {}
|
|
91
|
+
|
|
92
|
+
// preferred
|
|
93
|
+
function createUser(options: { name: string; email: string; role: string }) {}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Inline callbacks passed directly at the call site are exempt.
|
|
97
|
+
|
|
98
|
+
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.
|
|
99
|
+
|
|
100
|
+
9. **Fixed UTF-16 code frame rendering** — diagnostic code frames now render correctly for files containing non-ASCII characters or emoji.
|
|
101
|
+
|
|
1
102
|
## 0.7.1
|
|
2
103
|
|
|
3
104
|
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.
|
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 = "427f872946bb413f0e21cff585b6139c986c89d3";
|
|
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
|
@@ -16,11 +16,11 @@ import { execAsync } from "./exec.js";
|
|
|
16
16
|
// Pinned tsgolint fork commit — updated with each lintcn release.
|
|
17
17
|
// Uses remorses/tsgolint fork which adds internal/runner.Run() and
|
|
18
18
|
// TSGOLINT_SNAPSHOT_CWD env var for cwd-relative test snapshots.
|
|
19
|
-
export const DEFAULT_TSGOLINT_VERSION = '
|
|
19
|
+
export const DEFAULT_TSGOLINT_VERSION = '427f872946bb413f0e21cff585b6139c986c89d3';
|
|
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
|
@@ -21,20 +21,14 @@ cli
|
|
|
21
21
|
.example('lintcn add https://github.com/oxc-project/tsgolint/blob/main/internal/rules/await_thenable/await_thenable.go')
|
|
22
22
|
.example('# Add all rules from a repo (downloads .lintcn/ folder)')
|
|
23
23
|
.example('lintcn add https://github.com/someone/their-project')
|
|
24
|
-
.action(
|
|
25
|
-
await addRule(url);
|
|
26
|
-
});
|
|
24
|
+
.action(addRule);
|
|
27
25
|
cli
|
|
28
26
|
.command('remove <name>', 'Remove an installed rule from .lintcn/')
|
|
29
27
|
.example('lintcn remove no-floating-promises')
|
|
30
|
-
.action(
|
|
31
|
-
removeRule(name);
|
|
32
|
-
});
|
|
28
|
+
.action(removeRule);
|
|
33
29
|
cli
|
|
34
30
|
.command('list', 'List all installed rules')
|
|
35
|
-
.action(
|
|
36
|
-
listRules();
|
|
37
|
-
});
|
|
31
|
+
.action(listRules);
|
|
38
32
|
cli
|
|
39
33
|
.command('lint', 'Build custom tsgolint binary and run it against the project')
|
|
40
34
|
.option('--rebuild', 'Force rebuild even if cached binary exists')
|
|
@@ -55,10 +49,9 @@ cli
|
|
|
55
49
|
if (options.listFiles) {
|
|
56
50
|
passthroughArgs.push('--list-files');
|
|
57
51
|
}
|
|
58
|
-
// pass through anything after --
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
passthroughArgs.push(...doubleDash);
|
|
52
|
+
// pass through anything after -- (goke 6.6.0 always types this as string[])
|
|
53
|
+
if (options['--'].length > 0) {
|
|
54
|
+
passthroughArgs.push(...options['--']);
|
|
62
55
|
}
|
|
63
56
|
const exitCode = await lint({
|
|
64
57
|
rebuild: !!options.rebuild,
|
|
@@ -83,9 +76,7 @@ cli
|
|
|
83
76
|
});
|
|
84
77
|
cli
|
|
85
78
|
.command('clean', 'Remove cached tsgolint source and compiled binaries to free disk space')
|
|
86
|
-
.action(
|
|
87
|
-
clean();
|
|
88
|
-
});
|
|
79
|
+
.action(clean);
|
|
89
80
|
cli.help();
|
|
90
81
|
cli.version(packageJson.version);
|
|
91
82
|
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) {
|
package/dist/commands/lint.js
CHANGED
|
@@ -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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lintcn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The shadcn for type-aware TypeScript lint rules. Browse, pick, and copy rules into your project.",
|
|
6
6
|
"bin": "dist/cli.js",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"find-up": "^8.0.0",
|
|
59
|
-
"goke": "^6.
|
|
59
|
+
"goke": "^6.6.0",
|
|
60
60
|
"tar": "^7.5.12"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
package/src/cache.ts
CHANGED
|
@@ -18,12 +18,12 @@ import { execAsync } from './exec.ts'
|
|
|
18
18
|
// Pinned tsgolint fork commit — updated with each lintcn release.
|
|
19
19
|
// Uses remorses/tsgolint fork which adds internal/runner.Run() and
|
|
20
20
|
// TSGOLINT_SNAPSHOT_CWD env var for cwd-relative test snapshots.
|
|
21
|
-
export const DEFAULT_TSGOLINT_VERSION = '
|
|
21
|
+
export const DEFAULT_TSGOLINT_VERSION = '427f872946bb413f0e21cff585b6139c986c89d3'
|
|
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
|
@@ -26,22 +26,16 @@ cli
|
|
|
26
26
|
.example('lintcn add https://github.com/oxc-project/tsgolint/blob/main/internal/rules/await_thenable/await_thenable.go')
|
|
27
27
|
.example('# Add all rules from a repo (downloads .lintcn/ folder)')
|
|
28
28
|
.example('lintcn add https://github.com/someone/their-project')
|
|
29
|
-
.action(
|
|
30
|
-
await addRule(url)
|
|
31
|
-
})
|
|
29
|
+
.action(addRule)
|
|
32
30
|
|
|
33
31
|
cli
|
|
34
32
|
.command('remove <name>', 'Remove an installed rule from .lintcn/')
|
|
35
33
|
.example('lintcn remove no-floating-promises')
|
|
36
|
-
.action(
|
|
37
|
-
removeRule(name)
|
|
38
|
-
})
|
|
34
|
+
.action(removeRule)
|
|
39
35
|
|
|
40
36
|
cli
|
|
41
37
|
.command('list', 'List all installed rules')
|
|
42
|
-
.action(
|
|
43
|
-
listRules()
|
|
44
|
-
})
|
|
38
|
+
.action(listRules)
|
|
45
39
|
|
|
46
40
|
cli
|
|
47
41
|
.command('lint', 'Build custom tsgolint binary and run it against the project')
|
|
@@ -52,21 +46,20 @@ cli
|
|
|
52
46
|
.option('--all-warnings', 'Show warnings for all files, not just git-changed ones')
|
|
53
47
|
.option('--tsgolint-version [version]', 'Override the pinned tsgolint version (tag or commit). For testing unreleased tsgolint versions.')
|
|
54
48
|
.action(async (options) => {
|
|
55
|
-
const tsgolintVersion =
|
|
49
|
+
const tsgolintVersion = options.tsgolintVersion || DEFAULT_TSGOLINT_VERSION
|
|
56
50
|
const passthroughArgs: string[] = []
|
|
57
51
|
if (options.fix) {
|
|
58
52
|
passthroughArgs.push('--fix')
|
|
59
53
|
}
|
|
60
54
|
if (options.tsconfig) {
|
|
61
|
-
passthroughArgs.push('--tsconfig', options.tsconfig
|
|
55
|
+
passthroughArgs.push('--tsconfig', options.tsconfig)
|
|
62
56
|
}
|
|
63
57
|
if (options.listFiles) {
|
|
64
58
|
passthroughArgs.push('--list-files')
|
|
65
59
|
}
|
|
66
|
-
// pass through anything after --
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
passthroughArgs.push(...doubleDash)
|
|
60
|
+
// pass through anything after -- (goke 6.6.0 always types this as string[])
|
|
61
|
+
if (options['--'].length > 0) {
|
|
62
|
+
passthroughArgs.push(...options['--'])
|
|
70
63
|
}
|
|
71
64
|
const exitCode = await lint({
|
|
72
65
|
rebuild: !!options.rebuild,
|
|
@@ -86,16 +79,14 @@ cli
|
|
|
86
79
|
console.log('No .lintcn/ directory found. Run `lintcn add <url>` to add rules.')
|
|
87
80
|
return
|
|
88
81
|
}
|
|
89
|
-
const tsgolintVersion =
|
|
82
|
+
const tsgolintVersion = options.tsgolintVersion || DEFAULT_TSGOLINT_VERSION
|
|
90
83
|
const binaryPath = await buildBinary({ rebuild: !!options.rebuild, tsgolintVersion })
|
|
91
84
|
console.log(binaryPath)
|
|
92
85
|
})
|
|
93
86
|
|
|
94
87
|
cli
|
|
95
88
|
.command('clean', 'Remove cached tsgolint source and compiled binaries to free disk space')
|
|
96
|
-
.action(
|
|
97
|
-
clean()
|
|
98
|
-
})
|
|
89
|
+
.action(clean)
|
|
99
90
|
|
|
100
91
|
cli.help()
|
|
101
92
|
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
|
@@ -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)})`)
|