diff --git a/apps/oxlint/package.json b/apps/oxlint/package.json index 38ec2033d0ed6..2a1a93b875989 100644 --- a/apps/oxlint/package.json +++ b/apps/oxlint/package.json @@ -34,6 +34,7 @@ "build-napi-test": "pnpm run build-napi --features testing", "build-napi-release": "pnpm run build-napi --release --features allocator", "build-js": "node scripts/build.ts", + "generate-config-types": "node scripts/generate-config-types.ts", "test": "vitest --dir ./test run", "init-conformance": "cd conformance; ./init.sh", "conformance": "cross-env NODE_DISABLE_COLORS=1 tsx ./conformance/src/index.ts" @@ -54,6 +55,7 @@ "eslint-vitest-rule-tester": "^3.0.1", "esquery": "^1.6.0", "execa": "^9.6.0", + "json-schema-to-typescript": "^15.0.4", "json-stable-stringify-without-jsonify": "^1.0.1", "oxc-parser": "^0.111.0", "rolldown": "catalog:", diff --git a/apps/oxlint/scripts/generate-config-types.ts b/apps/oxlint/scripts/generate-config-types.ts new file mode 100644 index 0000000000000..03ccf28e23005 --- /dev/null +++ b/apps/oxlint/scripts/generate-config-types.ts @@ -0,0 +1,31 @@ +// oxlint-disable no-console + +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { compile } from "json-schema-to-typescript"; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const oxlintDir = resolve(scriptDir, ".."); +const repoRoot = resolve(oxlintDir, "..", ".."); + +const schemaPath = resolve(repoRoot, "npm/oxlint/configuration_schema.json"); +const outputPath = resolve(oxlintDir, "src-js/package/config.generated.ts"); + +if (!existsSync(schemaPath)) { + throw new Error(`Missing schema at ${schemaPath}. Run just linter-schema-json first.`); +} + +const schema = JSON.parse(readFileSync(schemaPath, "utf8")); + +const bannerComment = + "/*\n" + + " * This file is generated from npm/oxlint/configuration_schema.json.\n" + + " * Run `just linter-config-ts` to regenerate.\n" + + " */"; + +const ts = await compile(schema, "OxlintConfig", { bannerComment }); + +writeFileSync(outputPath, ts); +console.log(`Wrote ${outputPath}`); diff --git a/apps/oxlint/src-js/package/config.generated.ts b/apps/oxlint/src-js/package/config.generated.ts new file mode 100644 index 0000000000000..0d188d9cc2b8c --- /dev/null +++ b/apps/oxlint/src-js/package/config.generated.ts @@ -0,0 +1,559 @@ +/* + * This file is generated from npm/oxlint/configuration_schema.json. + * Run `just linter-config-ts` to regenerate. + */ + +export type AllowWarnDeny = ("allow" | "off" | "warn" | "error" | "deny") | number; +export type GlobalValue = "readonly" | "writable" | "off"; +export type ExternalPluginEntry = + | string + | { + /** + * Custom name/alias for the plugin. + * + * Note: The following plugin names are reserved because they are implemented natively in Rust within oxlint and cannot be used for JS plugins: + * - react (includes react-hooks) + * - unicorn + * - typescript (includes @typescript-eslint) + * - oxc + * - import (includes import-x) + * - jsdoc + * - jest + * - vitest + * - jsx-a11y + * - nextjs + * - react-perf + * - promise + * - node + * - vue + * - eslint + * + * If you need to use the JavaScript version of any of these plugins, provide a custom alias to avoid conflicts. + */ + name: string; + /** + * Path or package name of the plugin + */ + specifier: string; + }; +/** + * A set of glob patterns. + */ +export type GlobSet = string[]; +export type LintPluginOptionsSchema = + | "eslint" + | "react" + | "unicorn" + | "typescript" + | "oxc" + | "import" + | "jsdoc" + | "jest" + | "vitest" + | "jsx-a11y" + | "nextjs" + | "react-perf" + | "promise" + | "node" + | "vue"; +export type LintPlugins = LintPluginOptionsSchema[]; +export type DummyRule = AllowWarnDeny | unknown[]; +export type OxlintOverrides = OxlintOverride[]; +export type TagNamePreference = + | string + | { + message: string; + replacement: string; + [k: string]: unknown; + } + | { + message: string; + [k: string]: unknown; + } + | boolean; +export type OneOrManyFor_String = string | string[]; +export type CustomComponent = + | string + | { + attribute: string; + name: string; + [k: string]: unknown; + } + | { + attributes: string[]; + name: string; + [k: string]: unknown; + }; + +/** + * Oxlint Configuration File + * + * This configuration is aligned with ESLint v8's configuration schema (`eslintrc.json`). + * + * Usage: `oxlint -c oxlintrc.json --import-plugin` + * + * ::: danger NOTE + * + * Only the `.json` format is supported. You can use comments in configuration files. + * + * ::: + * + * Example + * + * `.oxlintrc.json` + * + * ```json + * { + * "$schema": "./node_modules/oxlint/configuration_schema.json", + * "plugins": ["import", "typescript", "unicorn"], + * "env": { + * "browser": true + * }, + * "globals": { + * "foo": "readonly" + * }, + * "settings": { + * }, + * "rules": { + * "eqeqeq": "warn", + * "import/no-cycle": "error", + * "react/self-closing-comp": ["error", { "html": false }] + * }, + * "overrides": [ + * { + * "files": ["*.test.ts", "*.spec.ts"], + * "rules": { + * "@typescript-eslint/no-explicit-any": "off" + * } + * } + * ] + * } + * ``` + */ +export interface Oxlintrc { + /** + * Schema URI for editor tooling. + */ + $schema?: string | null; + categories?: RuleCategories; + /** + * Environments enable and disable collections of global variables. + */ + env?: OxlintEnv; + /** + * Paths of configuration files that this configuration file extends (inherits from). The files + * are resolved relative to the location of the configuration file that contains the `extends` + * property. The configuration files are merged from the first to the last, with the last file + * overriding the previous ones. + */ + extends?: string[]; + /** + * Enabled or disabled specific global variables. + */ + globals?: OxlintGlobals; + /** + * Globs to ignore during linting. These are resolved from the configuration file path. + */ + ignorePatterns?: string[]; + /** + * JS plugins, allows usage of ESLint plugins with Oxlint. + * + * Read more about JS plugins in + * [the docs](https://oxc.rs/docs/guide/usage/linter/js-plugins.html). + * + * Note: JS plugins are experimental and not subject to semver. + * They are not supported in the language server (and thus editor integrations) at present. + */ + jsPlugins?: null | ExternalPluginEntry[]; + /** + * Add, remove, or otherwise reconfigure rules for specific files or groups of files. + */ + overrides?: OxlintOverrides; + /** + * Enabled built-in plugins for Oxlint. + * You can view the list of available plugins on + * [the website](https://oxc.rs/docs/guide/usage/linter/plugins.html#supported-plugins). + * + * NOTE: Setting the `plugins` field will overwrite the base set of plugins. + * The `plugins` array should reflect all of the plugins you want to use. + */ + plugins?: LintPlugins | null; + /** + * Example + * + * `.oxlintrc.json` + * + * ```json + * { + * "$schema": "./node_modules/oxlint/configuration_schema.json", + * "rules": { + * "eqeqeq": "warn", + * "import/no-cycle": "error", + * "prefer-const": ["error", { "ignoreReadBeforeAssign": true }] + * } + * } + * ``` + * + * See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html) for the list of + * rules. + */ + rules?: DummyRuleMap; + settings?: OxlintPluginSettings; +} +/** + * Configure an entire category of rules all at once. + * + * Rules enabled or disabled this way will be overwritten by individual rules in the `rules` field. + * + * Example + * ```json + * { + * "$schema": "./node_modules/oxlint/configuration_schema.json", + * "categories": { + * "correctness": "warn" + * }, + * "rules": { + * "eslint/no-unused-vars": "error" + * } + * } + * ``` + */ +export interface RuleCategories { + correctness?: AllowWarnDeny; + nursery?: AllowWarnDeny; + pedantic?: AllowWarnDeny; + perf?: AllowWarnDeny; + restriction?: AllowWarnDeny; + style?: AllowWarnDeny; + suspicious?: AllowWarnDeny; +} +/** + * Predefine global variables. + * + * Environments specify what global variables are predefined. + * See [ESLint's list of environments](https://eslint.org/docs/v8.x/use/configure/language-options#specifying-environments) + * for what environments are available and what each one provides. + */ +export type OxlintEnv = Record; +/** + * Add or remove global variables. + * + * For each global variable, set the corresponding value equal to `"writable"` + * to allow the variable to be overwritten or `"readonly"` to disallow overwriting. + * + * Globals can be disabled by setting their value to `"off"`. For example, in + * an environment where most Es2015 globals are available but `Promise` is unavailable, + * you might use this config: + * + * ```json + * + * { + * "$schema": "./node_modules/oxlint/configuration_schema.json", + * "env": { + * "es6": true + * }, + * "globals": { + * "Promise": "off" + * } + * } + * + * ``` + * + * You may also use `"readable"` or `false` to represent `"readonly"`, and + * `"writeable"` or `true` to represent `"writable"`. + */ +export type OxlintGlobals = Record; +export interface OxlintOverride { + /** + * Environments enable and disable collections of global variables. + */ + env?: OxlintEnv | null; + /** + * A list of glob patterns to override. + * + * ## Example + * `[ "*.test.ts", "*.spec.ts" ]` + */ + files: GlobSet; + /** + * Enabled or disabled specific global variables. + */ + globals?: OxlintGlobals | null; + /** + * JS plugins for this override, allows usage of ESLint plugins with Oxlint. + * + * Read more about JS plugins in + * [the docs](https://oxc.rs/docs/guide/usage/linter/js-plugins.html). + * + * Note: JS plugins are experimental and not subject to semver. + * They are not supported in the language server (and thus editor integrations) at present. + */ + jsPlugins?: null | ExternalPluginEntry[]; + /** + * Optionally change what plugins are enabled for this override. When + * omitted, the base config's plugins are used. + */ + plugins?: LintPlugins | null; + rules?: DummyRuleMap; +} +/** + * See [Oxlint Rules](https://oxc.rs/docs/guide/usage/linter/rules.html) + */ +export type DummyRuleMap = Record; +/** + * Configure the behavior of linter plugins. + * + * Here's an example if you're using Next.js in a monorepo: + * + * ```json + * { + * "settings": { + * "next": { + * "rootDir": "apps/dashboard/" + * }, + * "react": { + * "linkComponents": [ + * { "name": "Link", "linkAttribute": "to" } + * ] + * }, + * "jsx-a11y": { + * "components": { + * "Link": "a", + * "Button": "button" + * } + * } + * } + * } + * ``` + */ +export interface OxlintPluginSettings { + jsdoc?: JSDocPluginSettings; + "jsx-a11y"?: JSXA11YPluginSettings; + next?: NextPluginSettings; + react?: ReactPluginSettings; + vitest?: VitestPluginSettings; + [k: string]: unknown; +} +export interface JSDocPluginSettings { + /** + * Only for `require-(yields|returns|description|example|param|throws)` rule + */ + augmentsExtendsReplacesDocs?: boolean; + /** + * Only for `require-param-type` and `require-param-description` rule + */ + exemptDestructuredRootsFromChecks?: boolean; + /** + * For all rules but NOT apply to `empty-tags` rule + */ + ignoreInternal?: boolean; + /** + * For all rules but NOT apply to `check-access` and `empty-tags` rule + */ + ignorePrivate?: boolean; + /** + * Only for `require-(yields|returns|description|example|param|throws)` rule + */ + ignoreReplacesDocs?: boolean; + /** + * Only for `require-(yields|returns|description|example|param|throws)` rule + */ + implementsReplacesDocs?: boolean; + /** + * Only for `require-(yields|returns|description|example|param|throws)` rule + */ + overrideReplacesDocs?: boolean; + tagNamePreference?: Record; + [k: string]: unknown; +} +/** + * Configure JSX A11y plugin rules. + * + * See + * [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y#configurations)'s + * configuration for a full reference. + */ +export interface JSXA11YPluginSettings { + /** + * Map of attribute names to their DOM equivalents. + * This is useful for non-React frameworks that use different attribute names. + * + * Example: + * + * ```json + * { + * "settings": { + * "jsx-a11y": { + * "attributes": { + * "for": ["htmlFor", "for"] + * } + * } + * } + * } + * ``` + */ + attributes?: Record; + /** + * To have your custom components be checked as DOM elements, you can + * provide a mapping of your component names to the DOM element name. + * + * Example: + * + * ```json + * { + * "settings": { + * "jsx-a11y": { + * "components": { + * "Link": "a", + * "IconButton": "button" + * } + * } + * } + * } + * ``` + */ + components?: Record; + /** + * An optional setting that define the prop your code uses to create polymorphic components. + * This setting will be used to determine the element type in rules that + * require semantic context. + * + * For example, if you set the `polymorphicPropName` to `as`, then this element: + * + * ```jsx + * Hello + * ``` + * + * Will be treated as an `h3`. If not set, this component will be treated + * as a `Box`. + */ + polymorphicPropName?: string | null; + [k: string]: unknown; +} +/** + * Configure Next.js plugin rules. + */ +export interface NextPluginSettings { + /** + * The root directory of the Next.js project. + * + * This is particularly useful when you have a monorepo and your Next.js + * project is in a subfolder. + * + * Example: + * + * ```json + * { + * "settings": { + * "next": { + * "rootDir": "apps/dashboard/" + * } + * } + * } + * ``` + */ + rootDir?: OneOrManyFor_String; + [k: string]: unknown; +} +/** + * Configure React plugin rules. + * + * Derived from [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react#configuration-legacy-eslintrc-) + */ +export interface ReactPluginSettings { + /** + * Functions that wrap React components and should be treated as HOCs. + * + * Example: + * + * ```jsonc + * { + * "settings": { + * "react": { + * "componentWrapperFunctions": ["observer", "withRouter"] + * } + * } + * } + * ``` + */ + componentWrapperFunctions?: string[]; + /** + * Components used as alternatives to `
` for forms, such as ``. + * + * Example: + * + * ```jsonc + * { + * "settings": { + * "react": { + * "formComponents": [ + * "CustomForm", + * // OtherForm is considered a form component and has an endpoint attribute + * { "name": "OtherForm", "formAttribute": "endpoint" }, + * // allows specifying multiple properties if necessary + * { "name": "Form", "formAttribute": ["registerEndpoint", "loginEndpoint"] } + * ] + * } + * } + * } + * ``` + */ + formComponents?: CustomComponent[]; + /** + * Components used as alternatives to `` for linking, such as ``. + * + * Example: + * + * ```jsonc + * { + * "settings": { + * "react": { + * "linkComponents": [ + * "HyperLink", + * // Use `linkAttribute` for components that use a different prop name + * // than `href`. + * { "name": "MyLink", "linkAttribute": "to" }, + * // allows specifying multiple properties if necessary + * { "name": "Link", "linkAttribute": ["to", "href"] } + * ] + * } + * } + * } + * ``` + */ + linkComponents?: CustomComponent[]; + /** + * React version to use for version-specific rules. + * + * Accepts semver versions (e.g., "18.2.0", "17.0"). + * + * Example: + * + * ```jsonc + * { + * "settings": { + * "react": { + * "version": "18.2.0" + * } + * } + * } + * ``` + */ + version?: string | null; + [k: string]: unknown; +} +/** + * Configure Vitest plugin rules. + * + * See [eslint-plugin-vitest](https://github.com/vitest-dev/eslint-plugin-vitest)'s + * configuration for a full reference. + */ +export interface VitestPluginSettings { + /** + * Whether to enable typecheck mode for Vitest rules. + * When enabled, some rules will skip certain checks for describe blocks + * to accommodate TypeScript type checking scenarios. + */ + typecheck?: boolean; + [k: string]: unknown; +} diff --git a/apps/oxlint/src-js/package/config.ts b/apps/oxlint/src-js/package/config.ts index a859ed75ddc7b..75f2858c63232 100644 --- a/apps/oxlint/src-js/package/config.ts +++ b/apps/oxlint/src-js/package/config.ts @@ -1,47 +1,38 @@ /* * `defineConfig` helper and config types. + * + * Types are generated from npm/oxlint/configuration_schema.json. */ -import type { JsonObject, JsonValue } from "../plugins/json.ts"; - -export type AllowWarnDeny = "off" | "warn" | "error" | 0 | 1 | 2; - -export type DummyRule = AllowWarnDeny | [AllowWarnDeny, ...JsonValue[]]; - -export type DummyRuleMap = Record; - -export type RuleCategories = Record; -export type OxlintGlobals = Record< - string, - "readonly" | "writable" | "off" | "readable" | "writeable" | boolean ->; - -export type OxlintEnv = Record; - -export type ExternalPluginEntry = string | { name: string; specifier: string }; - -export type ExternalPluginsConfig = ExternalPluginEntry[] | null; - -export interface OxlintOverride { - files: string[]; - env?: OxlintEnv; - globals?: OxlintGlobals; - plugins?: string[]; - jsPlugins?: ExternalPluginsConfig; - rules?: DummyRuleMap; -} - -export interface OxlintConfig { - plugins?: string[]; - jsPlugins?: ExternalPluginsConfig; - categories?: RuleCategories; - rules?: DummyRuleMap; - settings?: JsonObject; - env?: OxlintEnv; - globals?: OxlintGlobals; - overrides?: OxlintOverride[]; - ignorePatterns?: string[]; -} +import type { + AllowWarnDeny, + DummyRule, + DummyRuleMap, + ExternalPluginEntry, + Oxlintrc as FullOxlintrc, + OxlintEnv, + OxlintGlobals, + OxlintOverride, + RuleCategories, +} from "./config.generated.ts"; + +type Oxlintrc = Omit; + +export type { + AllowWarnDeny, + DummyRule, + DummyRuleMap, + RuleCategories, + OxlintGlobals, + OxlintEnv, + ExternalPluginEntry, +}; + +export type ExternalPluginsConfig = Exclude; + +export type OxlintConfig = Oxlintrc; + +export type { OxlintOverride }; /** * Define an Oxlint configuration with type inference. diff --git a/justfile b/justfile index 28de48750d93e..3b8e9afc035aa 100755 --- a/justfile +++ b/justfile @@ -270,6 +270,10 @@ website path: linter-schema-json: cargo run -p website_linter schema-json > npm/oxlint/configuration_schema.json +# Generate linter config TypeScript types for `apps/oxlint/src-js/package/config.generated.ts` +linter-config-ts: + pnpm --filter oxlint-app generate-config-types + # Generate formatter schema json for `npm/oxfmt/configuration_schema.json` formatter-schema-json: cargo run -p website_formatter schema-json > npm/oxfmt/configuration_schema.json diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6acf6b7ddb62f..d483fb3cc6395 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -146,6 +146,9 @@ importers: execa: specifier: ^9.6.0 version: 9.6.1 + json-schema-to-typescript: + specifier: ^15.0.4 + version: 15.0.4 json-stable-stringify-without-jsonify: specifier: ^1.0.1 version: 1.0.1 @@ -538,6 +541,10 @@ packages: react: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@apidevtools/json-schema-ref-parser@11.9.3': + resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} + engines: {node: '>= 16'} + '@arethetypeswrong/core@0.18.2': resolution: {integrity: sha512-GiwTmBFOU1/+UVNqqCGzFJYfBXEytUkiI+iRZ6Qx7KmUVtLm00sYySkfe203C9QtPG11yOz1ZaMek8dT/xnlgg==} engines: {node: '>=20'} @@ -1239,6 +1246,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jsdevtools/ono@7.1.3': + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + '@jsonjoy.com/base64@1.1.2': resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==} engines: {node: '>=10.0'} @@ -2800,6 +2810,9 @@ packages: '@types/json-stable-stringify-without-jsonify@1.0.2': resolution: {integrity: sha512-X/Kn5f5fv1KBGqGDaegrj72Dlh+qEKN3ELwMAB6RdVlVzkf6NTeEnJpgR/Hr0AlpgTlYq/Vd0U3f79lavn6aDA==} + '@types/lodash@4.17.23': + resolution: {integrity: sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==} + '@types/methods@1.1.4': resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} @@ -4268,6 +4281,11 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-schema-to-typescript@15.0.4: + resolution: {integrity: sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==} + engines: {node: '>=16.0.0'} + hasBin: true + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -5855,6 +5873,12 @@ snapshots: react-dom: 19.2.3(react@19.2.3) throttle-debounce: 5.0.2 + '@apidevtools/json-schema-ref-parser@11.9.3': + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.1.1 + '@arethetypeswrong/core@0.18.2': dependencies: '@andrewbranch/untar.js': 1.0.3 @@ -6711,6 +6735,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@jsdevtools/ono@7.1.3': {} + '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)': dependencies: tslib: 2.8.1 @@ -8018,6 +8044,8 @@ snapshots: '@types/json-stable-stringify-without-jsonify@1.0.2': {} + '@types/lodash@4.17.23': {} + '@types/methods@1.1.4': {} '@types/mocha@10.0.10': {} @@ -9726,6 +9754,18 @@ snapshots: json-buffer@3.0.1: {} + json-schema-to-typescript@15.0.4: + dependencies: + '@apidevtools/json-schema-ref-parser': 11.9.3 + '@types/json-schema': 7.0.15 + '@types/lodash': 4.17.23 + is-glob: 4.0.3 + js-yaml: 4.1.1 + lodash: 4.17.21 + minimist: 1.2.8 + prettier: 3.8.1 + tinyglobby: 0.2.15 + json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {}