@r_masseater/oxlint-typed-rules 0.0.2 → 0.0.3

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.
@@ -0,0 +1,151 @@
1
+ import { Array, Cause, Effect, Exit, Layer, ManagedRuntime, Option, Predicate, Record, String, pipe } from "effect";
2
+ import { SyntaxKind } from "typescript-7/ast";
3
+ import { FetchHttpClient } from "effect/http";
4
+ import { OtlpSerialization, OtlpTracer } from "effect/observability";
5
+ import { createFileSystemLayer } from "typescript-7/fs";
6
+ import { API } from "typescript-7/sync";
7
+ // src/parser-services.ts
8
+ const covers = (node, [start, end]) => node.getStart() <= start && node.end >= end;
9
+ const spans = (node, [start, end]) => node.getStart() === start && node.end === end;
10
+ const innermostSpanning = (sourceFile, range) => {
11
+ const visit = (node, found) => {
12
+ if (!covers(node, range)) return found;
13
+ const matched = Option.liftPredicate(node, (candidate) => spans(candidate, range)).pipe(Option.getOrElse(() => found));
14
+ return Option.fromNullishOr(node.forEachChild((child) => Option.liftPredicate(visit(child, matched), (deeper) => deeper !== matched).pipe(Option.getOrUndefined))).pipe(Option.getOrElse(() => matched));
15
+ };
16
+ return visit(sourceFile, sourceFile);
17
+ };
18
+ const toMappedNode = ({ context }, node) => {
19
+ const start = node.getStart();
20
+ return {
21
+ type: SyntaxKind[node.kind],
22
+ range: [start, node.end],
23
+ loc: {
24
+ start: context.sourceCode.getLocFromIndex(start),
25
+ end: context.sourceCode.getLocFromIndex(node.end)
26
+ }
27
+ };
28
+ };
29
+ const createParserServices = (file) => {
30
+ const toTs = /* @__PURE__ */ new WeakMap();
31
+ return {
32
+ program: file.program,
33
+ esTreeNodeToTSNodeMap: { get: (node) => Option.fromNullishOr(toTs.get(node)).pipe(Option.getOrElse(() => {
34
+ const found = innermostSpanning(file.sourceFile, node.range);
35
+ toTs.set(node, found);
36
+ return found;
37
+ })) },
38
+ tsNodeToESTreeNodeMap: { get: (node) => Option.liftPredicate(node, (candidate) => candidate.getSourceFile().fileName === file.sourceFile.fileName).pipe(Option.map((candidate) => toMappedNode(file, candidate)), Option.getOrUndefined) }
39
+ };
40
+ };
41
+ // src/program.ts
42
+ const tracing = OtlpTracer.layerFromConfig({ resource: { serviceName: "oxlint-typed-rules" } }).pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer));
43
+ const runtime = ManagedRuntime.make(tracing);
44
+ const api = new API();
45
+ const snapshots = /* @__PURE__ */ new Map([[api, api.createSnapshot()]]);
46
+ const currentSnapshot = () => Option.fromNullishOr(snapshots.get(api)).pipe(Option.getOrElse(() => {
47
+ const created = api.createSnapshot();
48
+ snapshots.set(api, created);
49
+ return created;
50
+ }));
51
+ process.once("beforeExit", () => {
52
+ api.close();
53
+ Effect.runFork(runtime.disposeEffect);
54
+ });
55
+ const isOwnSourceFile = (fileName) => !String.includes("/node_modules/")(fileName) && !String.endsWith(".d.ts")(fileName);
56
+ const compatSymbols = /* @__PURE__ */ new WeakMap();
57
+ const toCompatSymbol = (project, native) => Option.fromNullishOr(compatSymbols.get(native)).pipe(Option.getOrElse(() => {
58
+ const created = {
59
+ native,
60
+ flags: native.flags,
61
+ name: native.name,
62
+ getName: () => native.name,
63
+ getFlags: () => native.flags,
64
+ getDeclarations: () => pipe(native.declarations, Array.flatMap((handle) => Option.fromNullishOr(handle.resolve(project)).pipe(Option.toArray)))
65
+ };
66
+ compatSymbols.set(native, created);
67
+ return created;
68
+ }));
69
+ const compatChecker = (project, checker) => ({
70
+ getSymbolAtLocation: (node) => Option.fromNullishOr(checker.getSymbolAtLocation(node)).pipe(Option.map((native) => toCompatSymbol(project, native)), Option.getOrUndefined),
71
+ getAliasedSymbol: (symbol) => toCompatSymbol(project, checker.getAliasedSymbol(symbol.native))
72
+ });
73
+ const ownSourceFiles = (program) => pipe(program.getSourceFileNames(), Array.filter(isOwnSourceFile), Array.flatMap((fileName) => Option.fromNullishOr(program.getSourceFile(fileName)).pipe(Option.toArray)));
74
+ const compatPrograms = /* @__PURE__ */ new WeakMap();
75
+ const toCompatProgram = (project) => Option.fromNullishOr(compatPrograms.get(project.program)).pipe(Option.getOrElse(() => {
76
+ const { program } = project;
77
+ const checker = compatChecker(project, project.checker);
78
+ const ownFiles = [];
79
+ const created = {
80
+ getSourceFile: (fileName) => program.getSourceFile(fileName),
81
+ getSourceFiles: () => Array.head(ownFiles).pipe(Option.getOrElse(() => {
82
+ const files = ownSourceFiles(program);
83
+ ownFiles.push(files);
84
+ return files;
85
+ })),
86
+ getTypeChecker: () => checker
87
+ };
88
+ compatPrograms.set(program, created);
89
+ return created;
90
+ }));
91
+ const matchesText = (project, overlay) => Option.fromNullishOr(project.program.getSourceFile(overlay.fileName)).pipe(Option.exists((sourceFile) => sourceFile.text === overlay.text));
92
+ const projectsByDirectory = /* @__PURE__ */ new WeakMap();
93
+ const directoryOf = (fileName) => String.replace(/[\\/][^\\/]*$/u, "")(fileName);
94
+ const directoryProjects = (snapshot) => Option.fromNullishOr(projectsByDirectory.get(snapshot)).pipe(Option.getOrElse(() => {
95
+ const created = /* @__PURE__ */ new Map();
96
+ projectsByDirectory.set(snapshot, created);
97
+ return created;
98
+ }));
99
+ const defaultProject = (snapshot, fileName) => Option.fromNullishOr(snapshot.getDefaultProjectForFile(fileName)).pipe(Option.map((project) => {
100
+ directoryProjects(snapshot).set(directoryOf(fileName), project);
101
+ return project;
102
+ }));
103
+ const currentProject = (overlay) => {
104
+ const snapshot = currentSnapshot();
105
+ return Option.fromNullishOr(directoryProjects(snapshot).get(directoryOf(overlay.fileName))).pipe(Option.filter((project) => matchesText(project, overlay)), Option.orElse(() => defaultProject(snapshot, overlay.fileName).pipe(Option.filter((project) => matchesText(project, overlay)))));
106
+ };
107
+ const openFile = (overlay) => Effect.sync(() => {
108
+ const updated = currentSnapshot().update({
109
+ openFiles: [overlay.fileName],
110
+ fileSystem: createFileSystemLayer([[overlay.fileName, overlay.text]])
111
+ });
112
+ snapshots.set(api, updated);
113
+ return defaultProject(updated, overlay.fileName);
114
+ }).pipe(Effect.withSpan("oxlint-typed-rules.open-file", { attributes: { fileName: overlay.fileName } }));
115
+ const projectOfFile = (overlay) => Effect.suspend(() => Option.match(currentProject(overlay), {
116
+ onSome: Effect.succeedSome,
117
+ onNone: () => openFile(overlay)
118
+ }));
119
+ const programFor = (overlay) => Exit.match(runtime.runSyncExit(projectOfFile(overlay)), {
120
+ onSuccess: Option.map(toCompatProgram),
121
+ onFailure: (cause) => {
122
+ throw Cause.squash(cause);
123
+ }
124
+ });
125
+ // src/typed-rule.ts
126
+ const isTypedRule = (rule) => Predicate.hasProperty(rule, "meta") && Predicate.hasProperty(rule, "create") && Predicate.isFunction(rule.create);
127
+ const typedContext = (context, parserServices) => ({
128
+ filename: context.filename,
129
+ cwd: context.cwd,
130
+ options: context.options,
131
+ languageOptions: context.languageOptions,
132
+ report: (diagnostic) => {
133
+ context.report(diagnostic);
134
+ },
135
+ sourceCode: { parserServices }
136
+ });
137
+ const withTypeInformation = (rule) => ({
138
+ meta: rule.meta,
139
+ create: (context) => programFor({
140
+ fileName: context.filename,
141
+ text: context.sourceCode.text
142
+ }).pipe(Option.flatMap((program) => Option.fromNullishOr(program.getSourceFile(context.filename)).pipe(Option.map((sourceFile) => rule.create(typedContext(context, createParserServices({
143
+ context,
144
+ program,
145
+ sourceFile
146
+ })))))), Option.getOrElse(() => ({})))
147
+ });
148
+ const typedRules = (rules) => pipe(rules, Record.filter(isTypedRule), Record.map(withTypeInformation));
149
+ export { withTypeInformation as n, typedRules as t };
150
+
151
+ //# sourceMappingURL=typed-rule-ClHYwCu8.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typed-rule-ClHYwCu8.mjs","names":["Str","Arr"],"sources":["../src/parser-services.ts","../src/program.ts","../src/typed-rule.ts"],"sourcesContent":["import { Option } from \"effect\";\nimport { SyntaxKind } from \"typescript-7/ast\";\nimport type { Node, SourceFile } from \"typescript-7/ast\";\nimport type { Context, Location, Range } from \"vite-plus/lint/plugins\";\n\nimport type { CompatProgram } from \"./program.ts\";\n\ninterface RangedNode {\n readonly range: Readonly<Range>;\n}\n\ninterface MappedNode {\n readonly type: string;\n readonly range: Readonly<Range>;\n readonly loc: Location;\n}\n\ninterface ParserServices {\n readonly program: CompatProgram;\n readonly esTreeNodeToTSNodeMap: { readonly get: (node: RangedNode) => Node };\n readonly tsNodeToESTreeNodeMap: {\n readonly get: (node: Node) => MappedNode | undefined;\n };\n}\n\ninterface FileProgram {\n readonly context: Context;\n readonly program: CompatProgram;\n readonly sourceFile: SourceFile;\n}\n\nconst covers = (node: Node, [start, end]: Readonly<Range>): boolean =>\n node.getStart() <= start && node.end >= end;\n\nconst spans = (node: Node, [start, end]: Readonly<Range>): boolean =>\n node.getStart() === start && node.end === end;\n\nconst innermostSpanning = (sourceFile: SourceFile, range: Readonly<Range>): Node => {\n const visit = (node: Node, found: Node): Node => {\n if (!covers(node, range)) {\n return found;\n }\n const matched = Option.liftPredicate(node, (candidate) => spans(candidate, range)).pipe(\n Option.getOrElse(() => found),\n );\n return Option.fromNullishOr(\n node.forEachChild((child) =>\n Option.liftPredicate(visit(child, matched), (deeper) => deeper !== matched).pipe(\n Option.getOrUndefined,\n ),\n ),\n ).pipe(Option.getOrElse(() => matched));\n };\n return visit(sourceFile, sourceFile);\n};\n\nconst toMappedNode = ({ context }: FileProgram, node: Node): MappedNode => {\n const start = node.getStart();\n return {\n type: SyntaxKind[node.kind],\n range: [start, node.end],\n loc: {\n start: context.sourceCode.getLocFromIndex(start),\n end: context.sourceCode.getLocFromIndex(node.end),\n },\n };\n};\n\nconst createParserServices = (file: FileProgram): ParserServices => {\n const toTs = new WeakMap<RangedNode, Node>();\n return {\n program: file.program,\n esTreeNodeToTSNodeMap: {\n get: (node) =>\n Option.fromNullishOr(toTs.get(node)).pipe(\n Option.getOrElse(() => {\n const found = innermostSpanning(file.sourceFile, node.range);\n toTs.set(node, found);\n return found;\n }),\n ),\n },\n tsNodeToESTreeNodeMap: {\n get: (node) =>\n Option.liftPredicate(\n node,\n (candidate) => candidate.getSourceFile().fileName === file.sourceFile.fileName,\n ).pipe(\n Option.map((candidate) => toMappedNode(file, candidate)),\n Option.getOrUndefined,\n ),\n },\n };\n};\n\nexport { createParserServices };\nexport type { FileProgram, MappedNode, ParserServices, RangedNode };\n","import {\n Array as Arr,\n Cause,\n Effect,\n Exit,\n Layer,\n ManagedRuntime,\n Option,\n String as Str,\n pipe,\n} from \"effect\";\nimport { FetchHttpClient } from \"effect/http\";\nimport { OtlpSerialization, OtlpTracer } from \"effect/observability\";\nimport type { Node, SourceFile } from \"typescript-7/ast\";\nimport { createFileSystemLayer } from \"typescript-7/fs\";\nimport { API } from \"typescript-7/sync\";\nimport type {\n Checker,\n Program as NativeProgram,\n Project,\n Snapshot,\n Symbol as NativeSymbol,\n SymbolFlags,\n} from \"typescript-7/sync\";\n\nconst tracing = OtlpTracer.layerFromConfig({\n resource: { serviceName: \"oxlint-typed-rules\" },\n}).pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer));\n\nconst runtime = ManagedRuntime.make(tracing);\n\ninterface Overlay {\n readonly fileName: string;\n readonly text: string;\n}\n\ninterface CompatSymbol {\n readonly native: NativeSymbol;\n readonly flags: SymbolFlags;\n readonly name: string;\n readonly getName: () => string;\n readonly getFlags: () => SymbolFlags;\n readonly getDeclarations: () => readonly Node[];\n}\n\ninterface CompatChecker {\n readonly getSymbolAtLocation: (node: Node) => CompatSymbol | undefined;\n readonly getAliasedSymbol: (symbol: CompatSymbol) => CompatSymbol;\n}\n\ninterface CompatProgram {\n readonly getSourceFile: (fileName: string) => SourceFile | undefined;\n readonly getSourceFiles: () => readonly SourceFile[];\n readonly getTypeChecker: () => CompatChecker;\n}\n\nconst api = new API();\n\nconst snapshots = new Map<API, Snapshot>([[api, api.createSnapshot()]]);\n\nconst currentSnapshot = (): Snapshot =>\n Option.fromNullishOr(snapshots.get(api)).pipe(\n Option.getOrElse(() => {\n const created = api.createSnapshot();\n snapshots.set(api, created);\n return created;\n }),\n );\n\nprocess.once(\"beforeExit\", () => {\n api.close();\n Effect.runFork(runtime.disposeEffect);\n});\n\nconst isOwnSourceFile = (fileName: string): boolean =>\n !Str.includes(\"/node_modules/\")(fileName) && !Str.endsWith(\".d.ts\")(fileName);\n\nconst compatSymbols = new WeakMap<NativeSymbol, CompatSymbol>();\n\nconst toCompatSymbol = (project: Project, native: NativeSymbol): CompatSymbol =>\n Option.fromNullishOr(compatSymbols.get(native)).pipe(\n Option.getOrElse(() => {\n const created: CompatSymbol = {\n native,\n flags: native.flags,\n name: native.name,\n getName: () => native.name,\n getFlags: () => native.flags,\n getDeclarations: () =>\n pipe(\n native.declarations,\n Arr.flatMap((handle) =>\n Option.fromNullishOr(handle.resolve(project)).pipe(Option.toArray),\n ),\n ),\n };\n compatSymbols.set(native, created);\n return created;\n }),\n );\n\nconst compatChecker = (project: Project, checker: Checker): CompatChecker => ({\n getSymbolAtLocation: (node) =>\n Option.fromNullishOr(checker.getSymbolAtLocation(node)).pipe(\n Option.map((native) => toCompatSymbol(project, native)),\n Option.getOrUndefined,\n ),\n getAliasedSymbol: (symbol) => toCompatSymbol(project, checker.getAliasedSymbol(symbol.native)),\n});\n\nconst ownSourceFiles = (program: NativeProgram): readonly SourceFile[] =>\n pipe(\n program.getSourceFileNames(),\n Arr.filter(isOwnSourceFile),\n Arr.flatMap((fileName) =>\n Option.fromNullishOr(program.getSourceFile(fileName)).pipe(Option.toArray),\n ),\n );\n\nconst compatPrograms = new WeakMap<NativeProgram, CompatProgram>();\n\nconst toCompatProgram = (project: Project): CompatProgram =>\n Option.fromNullishOr(compatPrograms.get(project.program)).pipe(\n Option.getOrElse(() => {\n const { program } = project;\n const checker = compatChecker(project, project.checker);\n const ownFiles: (readonly SourceFile[])[] = [];\n const created: CompatProgram = {\n getSourceFile: (fileName) => program.getSourceFile(fileName),\n getSourceFiles: () =>\n Arr.head(ownFiles).pipe(\n Option.getOrElse(() => {\n const files = ownSourceFiles(program);\n ownFiles.push(files);\n return files;\n }),\n ),\n getTypeChecker: () => checker,\n };\n compatPrograms.set(program, created);\n return created;\n }),\n );\n\nconst matchesText = (project: Project, overlay: Overlay): boolean =>\n Option.fromNullishOr(project.program.getSourceFile(overlay.fileName)).pipe(\n Option.exists((sourceFile) => sourceFile.text === overlay.text),\n );\n\nconst projectsByDirectory = new WeakMap<Snapshot, Map<string, Project>>();\n\nconst directoryOf = (fileName: string): string => Str.replace(/[\\\\/][^\\\\/]*$/u, \"\")(fileName);\n\nconst directoryProjects = (snapshot: Snapshot): Map<string, Project> =>\n Option.fromNullishOr(projectsByDirectory.get(snapshot)).pipe(\n Option.getOrElse(() => {\n const created = new Map<string, Project>();\n projectsByDirectory.set(snapshot, created);\n return created;\n }),\n );\n\nconst defaultProject = (snapshot: Snapshot, fileName: string): Option.Option<Project> =>\n Option.fromNullishOr(snapshot.getDefaultProjectForFile(fileName)).pipe(\n Option.map((project) => {\n directoryProjects(snapshot).set(directoryOf(fileName), project);\n return project;\n }),\n );\n\nconst currentProject = (overlay: Overlay): Option.Option<Project> => {\n const snapshot = currentSnapshot();\n return Option.fromNullishOr(directoryProjects(snapshot).get(directoryOf(overlay.fileName))).pipe(\n Option.filter((project) => matchesText(project, overlay)),\n Option.orElse(() =>\n defaultProject(snapshot, overlay.fileName).pipe(\n Option.filter((project) => matchesText(project, overlay)),\n ),\n ),\n );\n};\n\nconst openFile = (overlay: Overlay): Effect.Effect<Option.Option<Project>> =>\n Effect.sync(() => {\n const updated = currentSnapshot().update({\n openFiles: [overlay.fileName],\n fileSystem: createFileSystemLayer([[overlay.fileName, overlay.text]]),\n });\n snapshots.set(api, updated);\n return defaultProject(updated, overlay.fileName);\n }).pipe(\n Effect.withSpan(\"oxlint-typed-rules.open-file\", {\n attributes: { fileName: overlay.fileName },\n }),\n );\n\nconst projectOfFile = (overlay: Overlay): Effect.Effect<Option.Option<Project>> =>\n Effect.suspend(() =>\n Option.match(currentProject(overlay), {\n onSome: Effect.succeedSome,\n onNone: () => openFile(overlay),\n }),\n );\n\nconst programFor = (overlay: Overlay): Option.Option<CompatProgram> =>\n Exit.match(runtime.runSyncExit(projectOfFile(overlay)), {\n onSuccess: Option.map(toCompatProgram),\n onFailure: (cause) => {\n throw Cause.squash(cause);\n },\n });\n\nexport type { CompatChecker, CompatProgram, CompatSymbol, Overlay };\nexport { programFor };\n","import { Option, Predicate, Record, pipe } from \"effect\";\nimport type { Context, CreateRule, RuleMeta, Visitor } from \"vite-plus/lint/plugins\";\n\nimport { createParserServices } from \"./parser-services.ts\";\nimport type { ParserServices } from \"./parser-services.ts\";\nimport { programFor } from \"./program.ts\";\n\ninterface TypedContext {\n readonly filename: string;\n readonly cwd: string;\n readonly options: readonly unknown[];\n readonly languageOptions: Readonly<Context[\"languageOptions\"]>;\n readonly report: Context[\"report\"];\n readonly sourceCode: { readonly parserServices: ParserServices };\n}\n\ninterface TypedRule {\n readonly meta: RuleMeta;\n readonly create: (context: TypedContext) => Visitor;\n}\n\nconst isTypedRule = <Rule>(rule: Rule): rule is Rule & TypedRule =>\n Predicate.hasProperty(rule, \"meta\") &&\n Predicate.hasProperty(rule, \"create\") &&\n Predicate.isFunction(rule.create);\n\nconst typedContext = (context: Context, parserServices: ParserServices): TypedContext => ({\n filename: context.filename,\n cwd: context.cwd,\n options: context.options,\n languageOptions: context.languageOptions,\n report: (diagnostic) => {\n context.report(diagnostic);\n },\n sourceCode: { parserServices },\n});\n\nconst withTypeInformation = (rule: TypedRule): CreateRule => ({\n meta: rule.meta,\n create: (context) =>\n programFor({ fileName: context.filename, text: context.sourceCode.text }).pipe(\n Option.flatMap((program) =>\n Option.fromNullishOr(program.getSourceFile(context.filename)).pipe(\n Option.map((sourceFile) =>\n rule.create(\n typedContext(context, createParserServices({ context, program, sourceFile })),\n ),\n ),\n ),\n ),\n Option.getOrElse(() => ({})),\n ),\n});\n\nconst typedRules = (\n rules: Record.ReadonlyRecord<string, unknown>,\n): Record.ReadonlyRecord<string, CreateRule> =>\n pipe(rules, Record.filter(isTypedRule), Record.map(withTypeInformation));\n\nexport type { TypedContext, TypedRule };\nexport { typedRules, withTypeInformation };\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r_masseater/oxlint-typed-rules",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Run ESLint rules that need type information as oxlint JS plugins, starting with TanStack Start's React Server Component rules.",
5
5
  "keywords": [
6
6
  "eslint",
@@ -40,21 +40,33 @@
40
40
  "provenance": true
41
41
  },
42
42
  "dependencies": {
43
- "@effect/platform-node": "4.0.7",
44
43
  "effect": "4.0.7",
45
- "typescript-6": "npm:typescript@6.0.3"
44
+ "typescript-7": "npm:typescript@7.1.0-beta"
46
45
  },
47
46
  "devDependencies": {
47
+ "@arethetypeswrong/core": "0.18.5",
48
+ "@effect/platform-node": "4.0.7",
49
+ "@size-limit/file": "14.3.0",
48
50
  "@tanstack/eslint-plugin-start": "0.1.0",
49
51
  "@types/node": "26.6.6",
50
52
  "@typescript-eslint/utils": "^8.72.1",
53
+ "publint": "0.3.25",
54
+ "size-limit": "14.3.0",
55
+ "typescript": "6.0.3",
51
56
  "vite-plus": "1.4.0"
52
57
  },
58
+ "size-limit": [
59
+ {
60
+ "path": "dist/*.mjs",
61
+ "limit": "23995 B"
62
+ }
63
+ ],
53
64
  "engines": {
54
65
  "node": ">=26"
55
66
  },
56
67
  "scripts": {
57
68
  "build": "vp pack",
58
- "test": "vp test run"
69
+ "test": "vp test run",
70
+ "size": "vp run build && size-limit"
59
71
  }
60
72
  }
@@ -1,155 +0,0 @@
1
- import { Cause, Effect, Exit, Layer, ManagedRuntime, Option, Path, Predicate, Record, pipe } from "effect";
2
- import ts from "typescript-6";
3
- import { NodeServices } from "@effect/platform-node";
4
- import { FetchHttpClient } from "effect/http";
5
- import { OtlpSerialization, OtlpTracer } from "effect/observability";
6
- // src/parser-services.ts
7
- const covers = (node, sourceFile, [start, end]) => node.getStart(sourceFile) <= start && node.end >= end;
8
- const spans = (node, sourceFile, [start, end]) => node.getStart(sourceFile) === start && node.end === end;
9
- const innermostSpanning = (sourceFile, range) => {
10
- const visit = (node, found) => {
11
- if (!covers(node, sourceFile, range)) return found;
12
- const matched = Option.liftPredicate(node, (candidate) => spans(candidate, sourceFile, range)).pipe(Option.getOrElse(() => found));
13
- return Option.fromNullishOr(ts.forEachChild(node, (child) => Option.liftPredicate(visit(child, matched), (deeper) => deeper !== matched).pipe(Option.getOrUndefined))).pipe(Option.getOrElse(() => matched));
14
- };
15
- return visit(sourceFile, sourceFile);
16
- };
17
- const toMappedNode = ({ context, sourceFile }, node) => {
18
- const start = node.getStart(sourceFile);
19
- return {
20
- type: ts.SyntaxKind[node.kind],
21
- range: [start, node.end],
22
- loc: {
23
- start: context.sourceCode.getLocFromIndex(start),
24
- end: context.sourceCode.getLocFromIndex(node.end)
25
- }
26
- };
27
- };
28
- const createParserServices = (file) => {
29
- const toTs = /* @__PURE__ */ new WeakMap();
30
- return {
31
- program: file.program,
32
- esTreeNodeToTSNodeMap: { get: (node) => Option.fromNullishOr(toTs.get(node)).pipe(Option.getOrElse(() => {
33
- const found = innermostSpanning(file.sourceFile, node.range);
34
- toTs.set(node, found);
35
- return found;
36
- })) },
37
- tsNodeToESTreeNodeMap: { get: (node) => Option.liftPredicate(node, (candidate) => candidate.getSourceFile().fileName === file.sourceFile.fileName).pipe(Option.map((candidate) => toMappedNode(file, candidate)), Option.getOrUndefined) }
38
- };
39
- };
40
- // src/program.ts
41
- const tracing = OtlpTracer.layerFromConfig({ resource: { serviceName: "oxlint-typed-rules" } }).pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer));
42
- const runtime = ManagedRuntime.make(Layer.mergeAll(tracing, NodeServices.layer));
43
- process.once("beforeExit", () => {
44
- Effect.runFork(runtime.disposeEffect);
45
- });
46
- const sourceFiles = /* @__PURE__ */ new Map();
47
- const cachingHost = (options) => {
48
- const host = ts.createCompilerHost(options, true);
49
- return {
50
- ...host,
51
- getSourceFile: (...args) => {
52
- const [fileName, languageVersion] = args;
53
- const key = `${fileName}\0${JSON.stringify(languageVersion)}`;
54
- return Option.fromNullishOr(sourceFiles.get(key)).pipe(Option.orElse(() => Option.fromNullishOr(host.getSourceFile(...args)).pipe(Option.tap((created) => Option.some(sourceFiles.set(key, created))))), Option.getOrUndefined);
55
- }
56
- };
57
- };
58
- const ignoreUnrecoverable = () => {
59
- Option.none();
60
- };
61
- const parseConfig = (configPath) => Option.fromNullishOr(ts.getParsedCommandLineOfConfigFile(configPath, {}, {
62
- ...ts.sys,
63
- onUnRecoverableConfigFileDiagnostic: ignoreUnrecoverable
64
- }));
65
- const overlayHost = (host, overlay) => ({
66
- ...host,
67
- fileExists: (fileName) => fileName === overlay.fileName || host.fileExists(fileName),
68
- readFile: (fileName) => Option.match(Option.liftPredicate(fileName, (name) => name === overlay.fileName), {
69
- onNone: () => host.readFile(fileName),
70
- onSome: () => overlay.text
71
- }),
72
- getSourceFile: (...args) => {
73
- const [fileName, languageVersion] = args;
74
- return Option.match(Option.liftPredicate(fileName, (name) => name === overlay.fileName), {
75
- onNone: () => host.getSourceFile(...args),
76
- onSome: () => ts.createSourceFile(fileName, overlay.text, languageVersion, true)
77
- });
78
- }
79
- });
80
- const withProjectReferences = (config) => Option.match(Option.fromNullishOr(config.projectReferences), {
81
- onNone: () => ({}),
82
- onSome: (projectReferences) => ({ projectReferences })
83
- });
84
- const buildProgram = (configPath) => Effect.sync(() => parseConfig(configPath).pipe(Option.map((config) => ({
85
- config,
86
- program: ts.createProgram({
87
- rootNames: config.fileNames,
88
- options: config.options,
89
- host: cachingHost(config.options),
90
- ...withProjectReferences(config)
91
- })
92
- })))).pipe(Effect.withSpan("oxlint-typed-rules.build-program", { attributes: { configPath } }));
93
- const programs = /* @__PURE__ */ new Map();
94
- const cachedProgram = (configPath) => Option.fromNullishOr(programs.get(configPath)).pipe(Option.match({
95
- onSome: Effect.succeed,
96
- onNone: () => buildProgram(configPath).pipe(Effect.tap((built) => Effect.sync(() => programs.set(configPath, built))))
97
- }));
98
- const overlays = /* @__PURE__ */ new Map();
99
- const buildOverlayProgram = (configPath, base, overlay) => Effect.sync(() => ts.createProgram({
100
- rootNames: [.../* @__PURE__ */ new Set([...base.config.fileNames, overlay.fileName])],
101
- options: base.config.options,
102
- host: overlayHost(cachingHost(base.config.options), overlay),
103
- oldProgram: base.program,
104
- ...withProjectReferences(base.config)
105
- })).pipe(Effect.tap((program) => Effect.sync(() => overlays.set(configPath, program))), Effect.withSpan("oxlint-typed-rules.build-overlay-program", { attributes: { fileName: overlay.fileName } }));
106
- const matchesText = (program, overlay) => Option.fromNullishOr(program.getSourceFile(overlay.fileName)).pipe(Option.exists((sourceFile) => sourceFile.getFullText() === overlay.text));
107
- const programWithText = (configPath, base, overlay) => Option.liftPredicate(base.program, (program) => matchesText(program, overlay)).pipe(Option.orElse(() => Option.fromNullishOr(overlays.get(configPath)).pipe(Option.filter((program) => matchesText(program, overlay)))), Option.match({
108
- onSome: Effect.succeed,
109
- onNone: () => buildOverlayProgram(configPath, base, overlay)
110
- }));
111
- const fileExists = (fileName) => ts.sys.fileExists(fileName);
112
- const programOfFile = (overlay) => Effect.gen(function* findProgram() {
113
- const path = yield* Path.Path;
114
- const configPath = Option.fromNullishOr(ts.findConfigFile(path.dirname(overlay.fileName), fileExists));
115
- return yield* Option.match(configPath, {
116
- onNone: () => Effect.succeedNone,
117
- onSome: (found) => cachedProgram(found).pipe(Effect.flatMap(Option.match({
118
- onNone: () => Effect.succeedNone,
119
- onSome: (base) => programWithText(found, base, overlay).pipe(Effect.asSome)
120
- })))
121
- });
122
- });
123
- const programFor = (overlay) => Exit.match(runtime.runSyncExit(programOfFile(overlay)), {
124
- onSuccess: (program) => program,
125
- onFailure: (cause) => {
126
- throw Cause.squash(cause);
127
- }
128
- });
129
- // src/typed-rule.ts
130
- const isTypedRule = (rule) => Predicate.hasProperty(rule, "meta") && Predicate.hasProperty(rule, "create") && Predicate.isFunction(rule.create);
131
- const typedContext = (context, parserServices) => ({
132
- filename: context.filename,
133
- cwd: context.cwd,
134
- options: context.options,
135
- languageOptions: context.languageOptions,
136
- report: (diagnostic) => {
137
- context.report(diagnostic);
138
- },
139
- sourceCode: { parserServices }
140
- });
141
- const withTypeInformation = (rule) => ({
142
- meta: rule.meta,
143
- create: (context) => programFor({
144
- fileName: context.filename,
145
- text: context.sourceCode.text
146
- }).pipe(Option.flatMap((program) => Option.fromNullishOr(program.getSourceFile(context.filename)).pipe(Option.map((sourceFile) => rule.create(typedContext(context, createParserServices({
147
- context,
148
- program,
149
- sourceFile
150
- })))))), Option.getOrElse(() => ({})))
151
- });
152
- const typedRules = (rules) => pipe(rules, Record.filter(isTypedRule), Record.map(withTypeInformation));
153
- export { withTypeInformation as n, typedRules as t };
154
-
155
- //# sourceMappingURL=typed-rule-Bitx6iIi.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"typed-rule-Bitx6iIi.mjs","names":[],"sources":["../src/parser-services.ts","../src/program.ts","../src/typed-rule.ts"],"sourcesContent":["import { Option } from \"effect\";\nimport ts from \"typescript-6\";\nimport type { Context, Location, Range } from \"vite-plus/lint/plugins\";\n\ninterface RangedNode {\n readonly range: Readonly<Range>;\n}\n\ninterface MappedNode {\n readonly type: string;\n readonly range: Readonly<Range>;\n readonly loc: Location;\n}\n\ninterface ParserServices {\n readonly program: ts.Program;\n readonly esTreeNodeToTSNodeMap: { readonly get: (node: RangedNode) => ts.Node };\n readonly tsNodeToESTreeNodeMap: { readonly get: (node: ts.Node) => MappedNode | undefined };\n}\n\ninterface FileProgram {\n readonly context: Context;\n readonly program: ts.Program;\n readonly sourceFile: ts.SourceFile;\n}\n\nconst covers = (node: ts.Node, sourceFile: ts.SourceFile, [start, end]: Readonly<Range>): boolean =>\n node.getStart(sourceFile) <= start && node.end >= end;\n\nconst spans = (node: ts.Node, sourceFile: ts.SourceFile, [start, end]: Readonly<Range>): boolean =>\n node.getStart(sourceFile) === start && node.end === end;\n\nconst innermostSpanning = (sourceFile: ts.SourceFile, range: Readonly<Range>): ts.Node => {\n const visit = (node: ts.Node, found: ts.Node): ts.Node => {\n if (!covers(node, sourceFile, range)) {\n return found;\n }\n const matched = Option.liftPredicate(node, (candidate) =>\n spans(candidate, sourceFile, range),\n ).pipe(Option.getOrElse(() => found));\n return Option.fromNullishOr(\n ts.forEachChild(node, (child) =>\n Option.liftPredicate(visit(child, matched), (deeper) => deeper !== matched).pipe(\n Option.getOrUndefined,\n ),\n ),\n ).pipe(Option.getOrElse(() => matched));\n };\n return visit(sourceFile, sourceFile);\n};\n\nconst toMappedNode = ({ context, sourceFile }: FileProgram, node: ts.Node): MappedNode => {\n const start = node.getStart(sourceFile);\n return {\n type: ts.SyntaxKind[node.kind],\n range: [start, node.end],\n loc: {\n start: context.sourceCode.getLocFromIndex(start),\n end: context.sourceCode.getLocFromIndex(node.end),\n },\n };\n};\n\nconst createParserServices = (file: FileProgram): ParserServices => {\n const toTs = new WeakMap<RangedNode, ts.Node>();\n return {\n program: file.program,\n esTreeNodeToTSNodeMap: {\n get: (node) =>\n Option.fromNullishOr(toTs.get(node)).pipe(\n Option.getOrElse(() => {\n const found = innermostSpanning(file.sourceFile, node.range);\n toTs.set(node, found);\n return found;\n }),\n ),\n },\n tsNodeToESTreeNodeMap: {\n get: (node) =>\n Option.liftPredicate(\n node,\n (candidate) => candidate.getSourceFile().fileName === file.sourceFile.fileName,\n ).pipe(\n Option.map((candidate) => toMappedNode(file, candidate)),\n Option.getOrUndefined,\n ),\n },\n };\n};\n\nexport { createParserServices };\nexport type { FileProgram, MappedNode, ParserServices, RangedNode };\n","import { NodeServices } from \"@effect/platform-node\";\nimport { Cause, Effect, Exit, Layer, ManagedRuntime, Option, Path } from \"effect\";\nimport { FetchHttpClient } from \"effect/http\";\nimport { OtlpSerialization, OtlpTracer } from \"effect/observability\";\nimport ts from \"typescript-6\";\n\nconst tracing = OtlpTracer.layerFromConfig({\n resource: { serviceName: \"oxlint-typed-rules\" },\n}).pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer));\n\nconst runtime = ManagedRuntime.make(Layer.mergeAll(tracing, NodeServices.layer));\n\nprocess.once(\"beforeExit\", () => {\n Effect.runFork(runtime.disposeEffect);\n});\n\nconst sourceFiles = new Map<string, ts.SourceFile>();\n\nconst cachingHost = (options: ts.CompilerOptions): ts.CompilerHost => {\n const host = ts.createCompilerHost(options, true);\n return {\n ...host,\n getSourceFile: (...args) => {\n const [fileName, languageVersion] = args;\n const key = `${fileName}\\0${JSON.stringify(languageVersion)}`;\n return Option.fromNullishOr(sourceFiles.get(key)).pipe(\n Option.orElse(() =>\n Option.fromNullishOr(host.getSourceFile(...args)).pipe(\n Option.tap((created) => Option.some(sourceFiles.set(key, created))),\n ),\n ),\n Option.getOrUndefined,\n );\n },\n };\n};\n\nconst ignoreUnrecoverable = (): void => {\n Option.none();\n};\n\nconst parseConfig = (configPath: string): Option.Option<ts.ParsedCommandLine> =>\n Option.fromNullishOr(\n ts.getParsedCommandLineOfConfigFile(\n configPath,\n {},\n { ...ts.sys, onUnRecoverableConfigFileDiagnostic: ignoreUnrecoverable },\n ),\n );\n\ninterface Overlay {\n readonly fileName: string;\n readonly text: string;\n}\n\nconst overlayHost = (host: ts.CompilerHost, overlay: Overlay): ts.CompilerHost => ({\n ...host,\n fileExists: (fileName) => fileName === overlay.fileName || host.fileExists(fileName),\n readFile: (fileName) =>\n Option.match(\n Option.liftPredicate(fileName, (name) => name === overlay.fileName),\n {\n onNone: () => host.readFile(fileName),\n onSome: () => overlay.text,\n },\n ),\n getSourceFile: (...args) => {\n const [fileName, languageVersion] = args;\n return Option.match(\n Option.liftPredicate(fileName, (name) => name === overlay.fileName),\n {\n onNone: () => host.getSourceFile(...args),\n onSome: () => ts.createSourceFile(fileName, overlay.text, languageVersion, true),\n },\n );\n },\n});\n\nconst withProjectReferences = (\n config: ts.ParsedCommandLine,\n): Readonly<{ projectReferences?: readonly ts.ProjectReference[] }> =>\n Option.match(Option.fromNullishOr(config.projectReferences), {\n onNone: () => ({}),\n onSome: (projectReferences) => ({ projectReferences }),\n });\n\nconst buildProgram = (configPath: string): Effect.Effect<Option.Option<BaseProgram>> =>\n Effect.sync(() =>\n parseConfig(configPath).pipe(\n Option.map((config) => ({\n config,\n program: ts.createProgram({\n rootNames: config.fileNames,\n options: config.options,\n host: cachingHost(config.options),\n ...withProjectReferences(config),\n }),\n })),\n ),\n ).pipe(Effect.withSpan(\"oxlint-typed-rules.build-program\", { attributes: { configPath } }));\n\ninterface BaseProgram {\n readonly config: ts.ParsedCommandLine;\n readonly program: ts.Program;\n}\n\nconst programs = new Map<string, Option.Option<BaseProgram>>();\n\nconst cachedProgram = (configPath: string): Effect.Effect<Option.Option<BaseProgram>> =>\n Option.fromNullishOr(programs.get(configPath)).pipe(\n Option.match({\n onSome: Effect.succeed,\n onNone: () =>\n buildProgram(configPath).pipe(\n Effect.tap((built) => Effect.sync(() => programs.set(configPath, built))),\n ),\n }),\n );\n\nconst overlays = new Map<string, ts.Program>();\n\nconst buildOverlayProgram = (\n configPath: string,\n base: BaseProgram,\n overlay: Overlay,\n): Effect.Effect<ts.Program> =>\n Effect.sync(() =>\n ts.createProgram({\n rootNames: [...new Set([...base.config.fileNames, overlay.fileName])],\n options: base.config.options,\n host: overlayHost(cachingHost(base.config.options), overlay),\n oldProgram: base.program,\n ...withProjectReferences(base.config),\n }),\n ).pipe(\n Effect.tap((program) => Effect.sync(() => overlays.set(configPath, program))),\n Effect.withSpan(\"oxlint-typed-rules.build-overlay-program\", {\n attributes: { fileName: overlay.fileName },\n }),\n );\n\nconst matchesText = (program: ts.Program, overlay: Overlay): boolean =>\n Option.fromNullishOr(program.getSourceFile(overlay.fileName)).pipe(\n Option.exists((sourceFile) => sourceFile.getFullText() === overlay.text),\n );\n\nconst programWithText = (\n configPath: string,\n base: BaseProgram,\n overlay: Overlay,\n): Effect.Effect<ts.Program> =>\n Option.liftPredicate(base.program, (program) => matchesText(program, overlay)).pipe(\n Option.orElse(() =>\n Option.fromNullishOr(overlays.get(configPath)).pipe(\n Option.filter((program) => matchesText(program, overlay)),\n ),\n ),\n Option.match({\n onSome: Effect.succeed,\n onNone: () => buildOverlayProgram(configPath, base, overlay),\n }),\n );\n\nconst fileExists = (fileName: string): boolean => ts.sys.fileExists(fileName);\n\nconst programOfFile = (\n overlay: Overlay,\n): Effect.Effect<Option.Option<ts.Program>, never, Path.Path> =>\n Effect.gen(function* findProgram() {\n const path = yield* Path.Path;\n const configPath = Option.fromNullishOr(\n ts.findConfigFile(path.dirname(overlay.fileName), fileExists),\n );\n return yield* Option.match(configPath, {\n onNone: () => Effect.succeedNone,\n onSome: (found) =>\n cachedProgram(found).pipe(\n Effect.flatMap(\n Option.match({\n onNone: () => Effect.succeedNone,\n onSome: (base) => programWithText(found, base, overlay).pipe(Effect.asSome),\n }),\n ),\n ),\n });\n });\n\nconst programFor = (overlay: Overlay): Option.Option<ts.Program> =>\n Exit.match(runtime.runSyncExit(programOfFile(overlay)), {\n onSuccess: (program) => program,\n onFailure: (cause) => {\n throw Cause.squash(cause);\n },\n });\n\nexport type { Overlay };\nexport { programFor };\n","import { Option, Predicate, Record, pipe } from \"effect\";\nimport type { Context, CreateRule, RuleMeta, Visitor } from \"vite-plus/lint/plugins\";\n\nimport { createParserServices } from \"./parser-services.ts\";\nimport type { ParserServices } from \"./parser-services.ts\";\nimport { programFor } from \"./program.ts\";\n\ninterface TypedContext {\n readonly filename: string;\n readonly cwd: string;\n readonly options: readonly unknown[];\n readonly languageOptions: Readonly<Context[\"languageOptions\"]>;\n readonly report: Context[\"report\"];\n readonly sourceCode: { readonly parserServices: ParserServices };\n}\n\ninterface TypedRule {\n readonly meta: RuleMeta;\n readonly create: (context: TypedContext) => Visitor;\n}\n\nconst isTypedRule = <Rule>(rule: Rule): rule is Rule & TypedRule =>\n Predicate.hasProperty(rule, \"meta\") &&\n Predicate.hasProperty(rule, \"create\") &&\n Predicate.isFunction(rule.create);\n\nconst typedContext = (context: Context, parserServices: ParserServices): TypedContext => ({\n filename: context.filename,\n cwd: context.cwd,\n options: context.options,\n languageOptions: context.languageOptions,\n report: (diagnostic) => {\n context.report(diagnostic);\n },\n sourceCode: { parserServices },\n});\n\nconst withTypeInformation = (rule: TypedRule): CreateRule => ({\n meta: rule.meta,\n create: (context) =>\n programFor({ fileName: context.filename, text: context.sourceCode.text }).pipe(\n Option.flatMap((program) =>\n Option.fromNullishOr(program.getSourceFile(context.filename)).pipe(\n Option.map((sourceFile) =>\n rule.create(\n typedContext(context, createParserServices({ context, program, sourceFile })),\n ),\n ),\n ),\n ),\n Option.getOrElse(() => ({})),\n ),\n});\n\nconst typedRules = (\n rules: Record.ReadonlyRecord<string, unknown>,\n): Record.ReadonlyRecord<string, CreateRule> =>\n pipe(rules, Record.filter(isTypedRule), Record.map(withTypeInformation));\n\nexport type { TypedContext, TypedRule };\nexport { typedRules, withTypeInformation };\n"],"mappings":"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"}