@goodbones/oxlint 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/build/dts/config-loader.d.ts +5 -0
  3. package/build/dts/config-loader.d.ts.map +1 -0
  4. package/build/dts/exports-rule.d.ts +4 -0
  5. package/build/dts/exports-rule.d.ts.map +1 -0
  6. package/build/dts/imports-rule.d.ts +4 -0
  7. package/build/dts/imports-rule.d.ts.map +1 -0
  8. package/build/dts/members-rule.d.ts +4 -0
  9. package/build/dts/members-rule.d.ts.map +1 -0
  10. package/build/dts/oxlint-api.d.ts +46 -0
  11. package/build/dts/oxlint-api.d.ts.map +1 -0
  12. package/build/dts/plugin.d.ts +16 -0
  13. package/build/dts/plugin.d.ts.map +1 -0
  14. package/build/dts/structure-rule.d.ts +4 -0
  15. package/build/dts/structure-rule.d.ts.map +1 -0
  16. package/build/dts/surface-rule.d.ts +4 -0
  17. package/build/dts/surface-rule.d.ts.map +1 -0
  18. package/build/esm/config-loader.js +24 -0
  19. package/build/esm/config-loader.js.map +1 -0
  20. package/build/esm/exports-rule.js +137 -0
  21. package/build/esm/exports-rule.js.map +1 -0
  22. package/build/esm/imports-rule.js +71 -0
  23. package/build/esm/imports-rule.js.map +1 -0
  24. package/build/esm/members-rule.js +138 -0
  25. package/build/esm/members-rule.js.map +1 -0
  26. package/build/esm/oxlint-api.js +24 -0
  27. package/build/esm/oxlint-api.js.map +1 -0
  28. package/build/esm/plugin.js +30 -0
  29. package/build/esm/plugin.js.map +1 -0
  30. package/build/esm/structure-rule.js +36 -0
  31. package/build/esm/structure-rule.js.map +1 -0
  32. package/build/esm/surface-rule.js +161 -0
  33. package/build/esm/surface-rule.js.map +1 -0
  34. package/package.json +67 -0
  35. package/src/config-loader.ts +35 -0
  36. package/src/exports-rule.ts +195 -0
  37. package/src/imports-rule.ts +96 -0
  38. package/src/members-rule.ts +205 -0
  39. package/src/oxlint-api.ts +66 -0
  40. package/src/plugin.ts +44 -0
  41. package/src/structure-rule.ts +38 -0
  42. package/src/surface-rule.ts +237 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Data Quail
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ import { type LoadedPolicy } from "@goodbones/core";
2
+ export type { LoadedPolicy } from "@goodbones/core";
3
+ export { DEFAULT_CONFIG_FILENAME } from "@goodbones/core";
4
+ export declare const loadPolicyFromFile: (repoRoot: string, configFilename?: string) => Promise<LoadedPolicy>;
5
+ //# sourceMappingURL=config-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/config-loader.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,YAAY,EAIlB,MAAM,iBAAiB,CAAC;AAIzB,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAO1D,eAAO,MAAM,kBAAkB,aACnB,MAAM,mBACA,MAAM,KACrB,OAAO,CAAC,YAAY,CAWtB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { type LoadedPolicy } from "@goodbones/core";
2
+ import { type OxlintRule } from "./oxlint-api.js";
3
+ export declare const makeExportsRule: (policy: LoadedPolicy) => OxlintRule;
4
+ //# sourceMappingURL=exports-rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exports-rule.d.ts","sourceRoot":"","sources":["../../src/exports-rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,YAAY,EAElB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAOL,KAAK,UAAU,EAKhB,MAAM,iBAAiB,CAAC;AA4EzB,eAAO,MAAM,eAAe,WAAY,YAAY,KAAG,UAgGrD,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { type LoadedPolicy } from "@goodbones/core";
2
+ import { type OxlintRule } from "./oxlint-api.js";
3
+ export declare const makeImportsRule: (policy: LoadedPolicy) => OxlintRule;
4
+ //# sourceMappingURL=imports-rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imports-rule.d.ts","sourceRoot":"","sources":["../../src/imports-rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,YAAY,EAGlB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAML,KAAK,UAAU,EAOhB,MAAM,iBAAiB,CAAC;AAOzB,eAAO,MAAM,eAAe,WAAY,YAAY,KAAG,UAkErD,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { type LoadedPolicy } from "@goodbones/core";
2
+ import { type OxlintRule } from "./oxlint-api.js";
3
+ export declare const makeMembersRule: (policy: LoadedPolicy) => OxlintRule;
4
+ //# sourceMappingURL=members-rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"members-rule.d.ts","sourceRoot":"","sources":["../../src/members-rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,YAAY,EAElB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,KAAK,UAAU,EAIhB,MAAM,iBAAiB,CAAC;AAsGzB,eAAO,MAAM,eAAe,WAAY,YAAY,KAAG,UAwFrD,CAAC"}
@@ -0,0 +1,46 @@
1
+ import type { RuleTester } from "oxlint/plugins-dev";
2
+ export type OxlintRule = Parameters<RuleTester["run"]>[1];
3
+ export type RuleContext = Parameters<Extract<OxlintRule, {
4
+ createOnce: unknown;
5
+ }>["createOnce"]>[0];
6
+ type Diagnostic = Parameters<RuleContext["report"]>[0];
7
+ export type ReportableNode = Extract<Diagnostic, {
8
+ node: unknown;
9
+ }>["node"];
10
+ export type Fixer = Parameters<NonNullable<Diagnostic["fix"]>>[0];
11
+ export type SourceNode = ReportableNode & {
12
+ readonly source?: {
13
+ readonly value?: unknown;
14
+ } | null;
15
+ };
16
+ export declare const toRepoRelative: (repoRoot: string, filename: string) => string;
17
+ export declare const specifierOf: (node: SourceNode) => string | null;
18
+ export type CallNode = ReportableNode & {
19
+ readonly callee?: {
20
+ readonly type: string;
21
+ readonly name?: unknown;
22
+ } | null;
23
+ readonly arguments?: ReadonlyArray<{
24
+ readonly type: string;
25
+ readonly value?: unknown;
26
+ }> | null;
27
+ };
28
+ export type ImportExpressionNode = ReportableNode & {
29
+ readonly source?: {
30
+ readonly type?: string;
31
+ readonly value?: unknown;
32
+ } | null;
33
+ };
34
+ export type ImportEqualsNode = ReportableNode & {
35
+ readonly moduleReference?: {
36
+ readonly type: string;
37
+ readonly expression?: {
38
+ readonly value?: unknown;
39
+ } | null;
40
+ } | null;
41
+ };
42
+ export declare const requireSpecifierOf: (node: CallNode) => string | null;
43
+ export declare const importExpressionSpecifierOf: (node: ImportExpressionNode) => string | null;
44
+ export declare const importEqualsSpecifierOf: (node: ImportEqualsNode) => string | null;
45
+ export {};
46
+ //# sourceMappingURL=oxlint-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oxlint-api.d.ts","sourceRoot":"","sources":["../../src/oxlint-api.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAKrD,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1D,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,UAAU,EAAE;IAAE,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEpG,KAAK,UAAU,GAAG,UAAU,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,UAAU,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC,MAAM,CAAC,CAAC;AAC5E,MAAM,MAAM,KAAK,GAAG,UAAU,CAAC,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAElE,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG;IACxC,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CACvD,CAAC;AAEF,eAAO,MAAM,cAAc,aAAc,MAAM,YAAY,MAAM,KAAG,MACP,CAAC;AAE9D,eAAO,MAAM,WAAW,SAAU,UAAU,KAAG,MAAM,GAAG,IAGvD,CAAC;AAOF,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5E,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI,CAAC;CAChG,CAAC;AAGF,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG;IAClD,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CAC/E,CAAC;AAGF,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG;IAC9C,QAAQ,CAAC,eAAe,CAAC,EAAE;QACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,UAAU,CAAC,EAAE;YAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;SAAE,GAAG,IAAI,CAAC;KAC3D,GAAG,IAAI,CAAC;CACV,CAAC;AAEF,eAAO,MAAM,kBAAkB,SAAU,QAAQ,KAAG,MAAM,GAAG,IAI5D,CAAC;AAEF,eAAO,MAAM,2BAA2B,SAAU,oBAAoB,KAAG,MAAM,GAAG,IAGjF,CAAC;AAEF,eAAO,MAAM,uBAAuB,SAAU,gBAAgB,KAAG,MAAM,GAAG,IAKzE,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { OxlintRule } from "./oxlint-api.js";
2
+ export declare const rules: {
3
+ readonly imports: OxlintRule;
4
+ readonly exports: OxlintRule;
5
+ readonly members: OxlintRule;
6
+ readonly structure: OxlintRule;
7
+ readonly surface: OxlintRule;
8
+ };
9
+ declare const plugin: {
10
+ readonly meta: {
11
+ readonly name: string;
12
+ };
13
+ readonly rules: typeof rules;
14
+ };
15
+ export default plugin;
16
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAoBlD,eAAO,MAAM,KAAK,EAAE;IAClB,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;CAO9B,CAAC;AAEF,QAAA,MAAM,MAAM,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,KAAK,CAAA;CAGrF,CAAC;eAEa,MAAM"}
@@ -0,0 +1,4 @@
1
+ import { type LoadedPolicy } from "@goodbones/core";
2
+ import { type OxlintRule } from "./oxlint-api.js";
3
+ export declare const makeStructureRule: (policy: LoadedPolicy) => OxlintRule;
4
+ //# sourceMappingURL=structure-rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structure-rule.d.ts","sourceRoot":"","sources":["../../src/structure-rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEtF,OAAO,EAAE,KAAK,UAAU,EAAoC,MAAM,iBAAiB,CAAC;AAEpF,eAAO,MAAM,iBAAiB,WAAY,YAAY,KAAG,UAiCvD,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { type LoadedPolicy } from "@goodbones/core";
2
+ import { type OxlintRule } from "./oxlint-api.js";
3
+ export declare const makeSurfaceRule: (policy: LoadedPolicy) => OxlintRule;
4
+ //# sourceMappingURL=surface-rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surface-rule.d.ts","sourceRoot":"","sources":["../../src/surface-rule.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,YAAY,EAElB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,KAAK,UAAU,EAIhB,MAAM,iBAAiB,CAAC;AA+KzB,eAAO,MAAM,eAAe,WAAY,YAAY,KAAG,UA8CrD,CAAC"}
@@ -0,0 +1,24 @@
1
+ import * as path from "node:path";
2
+ import { DEFAULT_CONFIG_FILENAME, loadPolicy, makeFileSystemLive, readManifestFile, } from "@goodbones/core";
3
+ import { typescriptLanguage } from "@goodbones/typescript";
4
+ import * as Result from "effect/Result";
5
+ export { DEFAULT_CONFIG_FILENAME } from "@goodbones/core";
6
+ // The plugin's composition root: read the manifest file, construct the
7
+ // language packs and the live file system, and hand them to the loader. A load
8
+ // failure throws out of here and out of oxlint's import of the plugin — a
9
+ // plugin that came up with no policy would report nothing and be
10
+ // indistinguishable from a clean codebase.
11
+ export const loadPolicyFromFile = async (repoRoot, configFilename = DEFAULT_CONFIG_FILENAME) => {
12
+ const configPath = path.resolve(repoRoot, configFilename);
13
+ const loaded = loadPolicy({
14
+ repoRoot,
15
+ configPath,
16
+ manifest: await readManifestFile(configPath),
17
+ languages: [typescriptLanguage()],
18
+ fileSystem: makeFileSystemLive(repoRoot),
19
+ });
20
+ if (Result.isFailure(loaded))
21
+ throw loaded.failure;
22
+ return loaded.success;
23
+ };
24
+ //# sourceMappingURL=config-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../src/config-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EACL,uBAAuB,EAEvB,UAAU,EACV,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,uEAAuE;AACvE,+EAA+E;AAC/E,0EAA0E;AAC1E,iEAAiE;AACjE,2CAA2C;AAC3C,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EACrC,QAAgB,EAChB,cAAc,GAAW,uBAAuB,EACzB,EAAE;IACzB,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,UAAU,CAAC;QACxB,QAAQ;QACR,UAAU;QACV,QAAQ,EAAE,MAAM,gBAAgB,CAAC,UAAU,CAAC;QAC5C,SAAS,EAAE,CAAC,kBAAkB,EAAE,CAAC;QACjC,UAAU,EAAE,kBAAkB,CAAC,QAAQ,CAAC;KACzC,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;QAAE,MAAM,MAAM,CAAC,OAAO,CAAC;IACnD,OAAO,MAAM,CAAC,OAAO,CAAC;AACxB,CAAC,CAAC"}
@@ -0,0 +1,137 @@
1
+ import { evaluateSelectedBindings, exportRulesSelecting, formatMessage, } from "@goodbones/core";
2
+ import * as Result from "effect/Result";
3
+ import { importEqualsSpecifierOf, importExpressionSpecifierOf, requireSpecifierOf, toRepoRelative, } from "./oxlint-api.js";
4
+ const nameOf = (node) => {
5
+ if (node === null || node === undefined)
6
+ return null;
7
+ if (typeof node.name === "string")
8
+ return node.name;
9
+ // `import { "a-b" as ab }` — a string-literal export name.
10
+ return typeof node.value === "string" ? node.value : null;
11
+ };
12
+ // The whole module, as one binding. `export * from "m"`, `export * as ns from
13
+ // "m"`, `import x = require("m")`, `import("m")` and `require("m")` all carry
14
+ // every export of `m` at once, exactly as `import * as ns` does — and are the
15
+ // same way around a rule about a name. A side-effect import carries nothing.
16
+ const wholeModule = (node, local = "") => ({
17
+ symbol: "*",
18
+ kind: "namespace",
19
+ node,
20
+ local,
21
+ });
22
+ const boundOf = (specifier) => {
23
+ const local = nameOf(specifier.local) ?? "";
24
+ switch (specifier.type) {
25
+ case "ImportSpecifier": {
26
+ const symbol = nameOf(specifier.imported);
27
+ return symbol === null ? null : { symbol, kind: "named", node: specifier, local };
28
+ }
29
+ case "ImportDefaultSpecifier":
30
+ return { symbol: "default", kind: "default", node: specifier, local };
31
+ case "ImportNamespaceSpecifier":
32
+ return { symbol: "*", kind: "namespace", node: specifier, local };
33
+ // `export { a } from "…"` — `local` is the name in the source module.
34
+ case "ExportSpecifier": {
35
+ const symbol = nameOf(specifier.local);
36
+ return symbol === null ? null : { symbol, kind: "named", node: specifier, local: symbol };
37
+ }
38
+ default:
39
+ return null;
40
+ }
41
+ };
42
+ const specifierOf = (node) => {
43
+ const value = node.source?.value;
44
+ return typeof value === "string" ? value : null;
45
+ };
46
+ const boundSpecifiers = (node) => (node.specifiers ?? []).map(boundOf).filter((one) => one !== null);
47
+ // `import { A, B as C } from "pkg"` becomes `import * as A from "pkg/A"` and
48
+ // `import * as C from "pkg/B"`. Only whole-declaration rewrites are offered: a
49
+ // declaration mixing restricted named imports with a default or namespace one
50
+ // would need comma surgery inside the braces, and a fix that is subtly wrong is
51
+ // worse than a diagnostic the author resolves by hand.
52
+ const subpathNamespaceImport = (specifier, bound) => bound
53
+ .map((binding) => `import * as ${binding.local} from "${specifier}/${binding.symbol}";`)
54
+ .join("\n");
55
+ export const makeExportsRule = (policy) => ({
56
+ meta: {
57
+ type: "problem",
58
+ fixable: "code",
59
+ docs: {
60
+ description: "which exported symbols a file may import, for rules a path alone cannot express",
61
+ },
62
+ schema: [],
63
+ },
64
+ createOnce(context) {
65
+ let importer = "";
66
+ let selected = [];
67
+ // `fixable` is whether a rewrite could apply: only an `import` declaration
68
+ // can be rewritten into subpath namespace imports. `export *` and the
69
+ // whole-module forms are reported and left for the author.
70
+ const check = (node, specifierValue, bound, fixable) => {
71
+ if (specifierValue === null || bound.length === 0)
72
+ return;
73
+ const outcome = evaluateSelectedBindings(selected, policy.resolver, {
74
+ importer,
75
+ specifier: specifierValue,
76
+ bindings: bound,
77
+ });
78
+ if (Result.isFailure(outcome)) {
79
+ // `architecture/imports` reports the same unresolved edge, so staying
80
+ // quiet here avoids two diagnostics for one broken specifier.
81
+ return;
82
+ }
83
+ for (const { bindings, rule, violation } of outcome.success) {
84
+ if (policy.baseline.isBaselined(violation))
85
+ continue;
86
+ const offending = bound.filter((one) => bindings.some((binding) => binding.symbol === one.symbol && binding.kind === one.kind));
87
+ // The rewrite is `import * as X from "pkg/<name>"`, which only means
88
+ // something for a named binding — a namespace one has no name to put
89
+ // in the subpath.
90
+ const rewritable = fixable &&
91
+ rule.fix === "subpath-namespace-import" &&
92
+ offending.length === bound.length &&
93
+ offending.every((one) => one.kind === "named");
94
+ if (rewritable) {
95
+ context.report({
96
+ node,
97
+ message: formatMessage(violation),
98
+ fix: (fixer) => fixer.replaceText(node, subpathNamespaceImport(specifierValue, offending)),
99
+ });
100
+ continue;
101
+ }
102
+ context.report({
103
+ node: offending[0]?.node ?? node,
104
+ message: formatMessage(violation),
105
+ });
106
+ }
107
+ };
108
+ return {
109
+ before() {
110
+ importer = toRepoRelative(policy.repoRoot, context.filename);
111
+ if (importer.startsWith(".."))
112
+ return false;
113
+ selected = exportRulesSelecting(policy.exportRules, importer);
114
+ return selected.length > 0;
115
+ },
116
+ ImportDeclaration(node) {
117
+ check(node, specifierOf(node), boundSpecifiers(node), true);
118
+ },
119
+ ExportNamedDeclaration(node) {
120
+ check(node, specifierOf(node), boundSpecifiers(node), false);
121
+ },
122
+ ExportAllDeclaration(node) {
123
+ check(node, specifierOf(node), [wholeModule(node, nameOf(node.exported) ?? "")], false);
124
+ },
125
+ ImportExpression(node) {
126
+ check(node, importExpressionSpecifierOf(node), [wholeModule(node)], false);
127
+ },
128
+ CallExpression(node) {
129
+ check(node, requireSpecifierOf(node), [wholeModule(node)], false);
130
+ },
131
+ TSImportEqualsDeclaration(node) {
132
+ check(node, importEqualsSpecifierOf(node), [wholeModule(node)], false);
133
+ },
134
+ };
135
+ },
136
+ });
137
+ //# sourceMappingURL=exports-rule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exports-rule.js","sourceRoot":"","sources":["../../src/exports-rule.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import { evaluateSelectedEdge, formatMessage, rulesSelecting, } from "@goodbones/core";
2
+ import * as Result from "effect/Result";
3
+ import { importEqualsSpecifierOf, importExpressionSpecifierOf, requireSpecifierOf, specifierOf, toRepoRelative, } from "./oxlint-api.js";
4
+ const unresolvedMessage = (specifier, detail) => `[unresolved-import] "${specifier}" could not be resolved, so every import rule about it ` +
5
+ `enforces nothing. Fix the resolve scope in the architecture config, or list the specifier ` +
6
+ `in resolve.ignoreUnresolved. (${detail})`;
7
+ export const makeImportsRule = (policy) => ({
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ description: "import boundaries between folders and packages, matched against fully resolved module paths",
12
+ },
13
+ schema: [],
14
+ },
15
+ // `createOnce` is what makes resolution affordable: the resolver and the
16
+ // compiled rules are built once per lint run, and `before` decides per file
17
+ // whether any rule selects it at all — a file none select is skipped whole.
18
+ createOnce(context) {
19
+ let importer = "";
20
+ let selected = [];
21
+ const check = (node, specifier) => {
22
+ if (specifier === null)
23
+ return;
24
+ const outcome = evaluateSelectedEdge(selected, policy.resolver, { importer, specifier });
25
+ if (Result.isFailure(outcome)) {
26
+ if (policy.config.resolve.unresolved === "off")
27
+ return;
28
+ if (policy.ignoreUnresolved.some((pattern) => pattern.test(specifier)))
29
+ return;
30
+ context.report({ node, message: unresolvedMessage(specifier, outcome.failure.detail) });
31
+ return;
32
+ }
33
+ for (const violation of outcome.success) {
34
+ if (policy.baseline.isBaselined(violation))
35
+ continue;
36
+ context.report({ node, message: formatMessage(violation) });
37
+ }
38
+ };
39
+ const checkSource = (node) => {
40
+ check(node, specifierOf(node));
41
+ };
42
+ // Every form that names a module is an edge. The CLI adapter reads the same
43
+ // five out of TypeScript's tree, and the parity suite holds the two to it: a
44
+ // `require` the plugin skipped would be a rule that enforces nothing under
45
+ // `oxlint` while failing under `architecture check`.
46
+ return {
47
+ before() {
48
+ importer = toRepoRelative(policy.repoRoot, context.filename);
49
+ if (importer.startsWith(".."))
50
+ return false;
51
+ selected = rulesSelecting(policy.importRules, importer);
52
+ return selected.length > 0;
53
+ },
54
+ ImportDeclaration: checkSource,
55
+ ExportNamedDeclaration: checkSource,
56
+ ExportAllDeclaration: checkSource,
57
+ // `import("m")` with a literal argument. A computed one is not a fact a
58
+ // static policy can speak about, in either adapter.
59
+ ImportExpression(node) {
60
+ check(node, importExpressionSpecifierOf(node));
61
+ },
62
+ CallExpression(node) {
63
+ check(node, requireSpecifierOf(node));
64
+ },
65
+ TSImportEqualsDeclaration(node) {
66
+ check(node, importEqualsSpecifierOf(node));
67
+ },
68
+ };
69
+ },
70
+ });
71
+ //# sourceMappingURL=imports-rule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imports-rule.js","sourceRoot":"","sources":["../../src/imports-rule.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,aAAa,EAEb,cAAc,GAEf,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,EAGL,uBAAuB,EAEvB,2BAA2B,EAG3B,kBAAkB,EAGlB,WAAW,EACX,cAAc,GACf,MAAM,iBAAiB,CAAC;AAEzB,MAAM,iBAAiB,GAAG,CAAC,SAAiB,EAAE,MAAc,EAAU,EAAE,CACtE,wBAAwB,SAAS,yDAAyD;IAC1F,4FAA4F;IAC5F,iCAAiC,MAAM,GAAG,CAAC;AAE7C,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAoB,EAAc,EAAE,CAAC,CAAC;IACpE,IAAI,EAAE;QACJ,IAAI,EAAE,SAAkB;QACxB,IAAI,EAAE;YACJ,WAAW,EACT,6FAA6F;SAChG;QACD,MAAM,EAAE,EAAE;KACX;IAED,yEAAyE;IACzE,4EAA4E;IAC5E,4EAA4E;IAC5E,UAAU,CAAC,OAAoB;QAC7B,IAAI,QAAQ,GAAG,EAAE,CAAC;QAClB,IAAI,QAAQ,GAAgC,EAAE,CAAC;QAE/C,MAAM,KAAK,GAAG,CAAC,IAAoB,EAAE,SAAwB,EAAQ,EAAE;YACrE,IAAI,SAAS,KAAK,IAAI;gBAAE,OAAO;YAE/B,MAAM,OAAO,GAAG,oBAAoB,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;YAEzF,IAAI,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC9B,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,KAAK,KAAK;oBAAE,OAAO;gBACvD,IAAI,MAAM,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;oBAAE,OAAO;gBAC/E,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,iBAAiB,CAAC,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACxF,OAAO;YACT,CAAC;YAED,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;gBACxC,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,SAAS,CAAC;oBAAE,SAAS;gBACrD,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,WAAW,GAAG,CAAC,IAAgB,EAAQ,EAAE;YAC7C,KAAK,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QACjC,CAAC,CAAC;QAEF,4EAA4E;QAC5E,6EAA6E;QAC7E,2EAA2E;QAC3E,qDAAqD;QACrD,OAAO;YACL,MAAM;gBACJ,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAC7D,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC;oBAAE,OAAO,KAAK,CAAC;gBAC5C,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;gBACxD,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;YAC7B,CAAC;YACD,iBAAiB,EAAE,WAAW;YAC9B,sBAAsB,EAAE,WAAW;YACnC,oBAAoB,EAAE,WAAW;YACjC,wEAAwE;YACxE,oDAAoD;YACpD,gBAAgB,CAAC,IAA0B;gBACzC,KAAK,CAAC,IAAI,EAAE,2BAA2B,CAAC,IAAI,CAAC,CAAC,CAAC;YACjD,CAAC;YACD,cAAc,CAAC,IAAc;gBAC3B,KAAK,CAAC,IAAI,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;YACxC,CAAC;YACD,yBAAyB,CAAC,IAAsB;gBAC9C,KAAK,CAAC,IAAI,EAAE,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC;YAC7C,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,138 @@
1
+ import { evaluateMemberSite, formatMessage, memberRulesSelecting, } from "@goodbones/core";
2
+ import { toRepoRelative, } from "./oxlint-api.js";
3
+ const isMemberList = (value) => Array.isArray(value);
4
+ // The member shapes that carry a name a vocabulary rule can speak about: a
5
+ // property or method signature in a type, a property, method or accessor in a
6
+ // class. Mirrors the CLI's `isNamedMember`.
7
+ const DECLARED_MEMBER_TYPES = new Set([
8
+ "TSPropertySignature",
9
+ "TSMethodSignature",
10
+ "PropertyDefinition",
11
+ "MethodDefinition",
12
+ "TSAbstractPropertyDefinition",
13
+ "TSAbstractMethodDefinition",
14
+ ]);
15
+ // The type literals written in a type, through intersections, unions and
16
+ // parentheses. A reference is not followed: its members are declared where it
17
+ // is, and reported there under its own name. Mirrors the CLI's `literalsOf`.
18
+ const literalsOf = (node) => {
19
+ if (node === null || node === undefined)
20
+ return [];
21
+ switch (node.type) {
22
+ case "TSTypeLiteral":
23
+ return [node];
24
+ case "TSIntersectionType":
25
+ case "TSUnionType":
26
+ return (node.types ?? []).flatMap(literalsOf);
27
+ case "TSParenthesizedType":
28
+ return literalsOf(node.typeAnnotation);
29
+ default:
30
+ return [];
31
+ }
32
+ };
33
+ const nameOf = (node) => {
34
+ if (node === null || node === undefined)
35
+ return null;
36
+ if (typeof node.name === "string")
37
+ return node.name;
38
+ return typeof node.value === "string" ? node.value : null;
39
+ };
40
+ // The name a call is made by: `f()` and `x.f()` are both `f`. `x[f]()` and
41
+ // `x["f"]()` are not — a computed property is not a name a vocabulary rule can
42
+ // speak about — and neither is a private `x.#f()`. The CLI reads the same
43
+ // three cases out of TypeScript's tree; the parity suite keeps them agreeing.
44
+ const calleeName = (node) => {
45
+ const callee = node.callee;
46
+ if (callee === null || callee === undefined)
47
+ return null;
48
+ if (callee.type === "Identifier" && typeof callee.name === "string")
49
+ return callee.name;
50
+ if (callee.type !== "MemberExpression" || callee.computed === true)
51
+ return null;
52
+ const property = callee.property;
53
+ if (property?.type !== "Identifier")
54
+ return null;
55
+ return nameOf(property);
56
+ };
57
+ export const makeMembersRule = (policy) => ({
58
+ meta: {
59
+ type: "problem",
60
+ docs: {
61
+ description: "which names a file may declare or call — a port's method vocabulary, a tier's allowed hooks",
62
+ },
63
+ schema: [],
64
+ },
65
+ createOnce(context) {
66
+ let file = "";
67
+ let selected = [];
68
+ const report = (node, name, declaration) => {
69
+ const violations = evaluateMemberSite(selected, {
70
+ file,
71
+ subject: declaration === undefined ? "calls" : "members",
72
+ name,
73
+ ...(declaration === undefined
74
+ ? {}
75
+ : { in: declaration.name, declares: declaration.declares }),
76
+ });
77
+ for (const violation of violations) {
78
+ if (policy.baseline.isBaselined(violation))
79
+ continue;
80
+ context.report({ node, message: formatMessage(violation) });
81
+ }
82
+ };
83
+ // The members written in a declaration, under that declaration's name and
84
+ // kind. A computed key is not a name a vocabulary rule can speak about;
85
+ // neither is a private `#name`, an index, call or construct signature, or
86
+ // a constructor.
87
+ const declared = (declaration, declares, members) => {
88
+ for (const member of members) {
89
+ if (member.computed === true || !DECLARED_MEMBER_TYPES.has(member.type))
90
+ continue;
91
+ if (member.key?.type === "PrivateIdentifier" || member.kind === "constructor")
92
+ continue;
93
+ const name = nameOf(member.key);
94
+ if (name !== null)
95
+ report(member.key ?? member, name, { name: declaration, declares });
96
+ }
97
+ };
98
+ return {
99
+ before() {
100
+ file = toRepoRelative(policy.repoRoot, context.filename);
101
+ if (file.startsWith(".."))
102
+ return false;
103
+ selected = memberRulesSelecting(policy.memberRules, file);
104
+ return selected.length > 0;
105
+ },
106
+ TSTypeAliasDeclaration(node) {
107
+ const declaration = nameOf(node.id);
108
+ if (declaration === null)
109
+ return;
110
+ for (const literal of literalsOf(node.typeAnnotation)) {
111
+ declared(declaration, "type", literal.members ?? []);
112
+ }
113
+ },
114
+ TSInterfaceDeclaration(node) {
115
+ const declaration = nameOf(node.id);
116
+ const members = node.body?.body;
117
+ if (declaration === null || !isMemberList(members))
118
+ return;
119
+ declared(declaration, "interface", members);
120
+ },
121
+ // A named class only, as the CLI reads it: an anonymous default class has
122
+ // no name for `in`, and a class expression is a value.
123
+ ClassDeclaration(node) {
124
+ const declaration = nameOf(node.id);
125
+ const members = node.body?.body;
126
+ if (declaration === null || !isMemberList(members))
127
+ return;
128
+ declared(declaration, "class", members);
129
+ },
130
+ CallExpression(node) {
131
+ const name = calleeName(node);
132
+ if (name !== null)
133
+ report(node, name);
134
+ },
135
+ };
136
+ },
137
+ });
138
+ //# sourceMappingURL=members-rule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"members-rule.js","sourceRoot":"","sources":["../../src/members-rule.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import * as path from "node:path";
2
+ export const toRepoRelative = (repoRoot, filename) => path.relative(repoRoot, filename).replaceAll(path.sep, "/");
3
+ export const specifierOf = (node) => {
4
+ const value = node.source?.value;
5
+ return typeof value === "string" ? value : null;
6
+ };
7
+ export const requireSpecifierOf = (node) => {
8
+ if (node.callee?.type !== "Identifier" || node.callee.name !== "require")
9
+ return null;
10
+ const [first] = node.arguments ?? [];
11
+ return first?.type === "Literal" && typeof first.value === "string" ? first.value : null;
12
+ };
13
+ export const importExpressionSpecifierOf = (node) => {
14
+ const source = node.source;
15
+ return source?.type === "Literal" && typeof source.value === "string" ? source.value : null;
16
+ };
17
+ export const importEqualsSpecifierOf = (node) => {
18
+ const reference = node.moduleReference;
19
+ if (reference?.type !== "TSExternalModuleReference")
20
+ return null;
21
+ const value = reference.expression?.value;
22
+ return typeof value === "string" ? value : null;
23
+ };
24
+ //# sourceMappingURL=oxlint-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oxlint-api.js","sourceRoot":"","sources":["../../src/oxlint-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAkBlC,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,QAAgB,EAAE,QAAgB,EAAU,EAAE,CAC3E,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAE9D,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAgB,EAAiB,EAAE;IAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;IACjC,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAClD,CAAC,CAAC;AAyBF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAc,EAAiB,EAAE;IAClE,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,KAAK,YAAY,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACtF,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC;IACrC,OAAO,KAAK,EAAE,IAAI,KAAK,SAAS,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3F,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,IAA0B,EAAiB,EAAE;IACvF,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,OAAO,MAAM,EAAE,IAAI,KAAK,SAAS,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9F,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,IAAsB,EAAiB,EAAE;IAC/E,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC;IACvC,IAAI,SAAS,EAAE,IAAI,KAAK,2BAA2B;QAAE,OAAO,IAAI,CAAC;IACjE,MAAM,KAAK,GAAG,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC;IAC1C,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAClD,CAAC,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { DEFAULT_CONFIG_FILENAME, loadPolicyFromFile } from "./config-loader.js";
2
+ import { makeExportsRule } from "./exports-rule.js";
3
+ import { makeImportsRule } from "./imports-rule.js";
4
+ import { makeMembersRule } from "./members-rule.js";
5
+ import { makeStructureRule } from "./structure-rule.js";
6
+ import { makeSurfaceRule } from "./surface-rule.js";
7
+ // oxlint imports this module once per lint run and reads `rules` synchronously,
8
+ // so the policy is loaded here rather than inside a rule. A load failure throws
9
+ // out of the import and fails the run — which is the point: a plugin that came
10
+ // up with no policy would report nothing and be indistinguishable from a clean
11
+ // codebase.
12
+ const policy = await loadPolicyFromFile(process.env.ARCHITECTURE_ROOT ?? process.cwd(), process.env.ARCHITECTURE_CONFIG ?? DEFAULT_CONFIG_FILENAME);
13
+ // A manifest written in a shape on its way out still loads; it says so once,
14
+ // here, rather than on every file.
15
+ for (const notice of policy.notices) {
16
+ process.stderr.write(`[architecture] deprecated: ${notice}\n`);
17
+ }
18
+ export const rules = {
19
+ imports: makeImportsRule(policy),
20
+ exports: makeExportsRule(policy),
21
+ members: makeMembersRule(policy),
22
+ structure: makeStructureRule(policy),
23
+ surface: makeSurfaceRule(policy),
24
+ };
25
+ const plugin = {
26
+ meta: { name: "architecture" },
27
+ rules,
28
+ };
29
+ export default plugin;
30
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,gFAAgF;AAChF,gFAAgF;AAChF,+EAA+E;AAC/E,+EAA+E;AAC/E,YAAY;AACZ,MAAM,MAAM,GAAG,MAAM,kBAAkB,CACrC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,OAAO,CAAC,GAAG,EAAE,EAC9C,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,uBAAuB,CAC3D,CAAC;AAEF,6EAA6E;AAC7E,mCAAmC;AACnC,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;IACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,MAAM,IAAI,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAMd;IACF,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC;IAChC,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC;IAChC,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC;IAChC,SAAS,EAAE,iBAAiB,CAAC,MAAM,CAAC;IACpC,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,GAA+E;IACzF,IAAI,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE;IAC9B,KAAK;CACN,CAAC;AAEF,eAAe,MAAM,CAAC"}