@pyreon/lint 0.11.5 → 0.11.7

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 +1 -1
  3. package/lib/analysis/index.js.html +1 -1
  4. package/lib/cli.js +214 -1
  5. package/lib/cli.js.map +1 -1
  6. package/lib/index.js +207 -1
  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 +15 -15
  11. package/src/cache.ts +1 -1
  12. package/src/cli.ts +38 -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 -19
  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 -13
  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 +5 -5
  85. package/src/utils/source.ts +2 -2
  86. package/src/watcher.ts +19 -19
@@ -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.5",
4
- "description": "Pyreon-specific linter — 55 rules for signals, JSX, SSR, performance, router, and architecture",
3
+ "version": "0.11.7",
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,14 +21,11 @@
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",
@@ -38,19 +38,19 @@
38
38
  },
39
39
  "./package.json": "./package.json"
40
40
  },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
41
44
  "scripts": {
42
45
  "build": "vl_rolldown_build",
43
46
  "dev": "vl_rolldown_build-watch",
44
47
  "test": "vitest run",
45
48
  "typecheck": "tsc --noEmit",
46
- "lint": "biome check .",
49
+ "lint": "oxlint .",
47
50
  "prepublishOnly": "bun run build"
48
51
  },
49
52
  "dependencies": {
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,11 +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
8
  // Read version from package.json at build time; fallback for dev
8
- const VERSION = "0.11.4"
9
+ const VERSION = '0.11.4'
9
10
 
10
11
  function printUsage() {
11
12
  console.log(`
@@ -21,6 +22,7 @@ function printUsage() {
21
22
  --config <path> Config file path
22
23
  --ignore <path> Ignore file path
23
24
  --watch Watch mode — re-lint on file changes
25
+ --lsp Start LSP server (stdin/stdout JSON-RPC)
24
26
  --help, -h Show this help
25
27
  --version, -v Show version
26
28
  `)
@@ -32,7 +34,7 @@ function printList() {
32
34
  const maxCat = Math.max(...rules.map((r) => r.category.length))
33
35
 
34
36
  for (const rule of rules) {
35
- const fixLabel = rule.fixable ? " [fixable]" : ""
37
+ const fixLabel = rule.fixable ? ' [fixable]' : ''
36
38
  const id = rule.id.padEnd(maxId)
37
39
  const cat = rule.category.padEnd(maxCat)
38
40
  const sev = rule.severity.padEnd(5)
@@ -45,12 +47,13 @@ function printList() {
45
47
  interface CliArgs {
46
48
  preset: PresetName
47
49
  fix: boolean
48
- format: "text" | "json" | "compact"
50
+ format: 'text' | 'json' | 'compact'
49
51
  quiet: boolean
50
52
  showList: boolean
51
53
  showHelp: boolean
52
54
  showVersion: boolean
53
55
  watchMode: boolean
56
+ lspMode: boolean
54
57
  configPath: string | undefined
55
58
  ignorePath: string | undefined
56
59
  ruleOverrides: Record<string, Severity>
@@ -58,26 +61,28 @@ interface CliArgs {
58
61
  }
59
62
 
60
63
  const BOOLEAN_FLAGS: Record<string, keyof CliArgs> = {
61
- "--help": "showHelp",
62
- "-h": "showHelp",
63
- "--version": "showVersion",
64
- "-v": "showVersion",
65
- "--list": "showList",
66
- "--fix": "fix",
67
- "--quiet": "quiet",
68
- "--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',
69
73
  }
70
74
 
71
75
  function parseArgs(argv: string[]): CliArgs {
72
76
  const result: CliArgs = {
73
- preset: "recommended",
77
+ preset: 'recommended',
74
78
  fix: false,
75
- format: "text",
79
+ format: 'text',
76
80
  quiet: false,
77
81
  showList: false,
78
82
  showHelp: false,
79
83
  showVersion: false,
80
84
  watchMode: false,
85
+ lspMode: false,
81
86
  configPath: undefined,
82
87
  ignorePath: undefined,
83
88
  ruleOverrides: {},
@@ -102,23 +107,23 @@ function parseArgs(argv: string[]): CliArgs {
102
107
 
103
108
  /** Returns number of extra args consumed (0 or 1). */
104
109
  function parseValueFlag(arg: string, nextArg: string | undefined, result: CliArgs): number {
105
- if (arg === "--preset") {
106
- result.preset = (nextArg ?? "recommended") as PresetName
110
+ if (arg === '--preset') {
111
+ result.preset = (nextArg ?? 'recommended') as PresetName
107
112
  return 1
108
113
  }
109
- if (arg === "--format") {
110
- result.format = (nextArg ?? "text") as "text" | "json" | "compact"
114
+ if (arg === '--format') {
115
+ result.format = (nextArg ?? 'text') as 'text' | 'json' | 'compact'
111
116
  return 1
112
117
  }
113
- if (arg === "--config") {
118
+ if (arg === '--config') {
114
119
  result.configPath = nextArg
115
120
  return 1
116
121
  }
117
- if (arg === "--ignore") {
122
+ if (arg === '--ignore') {
118
123
  result.ignorePath = nextArg
119
124
  return 1
120
125
  }
121
- if (arg === "--rule") {
126
+ if (arg === '--rule') {
122
127
  parseRuleOverride(nextArg, result.ruleOverrides)
123
128
  return 1
124
129
  }
@@ -130,7 +135,7 @@ function parseValueFlag(arg: string, nextArg: string | undefined, result: CliArg
130
135
 
131
136
  function parseRuleOverride(val: string | undefined, overrides: Record<string, Severity>): void {
132
137
  if (!val) return
133
- const eqIdx = val.lastIndexOf("=")
138
+ const eqIdx = val.lastIndexOf('=')
134
139
  if (eqIdx === -1) return
135
140
  const ruleId = val.slice(0, eqIdx)
136
141
  const severity = val.slice(eqIdx + 1) as Severity
@@ -155,8 +160,13 @@ function main() {
155
160
  process.exit(0)
156
161
  }
157
162
 
163
+ if (args.lspMode) {
164
+ startLspServer()
165
+ return
166
+ }
167
+
158
168
  if (args.paths.length === 0) {
159
- args.paths.push(".")
169
+ args.paths.push('.')
160
170
  }
161
171
 
162
172
  if (args.watchMode) {
@@ -183,9 +193,9 @@ function main() {
183
193
  ignore: args.ignorePath,
184
194
  })
185
195
 
186
- if (args.format === "json") {
196
+ if (args.format === 'json') {
187
197
  console.log(formatJSON(result))
188
- } else if (args.format === "compact") {
198
+ } else if (args.format === 'compact') {
189
199
  console.log(formatCompact(result))
190
200
  } else {
191
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'
package/src/lint.ts CHANGED
@@ -1,16 +1,16 @@
1
- import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"
2
- import { join, resolve } from "node:path"
3
- import { AstCache } from "./cache"
4
- import { createIgnoreFilter } from "./config/ignore"
5
- import { loadConfig, loadConfigFromPath } from "./config/loader"
6
- import { getPreset } from "./config/presets"
7
- import { allRules } from "./rules/index"
8
- import { applyFixes, lintFile } from "./runner"
9
- import type { LintConfig, LintFileResult, LintOptions, LintResult, RuleMeta } from "./types"
10
- import { hasJsExtension } from "./utils/index"
1
+ import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
2
+ import { join, resolve } from 'node:path'
3
+ import { AstCache } from './cache'
4
+ import { createIgnoreFilter } from './config/ignore'
5
+ import { loadConfig, loadConfigFromPath } from './config/loader'
6
+ import { getPreset } from './config/presets'
7
+ import { allRules } from './rules/index'
8
+ import { applyFixes, lintFile } from './runner'
9
+ import type { LintConfig, LintFileResult, LintOptions, LintResult, RuleMeta } from './types'
10
+ import { hasJsExtension } from './utils/index'
11
11
 
12
12
  function isHiddenOrVendor(entry: string): boolean {
13
- return entry.startsWith(".") || entry === "node_modules" || entry === "lib" || entry === "dist"
13
+ return entry.startsWith('.') || entry === 'node_modules' || entry === 'lib' || entry === 'dist'
14
14
  }
15
15
 
16
16
  function matchesPatterns(
@@ -90,10 +90,10 @@ function buildConfig(options: LintOptions): {
90
90
  exclude: string[] | undefined
91
91
  isIgnored: (filePath: string) => boolean
92
92
  } {
93
- const cwd = resolve(".")
93
+ const cwd = resolve('.')
94
94
  const fileConfig = options.config ? loadConfigFromPath(options.config) : loadConfig(cwd)
95
95
 
96
- const presetName = options.preset ?? fileConfig?.preset ?? "recommended"
96
+ const presetName = options.preset ?? fileConfig?.preset ?? 'recommended'
97
97
  const config = getPreset(presetName)
98
98
 
99
99
  // Merge config file rule overrides
@@ -146,16 +146,16 @@ function applyFixesToFile(fileResult: LintFileResult, source: string): void {
146
146
  const fixable = fileResult.diagnostics.filter((d) => d.fix)
147
147
  if (fixable.length === 0) return
148
148
  const fixed = applyFixes(source, fileResult.diagnostics)
149
- writeFileSync(fileResult.filePath, fixed, "utf-8")
149
+ writeFileSync(fileResult.filePath, fixed, 'utf-8')
150
150
  fileResult.fixedSource = fixed
151
151
  fileResult.diagnostics = fileResult.diagnostics.filter((d) => !d.fix)
152
152
  }
153
153
 
154
154
  function countDiagnostics(fileResult: LintFileResult, results: LintResult): void {
155
155
  for (const d of fileResult.diagnostics) {
156
- if (d.severity === "error") results.totalErrors++
157
- else if (d.severity === "warn") results.totalWarnings++
158
- else if (d.severity === "info") results.totalInfos++
156
+ if (d.severity === 'error') results.totalErrors++
157
+ else if (d.severity === 'warn') results.totalWarnings++
158
+ else if (d.severity === 'info') results.totalInfos++
159
159
  }
160
160
  }
161
161
 
@@ -185,7 +185,7 @@ export function lint(options: LintOptions): LintResult {
185
185
  for (const filePath of files) {
186
186
  let source: string
187
187
  try {
188
- source = readFileSync(filePath, "utf-8")
188
+ source = readFileSync(filePath, 'utf-8')
189
189
  } catch {
190
190
  continue
191
191
  }
@@ -194,7 +194,7 @@ export function lint(options: LintOptions): LintResult {
194
194
  applyFixesToFile(fileResult, source)
195
195
  }
196
196
  if (options.quiet) {
197
- fileResult.diagnostics = fileResult.diagnostics.filter((d) => d.severity === "error")
197
+ fileResult.diagnostics = fileResult.diagnostics.filter((d) => d.severity === 'error')
198
198
  }
199
199
  countDiagnostics(fileResult, results)
200
200
  results.files.push(fileResult)