@r_masseater/oxlint-typed-rules 0.0.2 → 0.1.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.
@@ -0,0 +1,146 @@
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/memo.ts
8
+ const memoizeByKey = (compute) => {
9
+ const cache = /* @__PURE__ */ new WeakMap();
10
+ return (key) => Option.fromNullishOr(cache.get(key)).pipe(Option.getOrElse(() => {
11
+ const computed = compute(key);
12
+ cache.set(key, computed);
13
+ return computed;
14
+ }));
15
+ };
16
+ // src/parser-services.ts
17
+ const covers = (node, [start, end]) => node.getStart() <= start && node.end >= end;
18
+ const spans = (node, [start, end]) => node.getStart() === start && node.end === end;
19
+ const innermostSpanning = (sourceFile, range) => {
20
+ const visit = (node, found) => {
21
+ if (!covers(node, range)) return found;
22
+ const matched = Option.liftPredicate(node, (candidate) => spans(candidate, range)).pipe(Option.getOrElse(() => found));
23
+ return Option.fromNullishOr(node.forEachChild((child) => Option.liftPredicate(visit(child, matched), (deeper) => deeper !== matched).pipe(Option.getOrUndefined))).pipe(Option.getOrElse(() => matched));
24
+ };
25
+ return visit(sourceFile, sourceFile);
26
+ };
27
+ const toMappedNode = ({ context }, node) => {
28
+ const start = node.getStart();
29
+ return {
30
+ type: SyntaxKind[node.kind],
31
+ range: [start, node.end],
32
+ loc: {
33
+ start: context.sourceCode.getLocFromIndex(start),
34
+ end: context.sourceCode.getLocFromIndex(node.end)
35
+ }
36
+ };
37
+ };
38
+ const createParserServices = (file) => ({
39
+ program: file.program,
40
+ esTreeNodeToTSNodeMap: { get: memoizeByKey((node) => innermostSpanning(file.sourceFile, node.range)) },
41
+ tsNodeToESTreeNodeMap: { get: (node) => Option.liftPredicate(node, (candidate) => candidate.getSourceFile().fileName === file.sourceFile.fileName).pipe(Option.map((candidate) => toMappedNode(file, candidate)), Option.getOrUndefined) }
42
+ });
43
+ // src/program.ts
44
+ const tracing = OtlpTracer.layerFromConfig({ resource: { serviceName: "oxlint-typed-rules" } }).pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer));
45
+ const runtime = ManagedRuntime.make(tracing);
46
+ const startApi = () => {
47
+ const started = new API();
48
+ process.once("beforeExit", () => {
49
+ started.close();
50
+ Effect.runFork(runtime.disposeEffect);
51
+ });
52
+ return started;
53
+ };
54
+ const api = startApi();
55
+ const snapshots = /* @__PURE__ */ new Map([[api, api.createSnapshot()]]);
56
+ const currentSnapshot = () => Option.fromNullishOr(snapshots.get(api)).pipe(Option.getOrElse(() => {
57
+ const created = api.createSnapshot();
58
+ snapshots.set(api, created);
59
+ return created;
60
+ }));
61
+ const isOwnSourceFile = (fileName) => !String.includes("/node_modules/")(fileName) && !String.endsWith(".d.ts")(fileName);
62
+ const compatSymbols = /* @__PURE__ */ new WeakMap();
63
+ const internCompatSymbol = (project, native) => Option.fromNullishOr(compatSymbols.get(native)).pipe(Option.getOrElse(() => {
64
+ const created = {
65
+ native,
66
+ flags: native.flags,
67
+ name: native.name,
68
+ getName: () => native.name,
69
+ getFlags: () => native.flags,
70
+ getDeclarations: () => pipe(native.declarations, Array.flatMap((handle) => Option.fromNullishOr(handle.resolve(project)).pipe(Option.toArray)))
71
+ };
72
+ compatSymbols.set(native, created);
73
+ return created;
74
+ }));
75
+ const compatChecker = (project, checker) => ({
76
+ getSymbolAtLocation: (node) => Option.fromNullishOr(checker.getSymbolAtLocation(node)).pipe(Option.map((native) => internCompatSymbol(project, native)), Option.getOrUndefined),
77
+ getAliasedSymbol: (symbol) => internCompatSymbol(project, checker.getAliasedSymbol(symbol.native))
78
+ });
79
+ const ownSourceFiles = (program) => pipe(program.getSourceFileNames(), Array.filter(isOwnSourceFile), Array.flatMap((fileName) => Option.fromNullishOr(program.getSourceFile(fileName)).pipe(Option.toArray)));
80
+ const cachedOwnSourceFiles = memoizeByKey(ownSourceFiles);
81
+ const compatPrograms = /* @__PURE__ */ new WeakMap();
82
+ const internCompatProgram = (project) => Option.fromNullishOr(compatPrograms.get(project.program)).pipe(Option.getOrElse(() => {
83
+ const { program } = project;
84
+ const checker = compatChecker(project, project.checker);
85
+ const created = {
86
+ getSourceFile: program.getSourceFile.bind(program),
87
+ getSourceFiles: () => cachedOwnSourceFiles(program),
88
+ getTypeChecker: () => checker
89
+ };
90
+ compatPrograms.set(program, created);
91
+ return created;
92
+ }));
93
+ const matchesText = (project, overlay) => Option.fromNullishOr(project.program.getSourceFile(overlay.fileName)).pipe(Option.exists((sourceFile) => sourceFile.text === overlay.text));
94
+ const directoryOf = (fileName) => String.replace(/[\\/][^\\/]*$/u, "")(fileName);
95
+ const directoryProjects = memoizeByKey((_snapshot) => /* @__PURE__ */ new Map());
96
+ const defaultProject = (snapshot, fileName) => Option.fromNullishOr(snapshot.getDefaultProjectForFile(fileName)).pipe(Option.map((project) => {
97
+ directoryProjects(snapshot).set(directoryOf(fileName), project);
98
+ return project;
99
+ }));
100
+ const currentProject = (overlay) => {
101
+ const snapshot = currentSnapshot();
102
+ 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)))));
103
+ };
104
+ const openFile = (overlay) => Effect.sync(() => {
105
+ const updated = currentSnapshot().update({
106
+ openFiles: [overlay.fileName],
107
+ fileSystem: createFileSystemLayer([[overlay.fileName, overlay.text]])
108
+ });
109
+ snapshots.set(api, updated);
110
+ return defaultProject(updated, overlay.fileName);
111
+ }).pipe(Effect.withSpan("oxlint-typed-rules.open-file", { attributes: { fileName: overlay.fileName } }));
112
+ const projectOfFile = (overlay) => Effect.suspend(() => Option.match(currentProject(overlay), {
113
+ onSome: Effect.succeedSome,
114
+ onNone: () => openFile(overlay)
115
+ }));
116
+ const programFor = (overlay) => Exit.match(runtime.runSyncExit(projectOfFile(overlay)), {
117
+ onSuccess: Option.map(internCompatProgram),
118
+ onFailure: (cause) => {
119
+ throw Cause.squash(cause);
120
+ }
121
+ });
122
+ // src/typed-rule.ts
123
+ const isTypedRule = (rule) => Predicate.hasProperty(rule, "meta") && Predicate.hasProperty(rule, "create") && Predicate.isFunction(rule.create);
124
+ const typedContext = (context, parserServices) => ({
125
+ filename: context.filename,
126
+ cwd: context.cwd,
127
+ options: context.options,
128
+ languageOptions: context.languageOptions,
129
+ report: context.report,
130
+ sourceCode: { parserServices }
131
+ });
132
+ const withTypeInformation = (rule) => ({
133
+ meta: rule.meta,
134
+ create: (context) => programFor({
135
+ fileName: context.filename,
136
+ text: context.sourceCode.text
137
+ }).pipe(Option.flatMap((program) => Option.fromNullishOr(program.getSourceFile(context.filename)).pipe(Option.map((sourceFile) => rule.create(typedContext(context, createParserServices({
138
+ context,
139
+ program,
140
+ sourceFile
141
+ })))))), Option.getOrElse(() => ({})))
142
+ });
143
+ const typedRules = (rules) => pipe(rules, Record.filter(isTypedRule), Record.map(withTypeInformation));
144
+ export { withTypeInformation as n, typedRules as t };
145
+
146
+ //# sourceMappingURL=typed-rule-BPSyA0px.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typed-rule-BPSyA0px.mjs","names":["Str","Arr"],"sources":["../src/memo.ts","../src/parser-services.ts","../src/program.ts","../src/typed-rule.ts"],"sourcesContent":["import { Option } from \"effect\";\n\nconst memoizeByKey = <Key extends WeakKey, Value>(\n compute: (key: Key) => Value,\n): ((key: Key) => Value) => {\n const cache = new WeakMap<Key, Value>();\n return (key) =>\n Option.fromNullishOr(cache.get(key)).pipe(\n Option.getOrElse(() => {\n const computed = compute(key);\n cache.set(key, computed);\n return computed;\n }),\n );\n};\n\nexport { memoizeByKey };\n","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 { memoizeByKey } from \"./memo.ts\";\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 program: file.program,\n esTreeNodeToTSNodeMap: {\n get: memoizeByKey((node: RangedNode) => innermostSpanning(file.sourceFile, node.range)),\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\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\nimport { memoizeByKey } from \"./memo.ts\";\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 startApi = (): API => {\n const started = new API();\n process.once(\"beforeExit\", () => {\n started.close();\n Effect.runFork(runtime.disposeEffect);\n });\n return started;\n};\n\nconst api = startApi();\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\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 internCompatSymbol = (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) => internCompatSymbol(project, native)),\n Option.getOrUndefined,\n ),\n getAliasedSymbol: (symbol) =>\n internCompatSymbol(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 cachedOwnSourceFiles = memoizeByKey(ownSourceFiles);\n\nconst compatPrograms = new WeakMap<NativeProgram, CompatProgram>();\n\nconst internCompatProgram = (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 created: CompatProgram = {\n getSourceFile: program.getSourceFile.bind(program),\n getSourceFiles: () => cachedOwnSourceFiles(program),\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 directoryOf = (fileName: string): string => Str.replace(/[\\\\/][^\\\\/]*$/u, \"\")(fileName);\n\nconst directoryProjects = memoizeByKey(\n (_snapshot: Snapshot): Map<string, Project> => new Map<string, Project>(),\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(internCompatProgram),\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: context.report,\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":";;;;;;;AAEA,MAAM,gBACJ,YAC0B;CAC1B,MAAM,wBAAQ,IAAI,QAAoB;CACtC,QAAQ,QACN,OAAO,cAAc,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,KACnC,OAAO,gBAAgB;EACrB,MAAM,WAAW,QAAQ,GAAG;EAC5B,MAAM,IAAI,KAAK,QAAQ;EACvB,OAAO;CACT,CAAC,CACH;AACJ;;ACkBA,MAAM,UAAU,MAAY,CAAC,OAAO,SAClC,KAAK,SAAS,KAAK,SAAS,KAAK,OAAO;AAE1C,MAAM,SAAS,MAAY,CAAC,OAAO,SACjC,KAAK,SAAS,MAAM,SAAS,KAAK,QAAQ;AAE5C,MAAM,qBAAqB,YAAwB,UAAiC;CAClF,MAAM,SAAS,MAAY,UAAsB;EAC/C,IAAI,CAAC,OAAO,MAAM,KAAK,GACrB,OAAO;EAET,MAAM,UAAU,OAAO,cAAc,OAAO,cAAc,MAAM,WAAW,KAAK,CAAC,CAAC,CAAC,KACjF,OAAO,gBAAgB,KAAK,CAC9B;EACA,OAAO,OAAO,cACZ,KAAK,cAAc,UACjB,OAAO,cAAc,MAAM,OAAO,OAAO,IAAI,WAAW,WAAW,OAAO,CAAC,CAAC,KAC1E,OAAO,cACT,CACF,CACF,CAAC,CAAC,KAAK,OAAO,gBAAgB,OAAO,CAAC;CACxC;CACA,OAAO,MAAM,YAAY,UAAU;AACrC;AAEA,MAAM,gBAAgB,EAAE,WAAwB,SAA2B;CACzE,MAAM,QAAQ,KAAK,SAAS;CAC5B,OAAO;EACL,MAAM,WAAW,KAAK;EACtB,OAAO,CAAC,OAAO,KAAK,GAAG;EACvB,KAAK;GACH,OAAO,QAAQ,WAAW,gBAAgB,KAAK;GAC/C,KAAK,QAAQ,WAAW,gBAAgB,KAAK,GAAG;EAClD;CACF;AACF;AAEA,MAAM,wBAAwB,UAAuC;CACnE,SAAS,KAAK;CACd,uBAAuB,EACrB,KAAK,cAAc,SAAqB,kBAAkB,KAAK,YAAY,KAAK,KAAK,CAAC,EACxF;CACA,uBAAuB,EACrB,MAAM,SACJ,OAAO,cACL,OACC,cAAc,UAAU,cAAc,CAAC,CAAC,aAAa,KAAK,WAAW,QACxE,CAAC,CAAC,KACA,OAAO,KAAK,cAAc,aAAa,MAAM,SAAS,CAAC,GACvD,OAAO,cACT,EACJ;AACF;;ACzDA,MAAM,UAAU,WAAW,gBAAgB,EACzC,UAAU,EAAE,aAAa,qBAAqB,EAChD,CAAC,CAAC,CAAC,KAAK,MAAM,QAAQ,kBAAkB,SAAS,GAAG,MAAM,QAAQ,gBAAgB,KAAK,CAAC;AAExF,MAAM,UAAU,eAAe,KAAK,OAAO;AA2B3C,MAAM,iBAAsB;CAC1B,MAAM,UAAU,IAAI,IAAI;CACxB,QAAQ,KAAK,oBAAoB;EAC/B,QAAQ,MAAM;EACd,OAAO,QAAQ,QAAQ,aAAa;CACtC,CAAC;CACD,OAAO;AACT;AAEA,MAAM,MAAM,SAAS;AAErB,MAAM,4BAAY,IAAI,IAAmB,CAAC,CAAC,KAAK,IAAI,eAAe,CAAC,CAAC,CAAC;AAEtE,MAAM,wBACJ,OAAO,cAAc,UAAU,IAAI,GAAG,CAAC,CAAC,CAAC,KACvC,OAAO,gBAAgB;CACrB,MAAM,UAAU,IAAI,eAAe;CACnC,UAAU,IAAI,KAAK,OAAO;CAC1B,OAAO;AACT,CAAC,CACH;AAEF,MAAM,mBAAmB,aACvB,CAACA,OAAI,SAAS,gBAAgB,CAAC,CAAC,QAAQ,KAAK,CAACA,OAAI,SAAS,OAAO,CAAC,CAAC,QAAQ;AAE9E,MAAM,gCAAgB,IAAI,QAAoC;AAE9D,MAAM,sBAAsB,SAAkB,WAC5C,OAAO,cAAc,cAAc,IAAI,MAAM,CAAC,CAAC,CAAC,KAC9C,OAAO,gBAAgB;CACrB,MAAM,UAAwB;EAC5B;EACA,OAAO,OAAO;EACd,MAAM,OAAO;EACb,eAAe,OAAO;EACtB,gBAAgB,OAAO;EACvB,uBACE,KACE,OAAO,cACPC,MAAI,SAAS,WACX,OAAO,cAAc,OAAO,QAAQ,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,OAAO,CACnE,CACF;CACJ;CACA,cAAc,IAAI,QAAQ,OAAO;CACjC,OAAO;AACT,CAAC,CACH;AAEF,MAAM,iBAAiB,SAAkB,aAAqC;CAC5E,sBAAsB,SACpB,OAAO,cAAc,QAAQ,oBAAoB,IAAI,CAAC,CAAC,CAAC,KACtD,OAAO,KAAK,WAAW,mBAAmB,SAAS,MAAM,CAAC,GAC1D,OAAO,cACT;CACF,mBAAmB,WACjB,mBAAmB,SAAS,QAAQ,iBAAiB,OAAO,MAAM,CAAC;AACvE;AAEA,MAAM,kBAAkB,YACtB,KACE,QAAQ,mBAAmB,GAC3BA,MAAI,OAAO,eAAe,GAC1BA,MAAI,SAAS,aACX,OAAO,cAAc,QAAQ,cAAc,QAAQ,CAAC,CAAC,CAAC,KAAK,OAAO,OAAO,CAC3E,CACF;AAEF,MAAM,uBAAuB,aAAa,cAAc;AAExD,MAAM,iCAAiB,IAAI,QAAsC;AAEjE,MAAM,uBAAuB,YAC3B,OAAO,cAAc,eAAe,IAAI,QAAQ,OAAO,CAAC,CAAC,CAAC,KACxD,OAAO,gBAAgB;CACrB,MAAM,EAAE,YAAY;CACpB,MAAM,UAAU,cAAc,SAAS,QAAQ,OAAO;CACtD,MAAM,UAAyB;EAC7B,eAAe,QAAQ,cAAc,KAAK,OAAO;EACjD,sBAAsB,qBAAqB,OAAO;EAClD,sBAAsB;CACxB;CACA,eAAe,IAAI,SAAS,OAAO;CACnC,OAAO;AACT,CAAC,CACH;AAEF,MAAM,eAAe,SAAkB,YACrC,OAAO,cAAc,QAAQ,QAAQ,cAAc,QAAQ,QAAQ,CAAC,CAAC,CAAC,KACpE,OAAO,QAAQ,eAAe,WAAW,SAAS,QAAQ,IAAI,CAChE;AAEF,MAAM,eAAe,aAA6BD,OAAI,QAAQ,kBAAkB,EAAE,CAAC,CAAC,QAAQ;AAE5F,MAAM,oBAAoB,cACvB,8BAA8C,IAAI,IAAqB,CAC1E;AAEA,MAAM,kBAAkB,UAAoB,aAC1C,OAAO,cAAc,SAAS,yBAAyB,QAAQ,CAAC,CAAC,CAAC,KAChE,OAAO,KAAK,YAAY;CACtB,kBAAkB,QAAQ,CAAC,CAAC,IAAI,YAAY,QAAQ,GAAG,OAAO;CAC9D,OAAO;AACT,CAAC,CACH;AAEF,MAAM,kBAAkB,YAA6C;CACnE,MAAM,WAAW,gBAAgB;CACjC,OAAO,OAAO,cAAc,kBAAkB,QAAQ,CAAC,CAAC,IAAI,YAAY,QAAQ,QAAQ,CAAC,CAAC,CAAC,CAAC,KAC1F,OAAO,QAAQ,YAAY,YAAY,SAAS,OAAO,CAAC,GACxD,OAAO,aACL,eAAe,UAAU,QAAQ,QAAQ,CAAC,CAAC,KACzC,OAAO,QAAQ,YAAY,YAAY,SAAS,OAAO,CAAC,CAC1D,CACF,CACF;AACF;AAEA,MAAM,YAAY,YAChB,OAAO,WAAW;CAChB,MAAM,UAAU,gBAAgB,CAAC,CAAC,OAAO;EACvC,WAAW,CAAC,QAAQ,QAAQ;EAC5B,YAAY,sBAAsB,CAAC,CAAC,QAAQ,UAAU,QAAQ,IAAI,CAAC,CAAC;CACtE,CAAC;CACD,UAAU,IAAI,KAAK,OAAO;CAC1B,OAAO,eAAe,SAAS,QAAQ,QAAQ;AACjD,CAAC,CAAC,CAAC,KACD,OAAO,SAAS,gCAAgC,EAC9C,YAAY,EAAE,UAAU,QAAQ,SAAS,EAC3C,CAAC,CACH;AAEF,MAAM,iBAAiB,YACrB,OAAO,cACL,OAAO,MAAM,eAAe,OAAO,GAAG;CACpC,QAAQ,OAAO;CACf,cAAc,SAAS,OAAO;AAChC,CAAC,CACH;AAEF,MAAM,cAAc,YAClB,KAAK,MAAM,QAAQ,YAAY,cAAc,OAAO,CAAC,GAAG;CACtD,WAAW,OAAO,IAAI,mBAAmB;CACzC,YAAY,UAAU;EACpB,MAAM,MAAM,OAAO,KAAK;CAC1B;AACF,CAAC;;ACvLH,MAAM,eAAqB,SACzB,UAAU,YAAY,MAAM,MAAM,KAClC,UAAU,YAAY,MAAM,QAAQ,KACpC,UAAU,WAAW,KAAK,MAAM;AAElC,MAAM,gBAAgB,SAAkB,oBAAkD;CACxF,UAAU,QAAQ;CAClB,KAAK,QAAQ;CACb,SAAS,QAAQ;CACjB,iBAAiB,QAAQ;CACzB,QAAQ,QAAQ;CAChB,YAAY,EAAE,eAAe;AAC/B;AAEA,MAAM,uBAAuB,UAAiC;CAC5D,MAAM,KAAK;CACX,SAAS,YACP,WAAW;EAAE,UAAU,QAAQ;EAAU,MAAM,QAAQ,WAAW;CAAK,CAAC,CAAC,CAAC,KACxE,OAAO,SAAS,YACd,OAAO,cAAc,QAAQ,cAAc,QAAQ,QAAQ,CAAC,CAAC,CAAC,KAC5D,OAAO,KAAK,eACV,KAAK,OACH,aAAa,SAAS,qBAAqB;EAAE;EAAS;EAAS;CAAW,CAAC,CAAC,CAC9E,CACF,CACF,CACF,GACA,OAAO,iBAAiB,CAAC,EAAE,CAC7B;AACJ;AAEA,MAAM,cACJ,UAEA,KAAK,OAAO,OAAO,OAAO,WAAW,GAAG,OAAO,IAAI,mBAAmB,CAAC"}
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.1.0",
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",
@@ -9,14 +9,14 @@
9
9
  "tanstack-start",
10
10
  "typescript"
11
11
  ],
12
- "homepage": "https://github.com/masseater/oxlint-typed-rules#readme",
12
+ "homepage": "https://github.com/masseater/lint-kit/tree/main/libs/oxlint-typed-rules#readme",
13
13
  "bugs": {
14
- "url": "https://github.com/masseater/oxlint-typed-rules/issues"
14
+ "url": "https://github.com/masseater/lint-kit/issues"
15
15
  },
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
19
- "url": "git+https://github.com/masseater/oxlint-typed-rules.git",
19
+ "url": "git+https://github.com/masseater/lint-kit.git",
20
20
  "directory": "libs/oxlint-typed-rules"
21
21
  },
22
22
  "files": [
@@ -40,21 +40,34 @@
40
40
  "provenance": true
41
41
  },
42
42
  "dependencies": {
43
- "@effect/platform-node": "4.0.7",
44
- "effect": "4.0.7",
45
- "typescript-6": "npm:typescript@6.0.3"
43
+ "effect": "4.1.1",
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.1.1",
49
+ "@effect/vitest": "4.1.1",
50
+ "@size-limit/file": "14.3.0",
48
51
  "@tanstack/eslint-plugin-start": "0.1.0",
49
52
  "@types/node": "26.6.6",
50
53
  "@typescript-eslint/utils": "^8.72.1",
54
+ "publint": "0.3.25",
55
+ "size-limit": "14.3.0",
56
+ "typescript": "6.0.3",
51
57
  "vite-plus": "1.4.0"
52
58
  },
59
+ "size-limit": [
60
+ {
61
+ "path": "dist/*.mjs",
62
+ "limit": "23995 B"
63
+ }
64
+ ],
53
65
  "engines": {
54
66
  "node": ">=26"
55
67
  },
56
68
  "scripts": {
57
69
  "build": "vp pack",
58
- "test": "vp test run"
70
+ "test": "vp test run --coverage",
71
+ "size": "vp run build && size-limit"
59
72
  }
60
73
  }
@@ -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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"}