@amritk/lint 0.4.8 → 0.5.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.
Files changed (107) hide show
  1. package/AI.md +73 -4
  2. package/README.md +65 -14
  3. package/dist/core/filter-expression.js +17 -4
  4. package/dist/core/filter.js +82 -35
  5. package/dist/core/glob.js +25 -31
  6. package/dist/core/jsonpath.d.ts +9 -1
  7. package/dist/core/jsonpath.js +74 -16
  8. package/dist/core/lint.d.ts +8 -1
  9. package/dist/core/lint.js +4 -18
  10. package/dist/core/order.d.ts +30 -0
  11. package/dist/core/order.js +36 -0
  12. package/dist/core/pointers.d.ts +3 -2
  13. package/dist/core/pointers.js +27 -18
  14. package/dist/core/ruleset.js +62 -20
  15. package/dist/core/runner.js +16 -23
  16. package/dist/core/severity.d.ts +11 -0
  17. package/dist/core/severity.js +13 -0
  18. package/dist/fix/apply.js +6 -1
  19. package/dist/fix/plugin.js +2 -1
  20. package/dist/functions/or.js +1 -1
  21. package/dist/functions/ref-index.d.ts +15 -0
  22. package/dist/functions/ref-index.js +33 -0
  23. package/dist/functions/schema.d.ts +15 -0
  24. package/dist/functions/schema.js +2 -0
  25. package/dist/functions/typed-enum.js +2 -1
  26. package/dist/functions/unreferenced-reusable-object.js +3 -20
  27. package/dist/functions/xor.js +1 -1
  28. package/dist/index.d.ts +16 -16
  29. package/dist/index.js +24 -93
  30. package/dist/rules/asyncapi/asyncapi.d.ts +3 -0
  31. package/dist/rules/asyncapi/asyncapi.js +563 -0
  32. package/dist/rules/asyncapi/formats.d.ts +23 -0
  33. package/dist/rules/asyncapi/formats.js +49 -0
  34. package/dist/rules/asyncapi/functions/aas-server-variables.d.ts +8 -0
  35. package/dist/rules/asyncapi/functions/aas-server-variables.js +5 -0
  36. package/dist/rules/asyncapi/functions/aas-tags-unique.d.ts +2 -0
  37. package/dist/rules/asyncapi/functions/aas-tags-unique.js +5 -0
  38. package/dist/rules/asyncapi/functions/asyncapi-channel-parameters.d.ts +7 -0
  39. package/dist/rules/asyncapi/functions/asyncapi-channel-parameters.js +46 -0
  40. package/dist/rules/asyncapi/functions/asyncapi-channel-servers.d.ts +27 -0
  41. package/dist/rules/asyncapi/functions/asyncapi-channel-servers.js +61 -0
  42. package/dist/rules/asyncapi/functions/asyncapi-document-schema.d.ts +17 -0
  43. package/dist/rules/asyncapi/functions/asyncapi-document-schema.js +12 -0
  44. package/dist/rules/asyncapi/functions/asyncapi-headers-object.d.ts +23 -0
  45. package/dist/rules/asyncapi/functions/asyncapi-headers-object.js +25 -0
  46. package/dist/rules/asyncapi/functions/asyncapi-message-examples.d.ts +8 -0
  47. package/dist/rules/asyncapi/functions/asyncapi-message-examples.js +42 -0
  48. package/dist/rules/asyncapi/functions/asyncapi-message-id-unique.d.ts +3 -0
  49. package/dist/rules/asyncapi/functions/asyncapi-message-id-unique.js +30 -0
  50. package/dist/rules/asyncapi/functions/asyncapi-operation-id-unique.d.ts +3 -0
  51. package/dist/rules/asyncapi/functions/asyncapi-operation-id-unique.js +30 -0
  52. package/dist/rules/asyncapi/functions/asyncapi-payload.d.ts +14 -0
  53. package/dist/rules/asyncapi/functions/asyncapi-payload.js +32 -0
  54. package/dist/rules/asyncapi/functions/asyncapi-schema-validation.d.ts +11 -0
  55. package/dist/rules/asyncapi/functions/asyncapi-schema-validation.js +17 -0
  56. package/dist/rules/asyncapi/functions/asyncapi-security.d.ts +22 -0
  57. package/dist/rules/asyncapi/functions/asyncapi-security.js +78 -0
  58. package/dist/rules/asyncapi/functions/helpers.d.ts +51 -0
  59. package/dist/rules/asyncapi/functions/helpers.js +91 -0
  60. package/dist/rules/asyncapi/functions/index.d.ts +15 -0
  61. package/dist/rules/asyncapi/functions/index.js +53 -0
  62. package/dist/rules/asyncapi/functions/pointer.d.ts +11 -0
  63. package/dist/rules/asyncapi/functions/pointer.js +11 -0
  64. package/dist/rules/asyncapi/functions/schema-format.d.ts +4 -0
  65. package/dist/rules/asyncapi/functions/schema-format.js +7 -0
  66. package/dist/rules/asyncapi/index.d.ts +33 -0
  67. package/dist/rules/asyncapi/index.js +60 -0
  68. package/dist/rules/asyncapi/schemas/aas20.d.ts +2 -0
  69. package/dist/rules/asyncapi/schemas/aas20.js +4 -0
  70. package/dist/rules/asyncapi/schemas/aas21.d.ts +2 -0
  71. package/dist/rules/asyncapi/schemas/aas21.js +4 -0
  72. package/dist/rules/asyncapi/schemas/aas22.d.ts +2 -0
  73. package/dist/rules/asyncapi/schemas/aas22.js +4 -0
  74. package/dist/rules/asyncapi/schemas/aas23.d.ts +2 -0
  75. package/dist/rules/asyncapi/schemas/aas23.js +4 -0
  76. package/dist/rules/asyncapi/schemas/aas24.d.ts +2 -0
  77. package/dist/rules/asyncapi/schemas/aas24.js +4 -0
  78. package/dist/rules/asyncapi/schemas/aas25.d.ts +2 -0
  79. package/dist/rules/asyncapi/schemas/aas25.js +4 -0
  80. package/dist/rules/asyncapi/schemas/aas26.d.ts +2 -0
  81. package/dist/rules/asyncapi/schemas/aas26.js +4 -0
  82. package/dist/rules/asyncapi/schemas/aas30.d.ts +2 -0
  83. package/dist/rules/asyncapi/schemas/aas30.js +4 -0
  84. package/dist/rules/asyncapi/schemas/index.d.ts +20 -0
  85. package/dist/rules/asyncapi/schemas/index.js +45 -0
  86. package/dist/rules/openapi/fixers.d.ts +6 -2
  87. package/dist/rules/openapi/fixers.js +2 -0
  88. package/dist/rules/openapi/functions/oas-discriminator.js +1 -1
  89. package/dist/rules/openapi/functions/oas-mutually-exclusive.js +1 -1
  90. package/dist/rules/openapi/functions/oas-op-security-defined.js +1 -1
  91. package/dist/rules/openapi/functions/oas-server-variables.d.ts +6 -6
  92. package/dist/rules/openapi/functions/oas-server-variables.js +2 -45
  93. package/dist/rules/openapi/functions/oas-tags-unique.d.ts +2 -3
  94. package/dist/rules/openapi/functions/oas-tags-unique.js +2 -16
  95. package/dist/rules/openapi/functions/oas-unused-component.js +3 -25
  96. package/dist/rules/openapi/index.d.ts +9 -2
  97. package/dist/rules/openapi/index.js +15 -70
  98. package/dist/rules/openapi/schemas/index.js +5 -1
  99. package/dist/rules/shared/helpers.d.ts +2 -0
  100. package/dist/rules/shared/helpers.js +4 -0
  101. package/dist/rules/shared/server-variables.d.ts +27 -0
  102. package/dist/rules/shared/server-variables.js +55 -0
  103. package/dist/rules/shared/tags-unique.d.ts +7 -0
  104. package/dist/rules/shared/tags-unique.js +19 -0
  105. package/dist/ruleset-files.d.ts +47 -0
  106. package/dist/ruleset-files.js +92 -0
  107. package/package.json +8 -4
@@ -0,0 +1,11 @@
1
+ import { DiagnosticSeverity } from '../parsers/index.js';
2
+ /**
3
+ * Resolves a severity name, or `undefined` when it is not one we know.
4
+ *
5
+ * `ownKey`, never a bare index or `in`: these names come from rulesets, so
6
+ * `severity: 'constructor'` otherwise resolved to `Object` itself — and a
7
+ * severity that is a function is neither `'off'` nor `undefined`, so it sailed
8
+ * past both fallbacks and every later comparison against
9
+ * `DiagnosticSeverity.Error` read false.
10
+ */
11
+ export declare const severityFromName: (name: string) => DiagnosticSeverity | 'off' | undefined;
@@ -0,0 +1,13 @@
1
+ import { DiagnosticSeverity } from "../parsers/index.js";
2
+ import { ownKey } from "./own-key.js";
3
+ const SEVERITY_NAMES = {
4
+ error: DiagnosticSeverity.Error,
5
+ warn: DiagnosticSeverity.Warning,
6
+ info: DiagnosticSeverity.Information,
7
+ hint: DiagnosticSeverity.Hint,
8
+ off: "off"
9
+ };
10
+ const severityFromName = (name) => ownKey(SEVERITY_NAMES, name);
11
+ export {
12
+ severityFromName
13
+ };
package/dist/fix/apply.js CHANGED
@@ -19,7 +19,12 @@ const applyFixes = (input, format, data, diagnostics, fixers, options = {}) => {
19
19
  continue;
20
20
  if (safeOnly && fixer.safe === false)
21
21
  continue;
22
- const produced = fixer.fix({ diagnostic, data, format });
22
+ let produced;
23
+ try {
24
+ produced = fixer.fix({ diagnostic, data, format });
25
+ } catch {
26
+ continue;
27
+ }
23
28
  if (!produced)
24
29
  continue;
25
30
  const ops2 = Array.isArray(produced) ? produced : [produced];
@@ -3,7 +3,8 @@ const FIX_PLUGIN_NAME = "fix";
3
3
  const createFixPlugin = (fixers, options = {}) => ({
4
4
  name: FIX_PLUGIN_NAME,
5
5
  afterLint: (diagnostics, context) => {
6
- const result = applyFixes(context.input, context.format, context.document.data, diagnostics, fixers, options);
6
+ const own = diagnostics.filter((diagnostic) => diagnostic.source === context.source);
7
+ const result = applyFixes(context.input, context.format, context.document.data, own, fixers, options);
7
8
  if (!result.changed)
8
9
  return void 0;
9
10
  const data = { applied: result.applied };
@@ -4,7 +4,7 @@ const or = (input, options) => {
4
4
  const properties = options?.properties;
5
5
  if (!Array.isArray(properties) || properties.length < 2)
6
6
  return [];
7
- const present = properties.filter((property) => property in input);
7
+ const present = properties.filter((property) => Object.hasOwn(input, property));
8
8
  if (present.length > 0)
9
9
  return [];
10
10
  if (properties.length > 4) {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Indexes every `$ref` target in a document, plus each of its `/`-delimited
3
+ * ancestors, so "is this reusable object referenced?" is a single set lookup.
4
+ *
5
+ * A component counts as used when a `$ref` targets it *or* points into it
6
+ * (`#/components/schemas/Pet/properties/id` still uses `Pet`). Testing that
7
+ * per component meant scanning every ref for a prefix match — O(components ×
8
+ * refs), and the unreferenced-object rule copied the whole ref set into a fresh
9
+ * array on each component to do it. Recording the ancestors once turns the whole
10
+ * rule into O(refs + components).
11
+ *
12
+ * Shared by `unreferencedReusableObject` and the OpenAPI `oasUnusedComponent`,
13
+ * which had a byte-identical copy of the walk between them.
14
+ */
15
+ export declare const collectReferencedPointers: (document: unknown) => Set<string>;
@@ -0,0 +1,33 @@
1
+ const collectReferencedPointers = (document) => {
2
+ const pointers = /* @__PURE__ */ new Set();
3
+ const add = (ref) => {
4
+ pointers.add(ref);
5
+ for (let slash = ref.lastIndexOf("/"); slash > 0; slash = ref.lastIndexOf("/", slash - 1)) {
6
+ const prefix = ref.slice(0, slash);
7
+ if (pointers.has(prefix))
8
+ return;
9
+ pointers.add(prefix);
10
+ }
11
+ };
12
+ const stack = [document];
13
+ while (stack.length > 0) {
14
+ const node = stack.pop();
15
+ if (Array.isArray(node)) {
16
+ for (const item of node)
17
+ stack.push(item);
18
+ continue;
19
+ }
20
+ if (typeof node !== "object" || node === null)
21
+ continue;
22
+ for (const [key, value] of Object.entries(node)) {
23
+ if (key === "$ref" && typeof value === "string")
24
+ add(value);
25
+ else
26
+ stack.push(value);
27
+ }
28
+ }
29
+ return pointers;
30
+ };
31
+ export {
32
+ collectReferencedPointers
33
+ };
@@ -16,6 +16,21 @@ export type ISchemaOptions = {
16
16
  * without the caller naming a dialect.
17
17
  */
18
18
  dialect?: string;
19
+ /**
20
+ * Report nothing when the schema itself cannot be built or run, instead of
21
+ * reporting why.
22
+ *
23
+ * The default suits a schema written in the *ruleset*: if that one is
24
+ * unusable, its author wants to hear about it. A schema taken from the
25
+ * *document* — a message payload, a parameter's schema — is different. It can
26
+ * legitimately carry a `$ref` this package cannot resolve (an external file, or
27
+ * anything at all when no resolver was injected), and there the validator's own
28
+ * complaint is not a finding about the document: it is an error-severity
29
+ * diagnostic, on a valid document, whose text describes this package's API.
30
+ * Callers validating document-supplied schemas pass `true` and get silence
31
+ * where they cannot judge.
32
+ */
33
+ skipUnusableSchema?: boolean;
19
34
  };
20
35
  /** Validates a value against a JSON Schema supplied in the rule's options. */
21
36
  export declare const schema: RulesetFunction<unknown, ISchemaOptions>;
@@ -83,6 +83,8 @@ const schema = (input, options, context) => {
83
83
  try {
84
84
  result = getValidator(options.schema)(input);
85
85
  } catch (error) {
86
+ if (options.skipUnusableSchema)
87
+ return [];
86
88
  return [
87
89
  {
88
90
  message: `Invalid schema: ${error instanceof Error ? error.message : String(error)}`,
@@ -1,3 +1,4 @@
1
+ import { ownKey } from "../core/own-key.js";
1
2
  const JS_TYPES = {
2
3
  string: (v) => typeof v === "string",
3
4
  number: (v) => typeof v === "number",
@@ -18,7 +19,7 @@ const typedEnum = (input, _options, context) => {
18
19
  if ((input["nullable"] === true || input["x-nullable"] === true) && !types.includes("null")) {
19
20
  types.push("null");
20
21
  }
21
- const checkers = types.map((type) => JS_TYPES[String(type)]).filter((fn) => Boolean(fn));
22
+ const checkers = types.map((type) => ownKey(JS_TYPES, String(type))).filter((fn) => fn !== void 0);
22
23
  if (checkers.length === 0)
23
24
  return [];
24
25
  const results = [];
@@ -1,18 +1,4 @@
1
- const collectRefs = (node, into) => {
2
- if (Array.isArray(node)) {
3
- for (const item of node)
4
- collectRefs(item, into);
5
- return;
6
- }
7
- if (typeof node === "object" && node !== null) {
8
- for (const [key, value] of Object.entries(node)) {
9
- if (key === "$ref" && typeof value === "string")
10
- into.add(value);
11
- else
12
- collectRefs(value, into);
13
- }
14
- }
15
- };
1
+ import { collectReferencedPointers } from "./ref-index.js";
16
2
  const escapePointerSegment = (key) => key.replace(/~/g, "~0").replace(/\//g, "~1");
17
3
  const unreferencedReusableObject = (input, options, context) => {
18
4
  if (typeof input !== "object" || input === null)
@@ -20,13 +6,10 @@ const unreferencedReusableObject = (input, options, context) => {
20
6
  const location = options?.reusableObjectsLocation;
21
7
  if (!location)
22
8
  return [];
23
- const refs = /* @__PURE__ */ new Set();
24
- collectRefs(context.document.data, refs);
9
+ const referenced = collectReferencedPointers(context.document.data);
25
10
  const results = [];
26
11
  for (const key of Object.keys(input)) {
27
- const base = `${location}/${escapePointerSegment(key)}`;
28
- const referenced = refs.has(base) || [...refs].some((ref) => ref.startsWith(`${base}/`));
29
- if (!referenced) {
12
+ if (!referenced.has(`${location}/${escapePointerSegment(key)}`)) {
30
13
  results.push({
31
14
  message: "This reusable object is never referenced",
32
15
  path: [...context.path, key]
@@ -4,7 +4,7 @@ const xor = (input, options) => {
4
4
  const properties = options?.properties;
5
5
  if (!Array.isArray(properties) || properties.length < 2)
6
6
  return [];
7
- const present = properties.filter((property) => property in input);
7
+ const present = properties.filter((property) => Object.hasOwn(input, property));
8
8
  if (present.length !== 1) {
9
9
  return [{ message: `Exactly one of ${properties.map((p) => `"${p}"`).join(", ")} must be defined` }];
10
10
  }
package/dist/index.d.ts CHANGED
@@ -1,24 +1,12 @@
1
1
  import { type IDocumentOptions, type LintPlugin, type LintResolver, type ResolvedExtend, type Ruleset } from './core/index.js';
2
2
  import type { IDiagnostic, RulesetDefinition } from './core/types.js';
3
3
  import { type AppliedFix, type FixerRegistry } from './fix/index.js';
4
+ import { type IRulesetTrustOptions } from './ruleset-files.js';
4
5
  export { type AliasDefinition, type CompiledPath, compileQuery, createDocument, createLinter, type Document, detectFormats, type ExtendModifier, type ExtendResolver, type Format, globToRegExp, type IDocumentOptions, type IQueryMatch, type IRulesetProblem, type IRunOptions, type Linter, type LintOptions, type LintPlugin, type LintPluginContext, type LintPluginResult, type LintResolver, type LintResolverResult, type LintResult, lint, lintWithResult, matchesGlob, type PluginRunResult, pointerToPath, query, queryCompiled, queryMany, type ResolvedExtend, type Ruleset, type RulesetOptions, resolveSourceOrigin, resolveSourceOriginFromMap, resolveSourcePath, runPlugins, validateRuleset, } from './core/index.js';
5
6
  export { type AppliedFix, type ApplyFixesOptions, applyFixes, createFixPlugin, type EditOp, FIX_PLUGIN_NAME, type FixContext, type Fixer, type FixerRegistry, type FixPluginData, type FixResult, type ParserFormat, } from './fix/index.js';
6
7
  export { alphabetical, builtinFunctions, type CasingType, casing, defined, enumeration, falsy, type IAlphabeticalOptions, type ICasingOptions, type IOrOptions, type ISchemaOptions, type IUnreferencedReusableObjectOptions, type IXorOptions, length, or, pattern, schema, truthy, typedEnum, undefinedFn, unreferencedReusableObject, xor, } from './functions/index.js';
7
8
  export { detectFormat, parseWithPointers } from './parsers/index.js';
8
- /**
9
- * An optional directory that everything a ruleset pulls off disk — `extends`
10
- * targets and custom function modules — must resolve inside.
11
- *
12
- * This is opt-in and off by default, because `basePath` on its own is only a
13
- * *resolution origin*: an `extends` of `/etc/thing.js` or `../../../elsewhere`
14
- * resolves and loads exactly as written. See the "Trust boundary" section of the
15
- * README — a ruleset that can name a `.js` file can run code, restricted root or
16
- * not. This narrows *which* files it can name; it is not a sandbox.
17
- */
18
- export type IRulesetTrustOptions = {
19
- /** Directory that `extends` files and custom functions must resolve under. */
20
- restrictTo?: string;
21
- };
9
+ export type { IRulesetTrustOptions } from './ruleset-files.js';
22
10
  /**
23
11
  * Resolves an `extends` reference to a ruleset definition. Supports:
24
12
  * - local file paths (relative to `basePath`, or absolute): `.yaml` / `.yml` / `.json` / `.js`,
@@ -49,8 +37,20 @@ export declare const resolveNamedRuleset: (name: string, basePath?: string, opti
49
37
  export declare const createRuleset: (definition?: RulesetDefinition, basePath?: string, options?: IRulesetTrustOptions) => Ruleset;
50
38
  /** Options for {@link lintDocument}: the document options plus ruleset controls. */
51
39
  export type ILintOptions = IDocumentOptions & {
52
- /** The ruleset definition to evaluate. When omitted, no rules run. */
53
- ruleset?: RulesetDefinition;
40
+ /**
41
+ * The rules to evaluate: either a definition to build with the built-in
42
+ * functions, or a {@link Ruleset} that is already built. When omitted, no rules
43
+ * run.
44
+ *
45
+ * Pass a built one to use a preset that brings its own functions and format
46
+ * detectors — `createOpenApiRuleset()` from `@amritk/lint/rules/openapi`, say.
47
+ * A definition alone cannot express those, so an OpenAPI ruleset handed over as
48
+ * data would have every rule skipped: its functions are unknown here, and its
49
+ * `formats` gate matches nothing against an empty format registry.
50
+ * `rulesetBasePath` and `restrictTo` only apply while *building*, so they are
51
+ * ignored for a ruleset that already is.
52
+ */
53
+ ruleset?: RulesetDefinition | Ruleset;
54
54
  /** Directory that the ruleset's string `extends` references resolve relative to. */
55
55
  rulesetBasePath?: string;
56
56
  /**
package/dist/index.js CHANGED
@@ -1,99 +1,26 @@
1
- import { readFileSync } from "node:fs";
2
- import { createRequire } from "node:module";
3
- import { dirname, isAbsolute, resolve as resolvePath, sep } from "node:path";
4
1
  import { createBoundedCache, createRuleset as createCoreRuleset, lintWithResult } from "./core/index.js";
5
2
  import { createFixPlugin, FIX_PLUGIN_NAME } from "./fix/index.js";
6
3
  import { builtinFunctions } from "./functions/index.js";
7
- import { parseWithPointers } from "./parsers/index.js";
4
+ import { collectCustomFunctions, resolveRulesetFile } from "./ruleset-files.js";
8
5
  import { compileQuery, createDocument, createLinter, detectFormats, globToRegExp, lint, lintWithResult as lintWithResult2, matchesGlob, pointerToPath, query, queryCompiled, queryMany, resolveSourceOrigin, resolveSourceOriginFromMap, resolveSourcePath, runPlugins, validateRuleset } from "./core/index.js";
9
6
  import { applyFixes, createFixPlugin as createFixPlugin2, FIX_PLUGIN_NAME as FIX_PLUGIN_NAME2 } from "./fix/index.js";
10
7
  import { alphabetical, builtinFunctions as builtinFunctions2, casing, defined, enumeration, falsy, length, or, pattern, schema, truthy, typedEnum, undefinedFn, unreferencedReusableObject, xor } from "./functions/index.js";
11
- import { detectFormat, parseWithPointers as parseWithPointers2 } from "./parsers/index.js";
12
- const require2 = createRequire(import.meta.url);
13
- const loadRulesetFile = (file) => {
14
- if (/\.(ya?ml|json)$/i.test(file)) {
15
- return parseWithPointers(readFileSync(file, "utf8")).data;
16
- }
17
- const module = require2(file);
18
- return module.default ?? module;
19
- };
20
- const assertWithinRoot = (file, restrictTo, what) => {
21
- if (restrictTo === void 0)
22
- return;
23
- const root = resolvePath(restrictTo);
24
- const resolved = resolvePath(file);
25
- if (resolved !== root && !resolved.startsWith(root.endsWith(sep) ? root : `${root}${sep}`)) {
26
- throw new Error(`${what} "${file}" resolves outside the permitted root ${root}`);
27
- }
28
- };
29
- const resolveNamedRuleset = (name, basePath = process.cwd(), options = {}) => {
30
- if (name.startsWith(".") || isAbsolute(name)) {
31
- const file2 = resolvePath(basePath, name);
32
- assertWithinRoot(file2, options.restrictTo, "Extended ruleset");
33
- return { definition: loadRulesetFile(file2), basePath: dirname(file2) };
34
- }
35
- let file;
36
- try {
37
- file = require2.resolve(name, { paths: [basePath] });
38
- } catch {
39
- throw new Error(`Cannot resolve extended ruleset "${name}" from ${basePath}`);
40
- }
41
- assertWithinRoot(file, options.restrictTo, "Extended ruleset");
42
- return { definition: loadRulesetFile(file), basePath: dirname(file) };
43
- };
44
- const loadFunctionByName = (basePath, dir, name, restrictTo) => {
45
- const baseFile = resolvePath(basePath, dir, name);
46
- assertWithinRoot(baseFile, restrictTo, "Custom function");
47
- for (const candidate of [baseFile, `${baseFile}.js`, `${baseFile}.cjs`, `${baseFile}.mjs`]) {
48
- try {
49
- const resolvedFile = require2.resolve(candidate);
50
- const module = require2(resolvedFile);
51
- const fn = module.default ?? module;
52
- if (typeof fn !== "function")
53
- throw new Error(`"${name}" did not export a function`);
54
- return fn;
55
- } catch (error) {
56
- if (error.code !== "MODULE_NOT_FOUND")
57
- throw error;
58
- }
59
- }
60
- throw new Error(`Cannot resolve custom function "${name}" from ${resolvePath(basePath, dir)}`);
61
- };
62
- const collectCustomFunctions = (definition, basePath, into, seen, restrictTo) => {
63
- if (seen.has(definition))
64
- return;
65
- seen.add(definition);
66
- if (definition.extends) {
67
- const entries = Array.isArray(definition.extends) ? definition.extends : [definition.extends];
68
- for (const entry of entries) {
69
- const target = Array.isArray(entry) ? entry[0] : entry;
70
- if (typeof target === "string") {
71
- const key = `${basePath}\0${target}`;
72
- if (seen.has(key))
73
- continue;
74
- seen.add(key);
75
- const resolved = resolveNamedRuleset(target, basePath, { ...restrictTo !== void 0 ? { restrictTo } : {} });
76
- collectCustomFunctions(resolved.definition, resolved.basePath, into, seen, restrictTo);
77
- } else {
78
- collectCustomFunctions(target, basePath, into, seen, restrictTo);
79
- }
80
- }
81
- }
82
- if (Array.isArray(definition.functions)) {
83
- const dir = definition.functionsDir ?? "functions";
84
- for (const name of definition.functions)
85
- into[name] = loadFunctionByName(basePath, dir, name, restrictTo);
86
- }
87
- };
8
+ import { detectFormat, parseWithPointers } from "./parsers/index.js";
9
+ const resolveNamedRuleset = (name, basePath = process.cwd(), options = {}) => resolveRulesetFile(name, basePath, options);
88
10
  const buildRuleset = (definition, basePath, restrictTo) => {
11
+ const trust = restrictTo !== void 0 ? { restrictTo } : {};
12
+ const resolve = (name, from) => resolveNamedRuleset(name, from, trust);
89
13
  let functions = builtinFunctions;
90
14
  const custom = {};
91
- collectCustomFunctions(definition, basePath ?? process.cwd(), custom, /* @__PURE__ */ new Set(), restrictTo);
15
+ collectCustomFunctions(definition, basePath ?? process.cwd(), custom, /* @__PURE__ */ new Set(), {
16
+ ...trust,
17
+ resolveExtend: resolve
18
+ });
92
19
  if (Object.keys(custom).length > 0)
93
20
  functions = { ...builtinFunctions, ...custom };
94
21
  return createCoreRuleset(definition, {
95
22
  functions,
96
- resolve: (name, from) => resolveNamedRuleset(name, from, { ...restrictTo !== void 0 ? { restrictTo } : {} }),
23
+ resolve,
97
24
  ...basePath !== void 0 ? { basePath } : {}
98
25
  });
99
26
  };
@@ -102,6 +29,9 @@ const emptyRulesets = createBoundedCache(16);
102
29
  const createRuleset = (definition, basePath, options = {}) => {
103
30
  const { restrictTo } = options;
104
31
  const key = `${restrictTo ?? ""}\0${basePath ?? process.cwd()}`;
32
+ if (definition !== void 0 && (typeof definition !== "object" || definition === null)) {
33
+ return buildRuleset(definition, basePath, restrictTo);
34
+ }
105
35
  if (definition === void 0) {
106
36
  const cached2 = emptyRulesets.get(key);
107
37
  if (cached2)
@@ -122,13 +52,16 @@ const createRuleset = (definition, basePath, options = {}) => {
122
52
  byBasePath.set(key, built);
123
53
  return built;
124
54
  };
55
+ const isBuiltRuleset = (ruleset) => typeof ruleset.getFunction === "function";
125
56
  const lintDocument = async (input, options = {}) => (await lintDocumentWithResult(input, options)).diagnostics;
126
57
  const lintDocumentWithResult = async (input, options = {}) => {
127
- const { ruleset: rulesetDefinition, rulesetBasePath, restrictTo, ...rest } = options;
128
- const ruleset = createRuleset(rulesetDefinition, rulesetBasePath, {
129
- ...restrictTo !== void 0 ? { restrictTo } : {}
130
- });
131
- return runLint(input, ruleset, rest);
58
+ const { ruleset, rulesetBasePath, restrictTo, ...rest } = options;
59
+ return runLint(input, toRuleset(ruleset, rulesetBasePath, restrictTo), rest);
60
+ };
61
+ const toRuleset = (ruleset, basePath, restrictTo) => {
62
+ if (ruleset !== void 0 && isBuiltRuleset(ruleset))
63
+ return ruleset;
64
+ return createRuleset(ruleset, basePath, { ...restrictTo !== void 0 ? { restrictTo } : {} });
132
65
  };
133
66
  const runLint = async (input, ruleset, options) => {
134
67
  const { resolve, plugins, ...documentOptions } = options;
@@ -142,11 +75,9 @@ const runLint = async (input, ruleset, options) => {
142
75
  const appliedKey = (fix) => `${fix.code}\0${fix.path.map((segment) => typeof segment === "number" ? `[${segment}]` : `.${segment}`).join("")}`;
143
76
  const MAX_FIX_PASSES = 10;
144
77
  const fixDocument = async (input, options = {}) => {
145
- const { fixers = {}, safeOnly, ruleset: rulesetDefinition, rulesetBasePath, restrictTo, ...lintOptions } = options;
78
+ const { fixers = {}, safeOnly, ruleset: rulesetOption, rulesetBasePath, restrictTo, ...lintOptions } = options;
146
79
  const plugin = createFixPlugin(fixers, { safeOnly: safeOnly !== false });
147
- const ruleset = createRuleset(rulesetDefinition, rulesetBasePath, {
148
- ...restrictTo !== void 0 ? { restrictTo } : {}
149
- });
80
+ const ruleset = toRuleset(rulesetOption, rulesetBasePath, restrictTo);
150
81
  let current = input;
151
82
  let passes = 0;
152
83
  let converged = false;
@@ -199,7 +130,7 @@ export {
199
130
  lintWithResult2 as lintWithResult,
200
131
  matchesGlob,
201
132
  or,
202
- parseWithPointers2 as parseWithPointers,
133
+ parseWithPointers,
203
134
  pattern,
204
135
  pointerToPath,
205
136
  query,
@@ -0,0 +1,3 @@
1
+ import type { RulesetDefinition } from '../../core/types.js';
2
+ /** Loupe's built-in AsyncAPI ruleset (`loupe:asyncapi`). */
3
+ export declare const asyncapi: RulesetDefinition;