@swiftuijs/twill-linter 0.1.2 → 0.3.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/README.md CHANGED
@@ -41,3 +41,13 @@ Custom rule overrides should also match the virtual TS/TSX files, as with other
41
41
  Framework ESLint plugins can attach their ordinary TS/TSX rules to these virtual files. Keep ESLint formatting rules disabled and let Prettier handle layout; rewrites spanning generated syntax cannot always be mapped back safely.
42
42
 
43
43
  [Documentation](https://twill.evecalm.com/tooling) · [Issues](https://github.com/swiftuijs/twill/issues) · MIT licensed · Built by [forth.ink](https://forth.ink).
44
+
45
+ ## Added in 0.2.0
46
+
47
+ Twill 0.2.0 adds `if const value = lookup() { use(value); }` with branch-local nullish bindings and native destructuring. Group trailing calls in initializers, following Swift’s condition boundary. It emits native branches without a helper, closure or optional wrapper. See [syntax](https://twill.evecalm.com/syntax#branch-nullish-bindings) and [RFC 0018](https://github.com/swiftuijs/twill/blob/main/docs/rfcs/0018-optional-branch-bindings.md).
48
+
49
+ Twill 0.2.0 includes associated-value enums and `match` expressions: `match (state) { case State.loaded({ value }): value; default: 0; }`. Named payload bindings and erased factory descriptors compile to native switches without calling factories. Native switch statements retain JS fallthrough; match and switch expressions return one result without fallthrough. Use the coordinated 0.2.0 packages and extension. Compiler, formatter, linter, highlighter, export and editor changes are validated together. See [syntax and compatibility](https://twill.evecalm.com/syntax#match-expressions) and [RFC 0016](https://github.com/swiftuijs/twill/blob/main/docs/rfcs/0016-pattern-matching.md).
50
+
51
+ ## Development
52
+
53
+ This package's implementation uses Twill and emits ordinary JavaScript. Build and check a checkout with the workspace tools; see [developing tooling in Twill](https://github.com/swiftuijs/twill/blob/main/docs/contributing/dogfooding.md) for bootstrap and contribution instructions.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ESLint, Linter } from 'eslint';
1
+ import type { ESLint, Linter } from 'eslint';
2
2
  declare const plugin: ESLint.Plugin & {
3
3
  configs: Record<string, Linter.Config[]>;
4
4
  };
@@ -6,3 +6,4 @@ export declare const configs: Record<string, Linter.Config[]>;
6
6
  export declare const processors: Record<string, Linter.Processor>;
7
7
  export declare function disposeProjects(): void;
8
8
  export default plugin;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","mappings":"AAcA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAQ,MAAM,QAAQ;AA0NlD,cAAM,MAAM,EAAE,MAAM,CAAC,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;CAIvE;AAmCD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAkB;AACtE,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,CAAwB;AAChF,wBAAgB,eAAe;AAK/B,eAAe,MAAM","names":[],"ignoreList":[],"sources":["../src/index.twill"],"sourcesContent":["import {\n transform,\n originalPosition,\n generatedPosition,\n type TransformResult,\n} from '@swiftuijs/twill';\nimport { TwillProject, virtualFilename, sourceFilename } from '@swiftuijs/twill/project';\nimport * as parser from '@typescript-eslint/parser';\nimport typescriptPlugin from '@typescript-eslint/eslint-plugin';\nimport js from '@eslint/js';\nimport ts from 'typescript';\nimport { loadConfig } from '@swiftuijs/twill/config';\nimport { dirname, resolve } from 'node:path';\nimport { statSync } from 'node:fs';\nimport type { ESLint, Linter, Rule } from 'eslint';\nimport metadata from '../package.json' with { type: 'json' };\nimport { SourcePositions } from './positions.twill';\n\ntype State = {\n source: string;\n filename: string;\n result?: TransformResult;\n error?: any;\n original: SourcePositions;\n generated?: SourcePositions;\n project?: TwillProject;\n};\nconst pending = new Map<string, State>();\nconst projects = new Map<string, { project: TwillProject; times: Map<string, number> }>();\nconst normalize = (filename: string) => filename.replaceAll('\\\\', '/');\nconst stamp = (filename: string) => {\n try {\n return statSync(filename).mtimeMs;\n } catch {\n return -1;\n }\n};\n\nfunction projectFor(filename: string) {\n const config = ts.findConfigFile(dirname(filename), ts.sys.fileExists);\n guard config else {\n throw new Error('Typed Twill linting requires a tsconfig.json.');\n }\n let cached = projects.get(config);\n if (\n cached &&\n cached.project.configFiles.some((file) => cached!.times.get(file) !== stamp(file))\n ) {\n cached.project.dispose();\n projects.delete(config);\n cached = undefined;\n }\n if (!cached) {\n cached = { project: new TwillProject(config), times: new Map() };\n for (const file of cached.project.configFiles) cached.times.set(file, stamp(file));\n if (projects.size >= 8) {\n const oldest = projects.keys().next().value!;\n projects.get(oldest)!.project.dispose();\n projects.delete(oldest);\n }\n projects.set(config, cached);\n }\n for (const file of cached.project.sourceFiles()) {\n const time = stamp(file);\n if (cached.times.has(file) && cached.times.get(file) !== time) cached.project.refresh(file);\n cached.times.set(file, time);\n }\n return cached.project;\n}\n\nfunction originalOffset(state: State, offset: number) {\n const position = state.generated!.mapPosition(offset);\n const point = originalPosition(state.result!, position.line, position.column);\n return point.line == null || point.column == null\n ? undefined\n : state.original.mapOffset(point.line, point.column);\n}\nfunction fix(state: State, value: Rule.Fix | undefined): Rule.Fix | undefined {\n if (!value) return undefined;\n const [start, end] = value.range;\n const offset = originalOffset(state, start);\n if (offset === undefined) return undefined;\n const position = state.original.mapPosition(offset);\n const generated = generatedPosition(state.result!, position.line, position.column);\n if (\n generated.line == null ||\n generated.column == null ||\n state.generated!.mapOffset(generated.line, generated.column) !== start ||\n state.source.slice(offset, offset + end - start) !== state.result!.code.slice(start, end)\n )\n return undefined;\n return { range: [offset, offset + end - start], text: value.text };\n}\n\nconst processor: Linter.Processor = {\n supportsAutofix: true,\n preprocess(source, filename) {\n filename = normalize(filename);\n const state: State = {\n source,\n filename,\n original: new SourcePositions(source),\n };\n pending.set(filename, state);\n try {\n // A project supplies the same inherited JSX settings as builds/checking.\n const config = ts.findConfigFile(dirname(filename), ts.sys.fileExists);\n state.result = transform(source, {\n ...loadConfig(config ? dirname(config) : dirname(filename)),\n filename,\n });\n state.generated = new SourcePositions(state.result.code);\n return [\n {\n text: state.result.code,\n filename: filename.endsWith('.twillx') ? 'source.tsx' : 'source.ts',\n },\n ];\n } catch (error) {\n state.error = error;\n return [];\n }\n },\n postprocess(lists, filename) {\n filename = normalize(filename);\n const state = pending.get(filename)!;\n pending.delete(filename);\n state.project?.update(filename);\n if (state.error)\n return [\n {\n ruleId: null,\n severity: 2,\n fatal: true,\n message: state.error.message,\n line: state.error.line ?? 1,\n column: (state.error.column ?? 0) + 1,\n },\n ];\n return lists.flat().flatMap((message) => {\n if (!message.line || !message.column) return [message];\n const generated = state.generated!;\n const start = generated.getPositionOfLineAndCharacter(message.line - 1, message.column - 1);\n let offset = originalOffset(state, start);\n let expressionSwitch = false;\n let expressionKeywordLength = 6;\n if (\n message.ruleId === '@typescript-eslint/switch-exhaustiveness-check' &&\n state.project &&\n (offset === undefined || state.result!.code[start] !== state.source[offset])\n ) {\n // Expression subjects use a generated once-evaluated local. Report the\n // missing union arm on the source-backed switch/match keyword instead.\n const file = state.project.service.getProgram()?.getSourceFile(virtualFilename(filename));\n const visit = (node: ts.Node) => {\n if (ts.isSwitchStatement(node) && node.expression.getStart() === start) {\n const sourceStart = originalOffset(state, node.getStart());\n if (\n sourceStart !== undefined &&\n /^(?:switch|match)\\b/.test(state.source.slice(sourceStart))\n ) {\n offset = sourceStart;\n expressionSwitch = true;\n expressionKeywordLength = state.source.startsWith('match', sourceStart) ? 5 : 6;\n }\n } else if (start >= node.getFullStart() && start < node.end) ts.forEachChild(node, visit);\n };\n if (file) visit(file);\n }\n if (\n offset === undefined ||\n (!expressionSwitch &&\n start < state.result!.code.length &&\n state.result!.code[start] !== state.source[offset])\n )\n return [];\n const position = state.original.getLineAndCharacterOfPosition(offset);\n const endOffset = expressionSwitch\n ? offset + expressionKeywordLength\n : message.endLine && message.endColumn\n ? originalOffset(\n state,\n generated.getPositionOfLineAndCharacter(message.endLine - 1, message.endColumn - 1),\n )\n : undefined;\n const end =\n endOffset === undefined\n ? undefined\n : state.original.getLineAndCharacterOfPosition(endOffset);\n return [\n {\n ...message,\n line: position.line + 1,\n column: position.character + 1,\n endLine: end && end.line + 1,\n endColumn: end && end.character + 1,\n fix: expressionSwitch ? undefined : fix(state, message.fix),\n // Native statement-case suggestions introduce blocks/value labels that\n // are invalid in a Twill switch expression. Keep the diagnostic.\n suggestions: expressionSwitch\n ? []\n : message.suggestions?.flatMap((suggestion) => {\n const mapped = fix(state, suggestion.fix);\n return mapped ? [{ ...suggestion, fix: mapped }] : [];\n }),\n },\n ];\n });\n },\n};\n\nconst virtualParser = {\n meta: { name: '@swiftuijs/twill-linter/parser', version: metadata.version },\n parseForESLint(code: string, options: any) {\n const original = normalize(options.filePath ?? '').replace(/\\/(?:\\d+_)?source\\.tsx?$/, '');\n const state = pending.get(original);\n if (!state) return parser.parseForESLint(code, options);\n const typed = options.projectService || options.project || options.programs;\n if (!typed) return parser.parseForESLint(code, options);\n const project = projectFor(original);\n project.update(original, state.source);\n state.project = project;\n return parser.parseForESLint(code, {\n ...options,\n filePath: virtualFilename(original),\n projectService: false,\n project: undefined,\n programs: [project.service.getProgram()!],\n });\n },\n};\n\nconst plugin: ESLint.Plugin & { configs: Record<string, Linter.Config[]> } = {\n meta: { name: '@swiftuijs/twill-linter', version: metadata.version },\n processors: { twill: processor },\n configs: {} as Record<string, Linter.Config[]>,\n};\nconst files = ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'];\nfunction configuration(typed: boolean): Linter.Config[] {\n const recommended = typescriptPlugin.configs[\n typed ? 'flat/recommended-type-checked' : 'flat/recommended'\n ] as Linter.Config[];\n return [\n js.configs.recommended,\n { files: ['**/*.twill', '**/*.twillx'], plugins: { twill: plugin }, processor: 'twill/twill' },\n ...recommended.map((config) => ({\n ...config,\n files,\n languageOptions: {\n ...config.languageOptions,\n parser: virtualParser,\n ...(typed ? { parserOptions: { projectService: true } } : {}),\n },\n })),\n ...(typed\n ? [\n {\n files,\n rules: {\n '@typescript-eslint/switch-exhaustiveness-check': [\n 'error',\n { considerDefaultExhaustiveForUnions: false },\n ] satisfies Linter.RuleEntry,\n },\n },\n ]\n : []),\n ];\n}\nplugin.configs.recommended = configuration(false);\nplugin.configs.recommendedTypeChecked = configuration(true);\nexport const configs: Record<string, Linter.Config[]> = plugin.configs;\nexport const processors: Record<string, Linter.Processor> = { twill: processor };\nexport function disposeProjects() {\n for (const { project } of projects.values()) project.dispose();\n projects.clear();\n pending.clear();\n}\nexport default plugin;\n"]}
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { dirname } from "node:path";
9
9
  import { statSync } from "node:fs";
10
10
  var package_default = {
11
11
  name: "@swiftuijs/twill-linter",
12
- version: "0.1.2",
12
+ version: "0.3.0",
13
13
  type: "module",
14
14
  license: "MIT",
15
15
  repository: {
@@ -27,8 +27,8 @@ var package_default = {
27
27
  "import": "./dist/index.js"
28
28
  } },
29
29
  scripts: {
30
- "build": "vite build",
31
- "typecheck": "tsc --noEmit -p tsconfig.json",
30
+ "build": "vite build && twill declarations -p tsconfig.build.json -o dist",
31
+ "typecheck": "twill check -p tsconfig.json",
32
32
  "test:unit": "vitest run",
33
33
  "test:watch": "vitest",
34
34
  "test:coverage": "vitest run --coverage",
@@ -45,7 +45,6 @@ var package_default = {
45
45
  },
46
46
  devDependencies: {
47
47
  "vite": "catalog:",
48
- "vite-plugin-dts": "^5.1.2",
49
48
  "@types/node": "catalog:",
50
49
  "typescript": "catalog:",
51
50
  "eslint": "catalog:",
@@ -71,7 +70,7 @@ var package_default = {
71
70
  description: "ESLint flat configurations for Twill with TypeScript rules, original-source diagnostics and safe fixes."
72
71
  };
73
72
  //#endregion
74
- //#region src/positions.ts
73
+ //#region src/positions.twill
75
74
  /** Source-map lines use LF; ESLint positions also count CR and Unicode separators. */
76
75
  var SourcePositions = class {
77
76
  native = [0];
@@ -112,7 +111,7 @@ var SourcePositions = class {
112
111
  }
113
112
  };
114
113
  //#endregion
115
- //#region src/index.ts
114
+ //#region src/index.twill
116
115
  var pending = /* @__PURE__ */ new Map();
117
116
  var projects = /* @__PURE__ */ new Map();
118
117
  var normalize = (filename) => filename.replaceAll("\\", "/");
@@ -215,14 +214,16 @@ var processor = {
215
214
  const start = generated.getPositionOfLineAndCharacter(message.line - 1, message.column - 1);
216
215
  let offset = originalOffset(state, start);
217
216
  let expressionSwitch = false;
217
+ let expressionKeywordLength = 6;
218
218
  if (message.ruleId === "@typescript-eslint/switch-exhaustiveness-check" && state.project && (offset === void 0 || state.result.code[start] !== state.source[offset])) {
219
219
  const file = state.project.service.getProgram()?.getSourceFile(virtualFilename(filename));
220
220
  const visit = (node) => {
221
221
  if (ts.isSwitchStatement(node) && node.expression.getStart() === start) {
222
222
  const sourceStart = originalOffset(state, node.getStart());
223
- if (sourceStart !== void 0 && state.source.slice(sourceStart, sourceStart + 6) === "switch") {
223
+ if (sourceStart !== void 0 && /^(?:switch|match)\b/.test(state.source.slice(sourceStart))) {
224
224
  offset = sourceStart;
225
225
  expressionSwitch = true;
226
+ expressionKeywordLength = state.source.startsWith("match", sourceStart) ? 5 : 6;
226
227
  }
227
228
  } else if (start >= node.getFullStart() && start < node.end) ts.forEachChild(node, visit);
228
229
  };
@@ -230,7 +231,7 @@ var processor = {
230
231
  }
231
232
  if (offset === void 0 || !expressionSwitch && start < state.result.code.length && state.result.code[start] !== state.source[offset]) return [];
232
233
  const position = state.original.getLineAndCharacterOfPosition(offset);
233
- const endOffset = expressionSwitch ? offset + 6 : message.endLine && message.endColumn ? originalOffset(state, generated.getPositionOfLineAndCharacter(message.endLine - 1, message.endColumn - 1)) : void 0;
234
+ const endOffset = expressionSwitch ? offset + expressionKeywordLength : message.endLine && message.endColumn ? originalOffset(state, generated.getPositionOfLineAndCharacter(message.endLine - 1, message.endColumn - 1)) : void 0;
234
235
  const end = endOffset === void 0 ? void 0 : state.original.getLineAndCharacterOfPosition(endOffset);
235
236
  return [{
236
237
  ...message,
@@ -238,8 +239,8 @@ var processor = {
238
239
  column: position.character + 1,
239
240
  endLine: end && end.line + 1,
240
241
  endColumn: end && end.character + 1,
241
- fix: fix(state, message.fix),
242
- suggestions: message.suggestions?.flatMap((suggestion) => {
242
+ fix: expressionSwitch ? void 0 : fix(state, message.fix),
243
+ suggestions: expressionSwitch ? [] : message.suggestions?.flatMap((suggestion) => {
243
244
  const mapped = fix(state, suggestion.fix);
244
245
  return mapped ? [{
245
246
  ...suggestion,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["files","metadata"],"sources":["../package.json","../src/positions.ts","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@swiftuijs/twill-linter\",\n \"version\": \"0.1.2\",\n \"type\": \"module\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/swiftuijs/twill.git\",\n \"directory\": \"packages/linter\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n }\n },\n \"scripts\": {\n \"build\": \"vite build\",\n \"typecheck\": \"tsc --noEmit -p tsconfig.json\",\n \"test:unit\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"test:package\": \"node tests/consumer.mjs\",\n \"prepack\": \"pnpm run build\",\n \"test\": \"pnpm run test:unit\"\n },\n \"dependencies\": {\n \"@swiftuijs/twill\": \"workspace:*\",\n \"@typescript-eslint/parser\": \"^8.71.0\",\n \"@typescript-eslint/eslint-plugin\": \"^8.71.0\",\n \"@eslint/js\": \"catalog:\",\n \"typescript\": \"catalog:\"\n },\n \"devDependencies\": {\n \"vite\": \"catalog:\",\n \"vite-plugin-dts\": \"^5.1.2\",\n \"@types/node\": \"catalog:\",\n \"typescript\": \"catalog:\",\n \"eslint\": \"catalog:\",\n \"vitest\": \"catalog:\",\n \"@vitest/coverage-v8\": \"catalog:\"\n },\n \"engines\": {\n \"node\": \"^20.19.0 || >=22.12.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"peerDependencies\": {\n \"eslint\": \"^9.0.0 || ^10.0.0\"\n },\n \"author\": {\n \"name\": \"forth.ink\",\n \"url\": \"https://forth.ink\"\n },\n \"homepage\": \"https://twill.evecalm.com/\",\n \"bugs\": {\n \"url\": \"https://github.com/swiftuijs/twill/issues\"\n },\n \"keywords\": [\n \"twill\",\n \"eslint\",\n \"eslint-plugin\",\n \"typescript\",\n \"linter\"\n ],\n \"description\": \"ESLint flat configurations for Twill with TypeScript rules, original-source diagnostics and safe fixes.\"\n}\n","/** Source-map lines use LF; ESLint positions also count CR and Unicode separators. */\nexport class SourcePositions {\n private native = [0];\n private mapped = [0];\n\n constructor(text: string) {\n const breaks = /\\r\\n|[\\n\\r\\u2028\\u2029]/g;\n while (breaks.exec(text)) this.native.push(breaks.lastIndex);\n for (let index = 0; index < text.length; index++)\n if (text[index] === '\\n') this.mapped.push(index + 1);\n }\n\n private position(starts: number[], offset: number) {\n let low = 0;\n let high = starts.length;\n while (low + 1 < high) {\n const middle = (low + high) >>> 1;\n if (starts[middle]! <= offset) low = middle;\n else high = middle;\n }\n return { line: low, character: offset - starts[low]! };\n }\n\n getLineAndCharacterOfPosition(offset: number) {\n return this.position(this.native, offset);\n }\n\n getPositionOfLineAndCharacter(line: number, column: number) {\n return this.native[line]! + column;\n }\n\n mapPosition(offset: number) {\n const position = this.position(this.mapped, offset);\n return { line: position.line + 1, column: position.character };\n }\n\n mapOffset(line: number, column: number) {\n return this.mapped[line - 1]! + column;\n }\n}\n","import {\n transform,\n originalPosition,\n generatedPosition,\n type TransformResult,\n} from '@swiftuijs/twill';\nimport { TwillProject, virtualFilename, sourceFilename } from '@swiftuijs/twill/project';\nimport * as parser from '@typescript-eslint/parser';\nimport typescriptPlugin from '@typescript-eslint/eslint-plugin';\nimport js from '@eslint/js';\nimport ts from 'typescript';\nimport { loadConfig } from '@swiftuijs/twill/config';\nimport { dirname, resolve } from 'node:path';\nimport { statSync } from 'node:fs';\nimport type { ESLint, Linter, Rule } from 'eslint';\nimport metadata from '../package.json' with { type: 'json' };\nimport { SourcePositions } from './positions.js';\n\ntype State = {\n source: string;\n filename: string;\n result?: TransformResult;\n error?: any;\n original: SourcePositions;\n generated?: SourcePositions;\n project?: TwillProject;\n};\nconst pending = new Map<string, State>();\nconst projects = new Map<string, { project: TwillProject; times: Map<string, number> }>();\nconst normalize = (filename: string) => filename.replaceAll('\\\\', '/');\nconst stamp = (filename: string) => {\n try {\n return statSync(filename).mtimeMs;\n } catch {\n return -1;\n }\n};\n\nfunction projectFor(filename: string) {\n const config = ts.findConfigFile(dirname(filename), ts.sys.fileExists);\n if (!config) throw new Error('Typed Twill linting requires a tsconfig.json.');\n let cached = projects.get(config);\n if (\n cached &&\n cached.project.configFiles.some((file) => cached!.times.get(file) !== stamp(file))\n ) {\n cached.project.dispose();\n projects.delete(config);\n cached = undefined;\n }\n if (!cached) {\n cached = { project: new TwillProject(config), times: new Map() };\n for (const file of cached.project.configFiles) cached.times.set(file, stamp(file));\n if (projects.size >= 8) {\n const oldest = projects.keys().next().value!;\n projects.get(oldest)!.project.dispose();\n projects.delete(oldest);\n }\n projects.set(config, cached);\n }\n for (const file of cached.project.sourceFiles()) {\n const time = stamp(file);\n if (cached.times.has(file) && cached.times.get(file) !== time) cached.project.refresh(file);\n cached.times.set(file, time);\n }\n return cached.project;\n}\n\nfunction originalOffset(state: State, offset: number) {\n const position = state.generated!.mapPosition(offset);\n const point = originalPosition(state.result!, position.line, position.column);\n return point.line == null || point.column == null\n ? undefined\n : state.original.mapOffset(point.line, point.column);\n}\nfunction fix(state: State, value: Rule.Fix | undefined): Rule.Fix | undefined {\n if (!value) return undefined;\n const [start, end] = value.range;\n const offset = originalOffset(state, start);\n if (offset === undefined) return undefined;\n const position = state.original.mapPosition(offset);\n const generated = generatedPosition(state.result!, position.line, position.column);\n if (\n generated.line == null ||\n generated.column == null ||\n state.generated!.mapOffset(generated.line, generated.column) !== start ||\n state.source.slice(offset, offset + end - start) !== state.result!.code.slice(start, end)\n )\n return undefined;\n return { range: [offset, offset + end - start], text: value.text };\n}\n\nconst processor: Linter.Processor = {\n supportsAutofix: true,\n preprocess(source, filename) {\n filename = normalize(filename);\n const state: State = {\n source,\n filename,\n original: new SourcePositions(source),\n };\n pending.set(filename, state);\n try {\n // A project supplies the same inherited JSX settings as builds/checking.\n const config = ts.findConfigFile(dirname(filename), ts.sys.fileExists);\n state.result = transform(source, {\n ...loadConfig(config ? dirname(config) : dirname(filename)),\n filename,\n });\n state.generated = new SourcePositions(state.result.code);\n return [\n {\n text: state.result.code,\n filename: filename.endsWith('.twillx') ? 'source.tsx' : 'source.ts',\n },\n ];\n } catch (error) {\n state.error = error;\n return [];\n }\n },\n postprocess(lists, filename) {\n filename = normalize(filename);\n const state = pending.get(filename)!;\n pending.delete(filename);\n state.project?.update(filename);\n if (state.error)\n return [\n {\n ruleId: null,\n severity: 2,\n fatal: true,\n message: state.error.message,\n line: state.error.line ?? 1,\n column: (state.error.column ?? 0) + 1,\n },\n ];\n return lists.flat().flatMap((message) => {\n if (!message.line || !message.column) return [message];\n const generated = state.generated!;\n const start = generated.getPositionOfLineAndCharacter(message.line - 1, message.column - 1);\n let offset = originalOffset(state, start);\n let expressionSwitch = false;\n if (\n message.ruleId === '@typescript-eslint/switch-exhaustiveness-check' &&\n state.project &&\n (offset === undefined || state.result!.code[start] !== state.source[offset])\n ) {\n // Expression subjects use a generated once-evaluated local. Report the\n // missing union arm on the source-backed switch keyword instead.\n const file = state.project.service.getProgram()?.getSourceFile(virtualFilename(filename));\n const visit = (node: ts.Node) => {\n if (ts.isSwitchStatement(node) && node.expression.getStart() === start) {\n const sourceStart = originalOffset(state, node.getStart());\n if (\n sourceStart !== undefined &&\n state.source.slice(sourceStart, sourceStart + 6) === 'switch'\n ) {\n offset = sourceStart;\n expressionSwitch = true;\n }\n } else if (start >= node.getFullStart() && start < node.end) ts.forEachChild(node, visit);\n };\n if (file) visit(file);\n }\n if (\n offset === undefined ||\n (!expressionSwitch &&\n start < state.result!.code.length &&\n state.result!.code[start] !== state.source[offset])\n )\n return [];\n const position = state.original.getLineAndCharacterOfPosition(offset);\n const endOffset = expressionSwitch\n ? offset + 6\n : message.endLine && message.endColumn\n ? originalOffset(\n state,\n generated.getPositionOfLineAndCharacter(message.endLine - 1, message.endColumn - 1),\n )\n : undefined;\n const end =\n endOffset === undefined\n ? undefined\n : state.original.getLineAndCharacterOfPosition(endOffset);\n return [\n {\n ...message,\n line: position.line + 1,\n column: position.character + 1,\n endLine: end && end.line + 1,\n endColumn: end && end.character + 1,\n fix: fix(state, message.fix),\n suggestions: message.suggestions?.flatMap((suggestion) => {\n const mapped = fix(state, suggestion.fix);\n return mapped ? [{ ...suggestion, fix: mapped }] : [];\n }),\n },\n ];\n });\n },\n};\n\nconst virtualParser = {\n meta: { name: '@swiftuijs/twill-linter/parser', version: metadata.version },\n parseForESLint(code: string, options: any) {\n const original = normalize(options.filePath ?? '').replace(/\\/(?:\\d+_)?source\\.tsx?$/, '');\n const state = pending.get(original);\n if (!state) return parser.parseForESLint(code, options);\n const typed = options.projectService || options.project || options.programs;\n if (!typed) return parser.parseForESLint(code, options);\n const project = projectFor(original);\n project.update(original, state.source);\n state.project = project;\n return parser.parseForESLint(code, {\n ...options,\n filePath: virtualFilename(original),\n projectService: false,\n project: undefined,\n programs: [project.service.getProgram()!],\n });\n },\n};\n\nconst plugin: ESLint.Plugin & { configs: Record<string, Linter.Config[]> } = {\n meta: { name: '@swiftuijs/twill-linter', version: metadata.version },\n processors: { twill: processor },\n configs: {} as Record<string, Linter.Config[]>,\n};\nconst files = ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'];\nfunction configuration(typed: boolean): Linter.Config[] {\n const recommended = typescriptPlugin.configs[\n typed ? 'flat/recommended-type-checked' : 'flat/recommended'\n ] as Linter.Config[];\n return [\n js.configs.recommended,\n { files: ['**/*.twill', '**/*.twillx'], plugins: { twill: plugin }, processor: 'twill/twill' },\n ...recommended.map((config) => ({\n ...config,\n files,\n languageOptions: {\n ...config.languageOptions,\n parser: virtualParser,\n ...(typed ? { parserOptions: { projectService: true } } : {}),\n },\n })),\n ...(typed\n ? [\n {\n files,\n rules: {\n '@typescript-eslint/switch-exhaustiveness-check': [\n 'error',\n { considerDefaultExhaustiveForUnions: false },\n ] satisfies Linter.RuleEntry,\n },\n },\n ]\n : []),\n ];\n}\nplugin.configs.recommended = configuration(false);\nplugin.configs.recommendedTypeChecked = configuration(true);\nexport const configs: Record<string, Linter.Config[]> = plugin.configs;\nexport const processors: Record<string, Linter.Processor> = { twill: processor };\nexport function disposeProjects() {\n for (const { project } of projects.values()) project.dispose();\n projects.clear();\n pending.clear();\n}\nexport default plugin;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["files","metadata"],"sources":["../package.json","../src/positions.twill","../src/index.twill"],"sourcesContent":["{\n \"name\": \"@swiftuijs/twill-linter\",\n \"version\": \"0.3.0\",\n \"type\": \"module\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/swiftuijs/twill.git\",\n \"directory\": \"packages/linter\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n }\n },\n \"scripts\": {\n \"build\": \"vite build && twill declarations -p tsconfig.build.json -o dist\",\n \"typecheck\": \"twill check -p tsconfig.json\",\n \"test:unit\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"test:package\": \"node tests/consumer.mjs\",\n \"prepack\": \"pnpm run build\",\n \"test\": \"pnpm run test:unit\"\n },\n \"dependencies\": {\n \"@swiftuijs/twill\": \"workspace:*\",\n \"@typescript-eslint/parser\": \"^8.71.0\",\n \"@typescript-eslint/eslint-plugin\": \"^8.71.0\",\n \"@eslint/js\": \"catalog:\",\n \"typescript\": \"catalog:\"\n },\n \"devDependencies\": {\n \"vite\": \"catalog:\",\n \"@types/node\": \"catalog:\",\n \"typescript\": \"catalog:\",\n \"eslint\": \"catalog:\",\n \"vitest\": \"catalog:\",\n \"@vitest/coverage-v8\": \"catalog:\"\n },\n \"engines\": {\n \"node\": \"^20.19.0 || >=22.12.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"peerDependencies\": {\n \"eslint\": \"^9.0.0 || ^10.0.0\"\n },\n \"author\": {\n \"name\": \"forth.ink\",\n \"url\": \"https://forth.ink\"\n },\n \"homepage\": \"https://twill.evecalm.com/\",\n \"bugs\": {\n \"url\": \"https://github.com/swiftuijs/twill/issues\"\n },\n \"keywords\": [\n \"twill\",\n \"eslint\",\n \"eslint-plugin\",\n \"typescript\",\n \"linter\"\n ],\n \"description\": \"ESLint flat configurations for Twill with TypeScript rules, original-source diagnostics and safe fixes.\"\n}\n","/** Source-map lines use LF; ESLint positions also count CR and Unicode separators. */\nexport class SourcePositions {\n private native = [0];\n private mapped = [0];\n\n constructor(text: string) {\n const breaks = /\\r\\n|[\\n\\r\\u2028\\u2029]/g;\n while (breaks.exec(text)) this.native.push(breaks.lastIndex);\n for (let index = 0; index < text.length; index++)\n if (text[index] === '\\n') this.mapped.push(index + 1);\n }\n\n private position(starts: number[], offset: number) {\n let low = 0;\n let high = starts.length;\n while (low + 1 < high) {\n const middle = (low + high) >>> 1;\n if (starts[middle]! <= offset) low = middle;\n else high = middle;\n }\n return { line: low, character: offset - starts[low]! };\n }\n\n getLineAndCharacterOfPosition(offset: number) {\n return this.position(this.native, offset);\n }\n\n getPositionOfLineAndCharacter(line: number, column: number) {\n return this.native[line]! + column;\n }\n\n mapPosition(offset: number) {\n const position = this.position(this.mapped, offset);\n return { line: position.line + 1, column: position.character };\n }\n\n mapOffset(line: number, column: number) {\n return this.mapped[line - 1]! + column;\n }\n}\n","import {\n transform,\n originalPosition,\n generatedPosition,\n type TransformResult,\n} from '@swiftuijs/twill';\nimport { TwillProject, virtualFilename, sourceFilename } from '@swiftuijs/twill/project';\nimport * as parser from '@typescript-eslint/parser';\nimport typescriptPlugin from '@typescript-eslint/eslint-plugin';\nimport js from '@eslint/js';\nimport ts from 'typescript';\nimport { loadConfig } from '@swiftuijs/twill/config';\nimport { dirname, resolve } from 'node:path';\nimport { statSync } from 'node:fs';\nimport type { ESLint, Linter, Rule } from 'eslint';\nimport metadata from '../package.json' with { type: 'json' };\nimport { SourcePositions } from './positions.twill';\n\ntype State = {\n source: string;\n filename: string;\n result?: TransformResult;\n error?: any;\n original: SourcePositions;\n generated?: SourcePositions;\n project?: TwillProject;\n};\nconst pending = new Map<string, State>();\nconst projects = new Map<string, { project: TwillProject; times: Map<string, number> }>();\nconst normalize = (filename: string) => filename.replaceAll('\\\\', '/');\nconst stamp = (filename: string) => {\n try {\n return statSync(filename).mtimeMs;\n } catch {\n return -1;\n }\n};\n\nfunction projectFor(filename: string) {\n const config = ts.findConfigFile(dirname(filename), ts.sys.fileExists);\n guard config else {\n throw new Error('Typed Twill linting requires a tsconfig.json.');\n }\n let cached = projects.get(config);\n if (\n cached &&\n cached.project.configFiles.some((file) => cached!.times.get(file) !== stamp(file))\n ) {\n cached.project.dispose();\n projects.delete(config);\n cached = undefined;\n }\n if (!cached) {\n cached = { project: new TwillProject(config), times: new Map() };\n for (const file of cached.project.configFiles) cached.times.set(file, stamp(file));\n if (projects.size >= 8) {\n const oldest = projects.keys().next().value!;\n projects.get(oldest)!.project.dispose();\n projects.delete(oldest);\n }\n projects.set(config, cached);\n }\n for (const file of cached.project.sourceFiles()) {\n const time = stamp(file);\n if (cached.times.has(file) && cached.times.get(file) !== time) cached.project.refresh(file);\n cached.times.set(file, time);\n }\n return cached.project;\n}\n\nfunction originalOffset(state: State, offset: number) {\n const position = state.generated!.mapPosition(offset);\n const point = originalPosition(state.result!, position.line, position.column);\n return point.line == null || point.column == null\n ? undefined\n : state.original.mapOffset(point.line, point.column);\n}\nfunction fix(state: State, value: Rule.Fix | undefined): Rule.Fix | undefined {\n if (!value) return undefined;\n const [start, end] = value.range;\n const offset = originalOffset(state, start);\n if (offset === undefined) return undefined;\n const position = state.original.mapPosition(offset);\n const generated = generatedPosition(state.result!, position.line, position.column);\n if (\n generated.line == null ||\n generated.column == null ||\n state.generated!.mapOffset(generated.line, generated.column) !== start ||\n state.source.slice(offset, offset + end - start) !== state.result!.code.slice(start, end)\n )\n return undefined;\n return { range: [offset, offset + end - start], text: value.text };\n}\n\nconst processor: Linter.Processor = {\n supportsAutofix: true,\n preprocess(source, filename) {\n filename = normalize(filename);\n const state: State = {\n source,\n filename,\n original: new SourcePositions(source),\n };\n pending.set(filename, state);\n try {\n // A project supplies the same inherited JSX settings as builds/checking.\n const config = ts.findConfigFile(dirname(filename), ts.sys.fileExists);\n state.result = transform(source, {\n ...loadConfig(config ? dirname(config) : dirname(filename)),\n filename,\n });\n state.generated = new SourcePositions(state.result.code);\n return [\n {\n text: state.result.code,\n filename: filename.endsWith('.twillx') ? 'source.tsx' : 'source.ts',\n },\n ];\n } catch (error) {\n state.error = error;\n return [];\n }\n },\n postprocess(lists, filename) {\n filename = normalize(filename);\n const state = pending.get(filename)!;\n pending.delete(filename);\n state.project?.update(filename);\n if (state.error)\n return [\n {\n ruleId: null,\n severity: 2,\n fatal: true,\n message: state.error.message,\n line: state.error.line ?? 1,\n column: (state.error.column ?? 0) + 1,\n },\n ];\n return lists.flat().flatMap((message) => {\n if (!message.line || !message.column) return [message];\n const generated = state.generated!;\n const start = generated.getPositionOfLineAndCharacter(message.line - 1, message.column - 1);\n let offset = originalOffset(state, start);\n let expressionSwitch = false;\n let expressionKeywordLength = 6;\n if (\n message.ruleId === '@typescript-eslint/switch-exhaustiveness-check' &&\n state.project &&\n (offset === undefined || state.result!.code[start] !== state.source[offset])\n ) {\n // Expression subjects use a generated once-evaluated local. Report the\n // missing union arm on the source-backed switch/match keyword instead.\n const file = state.project.service.getProgram()?.getSourceFile(virtualFilename(filename));\n const visit = (node: ts.Node) => {\n if (ts.isSwitchStatement(node) && node.expression.getStart() === start) {\n const sourceStart = originalOffset(state, node.getStart());\n if (\n sourceStart !== undefined &&\n /^(?:switch|match)\\b/.test(state.source.slice(sourceStart))\n ) {\n offset = sourceStart;\n expressionSwitch = true;\n expressionKeywordLength = state.source.startsWith('match', sourceStart) ? 5 : 6;\n }\n } else if (start >= node.getFullStart() && start < node.end) ts.forEachChild(node, visit);\n };\n if (file) visit(file);\n }\n if (\n offset === undefined ||\n (!expressionSwitch &&\n start < state.result!.code.length &&\n state.result!.code[start] !== state.source[offset])\n )\n return [];\n const position = state.original.getLineAndCharacterOfPosition(offset);\n const endOffset = expressionSwitch\n ? offset + expressionKeywordLength\n : message.endLine && message.endColumn\n ? originalOffset(\n state,\n generated.getPositionOfLineAndCharacter(message.endLine - 1, message.endColumn - 1),\n )\n : undefined;\n const end =\n endOffset === undefined\n ? undefined\n : state.original.getLineAndCharacterOfPosition(endOffset);\n return [\n {\n ...message,\n line: position.line + 1,\n column: position.character + 1,\n endLine: end && end.line + 1,\n endColumn: end && end.character + 1,\n fix: expressionSwitch ? undefined : fix(state, message.fix),\n // Native statement-case suggestions introduce blocks/value labels that\n // are invalid in a Twill switch expression. Keep the diagnostic.\n suggestions: expressionSwitch\n ? []\n : message.suggestions?.flatMap((suggestion) => {\n const mapped = fix(state, suggestion.fix);\n return mapped ? [{ ...suggestion, fix: mapped }] : [];\n }),\n },\n ];\n });\n },\n};\n\nconst virtualParser = {\n meta: { name: '@swiftuijs/twill-linter/parser', version: metadata.version },\n parseForESLint(code: string, options: any) {\n const original = normalize(options.filePath ?? '').replace(/\\/(?:\\d+_)?source\\.tsx?$/, '');\n const state = pending.get(original);\n if (!state) return parser.parseForESLint(code, options);\n const typed = options.projectService || options.project || options.programs;\n if (!typed) return parser.parseForESLint(code, options);\n const project = projectFor(original);\n project.update(original, state.source);\n state.project = project;\n return parser.parseForESLint(code, {\n ...options,\n filePath: virtualFilename(original),\n projectService: false,\n project: undefined,\n programs: [project.service.getProgram()!],\n });\n },\n};\n\nconst plugin: ESLint.Plugin & { configs: Record<string, Linter.Config[]> } = {\n meta: { name: '@swiftuijs/twill-linter', version: metadata.version },\n processors: { twill: processor },\n configs: {} as Record<string, Linter.Config[]>,\n};\nconst files = ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'];\nfunction configuration(typed: boolean): Linter.Config[] {\n const recommended = typescriptPlugin.configs[\n typed ? 'flat/recommended-type-checked' : 'flat/recommended'\n ] as Linter.Config[];\n return [\n js.configs.recommended,\n { files: ['**/*.twill', '**/*.twillx'], plugins: { twill: plugin }, processor: 'twill/twill' },\n ...recommended.map((config) => ({\n ...config,\n files,\n languageOptions: {\n ...config.languageOptions,\n parser: virtualParser,\n ...(typed ? { parserOptions: { projectService: true } } : {}),\n },\n })),\n ...(typed\n ? [\n {\n files,\n rules: {\n '@typescript-eslint/switch-exhaustiveness-check': [\n 'error',\n { considerDefaultExhaustiveForUnions: false },\n ] satisfies Linter.RuleEntry,\n },\n },\n ]\n : []),\n ];\n}\nplugin.configs.recommended = configuration(false);\nplugin.configs.recommendedTypeChecked = configuration(true);\nexport const configs: Record<string, Linter.Config[]> = plugin.configs;\nexport const processors: Record<string, Linter.Processor> = { twill: processor };\nexport function disposeProjects() {\n for (const { project } of projects.values()) project.dispose();\n projects.clear();\n pending.clear();\n}\nexport default plugin;\n"],"mappings":"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"}
@@ -15,3 +15,4 @@ export declare class SourcePositions {
15
15
  };
16
16
  mapOffset(line: number, column: number): number;
17
17
  }
18
+ //# sourceMappingURL=positions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"positions.d.ts","mappings":"AAAA;AACA,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;gBAEF,IAAI,EAAE,MAAM;IAOxB,OAAO,CAAC,QAAQ;IAWhB,6BAA6B,CAAC,MAAM,EAAE,MAAM;;;;IAI5C,6BAA6B,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAI1D,WAAW,CAAC,MAAM,EAAE,MAAM;;;;IAK1B,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM","names":[],"ignoreList":[],"sources":["../src/positions.twill"],"sourcesContent":["/** Source-map lines use LF; ESLint positions also count CR and Unicode separators. */\nexport class SourcePositions {\n private native = [0];\n private mapped = [0];\n\n constructor(text: string) {\n const breaks = /\\r\\n|[\\n\\r\\u2028\\u2029]/g;\n while (breaks.exec(text)) this.native.push(breaks.lastIndex);\n for (let index = 0; index < text.length; index++)\n if (text[index] === '\\n') this.mapped.push(index + 1);\n }\n\n private position(starts: number[], offset: number) {\n let low = 0;\n let high = starts.length;\n while (low + 1 < high) {\n const middle = (low + high) >>> 1;\n if (starts[middle]! <= offset) low = middle;\n else high = middle;\n }\n return { line: low, character: offset - starts[low]! };\n }\n\n getLineAndCharacterOfPosition(offset: number) {\n return this.position(this.native, offset);\n }\n\n getPositionOfLineAndCharacter(line: number, column: number) {\n return this.native[line]! + column;\n }\n\n mapPosition(offset: number) {\n const position = this.position(this.mapped, offset);\n return { line: position.line + 1, column: position.character };\n }\n\n mapOffset(line: number, column: number) {\n return this.mapped[line - 1]! + column;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swiftuijs/twill-linter",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -24,11 +24,10 @@
24
24
  "@typescript-eslint/eslint-plugin": "^8.71.0",
25
25
  "@eslint/js": "^9.39.5",
26
26
  "typescript": "5.9.3",
27
- "@swiftuijs/twill": "0.1.2"
27
+ "@swiftuijs/twill": "0.3.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "vite": "^8.3.2",
31
- "vite-plugin-dts": "^5.1.2",
32
31
  "@types/node": "^26.6.4",
33
32
  "typescript": "5.9.3",
34
33
  "eslint": "^10.12.0",
@@ -61,8 +60,8 @@
61
60
  ],
62
61
  "description": "ESLint flat configurations for Twill with TypeScript rules, original-source diagnostics and safe fixes.",
63
62
  "scripts": {
64
- "build": "vite build",
65
- "typecheck": "tsc --noEmit -p tsconfig.json",
63
+ "build": "vite build && twill declarations -p tsconfig.build.json -o dist",
64
+ "typecheck": "twill check -p tsconfig.json",
66
65
  "test:unit": "vitest run",
67
66
  "test:watch": "vitest",
68
67
  "test:coverage": "vitest run --coverage",