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 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 git-changed files** — warnings for unchanged files are silently skipped
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 changed and untracked files. Warnings are only printed for files in that list:
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 in git diff (default)
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 | Warnings shown? |
188
- | ---------------------------------- | ----------------- |
189
- | File is in `git diff` or untracked | Yes |
190
- | File is committed and unchanged | No |
191
- | `--all-warnings` flag is passed | Yes, all files |
192
- | Git is not installed or not a repo | No warnings shown |
193
- | Clean git tree (no changes) | No warnings shown |
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 = "23190a08a6315eba8ef11818fc1c38d7b01c9e10";
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
- // Only 1 commit on top of upstream — zero modifications to existing files.
19
- export const DEFAULT_TSGOLINT_VERSION = '23190a08a6315eba8ef11818fc1c38d7b01c9e10';
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 = '1b7eabe122e1575a0df9c77eccdf4e063c623224';
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(async (url) => {
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((name) => {
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":"AAqQA,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0ExD"}
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"}
@@ -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,CAoClB"}
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"}
@@ -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('No rules found in .lintcn/. Run `lintcn add <url>` to add rules.');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lintcn",
3
- "version": "0.7.0",
3
+ "version": "0.8.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",
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
- // Only 1 commit on top of upstream — zero modifications to existing files.
21
- export const DEFAULT_TSGOLINT_VERSION = '23190a08a6315eba8ef11818fc1c38d7b01c9e10'
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 = '1b7eabe122e1575a0df9c77eccdf4e063c623224'
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(async (url) => {
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((name) => {
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)
@@ -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
- return data as GitHubContentItem[]
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> {
@@ -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('No rules found in .lintcn/. Run `lintcn add <url>` to add rules.')
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.
package/src/hash.ts CHANGED
@@ -52,5 +52,3 @@ export async function computeContentHash({
52
52
  const full = hash.digest('hex')
53
53
  return { short: full.slice(0, 16), full }
54
54
  }
55
-
56
-