@pyreon/lint 0.11.4 → 0.11.6

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.
Files changed (86) hide show
  1. package/README.md +91 -91
  2. package/lib/analysis/cli.js.html +5406 -0
  3. package/lib/analysis/index.js.html +1 -1
  4. package/lib/cli.js +3290 -0
  5. package/lib/cli.js.map +1 -0
  6. package/lib/index.js +220 -29
  7. package/lib/index.js.map +1 -1
  8. package/lib/types/index.d.ts +30 -5
  9. package/lib/types/index.d.ts.map +1 -1
  10. package/package.json +19 -19
  11. package/src/cache.ts +1 -1
  12. package/src/cli.ts +39 -28
  13. package/src/config/ignore.ts +23 -23
  14. package/src/config/loader.ts +8 -8
  15. package/src/config/presets.ts +11 -11
  16. package/src/index.ts +14 -12
  17. package/src/lint.ts +19 -25
  18. package/src/lsp/index.ts +225 -0
  19. package/src/reporter.ts +17 -17
  20. package/src/rules/accessibility/dialog-a11y.ts +10 -10
  21. package/src/rules/accessibility/overlay-a11y.ts +11 -11
  22. package/src/rules/accessibility/toast-a11y.ts +11 -11
  23. package/src/rules/architecture/dev-guard-warnings.ts +19 -19
  24. package/src/rules/architecture/no-circular-import.ts +16 -16
  25. package/src/rules/architecture/no-cross-layer-import.ts +35 -35
  26. package/src/rules/architecture/no-deep-import.ts +7 -7
  27. package/src/rules/architecture/no-error-without-prefix.ts +20 -20
  28. package/src/rules/form/no-submit-without-validation.ts +13 -13
  29. package/src/rules/form/no-unregistered-field.ts +12 -12
  30. package/src/rules/form/prefer-field-array.ts +11 -11
  31. package/src/rules/hooks/no-raw-addeventlistener.ts +9 -9
  32. package/src/rules/hooks/no-raw-localstorage.ts +11 -11
  33. package/src/rules/hooks/no-raw-setinterval.ts +11 -11
  34. package/src/rules/index.ts +60 -57
  35. package/src/rules/jsx/no-and-conditional.ts +8 -8
  36. package/src/rules/jsx/no-children-access.ts +12 -12
  37. package/src/rules/jsx/no-classname.ts +10 -10
  38. package/src/rules/jsx/no-htmlfor.ts +10 -10
  39. package/src/rules/jsx/no-index-as-by.ts +17 -17
  40. package/src/rules/jsx/no-map-in-jsx.ts +9 -9
  41. package/src/rules/jsx/no-missing-for-by.ts +9 -9
  42. package/src/rules/jsx/no-onchange.ts +12 -12
  43. package/src/rules/jsx/no-props-destructure.ts +11 -11
  44. package/src/rules/jsx/no-ternary-conditional.ts +8 -8
  45. package/src/rules/jsx/use-by-not-key.ts +12 -12
  46. package/src/rules/lifecycle/no-dom-in-setup.ts +18 -18
  47. package/src/rules/lifecycle/no-effect-in-mount.ts +11 -11
  48. package/src/rules/lifecycle/no-missing-cleanup.ts +19 -19
  49. package/src/rules/lifecycle/no-mount-in-effect.ts +11 -11
  50. package/src/rules/performance/no-eager-import.ts +7 -7
  51. package/src/rules/performance/no-effect-in-for.ts +10 -10
  52. package/src/rules/performance/no-large-for-without-by.ts +9 -9
  53. package/src/rules/performance/prefer-show-over-display.ts +16 -16
  54. package/src/rules/reactivity/no-bare-signal-in-jsx.ts +10 -10
  55. package/src/rules/reactivity/no-context-destructure.ts +45 -0
  56. package/src/rules/reactivity/no-effect-assignment.ts +16 -16
  57. package/src/rules/reactivity/no-nested-effect.ts +10 -10
  58. package/src/rules/reactivity/no-peek-in-tracked.ts +10 -10
  59. package/src/rules/reactivity/no-signal-in-loop.ts +13 -13
  60. package/src/rules/reactivity/no-signal-leak.ts +9 -9
  61. package/src/rules/reactivity/no-unbatched-updates.ts +12 -12
  62. package/src/rules/reactivity/prefer-computed.ts +13 -13
  63. package/src/rules/router/index.ts +4 -4
  64. package/src/rules/router/no-href-navigation.ts +14 -14
  65. package/src/rules/router/no-imperative-navigate-in-render.ts +19 -19
  66. package/src/rules/router/no-missing-fallback.ts +16 -16
  67. package/src/rules/router/prefer-use-is-active.ts +11 -11
  68. package/src/rules/ssr/no-mismatch-risk.ts +11 -11
  69. package/src/rules/ssr/no-window-in-ssr.ts +22 -22
  70. package/src/rules/ssr/prefer-request-context.ts +14 -14
  71. package/src/rules/store/no-duplicate-store-id.ts +9 -9
  72. package/src/rules/store/no-mutate-store-state.ts +11 -11
  73. package/src/rules/store/no-store-outside-provider.ts +15 -15
  74. package/src/rules/styling/no-dynamic-styled.ts +13 -13
  75. package/src/rules/styling/no-inline-style-object.ts +10 -10
  76. package/src/rules/styling/no-theme-outside-provider.ts +11 -11
  77. package/src/rules/styling/prefer-cx.ts +12 -12
  78. package/src/runner.ts +13 -14
  79. package/src/tests/lsp.test.ts +88 -0
  80. package/src/tests/runner.test.ts +325 -325
  81. package/src/types.ts +15 -15
  82. package/src/utils/ast.ts +50 -50
  83. package/src/utils/imports.ts +53 -53
  84. package/src/utils/index.ts +12 -3
  85. package/src/utils/source.ts +2 -2
  86. package/src/watcher.ts +19 -25
@@ -1,5 +1,5 @@
1
1
  //#region src/types.d.ts
2
- type Severity = "error" | "warn" | "info" | "off";
2
+ type Severity = 'error' | 'warn' | 'info' | 'off';
3
3
  interface SourceLocation {
4
4
  line: number;
5
5
  column: number;
@@ -20,7 +20,7 @@ interface Diagnostic {
20
20
  loc: SourceLocation;
21
21
  fix?: Fix | undefined;
22
22
  }
23
- type RuleCategory = "reactivity" | "jsx" | "lifecycle" | "performance" | "ssr" | "architecture" | "store" | "form" | "styling" | "hooks" | "accessibility" | "router";
23
+ type RuleCategory = 'reactivity' | 'jsx' | 'lifecycle' | 'performance' | 'ssr' | 'architecture' | 'store' | 'form' | 'styling' | 'hooks' | 'accessibility' | 'router';
24
24
  interface RuleMeta {
25
25
  id: string;
26
26
  category: RuleCategory;
@@ -29,7 +29,7 @@ interface RuleMeta {
29
29
  fixable: boolean;
30
30
  }
31
31
  interface RuleContext {
32
- report(diagnostic: Omit<Diagnostic, "ruleId" | "severity" | "loc">): void;
32
+ report(diagnostic: Omit<Diagnostic, 'ruleId' | 'severity' | 'loc'>): void;
33
33
  getSourceText(): string;
34
34
  getFilePath(): string;
35
35
  }
@@ -52,7 +52,7 @@ interface LintConfigFile {
52
52
  include?: string[] | undefined;
53
53
  exclude?: string[] | undefined;
54
54
  }
55
- type PresetName = "recommended" | "strict" | "app" | "lib";
55
+ type PresetName = 'recommended' | 'strict' | 'app' | 'lib';
56
56
  interface LintFileResult {
57
57
  filePath: string;
58
58
  diagnostics: Diagnostic[];
@@ -211,6 +211,31 @@ declare function formatJSON(result: LintResult): string;
211
211
  */
212
212
  declare function formatCompact(result: LintResult): string;
213
213
  //#endregion
214
+ //#region src/lsp/index.d.ts
215
+ /**
216
+ * Minimal LSP server for @pyreon/lint.
217
+ *
218
+ * Provides real-time Pyreon-specific diagnostics in editors that support
219
+ * the Language Server Protocol (VS Code, Neovim, etc.).
220
+ *
221
+ * Usage: pyreon-lint --lsp
222
+ *
223
+ * The server communicates via JSON-RPC over stdin/stdout following the
224
+ * LSP specification (https://microsoft.github.io/language-server-protocol/).
225
+ *
226
+ * Supported capabilities:
227
+ * - textDocument/didOpen — lint on open
228
+ * - textDocument/didSave — lint on save
229
+ * - textDocument/didChange — lint on change (debounced)
230
+ *
231
+ * @module
232
+ */
233
+ /**
234
+ * Start the LSP server. Reads JSON-RPC messages from stdin,
235
+ * processes them, and writes responses to stdout.
236
+ */
237
+ declare function startLspServer(): void;
238
+ //#endregion
214
239
  //#region src/rules/index.d.ts
215
240
  declare const allRules: Rule[];
216
241
  //#endregion
@@ -256,5 +281,5 @@ declare function watchAndLint(options: LintOptions & {
256
281
  format: string;
257
282
  }): void;
258
283
  //#endregion
259
- export { AstCache, type Diagnostic, type Fix, type ImportInfo, LineIndex, type LintConfig, type LintConfigFile, type LintFileResult, type LintOptions, type LintResult, type PresetName, type Rule, type RuleCategory, type RuleContext, type RuleMeta, type Severity, type SourceLocation, type Span, type VisitorCallbacks, allRules, applyFixes, createIgnoreFilter, extractImportInfo, formatCompact, formatJSON, formatText, getLocalName, getPreset, importsName, isPyreonImport, isPyreonPackage, lint, lintFile, listRules, loadConfig, loadConfigFromPath, watchAndLint };
284
+ export { AstCache, type Diagnostic, type Fix, type ImportInfo, LineIndex, type LintConfig, type LintConfigFile, type LintFileResult, type LintOptions, type LintResult, type PresetName, type Rule, type RuleCategory, type RuleContext, type RuleMeta, type Severity, type SourceLocation, type Span, type VisitorCallbacks, allRules, applyFixes, createIgnoreFilter, extractImportInfo, formatCompact, formatJSON, formatText, getLocalName, getPreset, importsName, isPyreonImport, isPyreonPackage, lint, lintFile, listRules, loadConfig, loadConfigFromPath, startLspServer, watchAndLint };
260
285
  //# sourceMappingURL=index2.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/types.ts","../../../src/utils/source.ts","../../../src/cache.ts","../../../src/config/ignore.ts","../../../src/config/loader.ts","../../../src/config/presets.ts","../../../src/lint.ts","../../../src/reporter.ts","../../../src/rules/index.ts","../../../src/runner.ts","../../../src/utils/imports.ts","../../../src/watcher.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/types.ts","../../../src/utils/source.ts","../../../src/cache.ts","../../../src/config/ignore.ts","../../../src/config/loader.ts","../../../src/config/presets.ts","../../../src/lint.ts","../../../src/reporter.ts","../../../src/lsp/index.ts","../../../src/rules/index.ts","../../../src/runner.ts","../../../src/utils/imports.ts","../../../src/watcher.ts"],"mappings":"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"}
package/package.json CHANGED
@@ -1,16 +1,19 @@
1
1
  {
2
2
  "name": "@pyreon/lint",
3
- "version": "0.11.4",
4
- "description": "Pyreon-specific linter — 55 rules for signals, JSX, SSR, performance, router, and architecture",
3
+ "version": "0.11.6",
4
+ "description": "Pyreon-specific linter — 56 rules for signals, JSX, SSR, performance, router, and architecture",
5
+ "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/lint#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/pyreon/pyreon/issues"
8
+ },
5
9
  "license": "MIT",
6
10
  "repository": {
7
11
  "type": "git",
8
12
  "url": "https://github.com/pyreon/pyreon.git",
9
13
  "directory": "packages/tools/lint"
10
14
  },
11
- "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/lint#readme",
12
- "bugs": {
13
- "url": "https://github.com/pyreon/pyreon/issues"
15
+ "bin": {
16
+ "pyreon-lint": "./lib/cli.js"
14
17
  },
15
18
  "files": [
16
19
  "lib",
@@ -18,39 +21,36 @@
18
21
  "README.md",
19
22
  "LICENSE"
20
23
  ],
21
- "sideEffects": false,
22
24
  "type": "module",
25
+ "sideEffects": false,
23
26
  "main": "./lib/index.js",
24
27
  "module": "./lib/index.js",
25
28
  "types": "./lib/types/index.d.ts",
26
- "bin": {
27
- "pyreon-lint": "./lib/cli.js"
28
- },
29
29
  "exports": {
30
30
  ".": {
31
31
  "bun": "./src/index.ts",
32
32
  "import": "./lib/index.js",
33
33
  "types": "./lib/types/index.d.ts"
34
34
  },
35
+ "./cli": {
36
+ "bun": "./src/cli.ts",
37
+ "import": "./lib/cli.js"
38
+ },
35
39
  "./package.json": "./package.json"
36
40
  },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
37
44
  "scripts": {
38
45
  "build": "vl_rolldown_build",
39
46
  "dev": "vl_rolldown_build-watch",
40
47
  "test": "vitest run",
41
48
  "typecheck": "tsc --noEmit",
42
- "lint": "biome check .",
49
+ "lint": "oxlint .",
43
50
  "prepublishOnly": "bun run build"
44
51
  },
45
52
  "dependencies": {
46
- "oxc-parser": "^0.121.0",
47
- "@oxc-project/types": "^0.121.0"
48
- },
49
- "devDependencies": {
50
- "oxc-parser": "^0.121.0",
51
- "@oxc-project/types": "^0.121.0"
52
- },
53
- "publishConfig": {
54
- "access": "public"
53
+ "@oxc-project/types": "^0.121.0",
54
+ "oxc-parser": "^0.121.0"
55
55
  }
56
56
  }
package/src/cache.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { LineIndex } from "./utils/source"
1
+ import type { LineIndex } from './utils/source'
2
2
 
3
3
  /**
4
4
  * Simple in-memory cache for parsed ASTs keyed by file content hash.
package/src/cli.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { lint, listRules } from "./lint"
3
- import { formatCompact, formatJSON, formatText } from "./reporter"
4
- import type { PresetName, Severity } from "./types"
5
- import { watchAndLint } from "./watcher"
2
+ import { lint, listRules } from './lint'
3
+ import { startLspServer } from './lsp/index'
4
+ import { formatCompact, formatJSON, formatText } from './reporter'
5
+ import type { PresetName, Severity } from './types'
6
+ import { watchAndLint } from './watcher'
6
7
 
7
- const VERSION = "0.10.0"
8
+ // Read version from package.json at build time; fallback for dev
9
+ const VERSION = '0.11.4'
8
10
 
9
11
  function printUsage() {
10
12
  console.log(`
@@ -20,6 +22,7 @@ function printUsage() {
20
22
  --config <path> Config file path
21
23
  --ignore <path> Ignore file path
22
24
  --watch Watch mode — re-lint on file changes
25
+ --lsp Start LSP server (stdin/stdout JSON-RPC)
23
26
  --help, -h Show this help
24
27
  --version, -v Show version
25
28
  `)
@@ -31,7 +34,7 @@ function printList() {
31
34
  const maxCat = Math.max(...rules.map((r) => r.category.length))
32
35
 
33
36
  for (const rule of rules) {
34
- const fixLabel = rule.fixable ? " [fixable]" : ""
37
+ const fixLabel = rule.fixable ? ' [fixable]' : ''
35
38
  const id = rule.id.padEnd(maxId)
36
39
  const cat = rule.category.padEnd(maxCat)
37
40
  const sev = rule.severity.padEnd(5)
@@ -44,12 +47,13 @@ function printList() {
44
47
  interface CliArgs {
45
48
  preset: PresetName
46
49
  fix: boolean
47
- format: "text" | "json" | "compact"
50
+ format: 'text' | 'json' | 'compact'
48
51
  quiet: boolean
49
52
  showList: boolean
50
53
  showHelp: boolean
51
54
  showVersion: boolean
52
55
  watchMode: boolean
56
+ lspMode: boolean
53
57
  configPath: string | undefined
54
58
  ignorePath: string | undefined
55
59
  ruleOverrides: Record<string, Severity>
@@ -57,26 +61,28 @@ interface CliArgs {
57
61
  }
58
62
 
59
63
  const BOOLEAN_FLAGS: Record<string, keyof CliArgs> = {
60
- "--help": "showHelp",
61
- "-h": "showHelp",
62
- "--version": "showVersion",
63
- "-v": "showVersion",
64
- "--list": "showList",
65
- "--fix": "fix",
66
- "--quiet": "quiet",
67
- "--watch": "watchMode",
64
+ '--help': 'showHelp',
65
+ '-h': 'showHelp',
66
+ '--version': 'showVersion',
67
+ '-v': 'showVersion',
68
+ '--list': 'showList',
69
+ '--fix': 'fix',
70
+ '--quiet': 'quiet',
71
+ '--watch': 'watchMode',
72
+ '--lsp': 'lspMode',
68
73
  }
69
74
 
70
75
  function parseArgs(argv: string[]): CliArgs {
71
76
  const result: CliArgs = {
72
- preset: "recommended",
77
+ preset: 'recommended',
73
78
  fix: false,
74
- format: "text",
79
+ format: 'text',
75
80
  quiet: false,
76
81
  showList: false,
77
82
  showHelp: false,
78
83
  showVersion: false,
79
84
  watchMode: false,
85
+ lspMode: false,
80
86
  configPath: undefined,
81
87
  ignorePath: undefined,
82
88
  ruleOverrides: {},
@@ -101,23 +107,23 @@ function parseArgs(argv: string[]): CliArgs {
101
107
 
102
108
  /** Returns number of extra args consumed (0 or 1). */
103
109
  function parseValueFlag(arg: string, nextArg: string | undefined, result: CliArgs): number {
104
- if (arg === "--preset") {
105
- result.preset = (nextArg ?? "recommended") as PresetName
110
+ if (arg === '--preset') {
111
+ result.preset = (nextArg ?? 'recommended') as PresetName
106
112
  return 1
107
113
  }
108
- if (arg === "--format") {
109
- result.format = (nextArg ?? "text") as "text" | "json" | "compact"
114
+ if (arg === '--format') {
115
+ result.format = (nextArg ?? 'text') as 'text' | 'json' | 'compact'
110
116
  return 1
111
117
  }
112
- if (arg === "--config") {
118
+ if (arg === '--config') {
113
119
  result.configPath = nextArg
114
120
  return 1
115
121
  }
116
- if (arg === "--ignore") {
122
+ if (arg === '--ignore') {
117
123
  result.ignorePath = nextArg
118
124
  return 1
119
125
  }
120
- if (arg === "--rule") {
126
+ if (arg === '--rule') {
121
127
  parseRuleOverride(nextArg, result.ruleOverrides)
122
128
  return 1
123
129
  }
@@ -129,7 +135,7 @@ function parseValueFlag(arg: string, nextArg: string | undefined, result: CliArg
129
135
 
130
136
  function parseRuleOverride(val: string | undefined, overrides: Record<string, Severity>): void {
131
137
  if (!val) return
132
- const eqIdx = val.lastIndexOf("=")
138
+ const eqIdx = val.lastIndexOf('=')
133
139
  if (eqIdx === -1) return
134
140
  const ruleId = val.slice(0, eqIdx)
135
141
  const severity = val.slice(eqIdx + 1) as Severity
@@ -154,8 +160,13 @@ function main() {
154
160
  process.exit(0)
155
161
  }
156
162
 
163
+ if (args.lspMode) {
164
+ startLspServer()
165
+ return
166
+ }
167
+
157
168
  if (args.paths.length === 0) {
158
- args.paths.push(".")
169
+ args.paths.push('.')
159
170
  }
160
171
 
161
172
  if (args.watchMode) {
@@ -182,9 +193,9 @@ function main() {
182
193
  ignore: args.ignorePath,
183
194
  })
184
195
 
185
- if (args.format === "json") {
196
+ if (args.format === 'json') {
186
197
  console.log(formatJSON(result))
187
- } else if (args.format === "compact") {
198
+ } else if (args.format === 'compact') {
188
199
  console.log(formatCompact(result))
189
200
  } else {
190
201
  const output = formatText(result)
@@ -1,5 +1,5 @@
1
- import { existsSync, readFileSync } from "node:fs"
2
- import { join, relative, resolve } from "node:path"
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { join, relative, resolve } from 'node:path'
3
3
 
4
4
  /**
5
5
  * Create a filter function that returns true if a file path should be ignored.
@@ -22,10 +22,10 @@ export function createIgnoreFilter(
22
22
  const resolvedCwd = resolve(cwd)
23
23
 
24
24
  // Load .pyreonlintignore
25
- loadPatternsFromFile(join(resolvedCwd, ".pyreonlintignore"), patterns)
25
+ loadPatternsFromFile(join(resolvedCwd, '.pyreonlintignore'), patterns)
26
26
 
27
27
  // Load .gitignore
28
- loadPatternsFromFile(join(resolvedCwd, ".gitignore"), patterns)
28
+ loadPatternsFromFile(join(resolvedCwd, '.gitignore'), patterns)
29
29
 
30
30
  // Load extra ignore file if provided
31
31
  if (extraIgnore) {
@@ -38,7 +38,7 @@ export function createIgnoreFilter(
38
38
  return (filePath: string): boolean => {
39
39
  const rel = relative(resolvedCwd, resolve(filePath))
40
40
  // Normalize to forward slashes
41
- const normalized = rel.replace(/\\/g, "/")
41
+ const normalized = rel.replace(/\\/g, '/')
42
42
 
43
43
  for (const matcher of matchers) {
44
44
  if (matcher(normalized)) return true
@@ -50,11 +50,11 @@ export function createIgnoreFilter(
50
50
  function loadPatternsFromFile(filePath: string, patterns: string[]): void {
51
51
  if (!existsSync(filePath)) return
52
52
  try {
53
- const content = readFileSync(filePath, "utf-8")
54
- for (const line of content.split("\n")) {
53
+ const content = readFileSync(filePath, 'utf-8')
54
+ for (const line of content.split('\n')) {
55
55
  const trimmed = line.trim()
56
56
  // Skip empty lines and comments
57
- if (!trimmed || trimmed.startsWith("#")) continue
57
+ if (!trimmed || trimmed.startsWith('#')) continue
58
58
  patterns.push(trimmed)
59
59
  }
60
60
  } catch {
@@ -72,12 +72,12 @@ function compileMatcher(pattern: string): (path: string) => boolean {
72
72
  let anchored = false
73
73
 
74
74
  // Negated patterns (not supported — just skip them)
75
- if (p.startsWith("!")) {
75
+ if (p.startsWith('!')) {
76
76
  return () => false
77
77
  }
78
78
 
79
79
  // Leading slash means anchored to root
80
- if (p.startsWith("/")) {
80
+ if (p.startsWith('/')) {
81
81
  anchored = true
82
82
  p = p.slice(1)
83
83
  }
@@ -85,7 +85,7 @@ function compileMatcher(pattern: string): (path: string) => boolean {
85
85
  // Trailing slash means only match directories (we treat all paths as files, so strip it
86
86
  // and match as a prefix)
87
87
  let dirOnly = false
88
- if (p.endsWith("/")) {
88
+ if (p.endsWith('/')) {
89
89
  dirOnly = true
90
90
  p = p.slice(0, -1)
91
91
  }
@@ -110,7 +110,7 @@ function compileMatcher(pattern: string): (path: string) => boolean {
110
110
  if (regex.test(path)) return true
111
111
 
112
112
  // Also try matching against just the filename
113
- const lastSlash = path.lastIndexOf("/")
113
+ const lastSlash = path.lastIndexOf('/')
114
114
  if (lastSlash !== -1) {
115
115
  const basename = path.slice(lastSlash + 1)
116
116
  return regex.test(basename)
@@ -121,26 +121,26 @@ function compileMatcher(pattern: string): (path: string) => boolean {
121
121
  }
122
122
 
123
123
  const GLOB_CHAR_MAP: Record<string, string> = {
124
- "?": "[^/]",
125
- ".": "\\.",
126
- "/": "/",
124
+ '?': '[^/]',
125
+ '.': '\\.',
126
+ '/': '/',
127
127
  }
128
128
 
129
129
  function handleStar(glob: string, pos: number): { pattern: string; advance: number } {
130
- if (glob[pos + 1] === "*") {
131
- if (glob[pos + 2] === "/") return { pattern: "(?:.*/)?", advance: 3 }
132
- return { pattern: ".*", advance: 2 }
130
+ if (glob[pos + 1] === '*') {
131
+ if (glob[pos + 2] === '/') return { pattern: '(?:.*/)?', advance: 3 }
132
+ return { pattern: '.*', advance: 2 }
133
133
  }
134
- return { pattern: "[^/]*", advance: 1 }
134
+ return { pattern: '[^/]*', advance: 1 }
135
135
  }
136
136
 
137
137
  function globToRegex(glob: string): RegExp {
138
- let result = "^"
138
+ let result = '^'
139
139
  let i = 0
140
140
 
141
141
  while (i < glob.length) {
142
142
  const ch = glob[i] as string
143
- if (ch === "*") {
143
+ if (ch === '*') {
144
144
  const star = handleStar(glob, i)
145
145
  result += star.pattern
146
146
  i += star.advance
@@ -150,10 +150,10 @@ function globToRegex(glob: string): RegExp {
150
150
  }
151
151
  }
152
152
 
153
- result += "$"
153
+ result += '$'
154
154
  return new RegExp(result)
155
155
  }
156
156
 
157
157
  function escapeRegex(str: string): string {
158
- return str.replace(/[\\^$+{}[\]|()]/g, "\\$&")
158
+ return str.replace(/[\\^$+{}[\]|()]/g, '\\$&')
159
159
  }
@@ -1,8 +1,8 @@
1
- import { existsSync, readFileSync } from "node:fs"
2
- import { dirname, join, resolve } from "node:path"
3
- import type { LintConfigFile } from "../types"
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { dirname, join, resolve } from 'node:path'
3
+ import type { LintConfigFile } from '../types'
4
4
 
5
- const CONFIG_FILENAMES = [".pyreonlintrc.json", ".pyreonlintrc", "pyreonlint.config.json"]
5
+ const CONFIG_FILENAMES = ['.pyreonlintrc.json', '.pyreonlintrc', 'pyreonlint.config.json']
6
6
 
7
7
  /**
8
8
  * Load a lint config file from the given directory or its parents.
@@ -42,14 +42,14 @@ function searchDirectory(dir: string): LintConfigFile | null {
42
42
  const content = tryReadJson(join(dir, filename))
43
43
  if (content !== null) return content
44
44
  }
45
- return extractPkgConfig(join(dir, "package.json"))
45
+ return extractPkgConfig(join(dir, 'package.json'))
46
46
  }
47
47
 
48
48
  function extractPkgConfig(pkgPath: string): LintConfigFile | null {
49
49
  const pkg = tryReadJson(pkgPath)
50
- if (pkg === null || typeof pkg !== "object" || !("pyreonlint" in pkg)) return null
50
+ if (pkg === null || typeof pkg !== 'object' || !('pyreonlint' in pkg)) return null
51
51
  const field = (pkg as Record<string, unknown>).pyreonlint
52
- if (field && typeof field === "object") return field as LintConfigFile
52
+ if (field && typeof field === 'object') return field as LintConfigFile
53
53
  return null
54
54
  }
55
55
 
@@ -63,7 +63,7 @@ export function loadConfigFromPath(filePath: string): LintConfigFile | null {
63
63
  function tryReadJson(filePath: string): any | null {
64
64
  if (!existsSync(filePath)) return null
65
65
  try {
66
- const raw = readFileSync(filePath, "utf-8").trim()
66
+ const raw = readFileSync(filePath, 'utf-8').trim()
67
67
  if (!raw) return null
68
68
  return JSON.parse(raw)
69
69
  } catch {
@@ -1,5 +1,5 @@
1
- import { allRules } from "../rules/index"
2
- import type { LintConfig, PresetName, Severity } from "../types"
1
+ import { allRules } from '../rules/index'
2
+ import type { LintConfig, PresetName, Severity } from '../types'
3
3
 
4
4
  /** Build a config where every rule uses its default severity. */
5
5
  function buildRecommended(): LintConfig {
@@ -15,7 +15,7 @@ function buildStrict(): LintConfig {
15
15
  const base = buildRecommended()
16
16
  const rules: Record<string, Severity> = {}
17
17
  for (const [id, sev] of Object.entries(base.rules)) {
18
- rules[id] = sev === "warn" ? "error" : sev
18
+ rules[id] = sev === 'warn' ? 'error' : sev
19
19
  }
20
20
  return { rules }
21
21
  }
@@ -26,10 +26,10 @@ function buildApp(): LintConfig {
26
26
  return {
27
27
  rules: {
28
28
  ...base.rules,
29
- "pyreon/dev-guard-warnings": "off",
30
- "pyreon/no-error-without-prefix": "off",
31
- "pyreon/no-circular-import": "off",
32
- "pyreon/no-cross-layer-import": "off",
29
+ 'pyreon/dev-guard-warnings': 'off',
30
+ 'pyreon/no-error-without-prefix': 'off',
31
+ 'pyreon/no-circular-import': 'off',
32
+ 'pyreon/no-cross-layer-import': 'off',
33
33
  },
34
34
  }
35
35
  }
@@ -40,10 +40,10 @@ function buildLib(): LintConfig {
40
40
  return {
41
41
  rules: {
42
42
  ...base.rules,
43
- "pyreon/no-circular-import": "error",
44
- "pyreon/no-cross-layer-import": "error",
45
- "pyreon/dev-guard-warnings": "error",
46
- "pyreon/no-error-without-prefix": "error",
43
+ 'pyreon/no-circular-import': 'error',
44
+ 'pyreon/no-cross-layer-import': 'error',
45
+ 'pyreon/dev-guard-warnings': 'error',
46
+ 'pyreon/no-error-without-prefix': 'error',
47
47
  },
48
48
  }
49
49
  }
package/src/index.ts CHANGED
@@ -1,13 +1,15 @@
1
1
  // Core API
2
- export { AstCache } from "./cache"
3
- export { createIgnoreFilter } from "./config/ignore"
4
- export { loadConfig, loadConfigFromPath } from "./config/loader"
5
- export { getPreset } from "./config/presets"
6
- export { lint, listRules } from "./lint"
7
- export { formatCompact, formatJSON, formatText } from "./reporter"
2
+ export { AstCache } from './cache'
3
+ export { createIgnoreFilter } from './config/ignore'
4
+ export { loadConfig, loadConfigFromPath } from './config/loader'
5
+ export { getPreset } from './config/presets'
6
+ export { lint, listRules } from './lint'
7
+ export { formatCompact, formatJSON, formatText } from './reporter'
8
+ // LSP
9
+ export { startLspServer } from './lsp/index'
8
10
  // Rules
9
- export { allRules } from "./rules/index"
10
- export { applyFixes, lintFile } from "./runner"
11
+ export { allRules } from './rules/index'
12
+ export { applyFixes, lintFile } from './runner'
11
13
  // Types
12
14
  export type {
13
15
  Diagnostic,
@@ -27,14 +29,14 @@ export type {
27
29
  SourceLocation,
28
30
  Span,
29
31
  VisitorCallbacks,
30
- } from "./types"
32
+ } from './types'
31
33
  export {
32
34
  extractImportInfo,
33
35
  getLocalName,
34
36
  importsName,
35
37
  isPyreonImport,
36
38
  isPyreonPackage,
37
- } from "./utils/imports"
39
+ } from './utils/imports'
38
40
  // Utilities
39
- export { LineIndex } from "./utils/source"
40
- export { watchAndLint } from "./watcher"
41
+ export { LineIndex } from './utils/source'
42
+ export { watchAndLint } from './watcher'