From a59af2898d10a4731b4ab596e2e9def14cffa974 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 09:14:38 +0100 Subject: [PATCH 01/77] feat(schemas): add public schema --- schemas/public.schema.json | 451 +++++++++++++++++++++++++++++++++++++ 1 file changed, 451 insertions(+) create mode 100644 schemas/public.schema.json diff --git a/schemas/public.schema.json b/schemas/public.schema.json new file mode 100644 index 00000000000000..2cd7b6cadf73b0 --- /dev/null +++ b/schemas/public.schema.json @@ -0,0 +1,451 @@ +{ + "$schema": "http://json-schema.org/schema", + + "definitions": { + "meta": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "tsType": "string" + } + } + }, + + "browser_type": { + "type": "string", + "enum": ["desktop", "mobile", "xr", "server"] + }, + + "browser_engine": { + "type": "string", + "enum": [ + "Blink", + "EdgeHTML", + "Gecko", + "Presto", + "Trident", + "WebKit", + "V8" + ] + }, + + "browser_status": { + "type": "string", + "enum": ["retired", "current", "beta", "nightly", "esr", "planned"] + }, + + "browsers": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/browser_statement" + }, + "minProperties": 1, + "errorMessage": { + "minProperties": "A browser must be described within the file." + }, + "tsType": "Record" + }, + + "browser_statement": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The browser brand name (e.g. Firefox, Firefox Android, Chrome, etc.)." + }, + "type": { + "$ref": "#/definitions/browser_type", + "description": "The platform the browser runs on (e.g. desktop, mobile, XR, or server engine).", + "tsType": "BrowserType" + }, + "upstream": { + "type": "string", + "description": "The upstream browser this browser derives from (e.g. Firefox Android is derived from Firefox, Edge is derived from Chrome).", + "tsType": "BrowserName" + }, + "preview_name": { + "type": "string", + "description": "The name of the browser's preview channel (e.g. 'Nightly' for Firefox or 'TP' for Safari)." + }, + "pref_url": { + "type": "string", + "description": "URL of the page where feature flags can be changed (e.g. 'about:config' for Firefox or 'chrome://flags' for Chrome)." + }, + "accepts_flags": { + "type": "boolean", + "description": "Whether the browser supports user-toggleable flags that enable or disable features." + }, + "accepts_webextensions": { + "type": "boolean", + "description": "Whether the browser supports extensions." + }, + "releases": { + "type": "object", + "additionalProperties": { "$ref": "#/definitions/release_statement" }, + "description": "The known versions of this browser.", + "tsType": "{ [version: string]: ReleaseStatement };" + } + }, + "required": [ + "name", + "type", + "releases", + "accepts_flags", + "accepts_webextensions" + ], + "additionalProperties": false + }, + + "release_statement": { + "type": "object", + "properties": { + "release_date": { + "type": "string", + "format": "date", + "description": "The date on which this version was released, formatted as `YYYY-MM-DD`." + }, + "release_notes": { + "type": "string", + "format": "uri", + "pattern": "^https://", + "description": "A link to the release notes or changelog for a given release." + }, + "status": { + "$ref": "#/definitions/browser_status", + "description": "A property indicating where in the lifetime cycle this release is in (e.g. current, retired, beta, nightly).", + "tsType": "BrowserStatus" + }, + "engine": { + "$ref": "#/definitions/browser_engine", + "description": "Name of the browser's underlying engine.", + "tsType": "BrowserEngine" + }, + "engine_version": { + "type": "string", + "description": "Version of the engine corresponding to the browser version." + } + }, + "required": ["status"], + "dependencies": { + "engine": ["engine_version"] + }, + "additionalProperties": false + }, + + "simple_support_statement": { + "type": "object", + "properties": { + "version_added": { + "description": "A string (indicating which browser version added this feature), or the value false (indicating the feature is not supported).", + "anyOf": [ + { + "type": "string", + "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$" + }, + { + "const": false + } + ], + "tsType": "VersionValue" + }, + "version_removed": { + "description": "A string, indicating which browser version removed this feature.", + "type": "string", + "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$", + "tsType": "string" + }, + "version_last": { + "description": "A string, indicating the last browser version that supported this feature. This is automatically generated.", + "type": "string", + "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$", + "tsType": "string" + }, + "prefix": { + "type": "string", + "description": "A prefix to add to the sub-feature name (defaults to empty string). If applicable, leading and trailing '-' must be included." + }, + "alternative_name": { + "type": "string", + "description": "An alternative name for the feature, for cases where a feature is implemented under an entirely different name and not just prefixed." + }, + "flags": { + "type": "array", + "description": "An optional array of objects describing flags that must be configured for this browser to support this feature.", + "minItems": 1, + "items": { + "$ref": "#/definitions/flag_statement" + }, + "tsType": "[FlagStatement, ...FlagStatement[]]" + }, + "impl_url": { + "anyOf": [ + { + "$ref": "#/definitions/impl_url_value" + }, + { + "type": "array", + "minItems": 2, + "items": { + "$ref": "#/definitions/impl_url_value" + } + } + ], + "description": "An optional changeset/commit URL for the revision which implemented the feature in the source code, or the URL to the bug tracking the implementation, for the associated browser.", + "tsType": "string | [string, string, ...string[]]", + "errorMessage": { + "pattern": "impl_url must be a link to a browser commit or bug URL. URLs must be in shortened form (ex. bugs.chromium.org -> crbug.com). Note: `npm run fix` may resolve these issues." + } + }, + "partial_implementation": { + "const": true, + "description": "A boolean value indicating whether or not the implementation of the sub-feature deviates from the specification in a way that may cause compatibility problems. It defaults to false (no interoperability problems expected). If set to true, it is recommended that you add a note explaining how it diverges from the standard (such as that it implements an old version of the standard, for example)." + }, + "notes": { + "description": "A string or array of strings containing additional information.", + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 2, + "items": { + "type": "string" + } + } + ], + "tsType": "string | [string, string, ...string[]]" + } + }, + "required": ["version_added"], + "dependencies": { + "partial_implementation": { + "if": { + "properties": { "partial_implementation": { "const": true } } + }, + "then": { "required": ["notes"] } + } + }, + "additionalProperties": false + }, + + "flag_statement": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "An enum that indicates the flag type.", + "enum": ["preference", "runtime_flag"] + }, + "name": { + "type": "string", + "description": "A string giving the name of the flag or preference that must be configured." + }, + "value_to_set": { + "type": "string", + "description": "A string giving the value which the specified flag must be set to for this feature to work." + } + }, + "additionalProperties": false, + "required": ["type", "name"] + }, + + "support_statement": { + "anyOf": [ + { "$ref": "#/definitions/simple_support_statement" }, + { + "type": "array", + "minItems": 2, + "items": { + "$ref": "#/definitions/simple_support_statement" + } + }, + { "const": "mirror" } + ], + "tsType": "SimpleSupportStatement | [SimpleSupportStatement, SimpleSupportStatement, ...SimpleSupportStatement[]]" + }, + + "status_block": { + "type": "object", + "properties": { + "experimental": { + "type": "boolean", + "deprecated": true, + "description": "(This property is deprecated. Prefer using more well-defined stability calculations, such as Baseline, instead.) A boolean value. Usually, this value is true for single-implementer features and false for multiple-implementer features or single-implementer features that are not expected to change." + }, + "standard_track": { + "type": "boolean", + "description": "A boolean value indicating whether the feature is part of an active specification or specification process." + }, + "deprecated": { + "type": "boolean", + "description": "A boolean value that indicates whether the feature is no longer recommended. It might be removed in the future or might only be kept for compatibility purposes. Avoid using this functionality." + } + }, + "required": ["experimental", "standard_track", "deprecated"], + "additionalProperties": false + }, + + "support_block": { + "type": "object", + "propertyNames": { + "enum": [ + "bun", + "chrome", + "chrome_android", + "deno", + "edge", + "firefox", + "firefox_android", + "ie", + "nodejs", + "oculus", + "opera", + "opera_android", + "safari", + "safari_ios", + "samsunginternet_android", + "webview_android", + "webview_ios" + ] + }, + "additionalProperties": { + "$ref": "#/definitions/support_statement" + }, + "tsType": "Partial>" + }, + + "spec_url_value": { + "type": "string", + "format": "uri", + "pattern": "(^https://[^#]+#.+)|(^https://github.com/WebAssembly/.+)|(^https://registry.khronos.org/webgl/extensions/[^/]+/)" + }, + + "impl_url_value": { + "type": "string", + "format": "uri", + "pattern": "^https://(trac.webkit.org/changeset/|hg.mozilla.org/mozilla-central/rev/|crrev.com/|bugzil.la/|crbug.com/|webkit.org/b/|github.com/GoogleChromeLabs/chromium-bidi/issues/)" + }, + + "compat_statement": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A string containing a human-readable description of the feature." + }, + "mdn_url": { + "type": "string", + "format": "uri", + "pattern": "^https://developer\\.mozilla\\.org/docs/", + "description": "A URL that points to an MDN reference page documenting the feature. The URL should be language-agnostic." + }, + "spec_url": { + "anyOf": [ + { + "$ref": "#/definitions/spec_url_value" + }, + { + "type": "array", + "minItems": 2, + "items": { + "$ref": "#/definitions/spec_url_value" + } + } + ], + "description": "An optional URL or array of URLs, each of which is for a specific part of a specification in which this feature is defined. Each URL must contain a fragment identifier.", + "tsType": "string | [string, string, ...string[]]" + }, + "tags": { + "description": "An optional array of strings allowing to assign tags to the feature.", + "type": "array", + "minItems": 1, + "tsType": "[string, ...string[]]" + }, + "source_file": { + "type": "string", + "description": "The path to the file that defines this feature in browser-compat-data, relative to the repository root. Useful for guiding potential contributors towards the correct file to edit. This is automatically generated at build time and should never manually be specified." + }, + "support": { + "$ref": "#/definitions/support_block", + "description": "The data for the support of each browser, containing a `support_statement` object for each browser identifier with information about versions, prefixes, or alternate names, as well as notes." + }, + "status": { + "$ref": "#/definitions/status_block", + "description": "An object containing information about the stability of the feature." + } + }, + "required": ["support"], + "additionalProperties": false + }, + + "identifier": { + "type": "object", + "properties": { + "__compat": { + "type": "object", + "$ref": "#/definitions/compat_statement", + "required": ["status"], + "description": "A feature is described by an identifier containing the `__compat` property.\n\nIn other words, identifiers without `__compat` aren't necessarily features, but help to nest the features properly.\n\nWhen an identifier has a `__compat` block, it represents its basic support, indicating that a minimal implementation of a functionality is included.\n\nWhat it represents exactly depends of the evolution of the feature over time, both in terms of specifications and of browser support.", + "tsType": "CompatStatement" + } + }, + "patternProperties": { + "^(?!__compat)[a-zA-Z_0-9-$@]*$": { "$ref": "#/definitions/identifier" } + }, + "additionalProperties": false, + "errorMessage": { + "additionalProperties": "Feature names can only contain alphanumerical characters or the following symbols: _-$@" + }, + "tsType": "{[key: string]: Identifier} & {__compat?: CompatStatement};" + }, + + "webextensions_identifier": { + "type": "object", + "properties": { + "__compat": { "$ref": "#/definitions/compat_statement" } + }, + "patternProperties": { + "^(?!__compat)[a-zA-Z_0-9-$@]*$": { + "$ref": "#/definitions/webextensions_identifier", + "tsType": "Identifier" + } + }, + "additionalProperties": false, + "errorMessage": { + "additionalProperties": "Feature names can only contain alphanumerical characters or the following symbols: _-$@" + }, + "tsType": "{[key: string]: Identifier} & {__compat?: CompatStatement}" + } + }, + + "title": "PublicDataFile", + "type": "object", + "patternProperties": { + "^(?!browsers)(?!__compat)(?!__meta)(?!webextensions)[a-zA-Z_0-9-$@]*$": { + "$ref": "#/definitions/identifier" + }, + "^__meta$": { + "$ref": "#/definitions/meta" + }, + "^browsers$": { + "$ref": "#/definitions/browsers" + }, + "^webextensions*$": { + "$ref": "#/definitions/webextensions_identifier", + "tsType": "Identifier" + }, + "^__compat$": { + "$ref": "#/definitions/compat_statement" + } + }, + "additionalProperties": false, + "errorMessage": { + "additionalProperties": "Feature names can only contain alphanumerical characters or the following symbols: _-$@" + } +} From b94173e0cc13aeb984b028e5ba0aa4edf7089255 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 09:15:00 +0100 Subject: [PATCH 02/77] chore(scripts/build): validate with public schema --- scripts/build/index.js | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/scripts/build/index.js b/scripts/build/index.js index f7ab95836893be..c5963dde77a5ac 100644 --- a/scripts/build/index.js +++ b/scripts/build/index.js @@ -12,8 +12,7 @@ import { compareVersions } from 'compare-versions'; import { marked } from 'marked'; import compileTS from '../generate-types.js'; -import compatDataSchema from '../../schemas/compat-data.schema.json' with { type: 'json' }; -import browserDataSchema from '../../schemas/browsers.schema.json' with { type: 'json' }; +import schema from '../../schemas/public.schema.json' with { type: 'json' }; import { createAjv } from '../lib/ajv.js'; import { walk } from '../../utils/index.js'; import bcd from '../../index.js'; @@ -244,25 +243,16 @@ export const createDataBundle = async () => { const validate = (data) => { const ajv = createAjv(); - for (const [key, value] of Object.entries(data)) { - if (key === '__meta') { - // Not covered by the schema. - continue; - } - - const schema = key === 'browsers' ? browserDataSchema : compatDataSchema; - const data = { [key]: value }; - if (!ajv.validate(schema, data)) { - const errors = ajv.errors || []; - if (!errors.length) { - console.error(`${key} data failed validation with unknown errors!`); - } - // Output messages by one since better-ajv-errors wrongly joins messages - // (see https://github.com/atlassian/better-ajv-errors/pull/21) - errors.forEach((e) => { - console.error(betterAjvErrors(schema, data, [e], { indent: 2 })); - }); + if (!ajv.validate(schema, data)) { + const errors = ajv.errors || []; + if (!errors.length) { + console.error('Public data failed validation with unknown errors!'); } + // Output messages by one since better-ajv-errors wrongly joins messages + // (see https://github.com/atlassian/better-ajv-errors/pull/21) + errors.forEach((e) => { + console.error(betterAjvErrors(schema, data, [e], { indent: 2 })); + }); } }; From 6e38d630283d8d8bc73d2cdfbf55785abb4802c4 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 09:17:32 +0100 Subject: [PATCH 03/77] fix(scripts/build): convert Date to string explicitly Otherwise `ajv` rejects the value for not being a string. --- scripts/build/index.js | 2 +- scripts/build/index.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build/index.js b/scripts/build/index.js index c5963dde77a5ac..c3e38586620be9 100644 --- a/scripts/build/index.js +++ b/scripts/build/index.js @@ -55,7 +55,7 @@ const logWrite = (url, description = '') => { */ export const generateMeta = () => ({ version: packageJson.version, - timestamp: new Date(), + timestamp: new Date().toISOString(), }); /** diff --git a/scripts/build/index.test.js b/scripts/build/index.test.js index dacd61e96a7e24..2b97add687b094 100644 --- a/scripts/build/index.test.js +++ b/scripts/build/index.test.js @@ -16,7 +16,7 @@ describe('Build functions', () => { it('generateMeta', () => { const result = generateMeta(); assert.ok(result.version); - assert.ok(result.timestamp instanceof Date); + assert.ok(result.timestamp); }); it('applyMirroring', () => { From 51415634a3a325138bf6d71730f2a7ef46533ea2 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 09:15:29 +0100 Subject: [PATCH 04/77] fix(schemas/browsers): restore "maxProperties" --- schemas/browsers.schema.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/schemas/browsers.schema.json b/schemas/browsers.schema.json index 91bb52e5686b8b..baf77891608baa 100644 --- a/schemas/browsers.schema.json +++ b/schemas/browsers.schema.json @@ -31,8 +31,10 @@ "$ref": "#/definitions/browser_statement" }, "minProperties": 1, + "maxProperties": 1, "errorMessage": { - "minProperties": "A browser must be described within the file." + "minProperties": "A browser must be described within the file.", + "maxProperties": "Each browser JSON file may only describe one browser." }, "tsType": "Record" }, From 6aab32a55fe087d7f4b90b69bcb908edc1de4956 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 09:23:38 +0100 Subject: [PATCH 05/77] fix(schemas): remove version_last from internal schema --- schemas/compat-data.schema.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index 888115301b380f..e369e62211a8d5 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -24,12 +24,6 @@ "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$", "tsType": "string" }, - "version_last": { - "description": "A string, indicating the last browser version that supported this feature. This is automatically generated.", - "type": "string", - "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$", - "tsType": "string" - }, "prefix": { "type": "string", "description": "A prefix to add to the sub-feature name (defaults to empty string). If applicable, leading and trailing '-' must be included." From 58bc6958e3700e42361dc05d9424eb3dc493b8ac Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 09:24:10 +0100 Subject: [PATCH 06/77] fix(schemas): remove "mirror" from public schema --- schemas/public.schema.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/schemas/public.schema.json b/schemas/public.schema.json index 2cd7b6cadf73b0..a0a0d884427b2d 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -264,8 +264,7 @@ "items": { "$ref": "#/definitions/simple_support_statement" } - }, - { "const": "mirror" } + } ], "tsType": "SimpleSupportStatement | [SimpleSupportStatement, SimpleSupportStatement, ...SimpleSupportStatement[]]" }, From 02512b2212ae842207e39b4b35f87af089d7c46c Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 09:28:38 +0100 Subject: [PATCH 07/77] chore(schemas): rename internal types that differ --- schemas/compat-data.schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index e369e62211a8d5..56ca8b110ffffd 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -126,7 +126,7 @@ }, { "const": "mirror" } ], - "tsType": "SimpleSupportStatement | [SimpleSupportStatement, SimpleSupportStatement, ...SimpleSupportStatement[]]" + "tsType": "InternalSimpleSupportStatement | [InternalSimpleSupportStatement, InternalSimpleSupportStatement, ...InternalSimpleSupportStatement[]]" }, "status_block": { @@ -176,7 +176,7 @@ "additionalProperties": { "$ref": "#/definitions/support_statement" }, - "tsType": "Partial>" + "tsType": "Partial>" }, "spec_url_value": { From e87f7f89b6a8b1a9244b0c2e3e58577b5935667d Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 09:52:31 +0100 Subject: [PATCH 08/77] chore(scripts/generate-types): use public schema --- scripts/generate-types.js | 146 +++----------------------------------- 1 file changed, 10 insertions(+), 136 deletions(-) diff --git a/scripts/generate-types.js b/scripts/generate-types.js index 5f3be0ef739cfd..fa2f0b1fb12a2b 100644 --- a/scripts/generate-types.js +++ b/scripts/generate-types.js @@ -4,151 +4,34 @@ /* c8 ignore start */ import fs from 'node:fs/promises'; -import path from 'node:path'; import { fileURLToPath } from 'node:url'; import esMain from 'es-main'; -import { fdir } from 'fdir'; import { compileFromFile } from 'json-schema-to-typescript'; import { spawn } from '../utils/index.js'; -import extend from './lib/extend.js'; - const dirname = fileURLToPath(new URL('.', import.meta.url)); const opts = { - bannerComment: '', + bannerComment: + '/* This file is a part of @mdn/browser-compat-data\n * See LICENSE file for more information. */\n\n/**\n* This file was automatically generated by json-schema-to-typescript.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source schema files in\n* schemas/*, and run "npm run gentypes" to regenerate this file.\n*/', unreachableDefinitions: true, }; -const header = - '/* This file is a part of @mdn/browser-compat-data\n * See LICENSE file for more information. */\n\n/**\n* This file was automatically generated by json-schema-to-typescript.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source schema files in\n* schemas/*, and run "npm run gentypes" to regenerate this file.\n*/'; - -const compatDataTypes = { - __meta: - 'Contains metadata for the current BCD information, such as the BCD version.', - api: 'Contains data for each [Web API](https://developer.mozilla.org/docs/Web/API) interface.', - browsers: 'Contains data for each known and tracked browser/engine.', - css: 'Contains data for [CSS](https://developer.mozilla.org/docs/Web/CSS) properties, selectors, and at-rules.', - html: 'Contains data for [HTML](https://developer.mozilla.org/docs/Web/HTML) elements, attributes, and global attributes.', - http: 'Contains data for [HTTP](https://developer.mozilla.org/docs/Web/HTTP) headers, statuses, and methods.', - javascript: - 'Contains data for [JavaScript](https://developer.mozilla.org/docs/Web/JavaScript) built-in Objects, statement, operators, and other ECMAScript language features.', - manifests: - 'Contains data for various manifests, such as the [Web Application Manifest](https://developer.mozilla.org/docs/Web/Progressive_web_apps/manifest).', - mathml: - 'Contains data for [MathML](https://developer.mozilla.org/docs/Web/MathML) elements, attributes, and global attributes.', - mediatypes: - 'Contains data for [Media types](https://developer.mozilla.org/docs/Web/HTTP/Guides/MIME_types).', - svg: 'Contains data for [SVG](https://developer.mozilla.org/docs/Web/SVG) elements, attributes, and global attributes.', - webassembly: - 'Contains data for [WebAssembly](https://developer.mozilla.org/docs/WebAssembly) features.', - webdriver: - 'Contains data for [WebDriver](https://developer.mozilla.org/docs/Web/WebDriver) commands.', - webextensions: - 'Contains data for [WebExtensions](https://developer.mozilla.org/Add-ons/WebExtensions) JavaScript APIs and manifest keys.', -}; - -/** - * Generate the browser names TypeScript - * @returns {Promise} The stringified TypeScript typedef - */ -const generateBrowserNames = async () => { - // Load browser data independently of index.ts, since index.ts depends - // on the output of this script - const browserData = { browsers: {} }; - - const paths = /** @type {string[]} */ ( - new fdir() - .withBasePath() - .filter((fp) => fp.endsWith('.json')) - .crawl(path.join(dirname, '..', 'browsers')) - .sync() - ); - - for (const fp of paths) { - try { - const contents = await fs.readFile(fp); - extend(browserData, JSON.parse(contents.toString('utf8'))); - } catch { - // Skip invalid JSON. Tests will flag the problem separately. - continue; - } - } - - // Generate BrowserName type - const browsers = Object.keys(browserData.browsers); - return `/**\n * The names of the known browsers.\n */\nexport type BrowserName = ${browsers - .map((b) => `"${b}"`) - .join(' | ')};`; -}; - -/** - * Generate the CompatData TypeScript - * @returns {string} The stringified TypeScript typedef - */ -const generateCompatDataTypes = () => { - const props = Object.entries(compatDataTypes).map( - (t) => - ` /**\n * ${t[1]}\n */\n ${t[0]}: ${ - t[0] === '__meta' - ? 'MetaBlock' - : t[0] === 'browsers' - ? 'Browsers' - : 'Identifier' - };`, - ); - - const metaType = - 'export interface MetaBlock {\n version: string;\n timestamp: string;\n}'; - - return `${metaType}\n\nexport interface CompatData {\n${props.join( - '\n\n', - )}\n}\n`; -}; - /** * Transform the TypeScript to remove unneeded bits of typedefs - * @param {string} browserTS Typedefs for BrowserName - * @param {string} compatTS Typedefs for CompatData + * @param {string} ts Typedefs * @returns {string} Updated typedefs */ -const transformTS = (browserTS, compatTS) => { - // XXX Temporary until the following PR is merged and released: - // https://github.com/bcherny/json-schema-to-typescript/pull/456 - let ts = browserTS + '\n\n' + compatTS; - +const transformTS = (ts) => { + // Remove "interface was referenced" comments. ts = ts .replace( - 'export interface BrowserDataFile {\n browsers?: Browsers;\n}', + /( \*\n)? \* This interface was referenced by [^\n]+\n \* via the [^\n]+\n/g, '', ) - .replace('export interface CompatDataFile {}', '') - .replace( - /\/\*\*\n \* This interface was referenced by `CompatDataFile`'s JSON-Schema definition\n \* via the `patternProperty` "\^webextensions\*\$"\.\n \*\/\nexport type WebextensionsIdentifier = Identifier;\n/, - '', - ) - .replace( - /\/\*\*\n \* This interface was referenced by `CompatDataFile`'s JSON-Schema\n \* via the `definition` "webextensions_identifier"\.\n \*\/\nexport type WebextensionsIdentifier1 = .*;\n/, - '', - ) - .replace( - /\/\*\*\n \* This interface was referenced by `CompatDataFile`'s JSON-Schema\n \* via the `definition` "spec_url_value"\.\n \*\/\nexport type SpecUrlValue = string;\n/, - '', - ) - .replace( - /\/\*\*\n \* This interface was referenced by `CompatDataFile`'s JSON-Schema\n \* via the `definition` "impl_url_value"\.\n \*\/\nexport type ImplUrlValue = string;\n/, - '', - ) - .replace( - '/**\n * This interface was referenced by `CompatDataFile`\'s JSON-Schema\n * via the `definition` "support_block".\n */\nexport type SupportBlock1 = Partial>;\n', - '', - ) - .replace( - /\/\*\*\n \* This interface was referenced by `CompatDataFile`'s JSON-Schema\n \* via the `definition` "status_block"\.\n \*\/\nexport interface StatusBlock1 {(.*\n)*}\n/, - '', - ); + .replace(/\/\*\*\n \*\/\n/g, ''); return ts; }; @@ -160,19 +43,10 @@ const transformTS = (browserTS, compatTS) => { const compile = async ( destination = new URL('../types/types.d.ts', import.meta.url), ) => { - const browserTS = await compileFromFile('schemas/browsers.schema.json', opts); - const compatTS = await compileFromFile( - 'schemas/compat-data.schema.json', - opts, - ); + let ts = await compileFromFile('schemas/public.schema.json', opts); + + ts = transformTS(ts); - const ts = [ - header, - await generateBrowserNames(), - 'export type VersionValue = string | false;', - transformTS(browserTS, compatTS), - generateCompatDataTypes(), - ].join('\n\n'); await fs.writeFile(destination, ts); spawn('tsc', ['--skipLibCheck', '../types/types.d.ts'], { cwd: dirname, From e6ec3f11d542a68ce8e6a86106ace580340785bb Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 11:36:53 +0100 Subject: [PATCH 09/77] chore(schemas): refine public schema - Avoid unnecessary `tsType`. - Avoid descriptions next to `$ref` (causes duplicate types). - Refine descriptions. --- schemas/public.schema.json | 247 +++++++++++++++++-------------------- 1 file changed, 110 insertions(+), 137 deletions(-) diff --git a/schemas/public.schema.json b/schemas/public.schema.json index a0a0d884427b2d..bf7b1deafc7293 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -2,27 +2,31 @@ "$schema": "http://json-schema.org/schema", "definitions": { - "meta": { + "meta_block": { "type": "object", + "description": "Metadata of the present BCD data.", "properties": { "version": { "type": "string" }, "timestamp": { "type": "string", - "format": "date-time", - "tsType": "string" + "format": "date-time" } - } + }, + "required": ["version", "timestamp"], + "additionalProperties": false }, "browser_type": { "type": "string", + "description": "Platform the browser runs on (e.g. desktop, mobile, XR, or server engine).", "enum": ["desktop", "mobile", "xr", "server"] }, "browser_engine": { "type": "string", + "description": "Browser engine.", "enum": [ "Blink", "EdgeHTML", @@ -34,21 +38,52 @@ ] }, + "browser_name": { + "type": "string", + "description": "Browser key (e.g. 'firefox', 'chrome_android', or 'webview_ios').", + "enum": [ + "bun", + "chrome", + "chrome_android", + "deno", + "edge", + "firefox", + "firefox_android", + "ie", + "nodejs", + "oculus", + "opera", + "opera_android", + "safari", + "safari_ios", + "samsunginternet_android", + "webview_android", + "webview_ios" + ] + }, + + "upstream_browser_name": { + "type": "string", + "description": "Upstream browser key (e.g. 'firefox' or 'chrome_android').", + "enum": ["chrome", "chrome_android", "firefox", "safari", "safari_ios"] + }, + "browser_status": { "type": "string", + "description": "Lifetime cycle status of a browser release (e.g. 'current', 'retired', 'beta', 'nightly').", "enum": ["retired", "current", "beta", "nightly", "esr", "planned"] }, "browsers": { "type": "object", + "description": "Data for each known and tracked browser/runtime.", + "propertyNames": { + "$ref": "#/definitions/browser_name" + }, "additionalProperties": { "$ref": "#/definitions/browser_statement" }, - "minProperties": 1, - "errorMessage": { - "minProperties": "A browser must be described within the file." - }, - "tsType": "Record" + "tsType": "{ [browser: BrowserName]: BrowserStatement }" }, "browser_statement": { @@ -56,21 +91,17 @@ "properties": { "name": { "type": "string", - "description": "The browser brand name (e.g. Firefox, Firefox Android, Chrome, etc.)." + "description": "Name of the browser (e.g. 'Firefox', 'Firefox Android', 'Chrome', etc.)." }, "type": { - "$ref": "#/definitions/browser_type", - "description": "The platform the browser runs on (e.g. desktop, mobile, XR, or server engine).", - "tsType": "BrowserType" + "$ref": "#/definitions/browser_type" }, "upstream": { - "type": "string", - "description": "The upstream browser this browser derives from (e.g. Firefox Android is derived from Firefox, Edge is derived from Chrome).", - "tsType": "BrowserName" + "$ref": "#/definitions/upstream_browser_name" }, "preview_name": { "type": "string", - "description": "The name of the browser's preview channel (e.g. 'Nightly' for Firefox or 'TP' for Safari)." + "description": "Name of the browser's preview channel (e.g. 'Nightly' for Firefox or 'TP' for Safari)." }, "pref_url": { "type": "string", @@ -87,8 +118,7 @@ "releases": { "type": "object", "additionalProperties": { "$ref": "#/definitions/release_statement" }, - "description": "The known versions of this browser.", - "tsType": "{ [version: string]: ReleaseStatement };" + "description": "The known versions of this browser." } }, "required": [ @@ -116,14 +146,10 @@ "description": "A link to the release notes or changelog for a given release." }, "status": { - "$ref": "#/definitions/browser_status", - "description": "A property indicating where in the lifetime cycle this release is in (e.g. current, retired, beta, nightly).", - "tsType": "BrowserStatus" + "$ref": "#/definitions/browser_status" }, "engine": { - "$ref": "#/definitions/browser_engine", - "description": "Name of the browser's underlying engine.", - "tsType": "BrowserEngine" + "$ref": "#/definitions/browser_engine" }, "engine_version": { "type": "string", @@ -141,29 +167,17 @@ "type": "object", "properties": { "version_added": { - "description": "A string (indicating which browser version added this feature), or the value false (indicating the feature is not supported).", - "anyOf": [ - { - "type": "string", - "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$" - }, - { - "const": false - } - ], - "tsType": "VersionValue" + "$ref": "#/definitions/version_value" }, "version_removed": { "description": "A string, indicating which browser version removed this feature.", "type": "string", - "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$", - "tsType": "string" + "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$" }, "version_last": { "description": "A string, indicating the last browser version that supported this feature. This is automatically generated.", "type": "string", - "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$", - "tsType": "string" + "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$" }, "prefix": { "type": "string", @@ -179,24 +193,24 @@ "minItems": 1, "items": { "$ref": "#/definitions/flag_statement" - }, - "tsType": "[FlagStatement, ...FlagStatement[]]" + } }, "impl_url": { "anyOf": [ { - "$ref": "#/definitions/impl_url_value" + "type": "string", + "format": "uri" }, { "type": "array", "minItems": 2, "items": { - "$ref": "#/definitions/impl_url_value" + "type": "string", + "format": "uri" } } ], "description": "An optional changeset/commit URL for the revision which implemented the feature in the source code, or the URL to the bug tracking the implementation, for the associated browser.", - "tsType": "string | [string, string, ...string[]]", "errorMessage": { "pattern": "impl_url must be a link to a browser commit or bug URL. URLs must be in shortened form (ex. bugs.chromium.org -> crbug.com). Note: `npm run fix` may resolve these issues." } @@ -218,8 +232,7 @@ "type": "string" } } - ], - "tsType": "string | [string, string, ...string[]]" + ] } }, "required": ["version_added"], @@ -229,11 +242,27 @@ "properties": { "partial_implementation": { "const": true } } }, "then": { "required": ["notes"] } + }, + "version_removed": { + "required": ["version_last"] } }, "additionalProperties": false }, + "version_value": { + "description": "A string (indicating which browser version added this feature), or the value false (indicating the feature is not supported).", + "anyOf": [ + { + "type": "string", + "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$" + }, + { + "const": false + } + ] + }, + "flag_statement": { "type": "object", "properties": { @@ -265,12 +294,12 @@ "$ref": "#/definitions/simple_support_statement" } } - ], - "tsType": "SimpleSupportStatement | [SimpleSupportStatement, SimpleSupportStatement, ...SimpleSupportStatement[]]" + ] }, "status_block": { "type": "object", + "description": "An object containing information about the stability of the feature.", "properties": { "experimental": { "type": "boolean", @@ -292,94 +321,67 @@ "support_block": { "type": "object", + "description": "The data for the support of each browser, containing a `support_statement` object for each browser identifier with information about versions, prefixes, or alternate names, as well as notes.", "propertyNames": { - "enum": [ - "bun", - "chrome", - "chrome_android", - "deno", - "edge", - "firefox", - "firefox_android", - "ie", - "nodejs", - "oculus", - "opera", - "opera_android", - "safari", - "safari_ios", - "samsunginternet_android", - "webview_android", - "webview_ios" - ] + "$ref": "#/definitions/browser_name" }, "additionalProperties": { "$ref": "#/definitions/support_statement" }, - "tsType": "Partial>" - }, - - "spec_url_value": { - "type": "string", - "format": "uri", - "pattern": "(^https://[^#]+#.+)|(^https://github.com/WebAssembly/.+)|(^https://registry.khronos.org/webgl/extensions/[^/]+/)" - }, - - "impl_url_value": { - "type": "string", - "format": "uri", - "pattern": "^https://(trac.webkit.org/changeset/|hg.mozilla.org/mozilla-central/rev/|crrev.com/|bugzil.la/|crbug.com/|webkit.org/b/|github.com/GoogleChromeLabs/chromium-bidi/issues/)" + "tsType": "{ [browser: BrowserName]: SupportStatement }" }, "compat_statement": { "type": "object", + "description": "A feature is described by an identifier containing the `__compat` property.\n\nIn other words, identifiers without `__compat` aren't necessarily features, but help to nest the features properly.\n\nWhen an identifier has a `__compat` block, it represents its basic support, indicating that a minimal implementation of a functionality is included.\n\nWhat it represents exactly depends of the evolution of the feature over time, both in terms of specifications and of browser support.", "properties": { "description": { "type": "string", - "description": "A string containing a human-readable description of the feature." + "description": "Human-readable description of the feature." }, "mdn_url": { "type": "string", "format": "uri", "pattern": "^https://developer\\.mozilla\\.org/docs/", - "description": "A URL that points to an MDN reference page documenting the feature. The URL should be language-agnostic." + "description": "Link to the MDN reference page documenting the feature." }, "spec_url": { "anyOf": [ { - "$ref": "#/definitions/spec_url_value" + "type": "string", + "format": "uri" }, { "type": "array", "minItems": 2, "items": { - "$ref": "#/definitions/spec_url_value" + "type": "string", + "format": "uri" } } ], - "description": "An optional URL or array of URLs, each of which is for a specific part of a specification in which this feature is defined. Each URL must contain a fragment identifier.", - "tsType": "string | [string, string, ...string[]]" + "description": "An optional URL or array of URLs, each of which is for a specific part of a specification in which this feature is defined. Each URL must contain a fragment identifier." }, "tags": { "description": "An optional array of strings allowing to assign tags to the feature.", "type": "array", "minItems": 1, - "tsType": "[string, ...string[]]" + "items": { + "type": "string" + } }, "source_file": { "type": "string", "description": "The path to the file that defines this feature in browser-compat-data, relative to the repository root. Useful for guiding potential contributors towards the correct file to edit. This is automatically generated at build time and should never manually be specified." }, "support": { - "$ref": "#/definitions/support_block", - "description": "The data for the support of each browser, containing a `support_statement` object for each browser identifier with information about versions, prefixes, or alternate names, as well as notes." + "$ref": "#/definitions/support_block" }, "status": { - "$ref": "#/definitions/status_block", - "description": "An object containing information about the stability of the feature." + "$ref": "#/definitions/status_block" } }, - "required": ["support"], + "required": ["source_file", "support"], "additionalProperties": false }, @@ -387,11 +389,7 @@ "type": "object", "properties": { "__compat": { - "type": "object", - "$ref": "#/definitions/compat_statement", - "required": ["status"], - "description": "A feature is described by an identifier containing the `__compat` property.\n\nIn other words, identifiers without `__compat` aren't necessarily features, but help to nest the features properly.\n\nWhen an identifier has a `__compat` block, it represents its basic support, indicating that a minimal implementation of a functionality is included.\n\nWhat it represents exactly depends of the evolution of the feature over time, both in terms of specifications and of browser support.", - "tsType": "CompatStatement" + "$ref": "#/definitions/compat_statement" } }, "patternProperties": { @@ -400,51 +398,26 @@ "additionalProperties": false, "errorMessage": { "additionalProperties": "Feature names can only contain alphanumerical characters or the following symbols: _-$@" - }, - "tsType": "{[key: string]: Identifier} & {__compat?: CompatStatement};" - }, - - "webextensions_identifier": { - "type": "object", - "properties": { - "__compat": { "$ref": "#/definitions/compat_statement" } - }, - "patternProperties": { - "^(?!__compat)[a-zA-Z_0-9-$@]*$": { - "$ref": "#/definitions/webextensions_identifier", - "tsType": "Identifier" - } - }, - "additionalProperties": false, - "errorMessage": { - "additionalProperties": "Feature names can only contain alphanumerical characters or the following symbols: _-$@" - }, - "tsType": "{[key: string]: Identifier} & {__compat?: CompatStatement}" + } } }, - "title": "PublicDataFile", + "title": "CompatData", "type": "object", - "patternProperties": { - "^(?!browsers)(?!__compat)(?!__meta)(?!webextensions)[a-zA-Z_0-9-$@]*$": { - "$ref": "#/definitions/identifier" - }, - "^__meta$": { - "$ref": "#/definitions/meta" + "description": "Shape of the `data.json` file in published BCD releases", + "properties": { + "__meta": { + "$ref": "#/definitions/meta_block" }, - "^browsers$": { + "browsers": { "$ref": "#/definitions/browsers" - }, - "^webextensions*$": { - "$ref": "#/definitions/webextensions_identifier", - "tsType": "Identifier" - }, - "^__compat$": { - "$ref": "#/definitions/compat_statement" } }, - "additionalProperties": false, - "errorMessage": { - "additionalProperties": "Feature names can only contain alphanumerical characters or the following symbols: _-$@" - } + "patternProperties": { + "^(api|css|html|http|javascript|manifests|mathml|mediatypes|svg|webassembly|webdriver|webextensions)$": { + "$ref": "#/definitions/identifier" + } + }, + "required": ["__meta", "browsers"], + "additionalProperties": false } From 3625d24cba7ae9d4ccc37e4c2277453d4f1e97d9 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 11:53:22 +0100 Subject: [PATCH 10/77] ci(test): add diff-build job Compares build output between PR and base. --- .github/workflows/test.yml | 62 +++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5304fe872f446a..37599061db55a0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,6 @@ on: branches: - main pull_request: - merge_group: permissions: contents: read @@ -71,3 +70,64 @@ jobs: - run: npm ci - run: npm run unittest + + diff-build: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + + concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }}-diff-build + cancel-in-progress: true + + steps: + - name: Determine base ref + id: base + run: | + if [[ "$GITHUB_HEAD_REF" == "release" ]]; then + VERSION=$(npm view @mdn/browser-compat-data version) + echo "ref=v$VERSION" >> "$GITHUB_OUTPUT" + echo "Comparing release branch against published v$VERSION" + else + echo "ref=$GITHUB_BASE_REF" >> "$GITHUB_OUTPUT" + echo "Comparing against base branch: $GITHUB_BASE_REF" + fi + + # Base + + - name: Checkout (base) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ steps.base.outputs.ref }} + path: base + persist-credentials: false + + - name: Checkout (PR) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + path: pr + persist-credentials: false + + - name: Setup Node + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version-file: pr/.nvmrc + cache: npm + cache-dependency-path: | + base/package-lock.json + pr/package-lock.json + package-manager-cache: true + + - name: Install + run: | + cd base && npm ci | sed "s/^/[base] /" & + cd pr && npm ci | sed "s/^/[pr] /" & + wait + + - name: Format + run: | + cd base/build && npx prettier --write . | sed "s/^/[base] /" & + cd pr/build && npx prettier --write . | sed "s/^/[pr] /" & + wait + + - name: Diff + run: git diff --color=always --no-index base/build/ pr/build/ || true From 9c4006699f410a7059b7b532c88eccf4ae68dde8 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 13:09:29 +0100 Subject: [PATCH 11/77] ci(test): add step names --- .github/workflows/test.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 37599061db55a0..ae48a3f6f0dd32 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,11 +17,13 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + - name: Setup Node + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version-file: ".nvmrc" cache: npm @@ -39,11 +41,13 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + - name: Setup Node + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version-file: ".nvmrc" cache: npm @@ -57,11 +61,13 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + - name: Setup Node + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version-file: ".nvmrc" cache: npm From bd382c9fc7129202b116eb5ac702926121a202a2 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 13:42:19 +0100 Subject: [PATCH 12/77] fix(scripts/generate-types): generate internal types as well --- .gitignore | 4 +++- eslint.config.js | 2 ++ scripts/build/index.js | 2 +- scripts/generate-types.js | 26 ++++++++++++++++---------- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 678f4bcd8b5159..9051151f0609d3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,7 @@ yarn.lock .nyc_output/ coverage.lcov coverage/ -types/types.d.ts +/types/browsers.d.ts +/types/compat-data.d.ts +/types/types.d.ts .DS_Store diff --git a/eslint.config.js b/eslint.config.js index 0dc3870b4cdd4c..d5b6b2caf6e1a8 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -34,6 +34,8 @@ export default [ 'CODE_OF_CONDUCT.md', 'build/', '**/coverage/', + '**/browsers.d.ts', + '**/compat-data.d.ts', '**/types.d.ts', ], }, diff --git a/scripts/build/index.js b/scripts/build/index.js index c3e38586620be9..b33f1785389e69 100644 --- a/scripts/build/index.js +++ b/scripts/build/index.js @@ -305,7 +305,7 @@ export * from "./types.js";`; await fs.writeFile(destImport, content); logWrite(destImport, 'ESM types'); - await compileTS(destTypes); + await compileTS('schemas/browsers.schema.json', destTypes); logWrite(destTypes, 'data types'); }; diff --git a/scripts/generate-types.js b/scripts/generate-types.js index fa2f0b1fb12a2b..e4ca4ffb641a20 100644 --- a/scripts/generate-types.js +++ b/scripts/generate-types.js @@ -11,7 +11,7 @@ import { compileFromFile } from 'json-schema-to-typescript'; import { spawn } from '../utils/index.js'; -const dirname = fileURLToPath(new URL('.', import.meta.url)); +const root = new URL('..', import.meta.url); const opts = { bannerComment: @@ -38,24 +38,30 @@ const transformTS = (ts) => { /** * Compile the TypeScript typedefs from the schema JSON - * @param {URL | string} [destination] Output destination + * @param {string} source - JSON schema source + * @param {URL | string} destination - Output destination */ -const compile = async ( - destination = new URL('../types/types.d.ts', import.meta.url), -) => { - let ts = await compileFromFile('schemas/public.schema.json', opts); +const compile = async (source, destination) => { + let ts = await compileFromFile(source, opts); ts = transformTS(ts); - await fs.writeFile(destination, ts); - spawn('tsc', ['--skipLibCheck', '../types/types.d.ts'], { - cwd: dirname, + const file = + destination instanceof URL ? destination : new URL(destination, root); + + await fs.writeFile(file, ts); + spawn('tsc', ['--skipLibCheck', fileURLToPath(file)], { + cwd: fileURLToPath(root), stdio: 'inherit', }); }; if (esMain(import.meta)) { - await compile(); + await Promise.all([ + compile('schemas/browsers.schema.json', 'types/browsers.d.ts'), + compile('schemas/compat-data.schema.json', 'types/compat-data.d.ts'), + compile('schemas/public.schema.json', 'types/types.d.ts'), + ]); } export default compile; From 02fe4a2b83eac29ebf5b2cc027ea7c430d2829f1 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 13:44:58 +0100 Subject: [PATCH 13/77] chore(scripts/build): use new public type --- scripts/build/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build/index.js b/scripts/build/index.js index b33f1785389e69..eda937ce9ecd20 100644 --- a/scripts/build/index.js +++ b/scripts/build/index.js @@ -21,7 +21,7 @@ import mirrorSupport from './mirror.js'; /** * @import { InternalSupportStatement } from '../../types/index.js' - * @import { BrowserName, CompatData, Identifier, VersionValue } from '../../types/types.js' + * @import { BrowserName, CompatData, Identifier, MetaBlock, VersionValue } from '../../types/types.js' * @import { WalkOutput } from '../../utils/walk.js' */ @@ -51,7 +51,7 @@ const logWrite = (url, description = '') => { /** * Generate metadata to embed into BCD builds - * @returns {*} Metadata to embed into BCD + * @returns {MetaBlock} Metadata to embed into BCD */ export const generateMeta = () => ({ version: packageJson.version, From 212d293cb5756e4084901db5082de619906fde79 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 13:47:21 +0100 Subject: [PATCH 14/77] chore: fix internal type usages --- index.js | 22 +++++-- index.test.js | 6 +- lint/common/overlap.js | 6 +- lint/fixer/browser-order.js | 12 ++-- lint/fixer/common-errors.js | 6 +- lint/fixer/common-errors.test.js | 2 +- lint/fixer/feature-order.js | 12 ++-- lint/fixer/flags.js | 12 ++-- lint/fixer/flags.test.js | 4 +- lint/fixer/mirror.js | 17 +++--- lint/fixer/overlap.js | 6 +- lint/fixer/overlap.test.js | 4 +- lint/fixer/statement-order.js | 6 +- lint/fixer/status.js | 10 +-- lint/fixer/status.test.js | 36 ++++++----- lint/lint.js | 6 +- lint/linter/test-browsers-data.js | 2 +- lint/linter/test-browsers-data.test.js | 2 +- lint/linter/test-browsers-presence.js | 35 +++++------ lint/linter/test-consistency.js | 61 ++++++++++--------- lint/linter/test-descriptions.js | 10 +-- lint/linter/test-descriptions.test.js | 16 ++--- lint/linter/test-filename.js | 8 +-- lint/linter/test-filename.test.js | 4 +- lint/linter/test-flags.js | 8 +-- lint/linter/test-mdn-urls.js | 9 ++- lint/linter/test-mirror.js | 4 +- lint/linter/test-multiple-statements.js | 8 +-- lint/linter/test-notes.js | 14 ++--- lint/linter/test-obsolete.js | 9 ++- lint/linter/test-obsolete.test.js | 9 ++- lint/linter/test-overlap.js | 6 +- lint/linter/test-overlap.test.js | 20 +++--- lint/linter/test-prefix.js | 13 ++-- lint/linter/test-spec-urls.js | 6 +- lint/linter/test-status-inheritance.js | 12 ++-- lint/linter/test-status.js | 22 ++++--- lint/linter/test-status.test.js | 20 +++--- lint/linter/test-tags.js | 6 +- lint/linter/test-tags.test.js | 12 ++-- lint/linter/test-versions.js | 7 +-- lint/types.d.ts | 2 +- lint/utils.js | 2 +- lint/utils.test.js | 2 +- scripts/build/index.js | 12 ++-- scripts/build/mirror.js | 31 +++++++--- scripts/build/mirror.test.js | 6 +- scripts/diff-flat.js | 10 +-- scripts/diff.js | 13 ++-- scripts/lib/compare-statements.js | 2 +- scripts/lib/compare-statements.test.js | 8 +-- scripts/lib/stringify-and-order-properties.js | 4 +- .../migrations/002-remove-webview-flags.js | 11 ++-- .../002-remove-webview-flags.test.js | 4 +- scripts/migrations/007-experimental-false.js | 11 ++-- .../migrations/007-experimental-false.test.js | 2 +- .../migrations/010-set-oculus-to-mirror.js | 7 +-- .../011-set-webview-ios-to-mirror.js | 7 +-- scripts/migrations/012-descriptions-to-md.js | 6 +- scripts/split.js | 8 +-- scripts/statistics.js | 25 +++++--- scripts/traverse.js | 49 ++++++++------- scripts/update-browser-releases/bun.js | 8 ++- scripts/update-browser-releases/firefox.js | 2 +- scripts/update-browser-releases/utils.js | 8 ++- types/index.d.ts | 38 ++++++++++-- utils/iter-support.js | 6 +- utils/iter-support.test.js | 4 +- utils/query.js | 6 +- utils/walk.js | 20 +++--- utils/walkingUtils.js | 4 +- 71 files changed, 452 insertions(+), 356 deletions(-) diff --git a/index.js b/index.js index 090a2943213788..f33c7b71ceb98d 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {CompatData} from './types/types.js' */ +/** @import {Browsers, InternalCompatData} from './types/index.js' */ import fs from 'node:fs/promises'; import path from 'node:path'; @@ -18,10 +18,13 @@ const dirname = fileURLToPath(new URL('.', import.meta.url)); /** * Recursively load one or more directories passed as arguments. * @param {...string} dirs The directories to load - * @returns {Promise} All of the browser compatibility data + * @returns {Promise} All of the browser compatibility data */ const load = async (...dirs) => { - const result = {}; + /** @type {InternalCompatData} */ + const result = { + browsers: {}, + }; for (const dir of dirs) { const paths = /** @type {string[]} */ ( @@ -35,12 +38,13 @@ const load = async (...dirs) => { for (const fp of paths) { try { const rawcontents = await fs.readFile(fp); - /** @type {CompatData} */ + /** @type {InternalCompatData} */ const contents = JSON.parse(rawcontents.toString('utf8')); // Add source_file props const walker = walk(undefined, contents); for (const { compat } of walker) { + // @ts-expect-error Need to better reflect transition from internal to public data. compat.source_file = normalizePath(path.relative(dirname, fp)); } @@ -54,7 +58,13 @@ const load = async (...dirs) => { } } - return /** @type {CompatData} */ (result); + return result; }; -export default await load(...dataFolders); +/** @type {InternalCompatData} */ +const bcd = await load(...dataFolders); + +export default bcd; + +/** @type {Browsers} */ +export const browsers = bcd.browsers; diff --git a/index.test.js b/index.test.js index 83a90c0144fd6c..e8b5e7219b7c54 100644 --- a/index.test.js +++ b/index.test.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {CompatStatement} from './types/types.js' */ +/** @import {InternalCompatStatement} from './types/index.js' */ import assert from 'node:assert/strict'; @@ -9,13 +9,13 @@ import bcd from './index.js'; describe('Using BCD', () => { it('subscript notation', () => { - /** @type {CompatStatement | undefined} */ + /** @type {InternalCompatStatement | undefined} */ const data = bcd['api']['AbortController']['__compat']; assert.ok(data); }); it('dot notation', () => { - /** @type {CompatStatement | undefined} */ + /** @type {InternalCompatStatement | undefined} */ const data = bcd.api.AbortController.__compat; assert.ok(data); }); diff --git a/lint/common/overlap.js b/lint/common/overlap.js index 625409e82be6c5..82c4ea2e082c5f 100644 --- a/lint/common/overlap.js +++ b/lint/common/overlap.js @@ -9,7 +9,7 @@ import { createStatementGroupKey } from '../utils.js'; import compareStatements from '../../scripts/lib/compare-statements.js'; /** @import {Logger} from '../utils.js' */ -/** @import {BrowserName, SimpleSupportStatement, SupportStatement} from '../../types/types.js' */ +/** @import {BrowserName, SimpleSupportStatement, InternalSupportStatement} from '../../types/index.js' */ /** * Groups statements by group key. @@ -52,12 +52,12 @@ const formatRange = (support) => { /** * Process data and check to make sure there aren't support statements whose version ranges overlap. - * @param {SupportStatement} data The data to test + * @param {InternalSupportStatement} data The data to test * @param {BrowserName} browser The name of the browser * @param {object} options The check options * @param {Logger} [options.logger] The logger to output errors to * @param {boolean} [options.fix] Whether the statements should be fixed (if possible) - * @returns {SupportStatement} the data (with fixes, if specified) + * @returns {InternalSupportStatement} the data (with fixes, if specified) */ export const checkOverlap = (data, browser, { logger, fix = false }) => { if (!Array.isArray(data)) { diff --git a/lint/fixer/browser-order.js b/lint/fixer/browser-order.js index 013a0ba5864f53..37f558c3316fa2 100644 --- a/lint/fixer/browser-order.js +++ b/lint/fixer/browser-order.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {BrowserName, CompatStatement, SupportBlock} from '../../types/types.js' */ +/** @import {BrowserName, InternalCompatStatement, InternalSupportBlock} from '../../types/index.js' */ /** * Return a new "support_block" object whose first-level properties @@ -10,8 +10,8 @@ * guaranteed "own" property ordering, which is insertion order for * non-integer keys (which is our case). * @param {string} key The key of the object - * @param {CompatStatement} value The value of the key - * @returns {CompatStatement} Value with sorting applied + * @param {InternalCompatStatement} value The value of the key + * @returns {InternalCompatStatement} Value with sorting applied */ export const orderSupportBlock = (key, value) => { if (key === '__compat') { @@ -21,15 +21,15 @@ export const orderSupportBlock = (key, value) => { .sort() .reduce( /** - * @param {SupportBlock} result + * @param {InternalSupportBlock} result * @param {BrowserName} key - * @returns {SupportBlock} + * @returns {InternalSupportBlock} */ (result, key) => { result[key] = value.support[key]; return result; }, - /** @type {SupportBlock} */ ({}), + /** @type {InternalSupportBlock} */ ({}), ); } return value; diff --git a/lint/fixer/common-errors.js b/lint/fixer/common-errors.js index 5e88244d1ac9e2..430d3329fbfb0a 100644 --- a/lint/fixer/common-errors.js +++ b/lint/fixer/common-errors.js @@ -3,14 +3,14 @@ import { walk } from '../../utils/index.js'; -/** @import {CompatStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement} from '../../types/index.js' */ /** - * Fixes common errors in CompatStatements. + * Fixes common errors in InternalCompatStatements. * * - Replaces `browser: { version_added: "mirror" }` with `browser: "mirror"` * - Wraps `browser: false` with `browser: `{ version_added: false }` - * @param {CompatStatement} compat The compat statement to fix + * @param {Pick} compat The compat statement to fix * @returns {void} */ export const fixCommonErrorsInCompatStatement = (compat) => { diff --git a/lint/fixer/common-errors.test.js b/lint/fixer/common-errors.test.js index e3e01140ef6ecb..18d8cafd809d1a 100644 --- a/lint/fixer/common-errors.test.js +++ b/lint/fixer/common-errors.test.js @@ -9,7 +9,7 @@ import { fixCommonErrorsInCompatStatement } from './common-errors.js'; * @import { InternalSupportBlock } from '../../types/index.js' */ -/** @type {{ input: any; output?: InternalSupportBlock }[]} */ +/** @type {{ input: any; output?: Partial }[]} */ const tests = [ // Replace unwrapped "false". { diff --git a/lint/fixer/feature-order.js b/lint/fixer/feature-order.js index d4b6838da8dd5c..fba8d6a2c213d8 100644 --- a/lint/fixer/feature-order.js +++ b/lint/fixer/feature-order.js @@ -3,7 +3,7 @@ import compareFeatures from '../../scripts/lib/compare-features.js'; -/** @import {Identifier} from '../../types/types.js' */ +/** @import {InternalIdentifier} from '../../types/index.js' */ /** * Return a new feature object whose first-level properties have been @@ -12,8 +12,8 @@ import compareFeatures from '../../scripts/lib/compare-features.js'; * property ordering, which is insertion order for non-integer keys * (which is our case). * @param {string} _ The key in the object - * @param {Identifier} value The value of the key - * @returns {Identifier} The new value + * @param {InternalIdentifier} value The value of the key + * @returns {InternalIdentifier} The new value */ export const orderFeatures = (_, value) => { if (value instanceof Object && '__compat' in value) { @@ -21,15 +21,15 @@ export const orderFeatures = (_, value) => { .sort(compareFeatures) .reduce( /** - * @param {Identifier} result + * @param {InternalIdentifier} result * @param {string} key - * @returns {Identifier} + * @returns {InternalIdentifier} */ (result, key) => { result[key] = value[key]; return result; }, - /** @type {Identifier} */ ({}), + /** @type {InternalIdentifier} */ ({}), ); } return value; diff --git a/lint/fixer/flags.js b/lint/fixer/flags.js index 8f6bc8b09876ac..4f7fea8860b0a4 100644 --- a/lint/fixer/flags.js +++ b/lint/fixer/flags.js @@ -7,12 +7,12 @@ import testFlags, { } from '../linter/test-flags.js'; import walk from '../../utils/walk.js'; -/** @import {BrowserName, CompatStatement, SupportStatement, SimpleSupportStatement, Identifier} from '../../types/types.js' */ +/** @import {BrowserName, InternalCompatStatement, InternalSupportStatement, SimpleSupportStatement, InternalIdentifier} from '../../types/index.js' */ /** * Removes irrelevant flags from the compatibility data - * @param {SupportStatement} supportData The compatibility statement to test - * @returns {SupportStatement} The compatibility statement with all of the flags removed + * @param {InternalSupportStatement} supportData The compatibility statement to test + * @returns {InternalSupportStatement} The compatibility statement with all of the flags removed */ export const removeIrrelevantFlags = (supportData) => { if (typeof supportData === 'string') { @@ -68,12 +68,14 @@ const fixFlags = (filename, actual) => { } const featureData = - /** @type {Identifier & {__compat: CompatStatement}} */ (feature.data); + /** @type {InternalIdentifier & {__compat: InternalCompatStatement}} */ ( + feature.data + ); for (const [ browser, supportData, - ] of /** @type {[BrowserName, SupportStatement][]} */ ( + ] of /** @type {[BrowserName, InternalSupportStatement][]} */ ( Object.entries(feature.compat.support) )) { featureData.__compat.support[browser] = diff --git a/lint/fixer/flags.test.js b/lint/fixer/flags.test.js index 24790f5af0c055..8fcad51d4f4f5b 100644 --- a/lint/fixer/flags.test.js +++ b/lint/fixer/flags.test.js @@ -1,13 +1,13 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {SupportStatement} from '../../types/types.js' */ +/** @import {InternalSupportStatement} from '../../types/index.js' */ import assert from 'node:assert/strict'; import { removeIrrelevantFlags } from './flags.js'; -/** @type {{ input: SupportStatement; output: SupportStatement }[]} */ +/** @type {{ input: InternalSupportStatement; output: InternalSupportStatement }[]} */ const tests = [ { input: [ diff --git a/lint/fixer/mirror.js b/lint/fixer/mirror.js index 5ef3fc15aaaae8..71eb7b373b8969 100644 --- a/lint/fixer/mirror.js +++ b/lint/fixer/mirror.js @@ -3,16 +3,18 @@ import stringify from 'fast-json-stable-stringify'; -import bcd from '../../index.js'; +import { browsers } from '../../index.js'; import { walk } from '../../utils/index.js'; import mirrorSupport from '../../scripts/build/mirror.js'; -/** @import {CompatData, BrowserName} from '../../types/types.js' */ +/** @import {InternalCompatData, BrowserName} from '../../types/index.js' */ /** @import {InternalSupportStatement, InternalSupportBlock} from '../../types/index.js' */ -const downstreamBrowsers = /** @type {(keyof typeof bcd.browsers)[]} */ ( - Object.keys(bcd.browsers) -).filter((browser) => bcd.browsers[browser].upstream); +const downstreamBrowsers = /** @type {BrowserName[]} */ ( + Object.entries(browsers).flatMap(([browser, stmt]) => + stmt.upstream ? [browser] : [], + ) +); /** * Check to see if the statement is equal to the mirrored statement @@ -45,7 +47,8 @@ export const isMirrorEquivalent = (support, browser) => { * @returns {boolean} Whether mirroring is required */ export const isMirrorRequired = (supportData, browser) => { - const current = bcd.browsers[browser]; + const current = browsers[browser]; + /** @type {BrowserName | undefined} */ const upstream = current.upstream; @@ -69,7 +72,7 @@ export const isMirrorRequired = (supportData, browser) => { /** * Set the support statement for each browser to mirror if it matches mirroring - * @param {CompatData} bcd The compat data to update + * @param {InternalCompatData} bcd The compat data to update * @returns {void} */ export const mirrorIfEquivalent = (bcd) => { diff --git a/lint/fixer/overlap.js b/lint/fixer/overlap.js index e500ad83c325f3..692b50b7c6c7d4 100644 --- a/lint/fixer/overlap.js +++ b/lint/fixer/overlap.js @@ -3,14 +3,14 @@ import { checkOverlap as checkOverlap } from '../common/overlap.js'; -/** @import {BrowserName, CompatStatement} from '../../types/types.js' */ +/** @import {BrowserName, InternalCompatStatement} from '../../types/index.js' */ /** * Return a new "support_block" object whose support statements have * been updated to avoid overlapping version ranges. * @param {string} key The key in the object - * @param {CompatStatement} value The value of the key - * @returns {CompatStatement} The new value + * @param {InternalCompatStatement} value The value of the key + * @returns {InternalCompatStatement} The new value */ export const processStatement = (key, value) => { if (key === '__compat') { diff --git a/lint/fixer/overlap.test.js b/lint/fixer/overlap.test.js index 932bff58d90ee7..c36dec11672f7d 100644 --- a/lint/fixer/overlap.test.js +++ b/lint/fixer/overlap.test.js @@ -1,13 +1,13 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {SupportStatement} from '../../types/types.js' */ +/** @import {InternalSupportStatement} from '../../types/index.js' */ import assert from 'node:assert/strict'; import { checkOverlap } from '../common/overlap.js'; -/** @type {{ input: SupportStatement; output?: SupportStatement }[]} */ +/** @type {{ input: InternalSupportStatement; output?: InternalSupportStatement }[]} */ const tests = [ // Use version_added from following as version_removed of previous. { diff --git a/lint/fixer/statement-order.js b/lint/fixer/statement-order.js index a72df08e29ca9f..734ad5a1385554 100644 --- a/lint/fixer/statement-order.js +++ b/lint/fixer/statement-order.js @@ -3,15 +3,15 @@ import compareStatements from '../../scripts/lib/compare-statements.js'; -/** @import {BrowserName, CompatStatement} from '../../types/types.js' */ +/** @import {BrowserName, InternalCompatStatement} from '../../types/index.js' */ /** * Return a new "support_block" object whose support statements have * been ordered in reverse chronological order, moving statements * with flags, partial support, prefixes, or alternative names lower. * @param {string} key The key in the object - * @param {CompatStatement} value The value of the key - * @returns {CompatStatement} The new value + * @param {InternalCompatStatement} value The value of the key + * @returns {InternalCompatStatement} The new value */ export const orderStatements = (key, value) => { if (key === '__compat') { diff --git a/lint/fixer/status.js b/lint/fixer/status.js index 0cf22b5b49a1a0..b23da351b84fdf 100644 --- a/lint/fixer/status.js +++ b/lint/fixer/status.js @@ -4,12 +4,12 @@ import { checkExperimental } from '../linter/test-status.js'; import walk from '../../utils/walk.js'; -/** @import {Identifier} from '../../types/types.js' */ +/** @import {InternalCompatStatement, InternalIdentifier} from '../../types/index.js' */ /** * Fix the status values - * @param {Identifier} value The value to update - * @returns {Identifier} The updated value + * @param {InternalIdentifier} value The value to update + * @returns {InternalIdentifier} The updated value */ export const fixStatusValue = (value) => { const compat = value?.__compat; @@ -22,7 +22,7 @@ export const fixStatusValue = (value) => { compat.status.standard_track = true; } - if (!checkExperimental(compat)) { + if (!checkExperimental(/** @type {InternalCompatStatement} */ (compat))) { compat.status.experimental = false; } @@ -71,7 +71,7 @@ const fixStatusFromFile = (filename, actual) => { return JSON.stringify( JSON.parse( actual, - (/** @type {string} */ _key, /** @type {Identifier} */ value) => + (/** @type {string} */ _key, /** @type {InternalIdentifier} */ value) => fixStatusValue(value), ), null, diff --git a/lint/fixer/status.test.js b/lint/fixer/status.test.js index e5f179ca46c471..85a99c34c80da8 100644 --- a/lint/fixer/status.test.js +++ b/lint/fixer/status.test.js @@ -1,17 +1,11 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {CompatStatement, Identifier} from '../../types/types.js' */ - -/** - * @typedef {Record} TestValue - */ - import assert from 'node:assert/strict'; import { fixStatusValue } from './status.js'; -/** @type {{ name: string; input: TestValue; output: TestValue }[]} */ +/** @type {{ name: string; input: *; output: * }[]} */ const tests = [ { name: 'should unset experimental when feature is deprecated', @@ -102,34 +96,46 @@ const tests = [ name: 'should set deprecated when parent feature is deprecated', input: { __compat: { - support: {}, + support: { + firefox: { + version_added: '1', + }, + }, status: { experimental: false, standard_track: true, deprecated: true, }, }, - subfeature: /** @type {Identifier} */ ({ + subfeature: { __compat: { - support: {}, + support: { + firefox: { + version_added: '1', + }, + }, status: { experimental: false, standard_track: true, deprecated: false, }, }, - }), + }, }, output: { __compat: { - support: {}, + support: { + firefox: { + version_added: '1', + }, + }, status: { experimental: false, standard_track: true, deprecated: true, }, }, - subfeature: /** @type {Identifier} */ ({ + subfeature: { __compat: { support: {}, status: { @@ -138,7 +144,7 @@ const tests = [ deprecated: true, }, }, - }), + }, }, }, ]; @@ -146,7 +152,7 @@ const tests = [ describe('fixStatus', () => { for (const test of tests) { it(test.name, () => { - const result = fixStatusValue(/** @type {Identifier} */ (test.input)); + const result = fixStatusValue(test.input); assert.deepStrictEqual(result, test.output); }); diff --git a/lint/lint.js b/lint/lint.js index 81a23b2f28db9e..a1a8e0da76f67f 100644 --- a/lint/lint.js +++ b/lint/lint.js @@ -19,7 +19,7 @@ import * as linterModules from './linter/index.js'; import { Linters } from './utils.js'; /** @import {Stats} from 'node:fs' */ -/** @import {BrowserName, CompatData} from '../types/types.js' */ +/** @import {BrowserName, InternalCompatData} from '../types/index.js' */ /** @import {LinterMessage, LinterMessageLevel, LinterPath} from './types.js' */ const dirname = fileURLToPath(new URL('.', import.meta.url)); @@ -51,7 +51,7 @@ const normalizeAndCategorizeFilePath = (file) => { /** * Recursively load * @param {...string} files The files to test - * @returns {Promise} The data from the loaded files + * @returns {Promise} The data from the loaded files */ const loadAndCheckFiles = async (...files) => { const data = {}; @@ -105,7 +105,7 @@ const loadAndCheckFiles = async (...files) => { } } - return /** @type {CompatData} */ (data); + return /** @type {InternalCompatData} */ (data); }; /** diff --git a/lint/linter/test-browsers-data.js b/lint/linter/test-browsers-data.js index f9d837f3196ba7..fc9e045fdf9d62 100644 --- a/lint/linter/test-browsers-data.js +++ b/lint/linter/test-browsers-data.js @@ -8,7 +8,7 @@ const { browsers } = bcd; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {BrowserStatement, BrowserName} from '../../types/types.js' */ +/** @import {BrowserStatement, BrowserName} from '../../types/index.js' */ /** * Process and test the data diff --git a/lint/linter/test-browsers-data.test.js b/lint/linter/test-browsers-data.test.js index 89e47c124bd777..6236ca5790edb1 100644 --- a/lint/linter/test-browsers-data.test.js +++ b/lint/linter/test-browsers-data.test.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {BrowserStatement} from '../../types/types.js' */ +/** @import {BrowserStatement} from '../../types/index.js' */ import assert from 'node:assert/strict'; diff --git a/lint/linter/test-browsers-presence.js b/lint/linter/test-browsers-presence.js index 4a91db063d0504..a15f43de75dbaa 100644 --- a/lint/linter/test-browsers-presence.js +++ b/lint/linter/test-browsers-presence.js @@ -3,16 +3,15 @@ import { styleText } from 'node:util'; -import bcd from '../../index.js'; -const { browsers } = bcd; +import { browsers } from '../../index.js'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {CompatStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement} from '../../types/index.js' */ /** * Check the data for any disallowed browsers or if it's missing required browsers - * @param {CompatStatement} data The data to test + * @param {InternalCompatStatement} data The data to test * @param {string} category The category the data belongs to. * @param {Logger} logger The logger to output errors to. * @returns {void} @@ -22,10 +21,8 @@ const processData = (data, category, logger) => { const support = data.support; const definedBrowsers = Object.keys(support); - const displayBrowsers = /** @type {(keyof typeof browsers)[]} */ ( - Object.keys(browsers) - ).filter( - (b) => + const displayBrowsers = Object.entries(browsers).flatMap( + ([name, browser]) => [ 'desktop', 'mobile', @@ -33,12 +30,12 @@ const processData = (data, category, logger) => { ...(['api', 'javascript', 'webassembly'].includes(category) ? ['server'] : []), - ].includes(browsers[b].type) && - (category !== 'webextensions' || browsers[b].accepts_webextensions), + ].includes(browser.type) && + (category !== 'webextensions' || browser.accepts_webextensions) + ? [name] + : [], ); - const requiredBrowsers = /** @type {(keyof typeof browsers)[]} */ ( - Object.keys(browsers) - ).filter( + const requiredBrowsers = Object.keys(browsers).filter( (b) => !['ie'].includes(b) && ['desktop', 'mobile'].includes(browsers[b].type) && @@ -54,9 +51,9 @@ const processData = (data, category, logger) => { ); } - const invalidEntries = /** @type {(keyof typeof support)[]} */ ( - Object.keys(support) - ).filter((value) => !displayBrowsers.includes(value)); + const invalidEntries = Object.keys(support).filter( + (value) => !displayBrowsers.includes(value), + ); if (invalidEntries.length > 0) { logger.error( `Has the following browsers, which are invalid for ${styleText('bold', category)} compat data: ${styleText('bold', invalidEntries.join(', '))}`, @@ -86,6 +83,10 @@ export default { * @param {LinterData} root The data to test */ check: (logger, { data, path: { category } }) => { - processData(/** @type {CompatStatement} */ (data), category, logger); + processData( + /** @type {InternalCompatStatement} */ (data), + category, + logger, + ); }, }; diff --git a/lint/linter/test-consistency.js b/lint/linter/test-consistency.js index 4644ce6b093c0b..79a97715f39fa8 100644 --- a/lint/linter/test-consistency.js +++ b/lint/linter/test-consistency.js @@ -11,7 +11,7 @@ import bcd from '../../index.js'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {BrowserName, CompatData, CompatStatement, Identifier, SimpleSupportStatement, VersionValue} from '../../types/types.js' */ +/** @import {BrowserName, InternalCompatData, InternalCompatStatement, InternalIdentifier, SimpleSupportStatement, VersionValue} from '../../types/index.js' */ /** @import {DataType, InternalSupportBlock, InternalSupportStatement} from '../../types/index.js' */ /** @@ -41,7 +41,7 @@ import bcd from '../../index.js'; export class ConsistencyChecker { /** * Checks the data for any errors - * @param {CompatData} data The data to test + * @param {InternalCompatData} data The data to test * @returns {ConsistencyError[]} Any errors found within the data */ check(data) { @@ -51,7 +51,7 @@ export class ConsistencyChecker { /** * Recursively checks the data for any errors - * @param {Identifier} data The data to test + * @param {InternalIdentifier} data The data to test * @param {string[]} [path] The path of the data * @returns {ConsistencyError[]} Any errors found within the data */ @@ -75,10 +75,10 @@ export class ConsistencyChecker { this.getSubfeatures(data).forEach((key) => { allErrors = [ ...allErrors, - ...this.checkSubfeatures(/** @type {Identifier} */ (query(key, data)), [ - ...path, - ...key.split('.'), - ]), + ...this.checkSubfeatures( + /** @type {InternalIdentifier} */ (query(key, data)), + [...path, ...key.split('.')], + ), ]; }); @@ -86,8 +86,8 @@ export class ConsistencyChecker { } /** - * Get the subfeatures of an identifier - * @param {Identifier} data The identifier + * Get the subfeatures of an Internalidentifier + * @param {InternalIdentifier} data The Internalidentifier * @returns {string[]} The subfeatures */ getSubfeatures(data) { @@ -115,7 +115,7 @@ export class ConsistencyChecker { /** * Checks a specific feature for errors - * @param {Identifier} data The data to test + * @param {InternalIdentifier} data The data to test * @returns {FeatureError[]} Any errors found within the data */ checkFeature(data) { @@ -126,13 +126,15 @@ export class ConsistencyChecker { // Test whether sub-features are supported when basic support is not implemented // For all unsupported browsers (basic support == false), sub-features should be set to false - const unsupportedInParent = this.extractUnsupportedBrowsers(data.__compat); + const unsupportedInParent = this.extractUnsupportedBrowsers( + /** @type {InternalCompatStatement} */ (data.__compat), + ); /** @type {Partial>} */ let inconsistentSubfeaturesByBrowser = {}; subfeatures.forEach((subfeature) => { const unsupportedInChild = this.extractUnsupportedBrowsers( - /** @type {Identifier} */ (query(subfeature, data)).__compat, + /** @type {InternalIdentifier} */ (query(subfeature, data)).__compat, ); const browsers = /** @type {BrowserName[]} */ ( @@ -145,7 +147,8 @@ export class ConsistencyChecker { browser, ); const subfeature_value = this.getVersionAdded( - /** @type {Identifier} */ (query(subfeature, data)).__compat?.support, + /** @type {InternalIdentifier} */ (query(subfeature, data)).__compat + ?.support, browser, ); if (feature_value === subfeature_value) { @@ -185,7 +188,7 @@ export class ConsistencyChecker { for (const subfeature of subfeatures) { for (const browser of supportInParent) { - const subfeatureData = /** @type {Identifier} */ ( + const subfeatureData = /** @type {InternalIdentifier} */ ( query(subfeature, data) ); if ( @@ -232,8 +235,8 @@ export class ConsistencyChecker { /** * Checks if the data is a feature - * @param {Identifier} data The data to test - * @returns {data is Identifier & {__compat: CompatStatement}} If the data is a feature statement + * @param {InternalIdentifier} data The data to test + * @returns {data is InternalIdentifier & {__compat: InternalCompatStatement}} If the data is a feature statement */ isFeature(data) { return '__compat' in data; @@ -241,12 +244,12 @@ export class ConsistencyChecker { /** * Get all of the unsupported browsers in a feature - * @param {CompatStatement} [compatData] The compat data to process + * @param {InternalCompatStatement} [InternalcompatData] The compat data to process * @returns {BrowserName[]} The list of browsers marked as unsupported */ - extractUnsupportedBrowsers(compatData) { + extractUnsupportedBrowsers(InternalcompatData) { return this.extractBrowsers( - compatData, + InternalcompatData, (data) => data.version_added === false || typeof data.version_removed !== 'undefined', @@ -255,12 +258,12 @@ export class ConsistencyChecker { /** * Get all of the browsers with a version number in a feature. - * @param {CompatStatement} [compatData] The compat data to process + * @param {InternalCompatStatement} [InternalcompatData] The compat data to process * @returns {BrowserName[]} The list of browsers with an exact version number */ - extractSupportedBrowsersWithVersion(compatData) { + extractSupportedBrowsersWithVersion(InternalcompatData) { return this.extractBrowsers( - compatData, + InternalcompatData, (/** @type {SimpleSupportStatement} */ data) => typeof data.version_added === 'string', ); @@ -374,28 +377,28 @@ export class ConsistencyChecker { /** * Get all of the browsers within the data and pass the data to the callback. - * @param {CompatStatement | undefined} compatData The compat data to process + * @param {InternalCompatStatement | undefined} InternalcompatData The compat data to process * @param {(browserData: SimpleSupportStatement) => boolean} callback The function to pass the data to * @returns {BrowserName[]} The list of browsers using the callback as a filter */ - extractBrowsers(compatData, callback) { - if (!compatData) { + extractBrowsers(InternalcompatData, callback) { + if (!InternalcompatData) { return []; } return /** @type {BrowserName[]} */ (Object.keys(bcd.browsers)).filter( (browser) => { - if (!(browser in compatData.support)) { + if (!(browser in InternalcompatData.support)) { return callback({ version_added: false }); } let browserData = /** @type {InternalSupportStatement | undefined} */ ( - compatData.support[browser] + InternalcompatData.support[browser] ); if ( /** @type {InternalSupportStatement} */ (browserData) === 'mirror' ) { - browserData = mirrorSupport(browser, compatData.support); + browserData = mirrorSupport(browser, InternalcompatData.support); } if (Array.isArray(browserData)) { @@ -421,7 +424,7 @@ export default { */ check: (logger, { data }) => { const checker = new ConsistencyChecker(); - const allErrors = checker.check(/** @type {CompatData} */ (data)); + const allErrors = checker.check(/** @type {InternalCompatData} */ (data)); for (const { path, errors } of allErrors) { for (const { type, browser, parentValue, subfeatures } of errors) { diff --git a/lint/linter/test-descriptions.js b/lint/linter/test-descriptions.js index 81178416cd02be..0fa189f18e30b8 100644 --- a/lint/linter/test-descriptions.js +++ b/lint/linter/test-descriptions.js @@ -7,7 +7,7 @@ import { validateHTML } from './test-notes.js'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {CompatStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement} from '../../types/index.js' */ /** * @typedef {object} DescriptionError @@ -21,7 +21,7 @@ import { validateHTML } from './test-notes.js'; * Check for errors in the description of a specified statement's description and return whether there's an error and log as such * @param {string} ruleName The name of the error * @param {string} path The feature path - * @param {CompatStatement} compat The compat data to test + * @param {InternalCompatStatement} compat The compat data to test * @param {string} expected Expected description * @param {(DescriptionError | string)[]} errors The array of errors to push to * @returns {void} @@ -40,7 +40,7 @@ const checkDescription = (ruleName, path, compat, expected, errors) => { /** * Process API data and check for any incorrect descriptions in said data, logging any errors - * @param {CompatStatement} data The data to test + * @param {InternalCompatStatement} data The data to test * @param {string} path The path of the feature * @param {(DescriptionError | string)[]} errors The array of errors to push to * @returns {void} @@ -94,7 +94,7 @@ const processApiData = (data, path, errors) => { /** * Process data and check for any incorrect descriptions in said data, logging any errors - * @param {CompatStatement} data The data to test + * @param {InternalCompatStatement} data The data to test * @param {string} category The feature category * @param {string} path The path of the feature * @returns {(DescriptionError | string)[]} The errors caught in the file @@ -135,7 +135,7 @@ export default { */ check: (logger, { data, path: { full, category } }) => { const errors = processData( - /** @type {CompatStatement} */ (data), + /** @type {InternalCompatStatement} */ (data), category, full, ); diff --git a/lint/linter/test-descriptions.test.js b/lint/linter/test-descriptions.test.js index ba4449e8adcd14..be04426de2704f 100644 --- a/lint/linter/test-descriptions.test.js +++ b/lint/linter/test-descriptions.test.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {CompatStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement} from '../../types/index.js' */ /** @import {DescriptionError} from './test-descriptions.js' */ import assert from 'node:assert/strict'; @@ -12,7 +12,7 @@ describe('test-descriptions', () => { describe('API data', () => { it('should ignore anything that is not an interface subfeature', () => { const path = 'api.Interface.feature.subfeature'; - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { support: {}, }; @@ -23,7 +23,7 @@ describe('test-descriptions', () => { it('should check description for constructor', () => { const path = 'api.Interface.Interface'; - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { description: '', support: {}, @@ -39,7 +39,7 @@ describe('test-descriptions', () => { it('should check description for event', () => { const path = 'api.Interface.click_event'; - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { description: '', support: {}, @@ -55,7 +55,7 @@ describe('test-descriptions', () => { it('should check description for permission', () => { const path = 'api.Interface.geolocation_permission'; - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { description: '', support: {}, @@ -71,7 +71,7 @@ describe('test-descriptions', () => { it('should check description for secure context required', () => { const path = 'api.Interface.secure_context_required'; - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { description: '', support: {}, @@ -87,7 +87,7 @@ describe('test-descriptions', () => { it('should check description for worker support', () => { const path = 'api.Interface.worker_support'; - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { description: '', support: {}, @@ -103,7 +103,7 @@ describe('test-descriptions', () => { it('should check for redundant description', () => { const path = 'css.properties.width.auto'; - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { description: '`auto`', support: {}, diff --git a/lint/linter/test-filename.js b/lint/linter/test-filename.js index 2f2491735fa862..4822d31ae4b720 100644 --- a/lint/linter/test-filename.js +++ b/lint/linter/test-filename.js @@ -1,13 +1,13 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {Identifier} from '../../types/types.js' */ +/** @import {InternalIdentifier} from '../../types/index.js' */ /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ /** * Test the filename based on the identifier - * @param {Identifier} data The identifier + * @param {InternalIdentifier} data The identifier * @param {string[]} pathParts Parts of the path * @param {string} currentPath The current path traversed * @returns {string | false} A string with the error message if the lint failed, or false if it passed @@ -33,7 +33,7 @@ const testFilename = (data, pathParts, currentPath) => { /** * Process the data to make sure it defines the features appropriate to the file's name - * @param {Identifier} data The raw contents of the file to test + * @param {InternalIdentifier} data The raw contents of the file to test * @param {string} filepath The file path * @param {Logger} logger The logger to output errors to * @returns {void} @@ -64,6 +64,6 @@ export default { * @param {LinterData} root The data to test */ check: (logger, { data, path: { full } }) => { - processData(/** @type {Identifier} */ (data), full, logger); + processData(/** @type {InternalIdentifier} */ (data), full, logger); }, }; diff --git a/lint/linter/test-filename.test.js b/lint/linter/test-filename.test.js index fe34b39d84747b..973d6d75875b5d 100644 --- a/lint/linter/test-filename.test.js +++ b/lint/linter/test-filename.test.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {Identifier} from '../../types/types.js' */ +/** @import {InternalIdentifier} from '../../types/index.js' */ import assert from 'node:assert/strict'; @@ -12,7 +12,7 @@ import test from './test-filename.js'; describe('test-filename', () => { /** @type {Logger} */ let logger; - /** @type {Identifier} */ + /** @type {InternalIdentifier} */ let data; beforeEach(() => { logger = new Logger('test', 'test'); diff --git a/lint/linter/test-flags.js b/lint/linter/test-flags.js index 2eae63618b71e2..9ca5f1f2f17e64 100644 --- a/lint/linter/test-flags.js +++ b/lint/linter/test-flags.js @@ -7,7 +7,7 @@ import { compare } from 'compare-versions'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {CompatStatement, BrowserName, SupportStatement, SimpleSupportStatement, FlagStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement, BrowserName, InternalSupportStatement, SimpleSupportStatement, FlagStatement} from '../../types/index.js' */ /** * @typedef {object} FlagError @@ -83,7 +83,7 @@ export const isIrrelevantFlagData = (statement, basicSupport) => { }; /** * Process data and check for any irrelevant flag data - * @param {CompatStatement} data The data to test + * @param {InternalCompatStatement} data The data to test * @returns {FlagError[]} The errors found */ export const processData = (data) => { @@ -93,7 +93,7 @@ export const processData = (data) => { for (const [ browser, supportData, - ] of /** @type {[BrowserName, SupportStatement][]} */ ( + ] of /** @type {[BrowserName, InternalSupportStatement][]} */ ( Object.entries(data.support) )) { if (typeof supportData === 'string') { @@ -130,7 +130,7 @@ export default { * @param {LinterData} root The data to test */ check: (logger, { data }) => { - const errors = processData(/** @type {CompatStatement} */ (data)); + const errors = processData(/** @type {InternalCompatStatement} */ (data)); for (const error of errors) { logger.error( diff --git a/lint/linter/test-mdn-urls.js b/lint/linter/test-mdn-urls.js index 47b09d186b545d..089cfb3aefed5e 100644 --- a/lint/linter/test-mdn-urls.js +++ b/lint/linter/test-mdn-urls.js @@ -7,7 +7,7 @@ import mdnContentInventory from '@ddbeck/mdn-content-inventory'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {CompatStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement} from '../../types/index.js' */ /** * @typedef {object} MDNURLError @@ -69,7 +69,7 @@ const redirects = mdnContentInventory.redirects; /** * Process the data for MDN URL issues - * @param {CompatStatement} data The data to test + * @param {InternalCompatStatement} data The data to test * @param {string} path The path of the feature * @returns {MDNURLError[]} The issues caught in the file */ @@ -152,7 +152,10 @@ export default { * @param {LinterData} root The data to test */ check: (logger, { data, path: { full } }) => { - const issues = processData(/** @type {CompatStatement} */ (data), full); + const issues = processData( + /** @type {InternalCompatStatement} */ (data), + full, + ); for (const issue of issues) { if (issue.expected === '') { logger.warning( diff --git a/lint/linter/test-mirror.js b/lint/linter/test-mirror.js index 85745667d9cd07..43823cae2e6b3c 100644 --- a/lint/linter/test-mirror.js +++ b/lint/linter/test-mirror.js @@ -9,7 +9,7 @@ import { isMirrorEquivalent, isMirrorRequired } from '../fixer/mirror.js'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {BrowserName, CompatStatement} from '../../types/types.js' */ +/** @import {BrowserName, InternalCompatStatement} from '../../types/index.js' */ /** @import {InternalSupportBlock} from '../../types/index.js' */ /** @@ -53,7 +53,7 @@ export default { */ check: (logger, { data, path: { category } }) => { checkMirroring( - /** @type {CompatStatement} */ (data).support, + /** @type {InternalCompatStatement} */ (data).support, category, logger, ); diff --git a/lint/linter/test-multiple-statements.js b/lint/linter/test-multiple-statements.js index 1918a2f7989666..3c215ed8ffdac0 100644 --- a/lint/linter/test-multiple-statements.js +++ b/lint/linter/test-multiple-statements.js @@ -7,12 +7,12 @@ import { createStatementGroupKey } from '../utils.js'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {BrowserName, CompatStatement, SupportStatement} from '../../types/types.js' */ +/** @import {BrowserName, InternalCompatStatement, InternalSupportStatement} from '../../types/index.js' */ /** * Process data and check to make sure there aren't multiple support statements without * `partial_implementation` or `prefix`/`alternative_name` - * @param {SupportStatement} data The data to test + * @param {InternalSupportStatement} data The data to test * @param {BrowserName} browser The name of the browser * @param {Logger} logger The logger to output errors to * @returns {void} @@ -56,10 +56,10 @@ export default { */ check: (logger, { data }) => { for (const [browser, support] of Object.entries( - /** @type {CompatStatement} */ (data).support, + /** @type {InternalCompatStatement} */ (data).support, )) { processData( - /** @type {SupportStatement} */ (support), + /** @type {InternalSupportStatement} */ (support), /** @type {BrowserName} */ (browser), logger, ); diff --git a/lint/linter/test-notes.js b/lint/linter/test-notes.js index 070832e914ac2b..b23b11626d1798 100644 --- a/lint/linter/test-notes.js +++ b/lint/linter/test-notes.js @@ -10,7 +10,7 @@ import { VALID_ELEMENTS } from '../utils.js'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {BrowserName, CompatStatement, SupportStatement} from '../../types/types.js' */ +/** @import {BrowserName, InternalCompatStatement, InternalSupportStatement} from '../../types/index.js' */ const parser = new HTMLParser(); @@ -92,7 +92,7 @@ export const validateHTML = (string) => { * Check the notes in the data * @param {string | string[]} notes The notes to test * @param {BrowserName} browser The browser the notes belong to - * @param {string} feature The identifier of the feature + * @param {string} feature The Internalidentifier of the feature * @param {Logger} logger The logger to output errors to * @returns {void} */ @@ -116,16 +116,16 @@ const checkNotes = (notes, browser, feature, logger) => { /** * Process the data for notes errors - * @param {CompatStatement} data The data to test + * @param {InternalCompatStatement} data The data to test * @param {Logger} logger The logger to output errors to - * @param {string} feature The identifier of the feature + * @param {string} feature The Internalidentifier of the feature * @returns {void} */ const processData = (data, logger, feature) => { for (const [ browser, support, - ] of /** @type {[BrowserName, SupportStatement][]} */ ( + ] of /** @type {[BrowserName, InternalSupportStatement][]} */ ( Object.entries(data.support) )) { if (Array.isArray(support)) { @@ -135,7 +135,7 @@ const processData = (data, logger, feature) => { } } } else { - if (support.notes) { + if (typeof support === 'object' && support.notes) { checkNotes(support.notes, browser, feature, logger); } } @@ -153,6 +153,6 @@ export default { * @param {LinterData} root The data to test */ check: (logger, { data, path: { full } }) => { - processData(/** @type {CompatStatement} */ (data), logger, full); + processData(/** @type {InternalCompatStatement} */ (data), logger, full); }, }; diff --git a/lint/linter/test-obsolete.js b/lint/linter/test-obsolete.js index 289a1f70b7645b..be4f4b7b7803a9 100644 --- a/lint/linter/test-obsolete.js +++ b/lint/linter/test-obsolete.js @@ -1,12 +1,11 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -import bcd from '../../index.js'; -const { browsers } = bcd; +import { browsers } from '../../index.js'; /** @import {Linter, LinterData, LinterMessageLevel} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {BrowserName, CompatStatement} from '../../types/types.js' */ +/** @import {BrowserName, InternalCompatStatement} from '../../types/index.js' */ /** @import {InternalSupportBlock, InternalSupportStatement} from '../../types/index.js' */ // Once a category has been stripped of unsupported features, remove it from this list @@ -99,7 +98,7 @@ export const implementedAndRemoved = (support) => { /** * Process and test the data * @param {Logger} logger The logger to output errors to - * @param {CompatStatement} data The data to test + * @param {InternalCompatStatement} data The data to test * @returns {void} */ export const processData = (logger, data) => { @@ -134,7 +133,7 @@ export default { */ check: (logger, { data, path: { category } }) => { if (!ignoredCategories.includes(category)) { - processData(logger, /** @type {CompatStatement} */ (data)); + processData(logger, /** @type {InternalCompatStatement} */ (data)); } }, exceptions: ['html.elements.track.kind.descriptions'], diff --git a/lint/linter/test-obsolete.test.js b/lint/linter/test-obsolete.test.js index 13cec0ad51f089..62a4e879137d0a 100644 --- a/lint/linter/test-obsolete.test.js +++ b/lint/linter/test-obsolete.test.js @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; -import bcd from '../../index.js'; +import { browsers } from '../../index.js'; import { Logger } from '../utils.js'; import { @@ -11,13 +11,12 @@ import { implementedAndRemoved, processData, } from './test-obsolete.js'; -const { browsers } = bcd; const errorTime = new Date(), infoTime = new Date(); errorTime.setFullYear(errorTime.getFullYear() - 2.5); infoTime.setFullYear(infoTime.getFullYear() - 2); -const release = Object.entries(browsers.chrome.releases).find((r) => { +const release = Object.entries(browsers['chrome'].releases).find((r) => { if (r[1].release_date === undefined) { return false; } @@ -124,7 +123,7 @@ describe('implementedAndRemoved', () => { implementedAndRemoved({ chrome: { version_added: '1', - version_removed: Object.keys(browsers.chrome.releases)[-1], + version_removed: Object.keys(browsers['chrome'].releases)[-1], }, }), false, @@ -134,7 +133,7 @@ describe('implementedAndRemoved', () => { chrome: [ { version_added: '2', - version_removed: Object.keys(browsers.chrome.releases)[-1], + version_removed: Object.keys(browsers['chrome'].releases)[-1], }, { version_added: '1', diff --git a/lint/linter/test-overlap.js b/lint/linter/test-overlap.js index 9704ea819a2b31..34c08ef9abda67 100644 --- a/lint/linter/test-overlap.js +++ b/lint/linter/test-overlap.js @@ -5,7 +5,7 @@ import { checkOverlap } from '../common/overlap.js'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {BrowserName, CompatStatement, SupportStatement} from '../../types/types.js' */ +/** @import {BrowserName, InternalCompatStatement, InternalSupportStatement} from '../../types/index.js' */ /** @type {Linter} */ export default { @@ -19,10 +19,10 @@ export default { */ check: (logger, { data }) => { for (const [browser, support] of Object.entries( - /** @type {CompatStatement} */ (data).support, + /** @type {InternalCompatStatement} */ (data).support, )) { checkOverlap( - /** @type {SupportStatement} */ (support), + /** @type {InternalSupportStatement} */ (support), /** @type {BrowserName} */ (browser), { logger, diff --git a/lint/linter/test-overlap.test.js b/lint/linter/test-overlap.test.js index 6bba710712a147..9539405017fb4d 100644 --- a/lint/linter/test-overlap.test.js +++ b/lint/linter/test-overlap.test.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {CompatStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement} from '../../types/index.js' */ import assert from 'node:assert/strict'; @@ -20,7 +20,7 @@ describe('overlap', () => { }); it('should skip processing when data is not an array', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { support: { chrome: { @@ -36,7 +36,7 @@ describe('overlap', () => { }); it('should log error when statements overlap', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { support: { firefox: [ @@ -53,7 +53,7 @@ describe('overlap', () => { }); it('should log error when overlapping statements are not sorted', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { support: { firefox: [ @@ -70,7 +70,7 @@ describe('overlap', () => { }); it('should log error when statements with same prefix overlap', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { support: { firefox: [ @@ -87,7 +87,7 @@ describe('overlap', () => { }); it('should log error when statements with same alternative name overlap', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { support: { firefox: [ @@ -108,7 +108,7 @@ describe('overlap', () => { }); it('should log error when there are two statements without version_added', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { support: { firefox: [ @@ -129,7 +129,7 @@ describe('overlap', () => { }); it('should log error when there are two statements without version_added incl. preview', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { support: { firefox: [ @@ -150,7 +150,7 @@ describe('overlap', () => { }); it('should ignore when partial support in stable and full support in preview overlap', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { support: { firefox: [ @@ -171,7 +171,7 @@ describe('overlap', () => { }); it('should ignore preview version without overlap', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { support: { firefox: [ diff --git a/lint/linter/test-prefix.js b/lint/linter/test-prefix.js index cdb921a458b030..c029ecaa0c892a 100644 --- a/lint/linter/test-prefix.js +++ b/lint/linter/test-prefix.js @@ -5,11 +5,11 @@ import { styleText } from 'node:util'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {CompatStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement} from '../../types/index.js' */ /** * Process the data for prefix errors - * @param {CompatStatement} data The data to test + * @param {InternalCompatStatement} data The data to test * @param {string} category The category the data belongs to * @param {string} feature The full feature path * @param {Logger} logger The logger to output errors to @@ -51,7 +51,7 @@ const processData = (data, category, feature, logger) => { const supportStatements = Array.isArray(support) ? support : [support]; for (const statement of supportStatements) { - if (!statement) { + if (typeof statement !== 'object') { continue; } if (statement.prefix && statement.alternative_name) { @@ -98,6 +98,11 @@ export default { * @param {LinterData} root The data to test */ check: (logger, { data, path: { category, full } }) => { - processData(/** @type {CompatStatement} */ (data), category, full, logger); + processData( + /** @type {InternalCompatStatement} */ (data), + category, + full, + logger, + ); }, }; diff --git a/lint/linter/test-spec-urls.js b/lint/linter/test-spec-urls.js index d416e6af75c332..f2fee7e5a386b1 100644 --- a/lint/linter/test-spec-urls.js +++ b/lint/linter/test-spec-urls.js @@ -7,7 +7,7 @@ import specData from 'web-specs' with { type: 'json' }; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {CompatStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement} from '../../types/index.js' */ /* * Before adding an exception, open an issue with https://github.com/w3c/browser-specs to @@ -79,7 +79,7 @@ const allowedSpecURLs = [ /** * Process the data for spec URL errors - * @param {CompatStatement} data The data to test + * @param {InternalCompatStatement} data The data to test * @param {Logger} logger The logger to output errors to * @returns {void} */ @@ -116,6 +116,6 @@ export default { * @param {LinterData} root The data to test */ check: (logger, { data }) => { - processData(/** @type {CompatStatement} */ (data), logger); + processData(/** @type {InternalCompatStatement} */ (data), logger); }, }; diff --git a/lint/linter/test-status-inheritance.js b/lint/linter/test-status-inheritance.js index 2a428a13fb95f3..3faece162c70a1 100644 --- a/lint/linter/test-status-inheritance.js +++ b/lint/linter/test-status-inheritance.js @@ -7,11 +7,11 @@ import walk from '../../utils/walk.js'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {CompatData, Identifier} from '../../types/types.js' */ +/** @import {InternalCompatData, InternalIdentifier} from '../../types/index.js' */ /** * Checks for correct inheritance of statuses. - * @param {CompatData} data The data to test + * @param {InternalCompatData} data The data to test * @param {Logger} logger The logger to output errors to * @returns {void} */ @@ -21,7 +21,7 @@ const checkStatusInheritance = (data, logger) => { if (feature.compat.status?.deprecated === true) { for (const subfeature of walk( undefined, - /** @type {Identifier} */ (feature.data), + /** @type {InternalIdentifier} */ (feature.data), )) { if (subfeature.compat.status?.deprecated === false) { logger.error( @@ -38,7 +38,7 @@ const checkStatusInheritance = (data, logger) => { if (feature.compat.status?.experimental === true) { for (const subfeature of walk( undefined, - /** @type {Identifier} */ (feature.data), + /** @type {InternalIdentifier} */ (feature.data), )) { if ( subfeature.compat.status?.experimental === false && @@ -58,7 +58,7 @@ const checkStatusInheritance = (data, logger) => { if (feature.compat.status?.standard_track === false) { for (const subfeature of walk( undefined, - /** @type {Identifier} */ (feature.data), + /** @type {InternalIdentifier} */ (feature.data), )) { if (subfeature.compat.status?.standard_track === true) { logger.error( @@ -85,6 +85,6 @@ export default { * @param {LinterData} root The data to test */ check: (logger, { data }) => { - checkStatusInheritance(/** @type {CompatData} */ (data), logger); + checkStatusInheritance(/** @type {InternalCompatData} */ (data), logger); }, }; diff --git a/lint/linter/test-status.js b/lint/linter/test-status.js index a129ca01ebf9ed..57077013ad359c 100644 --- a/lint/linter/test-status.js +++ b/lint/linter/test-status.js @@ -3,12 +3,11 @@ import { styleText } from 'node:util'; -import bcd from '../../index.js'; -const { browsers } = bcd; +import { browsers } from '../../index.js'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {BrowserName, CompatStatement} from '../../types/types.js' */ +/** @import {BrowserName, InternalCompatStatement} from '../../types/index.js' */ // See: https://github.com/web-platform-dx/web-features/blob/main/docs/baseline.md#core-browser-set const CORE_BROWSER_SET = new Set([ @@ -23,7 +22,7 @@ const CORE_BROWSER_SET = new Set([ /** * Check if experimental should be true or false - * @param {CompatStatement} data The data to check + * @param {InternalCompatStatement} data The data to check * @returns {boolean} The expected experimental status */ export const checkExperimental = (data) => { @@ -40,7 +39,12 @@ export const checkExperimental = (data) => { // Consider only the first part of an array statement. const statement = Array.isArray(support) ? support[0] : support; // Ignore anything behind flag, prefix or alternative name - if (statement.flags || statement.prefix || statement.alternative_name) { + if ( + typeof statement !== 'object' || + statement.flags || + statement.prefix || + statement.alternative_name + ) { continue; } if (statement.version_added && !statement.version_removed) { @@ -81,7 +85,7 @@ export const checkExperimental = (data) => { /** * Check the status blocks of the compat date - * @param {CompatStatement} data The data to test + * @param {InternalCompatStatement} data The data to test * @param {Logger} logger The logger to output errors to * @param {string} category The feature category * @returns {void} @@ -141,6 +145,10 @@ export default { * @param {LinterData} root The data to test */ check: (logger, { data, path: { category } }) => { - checkStatus(/** @type {CompatStatement} */ (data), logger, category); + checkStatus( + /** @type {InternalCompatStatement} */ (data), + logger, + category, + ); }, }; diff --git a/lint/linter/test-status.test.js b/lint/linter/test-status.test.js index 126afc9f56603f..515e15bef0c134 100644 --- a/lint/linter/test-status.test.js +++ b/lint/linter/test-status.test.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {CompatStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement} from '../../types/index.js' */ import assert from 'node:assert/strict'; @@ -11,7 +11,7 @@ import test, { checkExperimental } from './test-status.js'; describe('checkExperimental', () => { it('should return true when data is not experimental', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { status: { experimental: false, @@ -25,7 +25,7 @@ describe('checkExperimental', () => { }); it('should return true when data is experimental but supported by only one engine', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { status: { experimental: true, @@ -46,7 +46,7 @@ describe('checkExperimental', () => { }); it('should return false when data is experimental and supported by more than one engine', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { status: { experimental: true, @@ -67,7 +67,7 @@ describe('checkExperimental', () => { }); it('should ignore non-relevant browsers when determining experimental status', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { status: { experimental: true, @@ -101,7 +101,7 @@ describe('checkStatus', () => { }); it('should not log error when status is not defined', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { status: undefined, support: {}, @@ -113,7 +113,7 @@ describe('checkStatus', () => { }); it('should log error when category is webextensions and status is defined', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { status: { experimental: false, @@ -133,7 +133,7 @@ describe('checkStatus', () => { }); it('should log error when status is both experimental and deprecated', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { status: { experimental: true, @@ -150,7 +150,7 @@ describe('checkStatus', () => { }); it('should log error when status is non-standard but has a spec_url', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { status: { experimental: false, @@ -168,7 +168,7 @@ describe('checkStatus', () => { }); it('should log error when status is experimental and supported by more than one engine', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { status: { experimental: true, diff --git a/lint/linter/test-tags.js b/lint/linter/test-tags.js index fa238aca066337..8331b75883e5c4 100644 --- a/lint/linter/test-tags.js +++ b/lint/linter/test-tags.js @@ -7,14 +7,14 @@ import { features } from 'web-features'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {CompatStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement} from '../../types/index.js' */ const allowedNamespaces = ['web-features']; const validFeatureIDs = Object.keys(features); /** * Process the data for spec URL errors - * @param {CompatStatement} data The data to test + * @param {InternalCompatStatement} data The data to test * @param {Logger} logger The logger to output errors to * @returns {void} */ @@ -62,6 +62,6 @@ export default { * @param {LinterData} root The data to test */ check: (logger, { data }) => { - processData(/** @type {CompatStatement} */ (data), logger); + processData(/** @type {InternalCompatStatement} */ (data), logger); }, }; diff --git a/lint/linter/test-tags.test.js b/lint/linter/test-tags.test.js index 6c60855833b399..517bb9413d90ff 100644 --- a/lint/linter/test-tags.test.js +++ b/lint/linter/test-tags.test.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {CompatStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement} from '../../types/index.js' */ import assert from 'node:assert/strict'; @@ -18,7 +18,7 @@ describe('test.check', () => { }); it('should not log error when tags are not defined', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { tags: undefined, support: {}, @@ -30,7 +30,7 @@ describe('test.check', () => { }); it('should not log error when tags are valid', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { tags: ['web-features:javascript'], support: {}, @@ -42,7 +42,7 @@ describe('test.check', () => { }); it('should log error when tags do not have a namespace', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { tags: ['tag1'], support: {}, @@ -55,7 +55,7 @@ describe('test.check', () => { }); it('should log error when tags do not use one of the allowed namespaces', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { tags: ['namespace3:tag1'], support: {}, @@ -68,7 +68,7 @@ describe('test.check', () => { }); it('should log an error when an invalid web-feature ID is used', () => { - /** @type {CompatStatement} */ + /** @type {InternalCompatStatement} */ const data = { tags: ['web-features:foo'], support: {}, diff --git a/lint/linter/test-versions.js b/lint/linter/test-versions.js index 9d1c493de2980c..6fb10c8e6e1ed9 100644 --- a/lint/linter/test-versions.js +++ b/lint/linter/test-versions.js @@ -5,12 +5,11 @@ import { styleText } from 'node:util'; import { compare, validate } from 'compare-versions'; -import bcd from '../../index.js'; -const { browsers } = bcd; +import { browsers } from '../../index.js'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {BrowserName, CompatStatement, SimpleSupportStatement, VersionValue} from '../../types/types.js' */ +/** @import {BrowserName, InternalCompatStatement, SimpleSupportStatement, VersionValue} from '../../types/index.js' */ /** @import {InternalSupportBlock, InternalSupportStatement} from '../../types/index.js' */ /* The latest date a range's release can correspond to */ @@ -205,7 +204,7 @@ export default { */ check: (logger, { data, path: { category } }) => { checkVersions( - /** @type {CompatStatement} */ (data).support, + /** @type {InternalCompatStatement} */ (data).support, category, logger, ); diff --git a/lint/types.d.ts b/lint/types.d.ts index 968059b9adb306..9cc867a135b209 100644 --- a/lint/types.d.ts +++ b/lint/types.d.ts @@ -2,7 +2,7 @@ * See LICENSE file for more information. */ import { InternalDataType } from '../types/index.js'; -import { BrowserName } from '../types/types.js'; +import { BrowserName } from '../types/index.js'; import { Logger } from './utils.js'; diff --git a/lint/utils.js b/lint/utils.js index 9f68684be88f17..6d5213ea6722a7 100644 --- a/lint/utils.js +++ b/lint/utils.js @@ -4,7 +4,7 @@ import { platform } from 'node:os'; import { styleText } from 'node:util'; -/** @import {SimpleSupportStatement} from '../types/types.js' */ +/** @import {SimpleSupportStatement} from '../types/index.js' */ /** @import {Linter, LinterData, LinterMessage, LinterScope} from './types.js' */ /** @type {Readonly>} */ diff --git a/lint/utils.test.js b/lint/utils.test.js index 3f331b9564aee2..4c5e0073c45b0f 100644 --- a/lint/utils.test.js +++ b/lint/utils.test.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {SimpleSupportStatement} from '../types/types.js' */ +/** @import {SimpleSupportStatement} from '../types/index.js' */ import assert from 'node:assert/strict'; diff --git a/scripts/build/index.js b/scripts/build/index.js index eda937ce9ecd20..39bb8122b0b70d 100644 --- a/scripts/build/index.js +++ b/scripts/build/index.js @@ -20,8 +20,8 @@ import bcd from '../../index.js'; import mirrorSupport from './mirror.js'; /** - * @import { InternalSupportStatement } from '../../types/index.js' - * @import { BrowserName, CompatData, Identifier, MetaBlock, VersionValue } from '../../types/types.js' + * @import { CompatData } from '../../types/types.js' + * @import { BrowserName, InternalCompatData, InternalIdentifier, InternalSupportStatement, MetaBlock, VersionValue } from '../../types/index.js' * @import { WalkOutput } from '../../utils/walk.js' */ @@ -149,7 +149,7 @@ export const addVersionLast = (feature) => { * @returns {void} */ export const transformMD = (feature) => { - const featureData = /** @type {Identifier} */ (feature.data); + const featureData = /** @type {InternalIdentifier} */ (feature.data); if ( featureData.__compat && 'description' in featureData.__compat && @@ -238,7 +238,7 @@ export const createDataBundle = async () => { /** * Validates the given data against the schema. - * @param {CompatData} data - The data to validate. + * @param {InternalCompatData} data - The data to validate. */ const validate = (data) => { const ajv = createAjv(); @@ -293,9 +293,9 @@ const writeTypeScript = async () => { const content = `/* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -import { CompatData } from "./types.js"; +import { InternalCompatData } from "./types.js"; -declare var bcd: CompatData; +declare var bcd: InternalCompatData; export default bcd; export * from "./types.js";`; diff --git a/scripts/build/mirror.js b/scripts/build/mirror.js index c8188a893cd067..37d670bdebf9ce 100644 --- a/scripts/build/mirror.js +++ b/scripts/build/mirror.js @@ -3,10 +3,10 @@ import { compareVersions, compare } from 'compare-versions'; -import bcd from '../../index.js'; +import { browsers } from '../../index.js'; /** - * @import { BrowserName, SimpleSupportStatement, SupportStatement } from '../../types/types.js' + * @import { BrowserName, SimpleSupportStatement, InternalSupportStatement } from '../../types/index.js' * @import { InternalSupportBlock } from '../../types/index.js' */ @@ -14,8 +14,6 @@ import bcd from '../../index.js'; * @typedef {string | [string, string, ...string[]] | null} Notes */ -const { browsers } = bcd; - const OS_NOTES = [ 'Available on macOS and Windows only.', 'Available only on macOS.', @@ -192,10 +190,10 @@ const copyStatement = (data) => { /** * Perform mirroring of data - * @param {SupportStatement} sourceData The data to mirror from + * @param {InternalSupportStatement} sourceData The data to mirror from * @param {BrowserName} sourceBrowser The source browser * @param {BrowserName} destination The destination browser - * @returns {SupportStatement} The mirrored support statement + * @returns {InternalSupportStatement} The mirrored support statement */ export const bumpSupport = (sourceData, sourceBrowser, destination) => { if (Array.isArray(sourceData)) { @@ -232,6 +230,7 @@ export const bumpSupport = (sourceData, sourceBrowser, destination) => { if ( browsers[sourceBrowser].type === 'desktop' && browsers[destination].type === 'mobile' && + typeof sourceData === 'object' && sourceData.partial_implementation ) { const notes = Array.isArray(sourceData.notes) @@ -258,19 +257,27 @@ export const bumpSupport = (sourceData, sourceBrowser, destination) => { return { version_added: false }; } - if (typeof sourceData.version_added === 'string') { + if ( + typeof sourceData === 'object' && + typeof sourceData.version_added === 'string' + ) { newData.version_added = getMatchingBrowserVersion( destination, sourceData.version_added, ); } - if (newData.version_added === false && sourceData.version_added !== false) { + if ( + newData.version_added === false && + typeof sourceData === 'object' && + sourceData.version_added !== false + ) { // If the feature is added in an upstream version newer than available in the downstream browser, don't copy notes, etc. return { version_added: false }; } if ( + typeof sourceData === 'object' && sourceData.version_removed && typeof sourceData.version_removed === 'string' ) { @@ -293,7 +300,11 @@ export const bumpSupport = (sourceData, sourceBrowser, destination) => { } // Only process notes if they weren't already removed (e.g., for OS-specific limitations) - if (sourceData.notes && newData.notes !== undefined) { + if ( + typeof sourceData === 'object' && + sourceData.notes && + newData.notes !== undefined + ) { const sourceBrowserName = sourceBrowser === 'chrome' ? '(Google )?Chrome' @@ -316,7 +327,7 @@ export const bumpSupport = (sourceData, sourceBrowser, destination) => { * Perform mirroring for the target browser * @param {BrowserName} destination The browser to mirror to * @param {InternalSupportBlock} data The data to mirror with - * @returns {SupportStatement} The mirrored data + * @returns {InternalSupportStatement} The mirrored data */ const mirrorSupport = (destination, data) => { /** @type {BrowserName | undefined} */ diff --git a/scripts/build/mirror.test.js b/scripts/build/mirror.test.js index 30468fe4bda99e..eb687286c07757 100644 --- a/scripts/build/mirror.test.js +++ b/scripts/build/mirror.test.js @@ -2,13 +2,13 @@ * See LICENSE file for more information. */ /** - * @import { BrowserName } from '../../types/types.js' + * @import { BrowserName } from '../../types/index.js' * @import { InternalSupportBlock } from '../../types/index.js' */ import assert from 'node:assert/strict'; -import bcd from '../../index.js'; +import { browsers } from '../../index.js'; import mirrorSupport, { isOSLimitation } from './mirror.js'; @@ -153,7 +153,7 @@ describe('mirror', () => { for (const [browser, versionMap] of Object.entries(mappings)) { describe(browser, () => { - const upstream = bcd.browsers[browser].upstream; + const upstream = browsers[browser].upstream; for (const pair of versionMap) { it(`${pair[0]} => ${pair[1]}`, () => { const support = { diff --git a/scripts/diff-flat.js b/scripts/diff-flat.js index 76eceac084f45d..d66ca193e9f209 100644 --- a/scripts/diff-flat.js +++ b/scripts/diff-flat.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {CompatData, SimpleSupportStatement} from '../types/types.js' */ +/** @import {InternalCompatData, SimpleSupportStatement} from '../types/index.js' */ /** * @typedef {'html' | 'plain'} Format @@ -281,9 +281,9 @@ const printDiffs = (base, head, options) => { /** @type {Map>} */ const groups = new Map(); - /** @type {CompatData} */ + /** @type {InternalCompatData} */ const baseContents = /** @type {*} */ ({}); - /** @type {CompatData} */ + /** @type {InternalCompatData} */ const headContents = /** @type {*} */ ({}); for (const status of getGitDiffStatuses(base, head)) { @@ -296,12 +296,12 @@ const printDiffs = (base, head, options) => { continue; } - const baseFileContents = /** @type {CompatData} */ ( + const baseFileContents = /** @type {InternalCompatData} */ ( status.value !== 'A' ? JSON.parse(getFileContent(base, status.basePath)) : {} ); - const headFileContents = /** @type {CompatData} */ ( + const headFileContents = /** @type {InternalCompatData} */ ( status.value !== 'D' ? JSON.parse(getFileContent(head, status.headPath)) : {} diff --git a/scripts/diff.js b/scripts/diff.js index 766544bf60353f..d631f38b3d56bc 100644 --- a/scripts/diff.js +++ b/scripts/diff.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {SupportStatement, Identifier, BrowserName} from '../types/types.js' */ +/** @import {InternalSupportStatement, InternalIdentifier, BrowserName} from '../types/index.js' */ import { styleText } from 'node:util'; @@ -54,11 +54,11 @@ const stringifyChange = (lhs, rhs) => /** * Perform mirroring on specified diff statement * @param {object} diff - The diff to perform mirroring on - * @param {SupportStatement} diff.base - * @param {SupportStatement} diff.head + * @param {InternalSupportStatement} diff.base + * @param {InternalSupportStatement} diff.head * @param {object} contents - The contents to mirror from - * @param {Identifier} contents.base - * @param {Identifier} contents.head + * @param {InternalIdentifier} contents.base + * @param {InternalIdentifier} contents.head * @param {string[]} path - The feature path to mirror * @param {'base' | 'head'} direction - Whether to mirror 'base' or 'head' */ @@ -66,9 +66,10 @@ const doMirror = (diff, contents, path, direction) => { const browser = /** @type {BrowserName} */ (path[path.length - 1]); const dataPath = path.slice(0, path.length - 3).join('.'); const data = contents[direction]; - const queried = /** @type {Identifier} */ (query(dataPath, data)); + const queried = /** @type {InternalIdentifier} */ (query(dataPath, data)); if (queried.__compat?.support) { + // @ts-expect-error I need to figure out what to do here. diff[direction] = mirror(browser, queried.__compat.support); } }; diff --git a/scripts/lib/compare-statements.js b/scripts/lib/compare-statements.js index 48d07e02de1232..ece79de4e00f15 100644 --- a/scripts/lib/compare-statements.js +++ b/scripts/lib/compare-statements.js @@ -3,7 +3,7 @@ import { compareVersions } from 'compare-versions'; -/** @import {SimpleSupportStatement} from '../../types/types.js' */ +/** @import {SimpleSupportStatement} from '../../types/index.js' */ /** * diff --git a/scripts/lib/compare-statements.test.js b/scripts/lib/compare-statements.test.js index 00bc6aafa0dc7e..25f6688afd0e86 100644 --- a/scripts/lib/compare-statements.test.js +++ b/scripts/lib/compare-statements.test.js @@ -1,13 +1,13 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {Identifier, CompatStatement} from '../../types/types.js' */ +/** @import {InternalIdentifier, InternalCompatStatement} from '../../types/index.js' */ import assert from 'node:assert/strict'; import compareStatements from './compare-statements.js'; -/** @type {{ input: Identifier; output: Identifier }[]} */ +/** @type {{ input: InternalIdentifier; output: InternalIdentifier }[]} */ const tests = /** @type {*} */ ([ { input: { @@ -130,8 +130,8 @@ const tests = /** @type {*} */ ([ /** * Update the order of the statements * @param {string} key The key of the object (make sure it's '__compat') - * @param {CompatStatement} value The compat statement to update - * @returns {CompatStatement} The updated compat statement + * @param {InternalCompatStatement} value The compat statement to update + * @returns {InternalCompatStatement} The updated compat statement */ const orderStatements = (key, value) => { if (key === '__compat') { diff --git a/scripts/lib/stringify-and-order-properties.js b/scripts/lib/stringify-and-order-properties.js index 762dc5e8e21f57..7e580baf86db6c 100644 --- a/scripts/lib/stringify-and-order-properties.js +++ b/scripts/lib/stringify-and-order-properties.js @@ -3,7 +3,7 @@ import { compareVersions } from 'compare-versions'; -/** @import {CompatStatement, SimpleSupportStatement, StatusBlock, BrowserStatement, ReleaseStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement, SimpleSupportStatement, StatusBlock, BrowserStatement, ReleaseStatement} from '../../types/index.js' */ const propOrder = { browsers: { @@ -115,7 +115,7 @@ export const orderProperties = (key, value) => { // Order properties for data if ('__compat' in value) { value.__compat = doOrder( - /** @type {CompatStatement} */ (value.__compat), + /** @type {InternalCompatStatement} */ (value.__compat), propOrder.data.__compat, ); diff --git a/scripts/migrations/002-remove-webview-flags.js b/scripts/migrations/002-remove-webview-flags.js index 1cd48ed9bdb79d..9cdb74a9a30bad 100644 --- a/scripts/migrations/002-remove-webview-flags.js +++ b/scripts/migrations/002-remove-webview-flags.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {CompatStatement, SimpleSupportStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement, SimpleSupportStatement} from '../../types/index.js' */ import fs from 'node:fs'; import path from 'node:path'; @@ -18,8 +18,8 @@ const dirname = fileURLToPath(new URL('.', import.meta.url)); * Check to see if the key is __compat and modify the value to remove * flags from WebView Android. * @param {string} key The key in the object - * @param {CompatStatement} value The value of the key - * @returns {CompatStatement} The new value with WebView flags removed + * @param {InternalCompatStatement} value The value of the key + * @returns {InternalCompatStatement} The new value with WebView flags removed */ export const removeWebViewFlags = (key, value) => { if (key === '__compat') { @@ -43,7 +43,10 @@ export const removeWebViewFlags = (key, value) => { result ); } - } else if (value.support.webview_android.flags !== undefined) { + } else if ( + typeof value.support.webview_android === 'object' && + value.support.webview_android.flags !== undefined + ) { value.support.webview_android = { version_added: false }; } } diff --git a/scripts/migrations/002-remove-webview-flags.test.js b/scripts/migrations/002-remove-webview-flags.test.js index 27105daace4e1b..44cd9347a03f52 100644 --- a/scripts/migrations/002-remove-webview-flags.test.js +++ b/scripts/migrations/002-remove-webview-flags.test.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {Identifier} from '../../types/types.js' */ +/** @import {InternalIdentifier} from '../../types/index.js' */ import assert from 'node:assert/strict'; @@ -9,7 +9,7 @@ import { removeWebViewFlags } from './002-remove-webview-flags.js'; /** * Objects of each test, with input and expected output - * @type {{ input: Identifier; output: Identifier }[]} + * @type {{ input: InternalIdentifier; output: InternalIdentifier }[]} */ const tests = /** @type {*} */ ([ { diff --git a/scripts/migrations/007-experimental-false.js b/scripts/migrations/007-experimental-false.js index 77d73daee0c3a9..6211fd4963d8b2 100644 --- a/scripts/migrations/007-experimental-false.js +++ b/scripts/migrations/007-experimental-false.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {CompatData, BrowserName, Identifier, ReleaseStatement, SimpleSupportStatement} from '../../types/types.js' */ +/** @import {InternalCompatData, BrowserName, InternalIdentifier, ReleaseStatement, SimpleSupportStatement} from '../../types/index.js' */ import fs from 'node:fs'; import path from 'node:path'; @@ -11,14 +11,13 @@ import esMain from 'es-main'; import { walk } from '../../utils/index.js'; import { dataFoldersMinusBrowsers } from '../lib/data-folders.js'; -import bcd from '../../index.js'; -const { browsers } = bcd; +import { browsers } from '../../index.js'; const dirname = fileURLToPath(new URL('.', import.meta.url)); /** * Fix the experimental status throughout compatibility data - * @param {CompatData | Identifier} bcd Parsed BCD object to be updated in place. + * @param {InternalCompatData | InternalIdentifier} bcd Parsed BCD object to be updated in place. * @returns {void} */ export const fixExperimental = (bcd) => { @@ -33,7 +32,7 @@ export const fixExperimental = (bcd) => { const browserSupport = new Set(); for (const [browser, support] of Object.entries(compat.support)) { - if (!support) { + if (!support || support === 'mirror') { continue; } @@ -56,7 +55,7 @@ export const fixExperimental = (bcd) => { for (const browser of browserSupport) { const currentRelease = Object.values(browsers[browser].releases).find( - (/** @type {ReleaseStatement} */ r) => r.status === 'current', + (r) => r.status === 'current', ); if (!currentRelease) { continue; diff --git a/scripts/migrations/007-experimental-false.test.js b/scripts/migrations/007-experimental-false.test.js index 52cbac5465d4f2..28c9171650ce1c 100644 --- a/scripts/migrations/007-experimental-false.test.js +++ b/scripts/migrations/007-experimental-false.test.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {CompatStatement} from '../../types/types.js' */ +/** @import {CompatStatement} from '../../types/index.js' */ import assert from 'node:assert/strict'; diff --git a/scripts/migrations/010-set-oculus-to-mirror.js b/scripts/migrations/010-set-oculus-to-mirror.js index b356ab34b3f58d..3e0756b99f8c4d 100644 --- a/scripts/migrations/010-set-oculus-to-mirror.js +++ b/scripts/migrations/010-set-oculus-to-mirror.js @@ -1,8 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {InternalSupportBlock} from '../../types/index.js' */ -/** @import {CompatStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement, InternalSupportBlock} from '../../types/index.js' */ import fs from 'node:fs'; import path from 'node:path'; @@ -18,8 +17,8 @@ const dirname = fileURLToPath(new URL('.', import.meta.url)); /** * Check to see if the key is __compat and set 'oculus' to 'mirror' * @param {string} key The key in the object - * @param {CompatStatement} value The value of the key - * @returns {CompatStatement} The new value with 'oculus' set to 'mirror' + * @param {InternalCompatStatement} value The value of the key + * @returns {InternalCompatStatement} The new value with 'oculus' set to 'mirror' */ export const doSetOculusToMirror = (key, value) => { if (key === '__compat') { diff --git a/scripts/migrations/011-set-webview-ios-to-mirror.js b/scripts/migrations/011-set-webview-ios-to-mirror.js index 78e3b09fa160e0..fbed277127890f 100644 --- a/scripts/migrations/011-set-webview-ios-to-mirror.js +++ b/scripts/migrations/011-set-webview-ios-to-mirror.js @@ -1,8 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {InternalSupportBlock} from '../../types/index.js' */ -/** @import {CompatStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement, InternalSupportBlock} from '../../types/index.js' */ import fs from 'node:fs'; import path from 'node:path'; @@ -18,8 +17,8 @@ const dirname = fileURLToPath(new URL('.', import.meta.url)); /** * Check to see if the key is __compat and set 'webview_ios' to 'mirror' * @param {string} key The key in the object - * @param {CompatStatement} value The value of the key - * @returns {CompatStatement} The new value with 'webview_ios' set to 'mirror' + * @param {InternalCompatStatement} value The value of the key + * @returns {InternalCompatStatement} The new value with 'webview_ios' set to 'mirror' */ export const doSetWebViewIOSToMirror = (key, value) => { if (key === '__compat') { diff --git a/scripts/migrations/012-descriptions-to-md.js b/scripts/migrations/012-descriptions-to-md.js index a4ef5ac0e6e60c..dbe8f25b259ffa 100644 --- a/scripts/migrations/012-descriptions-to-md.js +++ b/scripts/migrations/012-descriptions-to-md.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {CompatStatement} from '../../types/types.js' */ +/** @import {InternalCompatStatement} from '../../types/index.js' */ import fs from 'node:fs'; import path from 'node:path'; @@ -17,8 +17,8 @@ const dirname = fileURLToPath(new URL('.', import.meta.url)); /** * Check to see if the key is __compat and convert descriptions to markdown * @param {string} key The key in the object - * @param {CompatStatement} value The value of the key - * @returns {CompatStatement} The new value with descriptions converted to markdown + * @param {InternalCompatStatement} value The value of the key + * @returns {InternalCompatStatement} The new value with descriptions converted to markdown */ export const doDescriptionsToMarkdown = (key, value) => { if (key === '__compat') { diff --git a/scripts/split.js b/scripts/split.js index a001b6996a225d..5a9358af041d1a 100644 --- a/scripts/split.js +++ b/scripts/split.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {Identifier} from '../types/types.js' */ +/** @import {InternalIdentifier} from '../types/index.js' */ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'; import { join, relative, resolve, sep } from 'node:path'; @@ -36,7 +36,7 @@ const getBaseKeyFromPath = (filePath) => { * Creates the JSON for the subfeature. * @param {string[]} baseKeys - The parent keys in which to nest the data. * @param {string} key - The key of the subfeature. - * @param {Identifier} value - The value of the subfeature. + * @param {InternalIdentifier} value - The value of the subfeature. * @returns {object} JSON for the subfeature data nested in parent structure. */ const createSubfeatureJSON = (baseKeys, key, value) => { @@ -65,7 +65,7 @@ const hasCompatData = (data) => /** * Writes a JSON file. * @param {string} path - The path to the file. - * @param {Identifier} data - The data to write as JSON. + * @param {InternalIdentifier} data - The data to write as JSON. * @returns {Promise} Promise. */ const writeJSONFile = async (path, data) => @@ -80,7 +80,7 @@ const writeJSONFile = async (path, data) => const splitFile = async (file) => { const fullPath = resolve(file); const raw = await readFile(fullPath, 'utf-8'); - const data = /** @type {Identifier} */ (JSON.parse(raw)); + const data = /** @type {InternalIdentifier} */ (JSON.parse(raw)); const baseKeys = getBaseKeyFromPath(file); let current = data; diff --git a/scripts/statistics.js b/scripts/statistics.js index bf9ae675c6303b..fb8b0c15532f33 100644 --- a/scripts/statistics.js +++ b/scripts/statistics.js @@ -12,7 +12,7 @@ import bcdData from '../index.js'; import { getRefDate } from './release/utils.js'; -/** @import {BrowserName, CompatStatement, SupportStatement, Identifier} from '../types/types.js' */ +/** @import {BrowserName, InternalCompatStatement, InternalSupportStatement, InternalIdentifier} from '../types/index.js' */ /** * @typedef {object} VersionStatsEntry @@ -38,7 +38,7 @@ const webextensionsBrowsers = [ /** * Check whether a support statement is a specified type - * @param {SupportStatement | undefined} supportData The support statement to check + * @param {InternalSupportStatement | undefined} supportData The support statement to check * @param {string | boolean | null} type What type of support (true, null, ranged) * @returns {boolean} If the support statement has the type */ @@ -51,20 +51,23 @@ const checkSupport = (supportData, type) => { } if (type == '≤') { return ( - (typeof supportData.version_added == 'string' && + (typeof supportData === 'object' && + typeof supportData.version_added == 'string' && supportData.version_added.startsWith('≤')) || - (typeof supportData.version_removed == 'string' && + (typeof supportData === 'object' && + typeof supportData.version_removed == 'string' && supportData.version_removed.startsWith('≤')) ); } return ( - supportData.version_added === type || supportData.version_removed === type + typeof supportData === 'object' && + (supportData.version_added === type || supportData.version_removed === type) ); }; /** * Iterate through all of the browsers and count the number of exact ranged values for each browser - * @param {CompatStatement} data The data to process and count stats for + * @param {InternalCompatStatement} data The data to process and count stats for * @param {BrowserName[]} browsers The browsers to test * @param {VersionStats} stats The stats object to update * @returns {void} @@ -87,7 +90,7 @@ const processData = (data, browsers, stats) => { /** * Iterate through all of the data and process statistics - * @param {Identifier} data The compat data to iterate + * @param {InternalIdentifier} data The compat data to iterate * @param {BrowserName[]} browsers The browsers to test * @param {VersionStats} stats The stats object to update * @returns {void} @@ -95,8 +98,12 @@ const processData = (data, browsers, stats) => { const iterateData = (data, browsers, stats) => { for (const key in data) { if (key === '__compat') { - // "as CompatStatement" needed because TypeScript doesn't realize key is in data - processData(/** @type {CompatStatement} */ (data[key]), browsers, stats); + // "as InternalCompatStatement" needed because TypeScript doesn't realize key is in data + processData( + /** @type {InternalCompatStatement} */ (data[key]), + browsers, + stats, + ); } else { iterateData(data[key], browsers, stats); } diff --git a/scripts/traverse.js b/scripts/traverse.js index 231684e8a376bf..b23f367bda4adb 100644 --- a/scripts/traverse.js +++ b/scripts/traverse.js @@ -6,9 +6,9 @@ import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import dataFolders from '../scripts/lib/data-folders.js'; -import bcd from '../index.js'; +import bcd, { browsers } from '../index.js'; -/** @import {BrowserName, Identifier, SimpleSupportStatement} from '../types/types.js' */ +/** @import {BrowserName, InternalIdentifier, InternalSimpleSupportStatement, InternalSupportBlock, SimpleSupportStatement} from '../types/index.js' */ /** @import {InternalSupportStatement} from '../types/index.js' */ /** @@ -20,8 +20,8 @@ import bcd from '../index.js'; /** * Traverse all of the features within a specified object and find all features that have one of the specified values - * @param {Identifier} obj The compat data to traverse through - * @param {BrowserName[]} browsers The browsers to test for + * @param {InternalIdentifier} obj The compat data to traverse through + * @param {BrowserName[]} browserNames The browsers to test for * @param {string[]} values The values to test for * @param {number} depth The depth to traverse * @param {string} tag The tag to filter results with @@ -32,7 +32,7 @@ import bcd from '../index.js'; */ export function* iterateFeatures( obj, - browsers, + browserNames, values, depth, tag, @@ -62,16 +62,21 @@ export function* iterateFeatures( } if (tag) { const tags = obj[i].__compat?.tags; - if ((tags && tags.includes(tag)) || (!tags && tag == 'false')) { + if ( + (Array.isArray(tags) && tags.includes(tag)) || + (!tags && tag == 'false') + ) { yield `${identifier}${i}`; } } else { - const comp = obj[i].__compat?.support; + const comp = /** @type {InternalSupportBlock} */ ( + obj[i].__compat?.support + ); if (!comp) { continue; } - for (const browser of browsers) { - /** @type {SimpleSupportStatement | SimpleSupportStatement[] | undefined} */ + for (const browser of browserNames) { + /** @type {InternalSimpleSupportStatement | InternalSimpleSupportStatement[] | undefined} */ let browserData = comp[browser]; if (!browserData) { @@ -80,7 +85,7 @@ export function* iterateFeatures( if ( !( identifier.startsWith('webextensions.') && - bcd.browsers[browser].accepts_webextensions + browsers[browser].accepts_webextensions ) ) { continue; @@ -136,7 +141,7 @@ export function* iterateFeatures( } yield* iterateFeatures( obj[i], - browsers, + browserNames, values, depth, tag, @@ -150,8 +155,8 @@ export function* iterateFeatures( /** * Traverse all of the features within a specified object and find all features that have one of the specified values - * @param {Identifier} obj The compat data to traverse through - * @param {BrowserName[]} browsers The browsers to traverse for + * @param {InternalIdentifier} obj The compat data to traverse through + * @param {BrowserName[]} browserNames The browsers to traverse for * @param {string[]} values The version values to traverse for * @param {number} depth The depth to traverse * @param {string} tag The tag to filter results with @@ -161,7 +166,7 @@ export function* iterateFeatures( */ const traverseFeatures = ( obj, - browsers, + browserNames, values, depth, tag, @@ -169,7 +174,7 @@ const traverseFeatures = ( status, ) => { const features = Array.from( - iterateFeatures(obj, browsers, values, depth, tag, identifier, status), + iterateFeatures(obj, browserNames, values, depth, tag, identifier, status), ); return features.filter((item, pos) => features.indexOf(item) == pos); @@ -178,7 +183,7 @@ const traverseFeatures = ( /** * Traverse the features within BCD * @param {string[]} [folders] The folders to traverse - * @param {BrowserName[]} [browsers] The browsers to traverse for + * @param {BrowserName[]} [browserNames] The browsers to traverse for * @param {string[]} [values] The version values to traverse for * @param {number} [depth] The depth to traverse * @param {string} [tag] The tag to filter results with @@ -187,8 +192,8 @@ const traverseFeatures = ( */ const main = ( folders = dataFolders.concat('webextensions'), - browsers = /** @type {BrowserName[]} */ ( - Object.keys(bcd.browsers).filter((b) => bcd.browsers[b].type !== 'server') + browserNames = Object.entries(browsers).flatMap(([name, browser]) => + browser.type !== 'server' ? [/** @type {BrowserName} */ (name)] : [], ), values = [], depth = 100, @@ -202,7 +207,7 @@ const main = ( features.push( ...traverseFeatures( bcd[folders[folder]], - browsers, + browserNames, values, depth, tag, @@ -232,8 +237,8 @@ if (esMain(import.meta)) { describe: 'Filter by a browser. May repeat.', type: 'string', nargs: 1, - default: Object.keys(bcd.browsers).filter( - (b) => bcd.browsers[b].type !== 'server', + default: Object.keys(browsers).filter( + (b) => browsers[b].type !== 'server', ), /** * @@ -243,7 +248,7 @@ if (esMain(import.meta)) { coerce: (value) => /** @type {BrowserName[]} */ ( (Array.isArray(value) ? value : [value]).filter((value) => - Object.keys(bcd.browsers).some((browser) => browser === value), + Object.keys(browsers).some((browser) => browser === value), ) ), }) diff --git a/scripts/update-browser-releases/bun.js b/scripts/update-browser-releases/bun.js index 8d08f1bf3cd274..fd3df7739bc946 100644 --- a/scripts/update-browser-releases/bun.js +++ b/scripts/update-browser-releases/bun.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {CompatData} from '../../types/types.js' */ +/** @import {Browsers, InternalCompatData} from '../../types/index.js' */ /** * @typedef {object} BunVersionsResponse @@ -219,7 +219,7 @@ export const updateBunReleases = async (options) => { ); } - /** @type {CompatData} */ + /** @type {InternalCompatData} */ const data = JSON.parse(fileText); let result = ''; @@ -287,7 +287,9 @@ export const updateBunReleases = async (options) => { rel.release_notes, ); - const entry = data.browsers[browser].releases[rel.version]; + const entry = /** @type {Browsers} */ (data.browsers)[browser].releases[ + rel.version + ]; if (entry) { const { webkitRev } = await getBunInfoFromVersionData(rel); diff --git a/scripts/update-browser-releases/firefox.js b/scripts/update-browser-releases/firefox.js index 785db5c4b375fb..c78bc2c1c4024c 100644 --- a/scripts/update-browser-releases/firefox.js +++ b/scripts/update-browser-releases/firefox.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {ReleaseStatement} from '../../types/types.js' */ +/** @import {ReleaseStatement} from '../../types/index.js' */ import * as fs from 'node:fs'; diff --git a/scripts/update-browser-releases/utils.js b/scripts/update-browser-releases/utils.js index 90621a45659d2a..42fdea71476a2d 100644 --- a/scripts/update-browser-releases/utils.js +++ b/scripts/update-browser-releases/utils.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {BrowserName, BrowserStatus, CompatData} from '../../types/types.js' */ +/** @import {BrowserName, Browsers, BrowserStatus, InternalCompatData} from '../../types/index.js' */ /** * @typedef {object} RSSItem @@ -161,14 +161,16 @@ export const createOrUpdateBrowserEntry = ( /** * Updates the status of a browser release. - * @param {CompatData} json json file to update + * @param {InternalCompatData} json json file to update * @param {BrowserName} browser the entry name where to add it in the bcd file * @param {string} version the version to add * @param {BrowserStatus} status the status * @returns {string} Text describing what has been updated */ export const setBrowserReleaseStatus = (json, browser, version, status) => { - const release = json.browsers[browser].releases[version]; + const release = /** @type {Browsers} */ (json.browsers)[browser].releases[ + version + ]; if (release.status === status) { return ''; diff --git a/types/index.d.ts b/types/index.d.ts index a9bb941f9efecc..846f5fbc08883a 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -7,10 +7,27 @@ import type { CompatData, CompatStatement, Identifier, + SimpleSupportStatement, SupportStatement, -} from '../build/types.js'; +} from './types.js'; + +export type { + Browsers, + BrowserName, + BrowserStatement, + BrowserStatus, + FlagStatement, + MetaBlock, + ReleaseStatement, + SimpleSupportStatement, + StatusBlock, + VersionValue, +} from './types.js'; + +export type InternalCompatData = Omit; export type InternalSupportStatement = SupportStatement | 'mirror'; +export type InternalSimpleSupportStatement = SimpleSupportStatement | 'mirror'; export type InternalSupportBlock = Partial< Record @@ -18,11 +35,24 @@ export type InternalSupportBlock = Partial< export interface InternalCompatStatement extends Omit< CompatStatement, - 'support' + 'source_file' | 'support' > { support: InternalSupportBlock; } +export type InternalIdentifier = + | { + __compat: InternalCompatStatement; + [k: string]: InternalIdentifier; + } + | { + __compat?: InternalCompatStatement; + [k: string]: InternalIdentifier; + } + | { + [k: string]: InternalIdentifier; + }; + export type DataType = | CompatData | BrowserStatement @@ -30,7 +60,7 @@ export type DataType = | Identifier; export type InternalDataType = - | CompatData + | InternalCompatData | BrowserStatement | InternalCompatStatement - | Identifier; + | InternalIdentifier; diff --git a/utils/iter-support.js b/utils/iter-support.js index 5268d7319743f7..893246b35230fe 100644 --- a/utils/iter-support.js +++ b/utils/iter-support.js @@ -1,13 +1,13 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {CompatStatement, BrowserName, SimpleSupportStatement} from '../types/types.js' */ +/** @import {InternalCompatStatement, BrowserName, InternalSimpleSupportStatement} from '../types/index.js' */ /** * Get support for a specific browser in array form - * @param {CompatStatement} compat The compatibility data + * @param {Pick} compat The compatibility data * @param {BrowserName} browser The browser to get data for - * @returns {SimpleSupportStatement[]} The array of support statements for the browser + * @returns {InternalSimpleSupportStatement[]} The array of support statements for the browser */ export default (compat, browser) => { if (browser in compat.support) { diff --git a/utils/iter-support.test.js b/utils/iter-support.test.js index ac6b6a31f135f3..521735eb26c23f 100644 --- a/utils/iter-support.test.js +++ b/utils/iter-support.test.js @@ -5,7 +5,7 @@ import assert from 'node:assert/strict'; import iterSupport from './iter-support.js'; -/** @import {CompatStatement} from '../types/types.js' */ +/** @import {InternalCompatStatement} from '../types/index.js' */ describe('iterSupport()', () => { it('returns a `"version_added": false` support statement for non-existent browsers', () => { @@ -23,7 +23,7 @@ describe('iterSupport()', () => { }); it('returns an array of support statements as an array', () => { - /** @type {CompatStatement} */ + /** @type {Pick} */ const compatObj = { support: { firefox: [ diff --git a/utils/query.js b/utils/query.js index 5fd0ed2f0fb7e9..b478e92dd99302 100644 --- a/utils/query.js +++ b/utils/query.js @@ -3,13 +3,13 @@ import bcd from '../index.js'; -/** @import {DataType} from '../types/index.js' */ +/** @import {InternalDataType} from '../types/index.js' */ /** * Get a subtree of compat data. * @param {string} path Dotted path to a given feature (e.g., `css.properties.background`) - * @param {DataType} [data] A tree to query. All of BCD, by default. - * @returns {DataType} A BCD subtree + * @param {InternalDataType} [data] A tree to query. All of BCD, by default. + * @returns {InternalDataType} A BCD subtree * @throws {ReferenceError} For invalid identifiers */ export default (path, data = bcd) => { diff --git a/utils/walk.js b/utils/walk.js index 2124bff5b43bee..72bc12056c9503 100644 --- a/utils/walk.js +++ b/utils/walk.js @@ -11,13 +11,13 @@ import { } from './walkingUtils.js'; import query from './query.js'; -/** @import {CompatData, CompatStatement, Identifier, BrowserStatement, ReleaseStatement} from '../types/types.js' */ -/** @import {DataType} from '../types/index.js' */ +/** @import {InternalCompatStatement, BrowserStatement, ReleaseStatement, InternalIdentifier} from '../types/index.js' */ +/** @import {InternalDataType} from '../types/index.js' */ /** * @typedef {object} BrowserReleaseWalkOutput * @property {string} path - * @property {DataType} data + * @property {InternalDataType} data * @property {BrowserStatement} browser * @property {ReleaseStatement} browserRelease */ @@ -25,17 +25,17 @@ import query from './query.js'; /** * @typedef {object} LowLevelWalkOutput * @property {string} path - * @property {DataType} data + * @property {InternalDataType} data * @property {BrowserStatement} [browser] - * @property {CompatStatement} [compat] + * @property {InternalCompatStatement} [compat] * @property {ReleaseStatement} [browserRelease] */ /** * @typedef {object} WalkOutput * @property {string} path - * @property {DataType} data - * @property {CompatStatement} compat + * @property {InternalDataType} data + * @property {InternalCompatStatement} compat */ /** @@ -58,7 +58,7 @@ export function* browserReleaseWalk(data, path) { /** * Walk through the compatibility statements - * @param {DataType} [data] The data to iterate + * @param {InternalDataType} [data] The data to iterate * @param {string} [path] The current path * @param {number} [depth] The maximum depth to iterate * @yields {LowLevelWalkOutput} The feature info @@ -78,7 +78,7 @@ export function* lowLevelWalk(data = bcd, path, depth = Infinity) { yield* browserReleaseWalk(data, path); } else { if (isFeature(data)) { - next.compat = data.__compat; + next.compat = /** @type {InternalCompatStatement} */ (data.__compat); } yield next; } @@ -94,7 +94,7 @@ export function* lowLevelWalk(data = bcd, path, depth = Infinity) { /** * Walk the data for compat features * @param {string | string[]} [entryPoints] Entry points to iterate - * @param {CompatData | CompatStatement | Identifier} [data] The data to iterate + * @param {InternalDataType} [data] The data to iterate * @yields {WalkOutput} The feature info * @returns {IterableIterator} */ diff --git a/utils/walkingUtils.js b/utils/walkingUtils.js index 6b9ce06f404475..98f9642cab612c 100644 --- a/utils/walkingUtils.js +++ b/utils/walkingUtils.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {Identifier, BrowserStatement} from '../types/types.js' */ +/** @import {InternalIdentifier, BrowserStatement} from '../types/index.js' */ /** * Join a path array together @@ -13,7 +13,7 @@ export const joinPath = (...args) => Array.from(args).filter(Boolean).join('.'); /** * Check if an object is a BCD feature * @param {*} obj The object to check - * @returns {obj is Identifier} Whether the object is a BCD feature + * @returns {obj is InternalIdentifier} Whether the object is a BCD feature */ export const isFeature = (obj) => '__compat' in obj; From 4869566c3673263be1e88341313ac454f7d26c2d Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 14:19:16 +0100 Subject: [PATCH 15/77] chore(vscode): use TS version from workspace --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 44873e54892587..2fda6fed29b1fa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -31,5 +31,6 @@ ], "url": "/schemas/browsers.schema.json" } - ] + ], + "typescript.tsdk": "node_modules/typescript/lib" } From b9028b72857c1f6a895d24e4410949e315ccd6e9 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 14:31:33 +0100 Subject: [PATCH 16/77] chore(schemas): refine browsers schema --- schemas/browsers.schema.json | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/schemas/browsers.schema.json b/schemas/browsers.schema.json index baf77891608baa..56f05c485426a4 100644 --- a/schemas/browsers.schema.json +++ b/schemas/browsers.schema.json @@ -20,6 +20,36 @@ ] }, + "browser_name": { + "type": "string", + "description": "Browser key (e.g. 'firefox', 'chrome_android', or 'webview_ios').", + "enum": [ + "bun", + "chrome", + "chrome_android", + "deno", + "edge", + "firefox", + "firefox_android", + "ie", + "nodejs", + "oculus", + "opera", + "opera_android", + "safari", + "safari_ios", + "samsunginternet_android", + "webview_android", + "webview_ios" + ] + }, + + "upstream_browser_name": { + "type": "string", + "description": "Upstream browser key (e.g. 'firefox' or 'chrome_android').", + "enum": ["chrome", "chrome_android", "firefox", "safari", "safari_ios"] + }, + "browser_status": { "type": "string", "enum": ["retired", "current", "beta", "nightly", "esr", "planned"] @@ -27,6 +57,9 @@ "browsers": { "type": "object", + "propertyNames": { + "$ref": "#/definitions/browser_name" + }, "additionalProperties": { "$ref": "#/definitions/browser_statement" }, @@ -36,7 +69,7 @@ "minProperties": "A browser must be described within the file.", "maxProperties": "Each browser JSON file may only describe one browser." }, - "tsType": "Record" + "tsType": "{ [browser: BrowserName]: BrowserStatement }" }, "browser_statement": { From 8048401b6fece8d59178c2eb316e146ed2508785 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 13 Feb 2026 23:52:46 +0100 Subject: [PATCH 17/77] fix(generate-types): keep types.d.ts for internal types --- .gitignore | 3 +- eslint.config.js | 5 +- schemas/compat-data.schema.json | 52 ++++---- scripts/build/index.js | 4 +- scripts/generate-internal-types.js | 189 +++++++++++++++++++++++++++++ scripts/generate-public-types.js | 68 +++++++++++ scripts/generate-types.js | 62 +--------- 7 files changed, 293 insertions(+), 90 deletions(-) create mode 100644 scripts/generate-internal-types.js create mode 100644 scripts/generate-public-types.js diff --git a/.gitignore b/.gitignore index 9051151f0609d3..40b2ac96e881be 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ yarn.lock .nyc_output/ coverage.lcov coverage/ -/types/browsers.d.ts -/types/compat-data.d.ts +/types/public.d.ts /types/types.d.ts .DS_Store diff --git a/eslint.config.js b/eslint.config.js index d5b6b2caf6e1a8..a59418b179e515 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -34,9 +34,8 @@ export default [ 'CODE_OF_CONDUCT.md', 'build/', '**/coverage/', - '**/browsers.d.ts', - '**/compat-data.d.ts', - '**/types.d.ts', + 'types/public.d.ts', + 'types/types.d.ts', ], }, ...fixupConfigRules( diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index 56ca8b110ffffd..49f6cc6590f771 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/schema", "definitions": { - "simple_support_statement": { + "internal_simple_support_statement": { "type": "object", "properties": { "version_added": { @@ -116,12 +116,12 @@ "support_statement": { "anyOf": [ - { "$ref": "#/definitions/simple_support_statement" }, + { "$ref": "#/definitions/internal_simple_support_statement" }, { "type": "array", "minItems": 2, "items": { - "$ref": "#/definitions/simple_support_statement" + "$ref": "#/definitions/internal_simple_support_statement" } }, { "const": "mirror" } @@ -129,7 +129,7 @@ "tsType": "InternalSimpleSupportStatement | [InternalSimpleSupportStatement, InternalSimpleSupportStatement, ...InternalSimpleSupportStatement[]]" }, - "status_block": { + "internal_status_block": { "type": "object", "properties": { "experimental": { @@ -150,7 +150,7 @@ "additionalProperties": false }, - "support_block": { + "internal_support_block": { "type": "object", "propertyNames": { "enum": [ @@ -191,7 +191,7 @@ "pattern": "^https://(trac.webkit.org/changeset/|hg.mozilla.org/mozilla-central/rev/|crrev.com/|bugzil.la/|crbug.com/|webkit.org/b/|github.com/GoogleChromeLabs/chromium-bidi/issues/)" }, - "compat_statement": { + "internal_compat_statement": { "type": "object", "properties": { "description": { @@ -217,7 +217,7 @@ } } ], - "description": "An optional URL or array of URLs, each of which is for a specific part of a specification in which this feature is defined. Each URL must contain a fragment identifier.", + "description": "An optional URL or array of URLs, each of which is for a specific part of a specification in which this feature is defined. Each URL must contain a fragment internal_identifier.", "tsType": "string | [string, string, ...string[]]" }, "tags": { @@ -231,11 +231,11 @@ "description": "The path to the file that defines this feature in browser-compat-data, relative to the repository root. Useful for guiding potential contributors towards the correct file to edit. This is automatically generated at build time and should never manually be specified." }, "support": { - "$ref": "#/definitions/support_block", - "description": "The data for the support of each browser, containing a `support_statement` object for each browser identifier with information about versions, prefixes, or alternate names, as well as notes." + "$ref": "#/definitions/internal_support_block", + "description": "The data for the support of each browser, containing a `support_statement` object for each browser internal_identifier with information about versions, prefixes, or alternate names, as well as notes." }, "status": { - "$ref": "#/definitions/status_block", + "$ref": "#/definitions/internal_status_block", "description": "An object containing information about the stability of the feature." } }, @@ -243,43 +243,45 @@ "additionalProperties": false }, - "identifier": { + "internal_identifier": { "type": "object", "properties": { "__compat": { "type": "object", - "$ref": "#/definitions/compat_statement", + "$ref": "#/definitions/internal_compat_statement", "required": ["status"], - "description": "A feature is described by an identifier containing the `__compat` property.\n\nIn other words, identifiers without `__compat` aren't necessarily features, but help to nest the features properly.\n\nWhen an identifier has a `__compat` block, it represents its basic support, indicating that a minimal implementation of a functionality is included.\n\nWhat it represents exactly depends of the evolution of the feature over time, both in terms of specifications and of browser support.", - "tsType": "CompatStatement" + "description": "A feature is described by an internal_identifier containing the `__compat` property.\n\nIn other words, internal_identifiers without `__compat` aren't necessarily features, but help to nest the features properly.\n\nWhen an internal_identifier has a `__compat` block, it represents its basic support, indicating that a minimal implementation of a functionality is included.\n\nWhat it represents exactly depends of the evolution of the feature over time, both in terms of specifications and of browser support.", + "tsType": "InternalCompatStatement" } }, "patternProperties": { - "^(?!__compat)[a-zA-Z_0-9-$@]*$": { "$ref": "#/definitions/identifier" } + "^(?!__compat)[a-zA-Z_0-9-$@]*$": { + "$ref": "#/definitions/internal_identifier" + } }, "additionalProperties": false, "errorMessage": { "additionalProperties": "Feature names can only contain alphanumerical characters or the following symbols: _-$@" }, - "tsType": "{[key: string]: Identifier} & {__compat?: CompatStatement};" + "tsType": "{[key: string]: InternalIdentifier} & {__compat?: InternalCompatStatement};" }, - "webextensions_identifier": { + "webextensions_internal_identifier": { "type": "object", "properties": { - "__compat": { "$ref": "#/definitions/compat_statement" } + "__compat": { "$ref": "#/definitions/internal_compat_statement" } }, "patternProperties": { "^(?!__compat)[a-zA-Z_0-9-$@]*$": { - "$ref": "#/definitions/webextensions_identifier", - "tsType": "Identifier" + "$ref": "#/definitions/webextensions_internal_identifier", + "tsType": "InternalIdentifier" } }, "additionalProperties": false, "errorMessage": { "additionalProperties": "Feature names can only contain alphanumerical characters or the following symbols: _-$@" }, - "tsType": "{[key: string]: Identifier} & {__compat?: CompatStatement}" + "tsType": "{[key: string]: InternalIdentifier} & {__compat?: InternalCompatStatement}" } }, @@ -287,14 +289,14 @@ "type": "object", "patternProperties": { "^(?!__compat)(?!webextensions)[a-zA-Z_0-9-$@]*$": { - "$ref": "#/definitions/identifier" + "$ref": "#/definitions/internal_identifier" }, "^webextensions*$": { - "$ref": "#/definitions/webextensions_identifier", - "tsType": "Identifier" + "$ref": "#/definitions/webextensions_internal_identifier", + "tsType": "InternalIdentifier" }, "^__compat$": { - "$ref": "#/definitions/compat_statement" + "$ref": "#/definitions/internal_compat_statement" } }, "additionalProperties": false, diff --git a/scripts/build/index.js b/scripts/build/index.js index 39bb8122b0b70d..01306f98837d55 100644 --- a/scripts/build/index.js +++ b/scripts/build/index.js @@ -11,7 +11,7 @@ import stringify from 'fast-json-stable-stringify'; import { compareVersions } from 'compare-versions'; import { marked } from 'marked'; -import compileTS from '../generate-types.js'; +import compileTS from '../generate-public-types.js'; import schema from '../../schemas/public.schema.json' with { type: 'json' }; import { createAjv } from '../lib/ajv.js'; import { walk } from '../../utils/index.js'; @@ -305,7 +305,7 @@ export * from "./types.js";`; await fs.writeFile(destImport, content); logWrite(destImport, 'ESM types'); - await compileTS('schemas/browsers.schema.json', destTypes); + await compileTS('schemas/public.schema.json', destTypes); logWrite(destTypes, 'data types'); }; diff --git a/scripts/generate-internal-types.js b/scripts/generate-internal-types.js new file mode 100644 index 00000000000000..769ed666469abc --- /dev/null +++ b/scripts/generate-internal-types.js @@ -0,0 +1,189 @@ +/* This file is a part of @mdn/browser-compat-data + * See LICENSE file for more information. */ + +/* c8 ignore start */ + +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import esMain from 'es-main'; +import { fdir } from 'fdir'; +import { compileFromFile } from 'json-schema-to-typescript'; + +import { spawn } from '../utils/index.js'; + +import extend from './lib/extend.js'; + +const dirname = fileURLToPath(new URL('.', import.meta.url)); + +const opts = { + bannerComment: '', + unreachableDefinitions: true, +}; + +const header = + '/* This file is a part of @mdn/browser-compat-data\n * See LICENSE file for more information. */\n\n/**\n* This file was automatically generated by json-schema-to-typescript.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source schema files in\n* schemas/*, and run "npm run gentypes" to regenerate this file.\n*/'; + +const compatDataTypes = { + __meta: + 'Contains metadata for the current BCD information, such as the BCD version.', + api: 'Contains data for each [Web API](https://developer.mozilla.org/docs/Web/API) interface.', + browsers: 'Contains data for each known and tracked browser/engine.', + css: 'Contains data for [CSS](https://developer.mozilla.org/docs/Web/CSS) properties, selectors, and at-rules.', + html: 'Contains data for [HTML](https://developer.mozilla.org/docs/Web/HTML) elements, attributes, and global attributes.', + http: 'Contains data for [HTTP](https://developer.mozilla.org/docs/Web/HTTP) headers, statuses, and methods.', + javascript: + 'Contains data for [JavaScript](https://developer.mozilla.org/docs/Web/JavaScript) built-in Objects, statement, operators, and other ECMAScript language features.', + manifests: + 'Contains data for various manifests, such as the [Web Application Manifest](https://developer.mozilla.org/docs/Web/Progressive_web_apps/manifest).', + mathml: + 'Contains data for [MathML](https://developer.mozilla.org/docs/Web/MathML) elements, attributes, and global attributes.', + mediatypes: + 'Contains data for [Media types](https://developer.mozilla.org/docs/Web/HTTP/Guides/MIME_types).', + svg: 'Contains data for [SVG](https://developer.mozilla.org/docs/Web/SVG) elements, attributes, and global attributes.', + webassembly: + 'Contains data for [WebAssembly](https://developer.mozilla.org/docs/WebAssembly) features.', + webdriver: + 'Contains data for [WebDriver](https://developer.mozilla.org/docs/Web/WebDriver) commands.', + webextensions: + 'Contains data for [WebExtensions](https://developer.mozilla.org/Add-ons/WebExtensions) JavaScript APIs and manifest keys.', +}; + +/** + * Generate the browser names TypeScript + * @returns {Promise} The stringified TypeScript typedef + */ +const generateBrowserNames = async () => { + // Load browser data independently of index.ts, since index.ts depends + // on the output of this script + const browserData = { browsers: {} }; + + const paths = /** @type {string[]} */ ( + new fdir() + .withBasePath() + .filter((fp) => fp.endsWith('.json')) + .crawl(path.join(dirname, '..', 'browsers')) + .sync() + ); + + for (const fp of paths) { + try { + const contents = await fs.readFile(fp); + extend(browserData, JSON.parse(contents.toString('utf8'))); + } catch { + // Skip invalid JSON. Tests will flag the problem separately. + continue; + } + } + + // Generate BrowserName type + const browsers = Object.keys(browserData.browsers); + return `/**\n * The names of the known browsers.\n */\nexport type BrowserName = ${browsers + .map((b) => `"${b}"`) + .join(' | ')};`; +}; + +/** + * Generate the CompatData TypeScript + * @returns {string} The stringified TypeScript typedef + */ +const generateCompatDataTypes = () => { + const props = Object.entries(compatDataTypes).map( + (t) => + ` /**\n * ${t[1]}\n */\n ${t[0]}: ${ + t[0] === '__meta' + ? 'MetaBlock' + : t[0] === 'browsers' + ? 'Browsers' + : 'InternalIdentifier' + };`, + ); + + const metaType = + 'export interface MetaBlock {\n version: string;\n timestamp: string;\n}'; + + return `${metaType}\n\nexport interface InternalCompatData {\n${props.join( + '\n\n', + )}\n}\n`; +}; + +/** + * Transform the TypeScript to remove unneeded bits of typedefs + * @param {string} browserTS Typedefs for BrowserName + * @param {string} compatTS Typedefs for CompatData + * @returns {string} Updated typedefs + */ +const transformTS = (browserTS, compatTS) => { + // XXX Temporary until the following PR is merged and released: + // https://github.com/bcherny/json-schema-to-typescript/pull/456 + let ts = browserTS + '\n\n' + compatTS; + + ts = ts + .replace( + 'export interface BrowserDataFile {\n browsers?: Browsers;\n}', + '', + ) + .replace('export interface CompatDataFile {}', '') + .replace( + /\/\*\*\n \* This interface was referenced by `CompatDataFile`'s JSON-Schema definition\n \* via the `patternProperty` "\^webextensions\*\$"\.\n \*\/\nexport type WebextensionsIdentifier = Identifier;\n/, + '', + ) + .replace( + /\/\*\*\n \* This interface was referenced by `CompatDataFile`'s JSON-Schema\n \* via the `definition` "webextensions_identifier"\.\n \*\/\nexport type WebextensionsIdentifier1 = .*;\n/, + '', + ) + .replace( + /\/\*\*\n \* This interface was referenced by `CompatDataFile`'s JSON-Schema\n \* via the `definition` "spec_url_value"\.\n \*\/\nexport type SpecUrlValue = string;\n/, + '', + ) + .replace( + /\/\*\*\n \* This interface was referenced by `CompatDataFile`'s JSON-Schema\n \* via the `definition` "impl_url_value"\.\n \*\/\nexport type ImplUrlValue = string;\n/, + '', + ) + .replace( + '/**\n * This interface was referenced by `CompatDataFile`\'s JSON-Schema\n * via the `definition` "support_block".\n */\nexport type SupportBlock1 = Partial>;\n', + '', + ) + .replace( + /\/\*\*\n \* This interface was referenced by `CompatDataFile`'s JSON-Schema\n \* via the `definition` "status_block"\.\n \*\/\nexport interface StatusBlock1 {(.*\n)*}\n/, + '', + ); + + return ts; +}; + +/** + * Compile the TypeScript typedefs from the schema JSON + * @param {URL | string} [destination] Output destination + */ +const compile = async ( + destination = new URL('../types/types.d.ts', import.meta.url), +) => { + const browserTS = await compileFromFile('schemas/browsers.schema.json', opts); + const compatTS = await compileFromFile( + 'schemas/compat-data.schema.json', + opts, + ); + + const ts = [ + header, + await generateBrowserNames(), + 'export type VersionValue = string | false;', + transformTS(browserTS, compatTS), + generateCompatDataTypes(), + ].join('\n\n'); + await fs.writeFile(destination, ts); + spawn('tsc', ['--skipLibCheck', '../types/types.d.ts'], { + cwd: dirname, + stdio: 'inherit', + }); +}; + +if (esMain(import.meta)) { + await compile(); +} + +export default compile; + +/* c8 ignore stop */ diff --git a/scripts/generate-public-types.js b/scripts/generate-public-types.js new file mode 100644 index 00000000000000..0f45827ad5ce11 --- /dev/null +++ b/scripts/generate-public-types.js @@ -0,0 +1,68 @@ +/* This file is a part of @mdn/browser-compat-data + * See LICENSE file for more information. */ + +/* c8 ignore start */ + +import fs from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; + +import esMain from 'es-main'; +import { compileFromFile } from 'json-schema-to-typescript'; + +import { spawn } from '../utils/index.js'; + +const root = new URL('..', import.meta.url); + +const opts = { + bannerComment: + '/* This file is a part of @mdn/browser-compat-data\n * See LICENSE file for more information. */\n\n/**\n* This file was automatically generated by json-schema-to-typescript.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source schema files in\n* schemas/*, and run "npm run gentypes" to regenerate this file.\n*/', + unreachableDefinitions: true, +}; + +/** + * Transform the TypeScript to remove unneeded bits of typedefs + * @param {string} ts Typedefs + * @returns {string} Updated typedefs + */ +const transformTS = (ts) => { + // Remove "interface was referenced" comments. + ts = ts + .replace( + /( \*\n)? \* This interface was referenced by [^\n]+\n \* via the [^\n]+\n/g, + '', + ) + .replace(/\/\*\*\n \*\/\n/g, ''); + + return ts; +}; + +/** + * Compile the TypeScript typedefs from the schema JSON + * @param {string} source - JSON schema source + * @param {URL | string} destination - Output destination + */ +const compile = async ( + source = 'schemas/public.schema.json', + destination = 'types/public.d.ts', +) => { + let ts = await compileFromFile(source, opts); + + ts = transformTS(ts); + + const file = + destination instanceof URL ? destination : new URL(destination, root); + + await fs.writeFile(file, ts); + spawn('tsc', ['--skipLibCheck', fileURLToPath(file)], { + cwd: fileURLToPath(root), + stdio: 'inherit', + }); +}; + +if (esMain(import.meta)) { + compile(); +} + +export default compile; + +/* c8 ignore stop */ diff --git a/scripts/generate-types.js b/scripts/generate-types.js index e4ca4ffb641a20..c22dbc4b41f7c1 100644 --- a/scripts/generate-types.js +++ b/scripts/generate-types.js @@ -1,69 +1,15 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/* c8 ignore start */ - -import fs from 'node:fs/promises'; -import { fileURLToPath } from 'node:url'; - import esMain from 'es-main'; -import { compileFromFile } from 'json-schema-to-typescript'; - -import { spawn } from '../utils/index.js'; - -const root = new URL('..', import.meta.url); - -const opts = { - bannerComment: - '/* This file is a part of @mdn/browser-compat-data\n * See LICENSE file for more information. */\n\n/**\n* This file was automatically generated by json-schema-to-typescript.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source schema files in\n* schemas/*, and run "npm run gentypes" to regenerate this file.\n*/', - unreachableDefinitions: true, -}; -/** - * Transform the TypeScript to remove unneeded bits of typedefs - * @param {string} ts Typedefs - * @returns {string} Updated typedefs - */ -const transformTS = (ts) => { - // Remove "interface was referenced" comments. - ts = ts - .replace( - /( \*\n)? \* This interface was referenced by [^\n]+\n \* via the [^\n]+\n/g, - '', - ) - .replace(/\/\*\*\n \*\/\n/g, ''); +import compileInternal from './generate-internal-types.js'; +import compilePublic from './generate-public-types.js'; - return ts; -}; - -/** - * Compile the TypeScript typedefs from the schema JSON - * @param {string} source - JSON schema source - * @param {URL | string} destination - Output destination - */ -const compile = async (source, destination) => { - let ts = await compileFromFile(source, opts); - - ts = transformTS(ts); - - const file = - destination instanceof URL ? destination : new URL(destination, root); - - await fs.writeFile(file, ts); - spawn('tsc', ['--skipLibCheck', fileURLToPath(file)], { - cwd: fileURLToPath(root), - stdio: 'inherit', - }); -}; +/* c8 ignore start */ if (esMain(import.meta)) { - await Promise.all([ - compile('schemas/browsers.schema.json', 'types/browsers.d.ts'), - compile('schemas/compat-data.schema.json', 'types/compat-data.d.ts'), - compile('schemas/public.schema.json', 'types/types.d.ts'), - ]); + await Promise.all([compileInternal(), compilePublic()]); } -export default compile; - /* c8 ignore stop */ From 46185f406ad75b5cfeccba254b38330d3294dc7c Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Sat, 14 Feb 2026 00:06:57 +0100 Subject: [PATCH 18/77] fix(schemas): remove source_file from internal schema --- schemas/compat-data.schema.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index 49f6cc6590f771..65297e62c9909b 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -226,10 +226,6 @@ "minItems": 1, "tsType": "[string, ...string[]]" }, - "source_file": { - "type": "string", - "description": "The path to the file that defines this feature in browser-compat-data, relative to the repository root. Useful for guiding potential contributors towards the correct file to edit. This is automatically generated at build time and should never manually be specified." - }, "support": { "$ref": "#/definitions/internal_support_block", "description": "The data for the support of each browser, containing a `support_statement` object for each browser internal_identifier with information about versions, prefixes, or alternate names, as well as notes." From b7377874dd87e776541fc10c176c92190b288806 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Sat, 14 Feb 2026 00:08:16 +0100 Subject: [PATCH 19/77] chore(types): remove manual types --- types/index.d.ts | 52 +----------------------------------------------- 1 file changed, 1 insertion(+), 51 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index 846f5fbc08883a..2b95bd0a8d3a8f 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,57 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -import type { - BrowserName, - BrowserStatement, - CompatData, - CompatStatement, - Identifier, - SimpleSupportStatement, - SupportStatement, -} from './types.js'; - -export type { - Browsers, - BrowserName, - BrowserStatement, - BrowserStatus, - FlagStatement, - MetaBlock, - ReleaseStatement, - SimpleSupportStatement, - StatusBlock, - VersionValue, -} from './types.js'; - -export type InternalCompatData = Omit; - -export type InternalSupportStatement = SupportStatement | 'mirror'; -export type InternalSimpleSupportStatement = SimpleSupportStatement | 'mirror'; - -export type InternalSupportBlock = Partial< - Record ->; - -export interface InternalCompatStatement extends Omit< - CompatStatement, - 'source_file' | 'support' -> { - support: InternalSupportBlock; -} - -export type InternalIdentifier = - | { - __compat: InternalCompatStatement; - [k: string]: InternalIdentifier; - } - | { - __compat?: InternalCompatStatement; - [k: string]: InternalIdentifier; - } - | { - [k: string]: InternalIdentifier; - }; +export type * from './types.js'; export type DataType = | CompatData From 252bb46803ebd774eebef9c886dd7898290095bb Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Sat, 14 Feb 2026 00:36:44 +0100 Subject: [PATCH 20/77] fix(schemas): refine internal schema --- schemas/compat-data.schema.json | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index 65297e62c9909b..3cb60e4f15a5fd 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -114,7 +114,7 @@ "required": ["type", "name"] }, - "support_statement": { + "internal_support_statement": { "anyOf": [ { "$ref": "#/definitions/internal_simple_support_statement" }, { @@ -125,8 +125,7 @@ } }, { "const": "mirror" } - ], - "tsType": "InternalSimpleSupportStatement | [InternalSimpleSupportStatement, InternalSimpleSupportStatement, ...InternalSimpleSupportStatement[]]" + ] }, "internal_status_block": { @@ -174,7 +173,7 @@ ] }, "additionalProperties": { - "$ref": "#/definitions/support_statement" + "$ref": "#/definitions/internal_support_statement" }, "tsType": "Partial>" }, @@ -228,7 +227,7 @@ }, "support": { "$ref": "#/definitions/internal_support_block", - "description": "The data for the support of each browser, containing a `support_statement` object for each browser internal_identifier with information about versions, prefixes, or alternate names, as well as notes." + "description": "The data for the support of each browser, containing a `internal_support_statement` object for each browser internal_identifier with information about versions, prefixes, or alternate names, as well as notes." }, "status": { "$ref": "#/definitions/internal_status_block", From 02c1290214f8ecfaccce37c3ca9455fb94cb85f0 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Sat, 14 Feb 2026 00:36:53 +0100 Subject: [PATCH 21/77] chore: fix internal schema usage --- index.js | 13 +++++------ lint/common/overlap.js | 22 +++++++++++-------- lint/fixer/flags.js | 6 ++--- lint/linter/test-consistency.js | 9 ++++---- lint/linter/test-flags.js | 10 ++++----- lint/linter/test-versions.js | 4 ++-- lint/utils.js | 4 ++-- lint/utils.test.js | 4 ++-- scripts/build/index.js | 13 ++++++----- scripts/build/mirror.js | 17 +++++++------- scripts/diff-flat.js | 5 +++-- scripts/diff.js | 1 - scripts/generate-internal-types.js | 15 ++----------- scripts/lib/compare-statements.js | 6 ++--- scripts/lib/data-folders.js | 7 +++++- scripts/lib/stringify-and-order-properties.js | 8 +++---- .../migrations/002-remove-webview-flags.js | 10 ++++----- scripts/migrations/007-experimental-false.js | 4 ++-- scripts/traverse.js | 6 ++--- utils/iter-support.js | 4 +++- utils/walkingUtils.test.js | 6 ++--- 21 files changed, 87 insertions(+), 87 deletions(-) diff --git a/index.js b/index.js index f33c7b71ceb98d..1fe5fc728c1a9b 100644 --- a/index.js +++ b/index.js @@ -17,14 +17,13 @@ const dirname = fileURLToPath(new URL('.', import.meta.url)); /** * Recursively load one or more directories passed as arguments. - * @param {...string} dirs The directories to load - * @returns {Promise} All of the browser compatibility data + * @template {keyof InternalCompatData} Dir + * @param {...Dir} dirs The directories to load + * @returns {Promise>} All of the browser compatibility data */ const load = async (...dirs) => { - /** @type {InternalCompatData} */ - const result = { - browsers: {}, - }; + /** @type {Partial>} */ + const result = {}; for (const dir of dirs) { const paths = /** @type {string[]} */ ( @@ -58,7 +57,7 @@ const load = async (...dirs) => { } } - return result; + return /** @type {Pick} */ (result); }; /** @type {InternalCompatData} */ diff --git a/lint/common/overlap.js b/lint/common/overlap.js index 82c4ea2e082c5f..6da689e517a245 100644 --- a/lint/common/overlap.js +++ b/lint/common/overlap.js @@ -9,15 +9,15 @@ import { createStatementGroupKey } from '../utils.js'; import compareStatements from '../../scripts/lib/compare-statements.js'; /** @import {Logger} from '../utils.js' */ -/** @import {BrowserName, SimpleSupportStatement, InternalSupportStatement} from '../../types/index.js' */ +/** @import {BrowserName, InternalSimpleSupportStatement, InternalSupportStatement} from '../../types/index.js' */ /** * Groups statements by group key. - * @param {SimpleSupportStatement[]} data The support statements to group. - * @returns {Map} the statement groups + * @param {InternalSimpleSupportStatement[]} data The support statements to group. + * @returns {Map} the statement groups */ const groupByStatementKey = (data) => { - /** @type {Map} */ + /** @type {Map} */ const groups = new Map(); for (const support of data) { @@ -34,7 +34,7 @@ const groupByStatementKey = (data) => { /** * Formats a support statement as a simplified JSON-like version range. - * @param {SimpleSupportStatement} support The statement to format + * @param {InternalSimpleSupportStatement} support The statement to format * @returns {string} The formatted range */ const formatRange = (support) => { @@ -73,8 +73,12 @@ export const checkOverlap = (data, browser, { logger, fix = false }) => { const statements = groupData.slice().sort(compareStatements).reverse(); for (let i = 0; i < statements.length - 1; i++) { - const current = /** @type {SimpleSupportStatement} */ (statements.at(i)); - const next = /** @type {SimpleSupportStatement} */ (statements.at(i + 1)); + const current = /** @type {InternalSimpleSupportStatement} */ ( + statements.at(i) + ); + const next = /** @type {InternalSimpleSupportStatement} */ ( + statements.at(i + 1) + ); if (!statementsOverlap(current, next)) { continue; @@ -106,8 +110,8 @@ export const checkOverlap = (data, browser, { logger, fix = false }) => { /** * Checks if the support statements overlap in terms of their version ranges. - * @param {SimpleSupportStatement} current the current statement. - * @param {SimpleSupportStatement} next the chronologically following statement. + * @param {InternalSimpleSupportStatement} current the current statement. + * @param {InternalSimpleSupportStatement} next the chronologically following statement. * @returns {boolean} Whether the support statements overlap. */ const statementsOverlap = (current, next) => { diff --git a/lint/fixer/flags.js b/lint/fixer/flags.js index 4f7fea8860b0a4..9c9f299caf9d87 100644 --- a/lint/fixer/flags.js +++ b/lint/fixer/flags.js @@ -7,7 +7,7 @@ import testFlags, { } from '../linter/test-flags.js'; import walk from '../../utils/walk.js'; -/** @import {BrowserName, InternalCompatStatement, InternalSupportStatement, SimpleSupportStatement, InternalIdentifier} from '../../types/index.js' */ +/** @import {BrowserName, InternalCompatStatement, InternalSupportStatement, InternalSimpleSupportStatement, InternalIdentifier} from '../../types/index.js' */ /** * Removes irrelevant flags from the compatibility data @@ -27,7 +27,7 @@ export const removeIrrelevantFlags = (supportData) => { return supportData; } - /** @type {SimpleSupportStatement[]} */ + /** @type {InternalSimpleSupportStatement[]} */ const result = []; const basicSupport = getBasicSupportStatement(supportData); @@ -43,7 +43,7 @@ export const removeIrrelevantFlags = (supportData) => { if (result.length == 1) { return result[0]; } - return /** @type {[SimpleSupportStatement, SimpleSupportStatement, ...SimpleSupportStatement[]]} */ ( + return /** @type {[InternalSimpleSupportStatement, InternalSimpleSupportStatement, ...InternalSimpleSupportStatement[]]} */ ( result ); }; diff --git a/lint/linter/test-consistency.js b/lint/linter/test-consistency.js index 79a97715f39fa8..b3f8c44e80cc15 100644 --- a/lint/linter/test-consistency.js +++ b/lint/linter/test-consistency.js @@ -11,8 +11,7 @@ import bcd from '../../index.js'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {BrowserName, InternalCompatData, InternalCompatStatement, InternalIdentifier, SimpleSupportStatement, VersionValue} from '../../types/index.js' */ -/** @import {DataType, InternalSupportBlock, InternalSupportStatement} from '../../types/index.js' */ +/** @import {BrowserName, InternalCompatData, InternalCompatStatement, InternalIdentifier, InternalSimpleSupportStatement, InternalSupportBlock, InternalSupportStatement, VersionValue} from '../../types/index.js' */ /** * @typedef {'unsupported' | 'subfeature_earlier_implementation'} ErrorType @@ -264,7 +263,7 @@ export class ConsistencyChecker { extractSupportedBrowsersWithVersion(InternalcompatData) { return this.extractBrowsers( InternalcompatData, - (/** @type {SimpleSupportStatement} */ data) => + (/** @type {InternalSimpleSupportStatement} */ data) => typeof data.version_added === 'string', ); } @@ -294,7 +293,7 @@ export class ConsistencyChecker { /** * A convenience function to squash preview and flag support into `false` - * @param {SimpleSupportStatement} statement The statement to use + * @param {InternalSimpleSupportStatement} statement The statement to use * @returns {VersionValue} The version number or `false` */ const resolveVersionAddedValue = (statement) => @@ -378,7 +377,7 @@ export class ConsistencyChecker { /** * Get all of the browsers within the data and pass the data to the callback. * @param {InternalCompatStatement | undefined} InternalcompatData The compat data to process - * @param {(browserData: SimpleSupportStatement) => boolean} callback The function to pass the data to + * @param {(browserData: InternalSimpleSupportStatement) => boolean} callback The function to pass the data to * @returns {BrowserName[]} The list of browsers using the callback as a filter */ extractBrowsers(InternalcompatData, callback) { diff --git a/lint/linter/test-flags.js b/lint/linter/test-flags.js index 9ca5f1f2f17e64..bd8da9773ad204 100644 --- a/lint/linter/test-flags.js +++ b/lint/linter/test-flags.js @@ -7,7 +7,7 @@ import { compare } from 'compare-versions'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {InternalCompatStatement, BrowserName, InternalSupportStatement, SimpleSupportStatement, FlagStatement} from '../../types/index.js' */ +/** @import {InternalCompatStatement, BrowserName, InternalSupportStatement, InternalSimpleSupportStatement, FlagStatement} from '../../types/index.js' */ /** * @typedef {object} FlagError @@ -17,8 +17,8 @@ import { compare } from 'compare-versions'; /** * Get the support statement with basic, non-aliased and non-flagged support - * @param {SimpleSupportStatement[]} supportData The statements to check - * @returns {SimpleSupportStatement | undefined} The support statement with basic, non-aliased and non-flagged support + * @param {InternalSimpleSupportStatement[]} supportData The statements to check + * @returns {InternalSimpleSupportStatement | undefined} The support statement with basic, non-aliased and non-flagged support */ export const getBasicSupportStatement = (supportData) => supportData.find((statement) => { @@ -33,8 +33,8 @@ export const getBasicSupportStatement = (supportData) => /** * Determines if a support statement is for irrelevant flag data - * @param {SimpleSupportStatement} statement The statement to check - * @param {SimpleSupportStatement | undefined} basicSupport The support statement for the same browser that has no alt. name, prefix or flag + * @param {InternalSimpleSupportStatement} statement The statement to check + * @param {InternalSimpleSupportStatement | undefined} basicSupport The support statement for the same browser that has no alt. name, prefix or flag * @returns {boolean} Whether the support statement is irrelevant */ export const isIrrelevantFlagData = (statement, basicSupport) => { diff --git a/lint/linter/test-versions.js b/lint/linter/test-versions.js index 6fb10c8e6e1ed9..6194edcd73a2c6 100644 --- a/lint/linter/test-versions.js +++ b/lint/linter/test-versions.js @@ -9,7 +9,7 @@ import { browsers } from '../../index.js'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ -/** @import {BrowserName, InternalCompatStatement, SimpleSupportStatement, VersionValue} from '../../types/index.js' */ +/** @import {BrowserName, InternalCompatStatement, InternalSimpleSupportStatement, VersionValue} from '../../types/index.js' */ /** @import {InternalSupportBlock, InternalSupportStatement} from '../../types/index.js' */ /* The latest date a range's release can correspond to */ @@ -46,7 +46,7 @@ const isValidVersion = (browser, category, version) => { * Checks if the version number of version_removed is greater than or equal to * that of version_added, assuming they are both version strings. If either one * is not a valid version string, return null. - * @param {SimpleSupportStatement} statement The statement to test + * @param {InternalSimpleSupportStatement} statement The statement to test * @returns {boolean | null} Whether the version added was earlier than the version removed */ const addedBeforeRemoved = (statement) => { diff --git a/lint/utils.js b/lint/utils.js index 6d5213ea6722a7..8ed825aa230eec 100644 --- a/lint/utils.js +++ b/lint/utils.js @@ -4,7 +4,7 @@ import { platform } from 'node:os'; import { styleText } from 'node:util'; -/** @import {SimpleSupportStatement} from '../types/index.js' */ +/** @import {InternalSimpleSupportStatement} from '../types/index.js' */ /** @import {Linter, LinterData, LinterMessage, LinterScope} from './types.js' */ /** @type {Readonly>} */ @@ -264,7 +264,7 @@ export class Linters { /** * Returns the key for the group that this statement belongs to. - * @param {SimpleSupportStatement} support The support statement. + * @param {InternalSimpleSupportStatement} support The support statement. * @returns {string} The key of the support statement group. */ export const createStatementGroupKey = (support) => { diff --git a/lint/utils.test.js b/lint/utils.test.js index 4c5e0073c45b0f..16039575e63bce 100644 --- a/lint/utils.test.js +++ b/lint/utils.test.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {SimpleSupportStatement} from '../types/index.js' */ +/** @import {InternalSimpleSupportStatement} from '../types/index.js' */ import assert from 'node:assert/strict'; @@ -80,7 +80,7 @@ describe('utils', () => { }); it('createStatementGroupKey() works correctly', () => { - /** @type {Record} */ + /** @type {Record} */ const tests = { 'normal name': { version_added: '1', diff --git a/scripts/build/index.js b/scripts/build/index.js index 01306f98837d55..1e33bae8770670 100644 --- a/scripts/build/index.js +++ b/scripts/build/index.js @@ -20,8 +20,8 @@ import bcd from '../../index.js'; import mirrorSupport from './mirror.js'; /** - * @import { CompatData } from '../../types/types.js' - * @import { BrowserName, InternalCompatData, InternalIdentifier, InternalSupportStatement, MetaBlock, VersionValue } from '../../types/index.js' + * @import { BrowserName, InternalIdentifier, InternalSupportStatement, MetaBlock, VersionValue } from '../../types/index.js' + * @import { CompatData } from '../../types/public.js' * @import { WalkOutput } from '../../utils/walk.js' */ @@ -195,7 +195,7 @@ export const transformMD = (feature) => { const addIE = (feature) => { if ( feature.path.startsWith('webextensions.') && - !bcd.browsers.ie.accepts_webextensions + !bcd.browsers['ie'].accepts_webextensions ) { return; } @@ -230,15 +230,18 @@ export const createDataBundle = async () => { applyTransforms(bcd); - return { + /** @type {*} */ + const result = { ...bcd, __meta: generateMeta(), }; + + return /** @type {CompatData}*/ (result); }; /** * Validates the given data against the schema. - * @param {InternalCompatData} data - The data to validate. + * @param {CompatData} data - The data to validate. */ const validate = (data) => { const ajv = createAjv(); diff --git a/scripts/build/mirror.js b/scripts/build/mirror.js index 37d670bdebf9ce..f7c2f2485549d1 100644 --- a/scripts/build/mirror.js +++ b/scripts/build/mirror.js @@ -6,7 +6,7 @@ import { compareVersions, compare } from 'compare-versions'; import { browsers } from '../../index.js'; /** - * @import { BrowserName, SimpleSupportStatement, InternalSupportStatement } from '../../types/index.js' + * @import { BrowserName, InternalSimpleSupportStatement, InternalSupportStatement } from '../../types/index.js' * @import { InternalSupportBlock } from '../../types/index.js' */ @@ -175,17 +175,17 @@ const updateNotes = (notes, regex, replace, versionMapper) => { /** * Copy a support statement - * @param {SimpleSupportStatement} data The data to copied - * @returns {SimpleSupportStatement} The new copied object + * @param {InternalSimpleSupportStatement} data The data to copied + * @returns {InternalSimpleSupportStatement} The new copied object */ const copyStatement = (data) => { - /** @type {Partial} */ + /** @type {Partial} */ const newData = {}; for (const i in data) { newData[i] = data[i]; } - return /** @type {SimpleSupportStatement} */ (newData); + return /** @type {InternalSimpleSupportStatement} */ (newData); }; /** @@ -204,7 +204,7 @@ export const bumpSupport = (sourceData, sourceBrowser, destination) => { const newData = sourceData .map( (data) => - /** @type {SimpleSupportStatement} */ ( + /** @type {InternalSimpleSupportStatement} */ ( bumpSupport(data, sourceBrowser, destination) ), ) @@ -218,13 +218,14 @@ export const bumpSupport = (sourceData, sourceBrowser, destination) => { return newData[0]; default: - return /** @type {[SimpleSupportStatement, SimpleSupportStatement, ...SimpleSupportStatement[]]} */ ( + return /** @type {[InternalSimpleSupportStatement, InternalSimpleSupportStatement, ...InternalSimpleSupportStatement[]]} */ ( newData ); } } - /** @type {SimpleSupportStatement} */ + /** @type {InternalSimpleSupportStatement} */ + // @ts-expect-error FIXME Handle "mirror" value. const newData = copyStatement(sourceData); if ( diff --git a/scripts/diff-flat.js b/scripts/diff-flat.js index d66ca193e9f209..a24c31dce074a7 100644 --- a/scripts/diff-flat.js +++ b/scripts/diff-flat.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {InternalCompatData, SimpleSupportStatement} from '../types/index.js' */ +/** @import {InternalCompatData, InternalSimpleSupportStatement} from '../types/index.js' */ /** * @typedef {'html' | 'plain'} Format @@ -125,13 +125,14 @@ const flattenObject = (obj, parentKey = '', result = {}) => { const { version_added, + // @ts-expect-error FIXME Handle internal-public transition. version_last, partial_implementation, alternative_name, prefix, flags, notes, - } = /** @type {SimpleSupportStatement} */ (obj[key]); + } = /** @type {InternalSimpleSupportStatement} */ (obj[key]); const parts = [ typeof version_added === 'string' diff --git a/scripts/diff.js b/scripts/diff.js index d631f38b3d56bc..8f6b8328e34c1b 100644 --- a/scripts/diff.js +++ b/scripts/diff.js @@ -69,7 +69,6 @@ const doMirror = (diff, contents, path, direction) => { const queried = /** @type {InternalIdentifier} */ (query(dataPath, data)); if (queried.__compat?.support) { - // @ts-expect-error I need to figure out what to do here. diff[direction] = mirror(browser, queried.__compat.support); } }; diff --git a/scripts/generate-internal-types.js b/scripts/generate-internal-types.js index 769ed666469abc..f1fe3e37ba8c5e 100644 --- a/scripts/generate-internal-types.js +++ b/scripts/generate-internal-types.js @@ -26,8 +26,6 @@ const header = '/* This file is a part of @mdn/browser-compat-data\n * See LICENSE file for more information. */\n\n/**\n* This file was automatically generated by json-schema-to-typescript.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source schema files in\n* schemas/*, and run "npm run gentypes" to regenerate this file.\n*/'; const compatDataTypes = { - __meta: - 'Contains metadata for the current BCD information, such as the BCD version.', api: 'Contains data for each [Web API](https://developer.mozilla.org/docs/Web/API) interface.', browsers: 'Contains data for each known and tracked browser/engine.', css: 'Contains data for [CSS](https://developer.mozilla.org/docs/Web/CSS) properties, selectors, and at-rules.', @@ -92,20 +90,11 @@ const generateCompatDataTypes = () => { const props = Object.entries(compatDataTypes).map( (t) => ` /**\n * ${t[1]}\n */\n ${t[0]}: ${ - t[0] === '__meta' - ? 'MetaBlock' - : t[0] === 'browsers' - ? 'Browsers' - : 'InternalIdentifier' + t[0] === 'browsers' ? 'Browsers' : 'InternalIdentifier' };`, ); - const metaType = - 'export interface MetaBlock {\n version: string;\n timestamp: string;\n}'; - - return `${metaType}\n\nexport interface InternalCompatData {\n${props.join( - '\n\n', - )}\n}\n`; + return `export interface InternalCompatData {\n${props.join('\n\n')}\n}\n`; }; /** diff --git a/scripts/lib/compare-statements.js b/scripts/lib/compare-statements.js index ece79de4e00f15..7e9bac00a0f4b7 100644 --- a/scripts/lib/compare-statements.js +++ b/scripts/lib/compare-statements.js @@ -3,7 +3,7 @@ import { compareVersions } from 'compare-versions'; -/** @import {SimpleSupportStatement} from '../../types/index.js' */ +/** @import {InternalSimpleSupportStatement} from '../../types/index.js' */ /** * @@ -13,8 +13,8 @@ import { compareVersions } from 'compare-versions'; * 3. Statements with partial support * 4. Statements with flags * 5. Statements with a version removed (or otherwise no support) - * @param {SimpleSupportStatement} a - The first support statement object to perform comparison with - * @param {SimpleSupportStatement} b - The second support statement object to perform comparison with + * @param {InternalSimpleSupportStatement} a - The first support statement object to perform comparison with + * @param {InternalSimpleSupportStatement} b - The second support statement object to perform comparison with * @returns {number} Direction to sort */ const compareStatements = (a, b) => { diff --git a/scripts/lib/data-folders.js b/scripts/lib/data-folders.js index 2fc5afd880ef37..d8d886f0b52441 100644 --- a/scripts/lib/data-folders.js +++ b/scripts/lib/data-folders.js @@ -1,6 +1,8 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ +/** @import { InternalCompatData } from "../../types/types.js" */ + export const dataFoldersMinusBrowsers = [ 'api', 'css', @@ -16,4 +18,7 @@ export const dataFoldersMinusBrowsers = [ 'webextensions', ]; -export default [...dataFoldersMinusBrowsers, 'browsers']; +export default /** @type {Array} */ ([ + ...dataFoldersMinusBrowsers, + 'browsers', +]); diff --git a/scripts/lib/stringify-and-order-properties.js b/scripts/lib/stringify-and-order-properties.js index 7e580baf86db6c..f2fc165ba72e79 100644 --- a/scripts/lib/stringify-and-order-properties.js +++ b/scripts/lib/stringify-and-order-properties.js @@ -3,7 +3,7 @@ import { compareVersions } from 'compare-versions'; -/** @import {InternalCompatStatement, SimpleSupportStatement, StatusBlock, BrowserStatement, ReleaseStatement} from '../../types/index.js' */ +/** @import {InternalCompatStatement, InternalSimpleSupportStatement, InternalStatusBlock, BrowserStatement, ReleaseStatement} from '../../types/index.js' */ const propOrder = { browsers: { @@ -120,7 +120,7 @@ export const orderProperties = (key, value) => { ); for (const browser of Object.keys(value.__compat.support)) { - /** @type {SimpleSupportStatement[]} */ + /** @type {InternalSimpleSupportStatement[]} */ const result = []; let data = value.__compat.support[browser]; if (!Array.isArray(data)) { @@ -130,7 +130,7 @@ export const orderProperties = (key, value) => { for (const statement of data) { result.push( doOrder( - /** @type {SimpleSupportStatement} */ (statement), + /** @type {InternalSimpleSupportStatement} */ (statement), propOrder.data.support, ), ); @@ -142,7 +142,7 @@ export const orderProperties = (key, value) => { if ('status' in value.__compat) { value.__compat.status = doOrder( - /** @type {StatusBlock} */ (value.__compat.status), + /** @type {InternalStatusBlock} */ (value.__compat.status), propOrder.data.status, ); } diff --git a/scripts/migrations/002-remove-webview-flags.js b/scripts/migrations/002-remove-webview-flags.js index 9cdb74a9a30bad..f9c68816972627 100644 --- a/scripts/migrations/002-remove-webview-flags.js +++ b/scripts/migrations/002-remove-webview-flags.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {InternalCompatStatement, SimpleSupportStatement} from '../../types/index.js' */ +/** @import {InternalCompatStatement, InternalSimpleSupportStatement, InternalSupportStatement} from '../../types/index.js' */ import fs from 'node:fs'; import path from 'node:path'; @@ -25,10 +25,10 @@ export const removeWebViewFlags = (key, value) => { if (key === '__compat') { if (value.support.webview_android !== undefined) { if (Array.isArray(value.support.webview_android)) { - /** @type {SimpleSupportStatement[]} */ + /** @type {InternalSimpleSupportStatement[]} */ const result = []; for (const support of value.support.webview_android) { - if (support.flags === undefined) { + if (typeof support === 'object' && support.flags === undefined) { result.push(support); } } @@ -39,9 +39,7 @@ export const removeWebViewFlags = (key, value) => { value.support.webview_android = result[0]; } else { value.support.webview_android = - /** @type {[SimpleSupportStatement, SimpleSupportStatement, ...SimpleSupportStatement[]]} */ ( - result - ); + /** @type {InternalSupportStatement} */ (result); } } else if ( typeof value.support.webview_android === 'object' && diff --git a/scripts/migrations/007-experimental-false.js b/scripts/migrations/007-experimental-false.js index 6211fd4963d8b2..3131fcd3590878 100644 --- a/scripts/migrations/007-experimental-false.js +++ b/scripts/migrations/007-experimental-false.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {InternalCompatData, BrowserName, InternalIdentifier, ReleaseStatement, SimpleSupportStatement} from '../../types/index.js' */ +/** @import {InternalCompatData, BrowserName, InternalIdentifier, InternalSimpleSupportStatement} from '../../types/index.js' */ import fs from 'node:fs'; import path from 'node:path'; @@ -37,7 +37,7 @@ export const fixExperimental = (bcd) => { } // Consider only the first part of an array statement. - /** @type {SimpleSupportStatement} */ + /** @type {InternalSimpleSupportStatement} */ const statement = Array.isArray(support) ? support[0] : support; // Ignore anything behind flag, prefix or alternative name diff --git a/scripts/traverse.js b/scripts/traverse.js index b23f367bda4adb..c86adce739cb21 100644 --- a/scripts/traverse.js +++ b/scripts/traverse.js @@ -8,8 +8,7 @@ import { hideBin } from 'yargs/helpers'; import dataFolders from '../scripts/lib/data-folders.js'; import bcd, { browsers } from '../index.js'; -/** @import {BrowserName, InternalIdentifier, InternalSimpleSupportStatement, InternalSupportBlock, SimpleSupportStatement} from '../types/index.js' */ -/** @import {InternalSupportStatement} from '../types/index.js' */ +/** @import {BrowserName, InternalIdentifier, InternalSimpleSupportStatement, InternalSupportBlock, InternalSupportStatement} from '../types/index.js' */ /** * @typedef {object} StatusFilters @@ -76,7 +75,6 @@ export function* iterateFeatures( continue; } for (const browser of browserNames) { - /** @type {InternalSimpleSupportStatement | InternalSimpleSupportStatement[] | undefined} */ let browserData = comp[browser]; if (!browserData) { @@ -95,9 +93,11 @@ export function* iterateFeatures( continue; } if (!Array.isArray(browserData)) { + // @ts-expect-error FIXME Handle "mirror" value. browserData = [browserData]; } + // @ts-expect-error FIXME Handle "mirror" value. for (const range in browserData) { if ( /** @type {InternalSupportStatement} */ ( diff --git a/utils/iter-support.js b/utils/iter-support.js index 893246b35230fe..9b70d859ca531a 100644 --- a/utils/iter-support.js +++ b/utils/iter-support.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {InternalCompatStatement, BrowserName, InternalSimpleSupportStatement} from '../types/index.js' */ +/** @import {InternalCompatStatement, BrowserName, InternalSimpleSupportStatement, InternalSupportStatement} from '../types/index.js' */ /** * Get support for a specific browser in array form @@ -11,8 +11,10 @@ */ export default (compat, browser) => { if (browser in compat.support) { + /** @type {InternalSupportStatement|undefined} */ const data = compat.support[browser]; if (data) { + // @ts-expect-error FIXME Handle "mirror" value. return Array.isArray(data) ? data : [data]; } } diff --git a/utils/walkingUtils.test.js b/utils/walkingUtils.test.js index c3ffffc17fe620..930287f0b5bf9f 100644 --- a/utils/walkingUtils.test.js +++ b/utils/walkingUtils.test.js @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; -import bcd from '../index.js'; +import { browsers } from '../index.js'; import query from './query.js'; import { @@ -26,7 +26,7 @@ describe('joinPath()', () => { describe('isBrowser()', () => { it('returns true for browser-like objects', () => { - assert.equal(isBrowser(bcd.browsers.firefox), true); + assert.equal(isBrowser(browsers['firefox']), true); }); it('returns false for feature-like objects', () => { @@ -36,7 +36,7 @@ describe('isBrowser()', () => { describe('isFeature()', () => { it('returns false for browser-like objects', () => { - assert.equal(isFeature(bcd.browsers.chrome), false); + assert.equal(isFeature(browsers['chrome']), false); }); it('returns true for feature-like objects', () => { From 154e7fb5b7bc8c993c9199df49207e11137a2f9b Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Sat, 14 Feb 2026 00:38:33 +0100 Subject: [PATCH 22/77] chore: fix ESLint errors --- lint/types.d.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lint/types.d.ts b/lint/types.d.ts index 9cc867a135b209..0f81541209300f 100644 --- a/lint/types.d.ts +++ b/lint/types.d.ts @@ -1,11 +1,10 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -import { InternalDataType } from '../types/index.js'; -import { BrowserName } from '../types/index.js'; - import { Logger } from './utils.js'; +import type { BrowserName, InternalDataType } from '../types/index.js'; + export interface LintOptions { only?: string[]; } @@ -41,6 +40,6 @@ export interface Linter { name: string; description: string; scope: LinterScope; - check: (logger: Logger, data: LinterData) => void | Promise; + check: (_logger: Logger, _data: LinterData) => void | Promise; exceptions?: string[]; } From fe10aa993962933b82d7bc38ca887807be6d14c1 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Sat, 14 Feb 2026 00:39:54 +0100 Subject: [PATCH 23/77] fixup! chore(schemas): refine public schema --- schemas/public.schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schemas/public.schema.json b/schemas/public.schema.json index bf7b1deafc7293..b3380207571c5c 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -83,7 +83,7 @@ "additionalProperties": { "$ref": "#/definitions/browser_statement" }, - "tsType": "{ [browser: BrowserName]: BrowserStatement }" + "tsType": "Record" }, "browser_statement": { @@ -328,7 +328,7 @@ "additionalProperties": { "$ref": "#/definitions/support_statement" }, - "tsType": "{ [browser: BrowserName]: SupportStatement }" + "tsType": "Record" }, "compat_statement": { From 2a320b49e0905a948d3e0ad741455ef25f813ca2 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Sat, 14 Feb 2026 00:42:04 +0100 Subject: [PATCH 24/77] fixup! chore: fix internal type usages --- lint/fixer/status.test.js | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/lint/fixer/status.test.js b/lint/fixer/status.test.js index 85a99c34c80da8..41df0a75cda3b3 100644 --- a/lint/fixer/status.test.js +++ b/lint/fixer/status.test.js @@ -96,11 +96,7 @@ const tests = [ name: 'should set deprecated when parent feature is deprecated', input: { __compat: { - support: { - firefox: { - version_added: '1', - }, - }, + support: {}, status: { experimental: false, standard_track: true, @@ -109,11 +105,7 @@ const tests = [ }, subfeature: { __compat: { - support: { - firefox: { - version_added: '1', - }, - }, + support: {}, status: { experimental: false, standard_track: true, @@ -124,11 +116,7 @@ const tests = [ }, output: { __compat: { - support: { - firefox: { - version_added: '1', - }, - }, + support: {}, status: { experimental: false, standard_track: true, From 6d3341f7733fc4ca824b9e469325a15a12be56f8 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Sat, 14 Feb 2026 00:44:32 +0100 Subject: [PATCH 25/77] fixup! fixup! chore: fix internal type usages --- lint/linter/test-consistency.js | 2 +- scripts/build/index.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lint/linter/test-consistency.js b/lint/linter/test-consistency.js index b3f8c44e80cc15..ea6941160c7b32 100644 --- a/lint/linter/test-consistency.js +++ b/lint/linter/test-consistency.js @@ -44,7 +44,7 @@ export class ConsistencyChecker { * @returns {ConsistencyError[]} Any errors found within the data */ check(data) { - const { browsers: _browsers, __meta, ...rest } = data; + const { browsers: _browsers, ...rest } = data; return this.checkSubfeatures(rest); } diff --git a/scripts/build/index.js b/scripts/build/index.js index 1e33bae8770670..d5129948d755c2 100644 --- a/scripts/build/index.js +++ b/scripts/build/index.js @@ -20,8 +20,8 @@ import bcd from '../../index.js'; import mirrorSupport from './mirror.js'; /** - * @import { BrowserName, InternalIdentifier, InternalSupportStatement, MetaBlock, VersionValue } from '../../types/index.js' - * @import { CompatData } from '../../types/public.js' + * @import { BrowserName, InternalIdentifier, InternalSupportStatement, VersionValue } from '../../types/index.js' + * @import { CompatData, MetaBlock } from '../../types/public.js' * @import { WalkOutput } from '../../utils/walk.js' */ From bbffdde0efde4a232b01656cea06f1debeea9374 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 16 Feb 2026 20:33:54 +0100 Subject: [PATCH 26/77] chore(schemas): make SupportBlock partial --- schemas/public.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemas/public.schema.json b/schemas/public.schema.json index b3380207571c5c..f988df755f4f36 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -328,7 +328,7 @@ "additionalProperties": { "$ref": "#/definitions/support_statement" }, - "tsType": "Record" + "tsType": "Partial>" }, "compat_statement": { From 013300977db358e570d2b286b9e463ce1c73405c Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 16 Feb 2026 20:37:45 +0100 Subject: [PATCH 27/77] fix(schemas): replace patternProperties on root --- schemas/public.schema.json | 56 ++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/schemas/public.schema.json b/schemas/public.schema.json index f988df755f4f36..a7b2855256773d 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -409,15 +409,61 @@ "__meta": { "$ref": "#/definitions/meta_block" }, + "api": { + "$ref": "#/definitions/identifier" + }, "browsers": { "$ref": "#/definitions/browsers" - } - }, - "patternProperties": { - "^(api|css|html|http|javascript|manifests|mathml|mediatypes|svg|webassembly|webdriver|webextensions)$": { + }, + "css": { + "$ref": "#/definitions/identifier" + }, + "html": { + "$ref": "#/definitions/identifier" + }, + "http": { + "$ref": "#/definitions/identifier" + }, + "javascript": { + "$ref": "#/definitions/identifier" + }, + "manifests": { + "$ref": "#/definitions/identifier" + }, + "mathml": { + "$ref": "#/definitions/identifier" + }, + "mediatypes": { + "$ref": "#/definitions/identifier" + }, + "svg": { + "$ref": "#/definitions/identifier" + }, + "webassembly": { + "$ref": "#/definitions/identifier" + }, + "webdriver": { + "$ref": "#/definitions/identifier" + }, + "webextensions": { "$ref": "#/definitions/identifier" } }, - "required": ["__meta", "browsers"], + "required": [ + "__meta", + "api", + "browsers", + "css", + "html", + "http", + "javascript", + "manifests", + "mathml", + "mediatypes", + "svg", + "webassembly", + "webdriver", + "webextensions" + ], "additionalProperties": false } From 6fce3dab887f0aa8973461208d690a6fc9e674c7 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 16 Feb 2026 20:43:59 +0100 Subject: [PATCH 28/77] fix(schemas): use intersection type --- schemas/public.schema.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/schemas/public.schema.json b/schemas/public.schema.json index a7b2855256773d..249e5786016ba9 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -398,7 +398,8 @@ "additionalProperties": false, "errorMessage": { "additionalProperties": "Feature names can only contain alphanumerical characters or the following symbols: _-$@" - } + }, + "tsType": "{__compat?: CompatStatement} & {[key: string]: Identifier}" } }, From eb86b1a24224f83d757b6d240dba4c0916fc2ca1 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 16 Feb 2026 20:54:40 +0100 Subject: [PATCH 29/77] chore(schemas): prefer Record over index type --- schemas/public.schema.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/schemas/public.schema.json b/schemas/public.schema.json index 249e5786016ba9..b5f8df66c1fc75 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -118,7 +118,8 @@ "releases": { "type": "object", "additionalProperties": { "$ref": "#/definitions/release_statement" }, - "description": "The known versions of this browser." + "description": "The known versions of this browser.", + "tsType": "Record" } }, "required": [ From 4c78d36949c78c330cd469f96329b7671f261bc1 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 17 Feb 2026 09:39:18 +0100 Subject: [PATCH 30/77] fixup! fix(generate-types): keep types.d.ts for internal types --- .prettierignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.prettierignore b/.prettierignore index ccb78a52b7b309..896a570b08c567 100644 --- a/.prettierignore +++ b/.prettierignore @@ -11,4 +11,5 @@ LICENSE /build/ coverage/ .features.json +public.d.ts types.d.ts From 613889f1f6bddfdecfb384c8726786907899d188 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 17 Feb 2026 09:43:14 +0100 Subject: [PATCH 31/77] refactor: rename types/{types => internal}.d.ts --- .gitignore | 2 +- .prettierignore | 4 ++-- eslint.config.js | 2 +- scripts/generate-internal-types.js | 4 ++-- scripts/lib/data-folders.js | 2 +- types/index.d.ts | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 40b2ac96e881be..403e515d024c13 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,6 @@ yarn.lock .nyc_output/ coverage.lcov coverage/ +/types/internal.d.ts /types/public.d.ts -/types/types.d.ts .DS_Store diff --git a/.prettierignore b/.prettierignore index 896a570b08c567..01b486c0727703 100644 --- a/.prettierignore +++ b/.prettierignore @@ -11,5 +11,5 @@ LICENSE /build/ coverage/ .features.json -public.d.ts -types.d.ts +/types/internal.d.ts +/types/public.d.ts diff --git a/eslint.config.js b/eslint.config.js index a59418b179e515..56d28b394fca8b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -34,8 +34,8 @@ export default [ 'CODE_OF_CONDUCT.md', 'build/', '**/coverage/', + 'types/internal.d.ts', 'types/public.d.ts', - 'types/types.d.ts', ], }, ...fixupConfigRules( diff --git a/scripts/generate-internal-types.js b/scripts/generate-internal-types.js index f1fe3e37ba8c5e..efa8a7ef754774 100644 --- a/scripts/generate-internal-types.js +++ b/scripts/generate-internal-types.js @@ -147,7 +147,7 @@ const transformTS = (browserTS, compatTS) => { * @param {URL | string} [destination] Output destination */ const compile = async ( - destination = new URL('../types/types.d.ts', import.meta.url), + destination = new URL('../types/internal.d.ts', import.meta.url), ) => { const browserTS = await compileFromFile('schemas/browsers.schema.json', opts); const compatTS = await compileFromFile( @@ -163,7 +163,7 @@ const compile = async ( generateCompatDataTypes(), ].join('\n\n'); await fs.writeFile(destination, ts); - spawn('tsc', ['--skipLibCheck', '../types/types.d.ts'], { + spawn('tsc', ['--skipLibCheck', '../types/internal.d.ts'], { cwd: dirname, stdio: 'inherit', }); diff --git a/scripts/lib/data-folders.js b/scripts/lib/data-folders.js index d8d886f0b52441..da3d82798f204d 100644 --- a/scripts/lib/data-folders.js +++ b/scripts/lib/data-folders.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import { InternalCompatData } from "../../types/types.js" */ +/** @import { InternalCompatData } from "../../types/internal.js" */ export const dataFoldersMinusBrowsers = [ 'api', diff --git a/types/index.d.ts b/types/index.d.ts index 2b95bd0a8d3a8f..15baf46ac8c53e 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -export type * from './types.js'; +export type * from './internal.js'; export type DataType = | CompatData From a78207a22d4532c453c870814ed4fbb7b4b52c16 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 17 Feb 2026 16:09:21 +0100 Subject: [PATCH 32/77] chore(schemas): sync existing docs --- schemas/browsers-schema.md | 2 +- schemas/compat-data-schema.md | 17 ----------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/schemas/browsers-schema.md b/schemas/browsers-schema.md index 6d8c1b592f6940..c74aa1b137b407 100644 --- a/schemas/browsers-schema.md +++ b/schemas/browsers-schema.md @@ -44,7 +44,7 @@ The `type` string is a required property which indicates the platform category t ### `upstream` -The `upstream` string is an optional property which indicates the upstream browser updates are derived from. For example, Firefox Android's upstream browser is Firefox (desktop), and Edge's upstream browser is Chrome. This is used for mirroring data between browsers. Valid options are any browser defined in the data. +The `upstream` string is an optional property which indicates the upstream browser updates are derived from. For example, Firefox Android's upstream browser is Firefox (desktop), and Edge's upstream browser is Chrome. This is used for mirroring data between browsers. Valid options are `"chrome"`, `"chrome_android"`, `"firefox"`, `"safari"`, and `"safari_ios"`. ### `accepts_flags` diff --git a/schemas/compat-data-schema.md b/schemas/compat-data-schema.md index 8f84400a1de139..5c63db307a55ea 100644 --- a/schemas/compat-data-schema.md +++ b/schemas/compat-data-schema.md @@ -178,8 +178,6 @@ The `__compat` object consists of the following: It is intended to be used as a caption or title and should be kept short. This property may be formatted using Markdown, see the rules for `notes`. -- An automated `source_file` property containing the path to the source file containing the feature. This is used to create links to the repository source (in the form of `https://github.com/mdn/browser-compat-data/blob/main/`). For example, `api.History.forward` will contain a `source_file` property of `api/History.json` since the feature is defined in that file. - - An optional `mdn_url` property which **points to an MDN reference page documenting the feature**. It needs to be a valid URL, and should be the language-neutral URL (e.g. use `https://developer.mozilla.org/docs/Web/CSS/text-align` instead of `https://developer.mozilla.org/en-US/docs/Web/CSS/text-align`). @@ -320,21 +318,6 @@ Examples: } ``` -#### `version_last` - -> [!NOTE] -> This property is automatically generated at build time. - -If `version_removed` is present, a `version_last` is automatically generated during build time, which will be set to the version number of the last browser version that supported the feature. For example, assuming the browser version only incremented in whole numbers, if a feature was added in version 20 and supported until 29, then was no longer supported in 30, `version_removed` would be `30` and `version_last` will be `29`: - -```json -{ - "version_added": "20", - "version_removed": "30", - "version_last": "29" -} -``` - ### Ranged versions (≤) For certain browser versions, ranged versions (also called "ranged values") are allowed as it is sometimes impractical to find out in which early version of a browser a feature shipped. Ranged versions are a way to include some version data in BCD, while also stating the version number may not be accurate. These values state that the feature has been confirmed to be supported in at least a certain version of the browser, but may have been added in an earlier release. Ranged versions are indicated by the `Less Than or Equal To (U+2264)` (`≤`) symbol before the version number. From 57f2f8c4940626c97ecbf062cc1a1a8abcce7faf Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 17 Feb 2026 16:09:45 +0100 Subject: [PATCH 33/77] fixup! fix(schemas): refine internal schema --- schemas/compat-data.schema.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index 3cb60e4f15a5fd..d47a3c2ebf485d 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -216,7 +216,7 @@ } } ], - "description": "An optional URL or array of URLs, each of which is for a specific part of a specification in which this feature is defined. Each URL must contain a fragment internal_identifier.", + "description": "An optional URL or array of URLs, each of which is for a specific part of a specification in which this feature is defined. Each URL must contain a fragment identifier.", "tsType": "string | [string, string, ...string[]]" }, "tags": { @@ -227,7 +227,7 @@ }, "support": { "$ref": "#/definitions/internal_support_block", - "description": "The data for the support of each browser, containing a `internal_support_statement` object for each browser internal_identifier with information about versions, prefixes, or alternate names, as well as notes." + "description": "The data for the support of each browser, containing a `support_statement` object for each browser identifier with information about versions, prefixes, or alternate names, as well as notes." }, "status": { "$ref": "#/definitions/internal_status_block", @@ -245,7 +245,7 @@ "type": "object", "$ref": "#/definitions/internal_compat_statement", "required": ["status"], - "description": "A feature is described by an internal_identifier containing the `__compat` property.\n\nIn other words, internal_identifiers without `__compat` aren't necessarily features, but help to nest the features properly.\n\nWhen an internal_identifier has a `__compat` block, it represents its basic support, indicating that a minimal implementation of a functionality is included.\n\nWhat it represents exactly depends of the evolution of the feature over time, both in terms of specifications and of browser support.", + "description": "A feature is described by an identifier containing the `__compat` property.\n\nIn other words, identifiers without `__compat` aren't necessarily features, but help to nest the features properly.\n\nWhen an identifier has a `__compat` block, it represents its basic support, indicating that a minimal implementation of a functionality is included.\n\nWhat it represents exactly depends of the evolution of the feature over time, both in terms of specifications and of browser support.", "tsType": "InternalCompatStatement" } }, From 5bb0dc4f5a1fab91b9ac35a2a7e558b952fb8d50 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 17 Feb 2026 16:35:43 +0100 Subject: [PATCH 34/77] docs(schemas): describe public schema Same format as other two schema descriptions. --- schemas/public.schema.md | 380 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 380 insertions(+) create mode 100644 schemas/public.schema.md diff --git a/schemas/public.schema.md b/schemas/public.schema.md new file mode 100644 index 00000000000000..451a2b381e3940 --- /dev/null +++ b/schemas/public.schema.md @@ -0,0 +1,380 @@ +# The public JSON schema + +This document helps you to understand the structure of the `data.json` file included in the published `@mdn/browser-compat-data` package. The file combines all browser definitions, feature compatibility data, and metadata into one object. Several transformations are applied at build time (see [Build-time transformations](#build-time-transformations)). + +## JSON structure + +Below is a simplified example of the published data: + +```json +{ + "__meta": { + "version": "6.0.0", + "timestamp": "2024-01-15T00:00:00.000Z" + }, + "browsers": { + "firefox": { + "name": "Firefox", + "type": "desktop", + "preview_name": "Nightly", + "pref_url": "about:config", + "accepts_flags": true, + "accepts_webextensions": true, + "releases": { + "1.5": { + "release_date": "2005-11-29", + "release_notes": "https://developer.mozilla.org/Firefox/Releases/1.5", + "status": "retired", + "engine": "Gecko", + "engine_version": "1.8" + } + } + } + }, + "api": { + "AbortController": { + "__compat": { + "source_file": "api/AbortController.json", + "mdn_url": "https://developer.mozilla.org/docs/Web/API/AbortController", + "spec_url": "https://dom.spec.whatwg.org/#interface-abortcontroller", + "support": { + "chrome": { "version_added": "66" }, + "firefox": { "version_added": "57" } + }, + "status": { + "experimental": false, + "standard_track": true, + "deprecated": false + } + } + } + }, + "css": {}, + "html": {}, + "http": {}, + "javascript": {}, + "manifests": {}, + "mathml": {}, + "mediatypes": {}, + "svg": {}, + "webassembly": {}, + "webdriver": {}, + "webextensions": {} +} +``` + +## Properties + +### `__meta` + +The `__meta` object is a required property containing metadata about the published data. It has two required properties: + +- `version`: a string containing the package version (e.g. `"6.0.0"`). +- `timestamp`: a string containing the ISO 8601 date-time when the data was built (e.g. `"2024-01-15T00:00:00.000Z"`). + +### `browsers` + +The `browsers` object is a required property containing data for all browsers and JavaScript runtimes, keyed by browser identifier. See [browsers-schema.md](./browsers-schema.md) for the full browser data structure. + +### Feature categories + +The remaining top-level properties are all required and each contains a tree of feature identifiers and `__compat` objects: + +- `api` — Web API interfaces +- `css` — CSS properties, selectors, and at-rules +- `html` — HTML elements and attributes +- `http` — HTTP headers, methods, and status codes +- `javascript` — JavaScript language features +- `manifests` — Web App Manifest keys +- `mathml` — MathML elements and attributes +- `mediatypes` — Media types +- `svg` — SVG elements and attributes +- `webassembly` — WebAssembly features +- `webdriver` — WebDriver commands +- `webextensions` — WebExtension APIs and manifest keys + +### The `__compat` object + +The `__compat` object describes a feature's compatibility data. It consists of the following: + +- A mandatory `support` property for **compat information**. + A [`support_statement`](#the-support_statement-object) object for each browser identifier with information about versions, prefixes, or alternate names, as well as notes. + +- An optional `status` property for **status information**. + An object containing information about the stability of the feature: + Is it a functionality that is standard? Is it stable? Has it been deprecated and shouldn't be used anymore? ([see below](#status-information)) + +- An optional `description` property to **describe the feature**. + A string containing a human-readable description of the feature. + It is intended to be used as a caption or title and should be kept short. + In the published data, this property is formatted as HTML. + +- An optional `mdn_url` property which **points to an MDN reference page documenting the feature**. + It needs to be a valid URL, and should be the language-neutral URL (e.g. use `https://developer.mozilla.org/docs/Web/CSS/text-align` instead of `https://developer.mozilla.org/en-US/docs/Web/CSS/text-align`). + +- An optional `spec_url` property as a URL or an array of URLs, each of which is for a specific part of a specification in which this feature is defined. + Each URL must contain a fragment identifier. + +- An optional `tags` property which is an array of strings allowing to assign tags to the feature. + +- A mandatory `source_file` property containing the path to the source file that defines this feature, relative to the repository root (e.g. `"api/AbortController.json"`). This is automatically generated at build time. + +#### The `support_statement` object + +The `support_statement` object describes the support provided by a single browser type for the given subfeature. It is either a `simple_support_statement` object, or an array of two or more `simple_support_statement` objects. + +If there is an array, the `simple_support_statement` objects are sorted with the most relevant and general entries first. In other words, entries applying to the most recent browser releases come first and entries with prefixes or flags come after those without. + +Example of a `support` compat object (with an `array_support_statement` containing 2 entries): + +```json +"support": { + "firefox": [ + { + "version_added": "6" + }, + { + "prefix": "-moz-", + "version_added": "3.5", + "version_removed": "9", + "version_last": "8" + } + ] +} +``` + +Example of a `support` compat object (with 1 entry, array omitted): + +```json +"support": { + "ie": { "version_added": "6.0" } +} +``` + +### Compat data in support statements + +The `simple_support_statement` object is the core object containing the compatibility information for a browser. It consists of the following properties: + +#### `version_added` + +This is the only mandatory property and it contains a string with the version number indicating when a sub-feature has been added (and is therefore supported), or `false` indicating the feature is not supported. Examples: + +- Support from version 3.5 (inclusive): + +```json +{ + "version_added": "3.5" +} +``` + +- Support in version 79, but possibly supported earlier: + +```json +{ + "version_added": "≤79" +} +``` + +- Support in latest beta/preview release: + +```json +{ + "version_added": "preview" +} +``` + +- No support: + +```json +{ + "version_added": false +} +``` + +#### `version_removed` + +Contains a string with the version number the sub-feature was removed in. If the feature has not been removed from the browser, this property is omitted. When `version_removed` is present, `version_last` is always present as well. + +Example: + +- Removed in version 10 (added in 4 and supported up until 9): + +```json +{ + "version_added": "4", + "version_removed": "10", + "version_last": "9" +} +``` + +#### `version_last` + +A string indicating the last browser version that supported the feature. This property is always present when `version_removed` is present. For example, if a feature was removed in version 30, `version_last` will be `"29"`. + +```json +{ + "version_added": "20", + "version_removed": "30", + "version_last": "29" +} +``` + +#### `prefix` + +A prefix to add to the sub-feature name (defaults to empty string). +If applicable, leading and trailing `-` must be included. + +Examples: + +- A CSS property with a standard name of `prop-name` and a vendor-prefixed name of `-moz-prop-name`: + +```json +{ + "prefix": "-moz-", + "version_added": "3.5" +} +``` + +- An API with a standard name of `FeatureName` and a vendor-prefixed name of `webkitFeatureName`: + +```json +{ + "prefix": "webkit", + "version_added": "9" +} +``` + +#### `alternative_name` + +In some cases features are named entirely differently and not just prefixed. Example: + +- Prefixed version had a different capitalization + +```json +{ + "alternative_name": "mozRequestFullScreen", + "version_added": "4", + "version_removed": "9", + "version_last": "8" +} +``` + +Note that you can't have both `prefix` and `alternative_name`. + +#### `flags` + +An optional array of objects describing flags that must be configured for this browser to support this feature. Usually this array will have one item, but there are cases where two or more flags can be required to activate a feature. An object in the `flags` array consists of three properties: + +- `type` (mandatory): an enum that indicates the flag type: + - `preference` a flag the user can set (like in `about:config` in Firefox). + - `runtime_flag` a flag to be set before starting the browser. +- `name` (mandatory): a string giving the name of the flag or preference that must be configured. +- `value_to_set` (optional): representing the actual value to set the flag to. It is a string, that may be converted to the right type (that is `true` or `false` for Boolean value, or `4` for an integer value). + +Example for one flag required: + +```json +{ + "version_added": "40", + "flags": [ + { + "type": "preference", + "name": "browser.flag.name", + "value_to_set": "true" + } + ] +} +``` + +#### `impl_url` + +An optional changeset/commit URL (or array of URLs) for the revision which implemented the feature in the source code, or the URL to the bug tracking the implementation, for the associated browser. + +#### `notes` + +A string or array of strings containing additional information. In the published data, notes are formatted as HTML. + +#### `partial_implementation` + +A `boolean` value indicating whether or not the implementation of the sub-feature deviates from the specification in a way that may cause significant compatibility problems. It defaults to `false` (no interoperability problems expected). If set to `true`, a `notes` field explaining the divergence is always present. + +```json +{ + "version_added": "6", + "partial_implementation": true, + "notes": "The event handler is supported, but the event never fires." +} +``` + +### Ranged versions (≤) + +Ranged versions indicate that a feature has been confirmed to be supported in at least a certain version of the browser, but may have been added in an earlier release. Ranged versions are indicated by the `Less Than or Equal To (U+2264)` (`≤`) symbol before the version number. + +For example, the statement below means, "supported in at least version 37 and possibly in earlier versions as well". + +```json +{ + "version_added": "≤37" +} +``` + +### Status information + +The status property contains information about stability of the feature. It is an object named `status` and has three mandatory properties: + +- `experimental` (DEPRECATED): a `boolean` value. + + **Warning**: The `experimental` property is deprecated. + Prefer using a more well-defined stability calculations, such as Baseline, instead. + + If `experimental` is `true`, then it usually means that the feature is implemented in only one browser engine. + + If `experimental` is `false`, then it usually means that the feature is implemented in two or more browser engines. + Sometimes a `false` value means that a single-implementer feature is not expected to change. + +- `standard_track`: a `boolean` value. + + If `standard_track` is `true`, then the feature is part of an active specification or specification process. + +- `deprecated`: a `boolean` value. + + If `deprecated` is `true`, then the feature is no longer recommended. It might be removed in the future or might only be kept for compatibility purposes. Avoid using this functionality. + +```json +"__compat": { + "status": { + "experimental": true, + "standard_track": true, + "deprecated": false + } +} +``` + +## Build-time transformations + +The published data differs from the [source data](./compat-data-schema.md) in the following ways: + +- All `"mirror"` support statements are resolved to concrete support objects with real version numbers. The string `"mirror"` never appears in published data. +- A `source_file` property is added to every `__compat` object. +- A `version_last` property is added to every support statement that has a `version_removed`. +- Markdown formatting in `description` and `notes` fields is converted to HTML. + +## Exports + +This structure is exported for consumers of `@mdn/browser-compat-data`: + +```js +import bcd from '@mdn/browser-compat-data'; +bcd.__meta.version; // "6.0.0" +bcd.browsers.firefox.releases['1.5'].status; // "retired" +bcd.api.AbortController.__compat.support.chrome.version_added; // "66" +bcd.api.AbortController.__compat.source_file; // "api/AbortController.json" +``` + +```js +const bcd = require('@mdn/browser-compat-data'); +bcd.__meta.version; +// "6.0.0" +bcd.browsers.firefox.releases['1.5'].status; +// "retired" +``` From a64f7d63cc4254abbe5ae04f62adc1c524bd6791 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 17 Feb 2026 20:20:45 +0100 Subject: [PATCH 35/77] docs(schemas): sync internal schema docs --- schemas/browsers-schema.md | 11 ++++++----- schemas/compat-data-schema.md | 10 +++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/schemas/browsers-schema.md b/schemas/browsers-schema.md index c74aa1b137b407..03b71de81cb831 100644 --- a/schemas/browsers-schema.md +++ b/schemas/browsers-schema.md @@ -1,6 +1,6 @@ # The browser JSON schema -This document helps you to understand the structure of the browser (and JavaScript runtime) data in BCD, including the browser type, a display-friendly name, release data and more. Each browser is defined by a unique identifier (e.g. `firefox` or `chrome_android`). +This document helps you to understand the structure of the browser (and JavaScript runtime) data in BCD, including the browser type, a display-friendly name, release data and more. Each browser is defined by a unique identifier (e.g. `firefox` or `chrome_android`). Each browser JSON file describes exactly one browser. Note: while NodeJS and Deno are JavaScript runtimes and not browsers, data for them is placed in `browsers`, and are included whenever we use the term "browsers". @@ -48,11 +48,11 @@ The `upstream` string is an optional property which indicates the upstream brows ### `accepts_flags` -An optional boolean indicating whether the browser supports flags. If it is set to `false`, flag data will not be allowed for that browser. +A required boolean indicating whether the browser supports flags. If it is set to `false`, flag data will not be allowed for that browser. ### `accepts_webextensions` -An optional boolean indicating whether the browser supports web extensions. A `true` value will allow this browser to be defined in web extensions support. +A required boolean indicating whether the browser supports web extensions. A `true` value will allow this browser to be defined in web extensions support. ### `pref_url` @@ -78,9 +78,9 @@ The `releases` object contains data regarding the browsers' releases, using the - An optional `release_notes` property which points to release notes. It needs to be a valid URL. -- An optional `engine` property which is the name of the browser's engine. This property is placed on the individual release as a browser may switch to a different engine (e.g. Microsoft Edge switched to Chrome as its base engine). +- An optional `engine` property which is the name of the browser's engine. Valid values are `"Blink"`, `"EdgeHTML"`, `"Gecko"`, `"Presto"`, `"Trident"`, `"WebKit"`, and `"V8"`. This property is placed on the individual release as a browser may switch to a different engine (e.g. Microsoft Edge switched to Chrome as its base engine). If `engine` is specified, `engine_version` must also be provided. -- An optional `engine_version` property which is the version of the browser's engine. Depending on the browser, this may or may not differ from the browser version. +- An optional `engine_version` property which is the version of the browser's engine. Depending on the browser, this may or may not differ from the browser version. Required when `engine` is specified. #### Initial versions @@ -104,6 +104,7 @@ The following table indicates initial versions for browsers in BCD. These are th | iOS Safari | 1 | | | Samsung Internet | 1.0 | | | WebView Android | 1 | | +| WebView iOS | 1 | | ## Exports diff --git a/schemas/compat-data-schema.md b/schemas/compat-data-schema.md index 5c63db307a55ea..4434db1e186066 100644 --- a/schemas/compat-data-schema.md +++ b/schemas/compat-data-schema.md @@ -182,7 +182,7 @@ The `__compat` object consists of the following: It needs to be a valid URL, and should be the language-neutral URL (e.g. use `https://developer.mozilla.org/docs/Web/CSS/text-align` instead of `https://developer.mozilla.org/en-US/docs/Web/CSS/text-align`). - An optional `spec_url` property as a URL or an array of URLs, each of which is for a specific part of a specification in which this feature is defined. - Each URL must either contain a fragment identifier (e.g. `https://tc39.es/proposal-promise-allSettled/#sec-promise.allsettled`), or else must match the regular-expression pattern `^https://registry.khronos.org/webgl/extensions/[^/]+/` (e.g. `https://registry.khronos.org/webgl/extensions/ANGLE_instanced_arrays/`). + Each URL must either contain a fragment identifier (e.g. `https://tc39.es/proposal-promise-allSettled/#sec-promise.allsettled`), or else must match the regular-expression pattern `^https://registry.khronos.org/webgl/extensions/[^/]+/` (e.g. `https://registry.khronos.org/webgl/extensions/ANGLE_instanced_arrays/`) or `^https://github.com/WebAssembly/.+` for WebAssembly specs. Each URL must link to a specification published by a standards body or a formal proposal that may lead to such publication. - An optional `tags` property which is an array of strings allowing to assign tags to the feature. @@ -213,7 +213,7 @@ The currently accepted browser identifiers should be declared in alphabetical or - `webview_android`, WebView, the embedded browser for Android applications - `webview_ios`, WebKit WebView, the embedded browser for iOS applications, based on the iOS version -Desktop browser identifiers are mandatory, with the `version_added` property set to `null` if support is unknown. +Desktop browser identifiers are mandatory, with the `version_added` property set to `false` if the feature is not supported. #### The `support_statement` object @@ -269,7 +269,7 @@ The `simple_support_statement` object is the core object containing the compatib #### `version_added` -This is the only mandatory property and it contains a string with the version number indicating when a sub-feature has been added (and is therefore supported). The Boolean values indicate that a sub-feature is supported (`true`, with the additional meaning that it is unknown in which version support was added) or not supported (`false`). A value of `null` indicates that support information is entirely unknown. Examples: +This is the only mandatory property and it contains a string with the version number indicating when a sub-feature has been added (and is therefore supported), or the value `false` indicating the feature is not supported. Examples: - Support from version 3.5 (inclusive): @@ -305,7 +305,7 @@ This is the only mandatory property and it contains a string with the version nu #### `version_removed` -Contains a string with the version number the sub-feature was removed in. It may also be `true`, meaning that it is unknown in which version support was removed. If the feature has not been removed from the browser, this property is omitted, rather than being set to `false`. +Contains a string with the version number the sub-feature was removed in. If the feature has not been removed from the browser, this property is omitted, rather than being set to `false`. Examples: @@ -422,7 +422,7 @@ Example for two flags required: An optional changeset/commit URL for the revision which implemented the feature in the source code, or the URL to the bug tracking the implementation, for the associated browser. The presence of an `impl_url` value indicates that the associated browser has implemented the feature or intends to implement the feature. -For changeset/commit URLs, this is typically a https://trac.webkit.org/changeset/, https://hg.mozilla.org/mozilla-central/rev/, or https://crrev.com/ URL for a changeset with a subject line that will typically be something of the form _"Implement [feature]"_, _"Support [feature]"_, or _"Enable [feature]"_. For bug URLs, this is typically a https://webkit.org/b/, https://bugzil.la/, or https://crbug.com/ URL indicating an intent to implement and ship the feature. +For changeset/commit URLs, this is typically a https://trac.webkit.org/changeset/, https://hg.mozilla.org/mozilla-central/rev/, or https://crrev.com/ URL for a changeset with a subject line that will typically be something of the form _"Implement [feature]"_, _"Support [feature]"_, or _"Enable [feature]"_. For bug URLs, this is typically a https://webkit.org/b/, https://bugzil.la/, https://crbug.com/, or https://github.com/GoogleChromeLabs/chromium-bidi/issues/ URL indicating an intent to implement and ship the feature. #### `notes` From c43fb4120338de86af06e7281385de106d362fd8 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 17 Feb 2026 20:49:27 +0100 Subject: [PATCH 36/77] chore(scripts/generate-types): clean up old types --- scripts/generate-types.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/scripts/generate-types.js b/scripts/generate-types.js index c22dbc4b41f7c1..3f98f7e65e6346 100644 --- a/scripts/generate-types.js +++ b/scripts/generate-types.js @@ -1,6 +1,8 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ +import { rm } from 'node:fs/promises'; + import esMain from 'es-main'; import compileInternal from './generate-internal-types.js'; @@ -8,8 +10,20 @@ import compilePublic from './generate-public-types.js'; /* c8 ignore start */ +/** + * Cleans up old types. + */ +const cleanupObsolete = async () => { + const path = new URL('../types/types.d.ts', import.meta.url); + try { + await rm(path); + } catch { + // Ignore. + } +}; + if (esMain(import.meta)) { - await Promise.all([compileInternal(), compilePublic()]); + await Promise.all([compileInternal(), compilePublic(), cleanupObsolete()]); } /* c8 ignore stop */ From fa492d8daab84cbe77b1b552fdf8bb64a1a06173 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 12:24:29 +0100 Subject: [PATCH 37/77] refactor: revert named browsers export --- index.js | 5 +---- lint/fixer/mirror.js | 6 ++--- lint/linter/test-browsers-presence.js | 12 +++++----- lint/linter/test-obsolete.js | 4 ++-- lint/linter/test-obsolete.test.js | 8 +++---- lint/linter/test-status.js | 4 ++-- lint/linter/test-versions.js | 23 ++++++++++++-------- scripts/build/mirror.js | 18 +++++++-------- scripts/build/mirror.test.js | 4 ++-- scripts/migrations/007-experimental-false.js | 3 +-- scripts/traverse.js | 12 +++++----- utils/walkingUtils.test.js | 6 ++--- 12 files changed, 53 insertions(+), 52 deletions(-) diff --git a/index.js b/index.js index 1fe5fc728c1a9b..870d67d340b54e 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import {Browsers, InternalCompatData} from './types/index.js' */ +/** @import {InternalCompatData} from './types/index.js' */ import fs from 'node:fs/promises'; import path from 'node:path'; @@ -64,6 +64,3 @@ const load = async (...dirs) => { const bcd = await load(...dataFolders); export default bcd; - -/** @type {Browsers} */ -export const browsers = bcd.browsers; diff --git a/lint/fixer/mirror.js b/lint/fixer/mirror.js index 71eb7b373b8969..f92cba01163198 100644 --- a/lint/fixer/mirror.js +++ b/lint/fixer/mirror.js @@ -3,7 +3,7 @@ import stringify from 'fast-json-stable-stringify'; -import { browsers } from '../../index.js'; +import bcd from '../../index.js'; import { walk } from '../../utils/index.js'; import mirrorSupport from '../../scripts/build/mirror.js'; @@ -11,7 +11,7 @@ import mirrorSupport from '../../scripts/build/mirror.js'; /** @import {InternalSupportStatement, InternalSupportBlock} from '../../types/index.js' */ const downstreamBrowsers = /** @type {BrowserName[]} */ ( - Object.entries(browsers).flatMap(([browser, stmt]) => + Object.entries(bcd.browsers).flatMap(([browser, stmt]) => stmt.upstream ? [browser] : [], ) ); @@ -47,7 +47,7 @@ export const isMirrorEquivalent = (support, browser) => { * @returns {boolean} Whether mirroring is required */ export const isMirrorRequired = (supportData, browser) => { - const current = browsers[browser]; + const current = bcd.browsers[browser]; /** @type {BrowserName | undefined} */ const upstream = current.upstream; diff --git a/lint/linter/test-browsers-presence.js b/lint/linter/test-browsers-presence.js index a15f43de75dbaa..26f63eac475fd5 100644 --- a/lint/linter/test-browsers-presence.js +++ b/lint/linter/test-browsers-presence.js @@ -3,7 +3,7 @@ import { styleText } from 'node:util'; -import { browsers } from '../../index.js'; +import bcd from '../../index.js'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ @@ -21,7 +21,7 @@ const processData = (data, category, logger) => { const support = data.support; const definedBrowsers = Object.keys(support); - const displayBrowsers = Object.entries(browsers).flatMap( + const displayBrowsers = Object.entries(bcd.browsers).flatMap( ([name, browser]) => [ 'desktop', @@ -35,15 +35,15 @@ const processData = (data, category, logger) => { ? [name] : [], ); - const requiredBrowsers = Object.keys(browsers).filter( + const requiredBrowsers = Object.keys(bcd.browsers).filter( (b) => !['ie'].includes(b) && - ['desktop', 'mobile'].includes(browsers[b].type) && - (category !== 'webextensions' || browsers[b].accepts_webextensions), + ['desktop', 'mobile'].includes(bcd.browsers[b].type) && + (category !== 'webextensions' || bcd.browsers[b].accepts_webextensions), ); const undefEntries = definedBrowsers.filter( - (value) => !(value in browsers), + (value) => !(value in bcd.browsers), ); if (undefEntries.length > 0) { logger.error( diff --git a/lint/linter/test-obsolete.js b/lint/linter/test-obsolete.js index be4f4b7b7803a9..5cefdc1c1c313b 100644 --- a/lint/linter/test-obsolete.js +++ b/lint/linter/test-obsolete.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -import { browsers } from '../../index.js'; +import bcd from '../../index.js'; /** @import {Linter, LinterData, LinterMessageLevel} from '../types.js' */ /** @import {Logger} from '../utils.js' */ @@ -72,7 +72,7 @@ export const implementedAndRemoved = (support) => { } const releaseDateData = - browsers[browser].releases[d.version_removed.replace('≤', '')] + bcd.browsers[browser].releases[d.version_removed.replace('≤', '')] .release_date; // No browser release date diff --git a/lint/linter/test-obsolete.test.js b/lint/linter/test-obsolete.test.js index 62a4e879137d0a..50e7aa41f365d4 100644 --- a/lint/linter/test-obsolete.test.js +++ b/lint/linter/test-obsolete.test.js @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; -import { browsers } from '../../index.js'; +import bcd from '../../index.js'; import { Logger } from '../utils.js'; import { @@ -16,7 +16,7 @@ const errorTime = new Date(), infoTime = new Date(); errorTime.setFullYear(errorTime.getFullYear() - 2.5); infoTime.setFullYear(infoTime.getFullYear() - 2); -const release = Object.entries(browsers['chrome'].releases).find((r) => { +const release = Object.entries(bcd.browsers['chrome'].releases).find((r) => { if (r[1].release_date === undefined) { return false; } @@ -123,7 +123,7 @@ describe('implementedAndRemoved', () => { implementedAndRemoved({ chrome: { version_added: '1', - version_removed: Object.keys(browsers['chrome'].releases)[-1], + version_removed: Object.keys(bcd.browsers['chrome'].releases)[-1], }, }), false, @@ -133,7 +133,7 @@ describe('implementedAndRemoved', () => { chrome: [ { version_added: '2', - version_removed: Object.keys(browsers['chrome'].releases)[-1], + version_removed: Object.keys(bcd.browsers['chrome'].releases)[-1], }, { version_added: '1', diff --git a/lint/linter/test-status.js b/lint/linter/test-status.js index 57077013ad359c..8a38d73d780557 100644 --- a/lint/linter/test-status.js +++ b/lint/linter/test-status.js @@ -3,7 +3,7 @@ import { styleText } from 'node:util'; -import { browsers } from '../../index.js'; +import bcd from '../../index.js'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ @@ -59,7 +59,7 @@ export const checkExperimental = (data) => { const engineSupport = new Set(); for (const browser of browserSupport) { - const currentRelease = Object.values(browsers[browser].releases).find( + const currentRelease = Object.values(bcd.browsers[browser].releases).find( (r) => r.status === 'current', ); const engine = currentRelease?.engine; diff --git a/lint/linter/test-versions.js b/lint/linter/test-versions.js index 6194edcd73a2c6..3c33d4e1a512ca 100644 --- a/lint/linter/test-versions.js +++ b/lint/linter/test-versions.js @@ -5,7 +5,7 @@ import { styleText } from 'node:util'; import { compare, validate } from 'compare-versions'; -import { browsers } from '../../index.js'; +import bcd from '../../index.js'; /** @import {Linter, LinterData} from '../types.js' */ /** @import {Logger} from '../utils.js' */ @@ -35,9 +35,12 @@ const browserTips = { const isValidVersion = (browser, category, version) => { if (typeof version === 'string') { if (version === 'preview') { - return !!browsers[browser].preview_name; + return !!bcd.browsers[browser].preview_name; } - return Object.hasOwn(browsers[browser].releases, version.replace('≤', '')); + return Object.hasOwn( + bcd.browsers[browser].releases, + version.replace('≤', ''), + ); } return true; }; @@ -90,8 +93,10 @@ const addedBeforeRemoved = (statement) => { */ const checkVersions = (supportData, category, logger) => { const browsersToCheck = /** @type {BrowserName[]} */ ( - Object.keys(browsers).filter((b) => - category === 'webextensions' ? browsers[b].accepts_webextensions : !!b, + Object.keys(bcd.browsers).filter((b) => + category === 'webextensions' + ? bcd.browsers[b].accepts_webextensions + : !!b, ) ); @@ -108,7 +113,7 @@ const checkVersions = (supportData, category, logger) => { : [supportStatement]) { if (statement === 'mirror') { // If the data is to be mirrored, make sure it is mirrorable - if (!browsers[browser].upstream) { + if (!bcd.browsers[browser].upstream) { logger.error( `${styleText('bold', browser)} is set to mirror, however ${styleText('bold', browser)} does not have an upstream browser.`, ); @@ -124,14 +129,14 @@ const checkVersions = (supportData, category, logger) => { } if (!isValidVersion(browser, category, version)) { logger.error( - `${styleText('bold', `${property}: "${version}"`)} is ${styleText('bold', 'NOT')} a valid version number for ${styleText('bold', browser)}\n Valid ${styleText('bold', browser)} versions are: ${Object.keys(browsers[browser].releases).join(', ')}, false`, + `${styleText('bold', `${property}: "${version}"`)} is ${styleText('bold', 'NOT')} a valid version number for ${styleText('bold', browser)}\n Valid ${styleText('bold', browser)} versions are: ${Object.keys(bcd.browsers[browser].releases).join(', ')}, false`, { tip: browserTips[browser] }, ); } if (typeof version === 'string' && version.startsWith('≤')) { const releaseData = - browsers[browser].releases[version.replace('≤', '')]; + bcd.browsers[browser].releases[version.replace('≤', '')]; if ( !releaseData || !releaseData.release_date || @@ -156,7 +161,7 @@ const checkVersions = (supportData, category, logger) => { } } - if ('flags' in statement && !browsers[browser].accepts_flags) { + if ('flags' in statement && !bcd.browsers[browser].accepts_flags) { logger.error( `This browser (${styleText('bold', browser)}) does not support flags, so support cannot be behind a flag for this feature.`, ); diff --git a/scripts/build/mirror.js b/scripts/build/mirror.js index f7c2f2485549d1..7b953fc8f0780f 100644 --- a/scripts/build/mirror.js +++ b/scripts/build/mirror.js @@ -3,7 +3,7 @@ import { compareVersions, compare } from 'compare-versions'; -import { browsers } from '../../index.js'; +import bcd from '../../index.js'; /** * @import { BrowserName, InternalSimpleSupportStatement, InternalSupportStatement } from '../../types/index.js' @@ -72,7 +72,7 @@ const matchingSafariVersions = new Map([ * @throws An error when the downstream browser has no upstream */ export const getMatchingBrowserVersion = (targetBrowser, sourceVersion) => { - const browserData = browsers[targetBrowser]; + const browserData = bcd.browsers[targetBrowser]; const range = sourceVersion.includes('≤'); /* c8 ignore start */ @@ -106,7 +106,7 @@ export const getMatchingBrowserVersion = (targetBrowser, sourceVersion) => { releaseKeys.sort(compareVersions); const sourceRelease = - browsers[browserData.upstream].releases[sourceVersion.replace('≤', '')]; + bcd.browsers[browserData.upstream].releases[sourceVersion.replace('≤', '')]; if (!sourceRelease) { throw new Error( @@ -229,8 +229,8 @@ export const bumpSupport = (sourceData, sourceBrowser, destination) => { const newData = copyStatement(sourceData); if ( - browsers[sourceBrowser].type === 'desktop' && - browsers[destination].type === 'mobile' && + bcd.browsers[sourceBrowser].type === 'desktop' && + bcd.browsers[destination].type === 'mobile' && typeof sourceData === 'object' && sourceData.partial_implementation ) { @@ -253,7 +253,7 @@ export const bumpSupport = (sourceData, sourceBrowser, destination) => { } } - if (!browsers[destination].accepts_flags && newData.flags) { + if (!bcd.browsers[destination].accepts_flags && newData.flags) { // Remove flag data if the target browser doesn't accept flags return { version_added: false }; } @@ -309,11 +309,11 @@ export const bumpSupport = (sourceData, sourceBrowser, destination) => { const sourceBrowserName = sourceBrowser === 'chrome' ? '(Google )?Chrome' - : `(${browsers[sourceBrowser].name})`; + : `(${bcd.browsers[sourceBrowser].name})`; const newNotes = updateNotes( sourceData.notes, new RegExp(`\\b${sourceBrowserName}\\b`, 'g'), - browsers[destination].name, + bcd.browsers[destination].name, (v) => getMatchingBrowserVersion(destination, v), ); if (newNotes) { @@ -332,7 +332,7 @@ export const bumpSupport = (sourceData, sourceBrowser, destination) => { */ const mirrorSupport = (destination, data) => { /** @type {BrowserName | undefined} */ - const upstream = browsers[destination].upstream; + const upstream = bcd.browsers[destination].upstream; if (!upstream) { throw new Error( `Upstream is not defined for ${destination}, cannot mirror!`, diff --git a/scripts/build/mirror.test.js b/scripts/build/mirror.test.js index eb687286c07757..6b793e52930f17 100644 --- a/scripts/build/mirror.test.js +++ b/scripts/build/mirror.test.js @@ -8,7 +8,7 @@ import assert from 'node:assert/strict'; -import { browsers } from '../../index.js'; +import bcd from '../../index.js'; import mirrorSupport, { isOSLimitation } from './mirror.js'; @@ -153,7 +153,7 @@ describe('mirror', () => { for (const [browser, versionMap] of Object.entries(mappings)) { describe(browser, () => { - const upstream = browsers[browser].upstream; + const upstream = bcd.browsers[browser].upstream; for (const pair of versionMap) { it(`${pair[0]} => ${pair[1]}`, () => { const support = { diff --git a/scripts/migrations/007-experimental-false.js b/scripts/migrations/007-experimental-false.js index 3131fcd3590878..788128d8d2a881 100644 --- a/scripts/migrations/007-experimental-false.js +++ b/scripts/migrations/007-experimental-false.js @@ -11,7 +11,6 @@ import esMain from 'es-main'; import { walk } from '../../utils/index.js'; import { dataFoldersMinusBrowsers } from '../lib/data-folders.js'; -import { browsers } from '../../index.js'; const dirname = fileURLToPath(new URL('.', import.meta.url)); @@ -54,7 +53,7 @@ export const fixExperimental = (bcd) => { const engineSupport = new Set(); for (const browser of browserSupport) { - const currentRelease = Object.values(browsers[browser].releases).find( + const currentRelease = Object.values(bcd.browsers[browser].releases).find( (r) => r.status === 'current', ); if (!currentRelease) { diff --git a/scripts/traverse.js b/scripts/traverse.js index c86adce739cb21..37a1284c336e9a 100644 --- a/scripts/traverse.js +++ b/scripts/traverse.js @@ -6,7 +6,7 @@ import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import dataFolders from '../scripts/lib/data-folders.js'; -import bcd, { browsers } from '../index.js'; +import bcd from '../index.js'; /** @import {BrowserName, InternalIdentifier, InternalSimpleSupportStatement, InternalSupportBlock, InternalSupportStatement} from '../types/index.js' */ @@ -83,7 +83,7 @@ export function* iterateFeatures( if ( !( identifier.startsWith('webextensions.') && - browsers[browser].accepts_webextensions + bcd.browsers[browser].accepts_webextensions ) ) { continue; @@ -192,7 +192,7 @@ const traverseFeatures = ( */ const main = ( folders = dataFolders.concat('webextensions'), - browserNames = Object.entries(browsers).flatMap(([name, browser]) => + browserNames = Object.entries(bcd.browsers).flatMap(([name, browser]) => browser.type !== 'server' ? [/** @type {BrowserName} */ (name)] : [], ), values = [], @@ -237,8 +237,8 @@ if (esMain(import.meta)) { describe: 'Filter by a browser. May repeat.', type: 'string', nargs: 1, - default: Object.keys(browsers).filter( - (b) => browsers[b].type !== 'server', + default: Object.keys(bcd.browsers).filter( + (b) => bcd.browsers[b].type !== 'server', ), /** * @@ -248,7 +248,7 @@ if (esMain(import.meta)) { coerce: (value) => /** @type {BrowserName[]} */ ( (Array.isArray(value) ? value : [value]).filter((value) => - Object.keys(browsers).some((browser) => browser === value), + Object.keys(bcd.browsers).some((browser) => browser === value), ) ), }) diff --git a/utils/walkingUtils.test.js b/utils/walkingUtils.test.js index 930287f0b5bf9f..f43929318c9234 100644 --- a/utils/walkingUtils.test.js +++ b/utils/walkingUtils.test.js @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; -import { browsers } from '../index.js'; +import bcd from '../index.js'; import query from './query.js'; import { @@ -26,7 +26,7 @@ describe('joinPath()', () => { describe('isBrowser()', () => { it('returns true for browser-like objects', () => { - assert.equal(isBrowser(browsers['firefox']), true); + assert.equal(isBrowser(bcd.browsers['firefox']), true); }); it('returns false for feature-like objects', () => { @@ -36,7 +36,7 @@ describe('isBrowser()', () => { describe('isFeature()', () => { it('returns false for browser-like objects', () => { - assert.equal(isFeature(browsers['chrome']), false); + assert.equal(isFeature(bcd.browsers['chrome']), false); }); it('returns true for feature-like objects', () => { From 216041247fe9213f0bd94aa064eb6e6f3357789f Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 12:27:27 +0100 Subject: [PATCH 38/77] chore(eslint): prefer @typescript-eslint/no-unused-vars --- eslint.config.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 56d28b394fca8b..483c6a9d240522 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -78,9 +78,7 @@ export default [ rules: { '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-unused-expressions': 'error', - '@typescript-eslint/no-unused-vars': 'off', - - 'no-unused-vars': [ + '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', @@ -167,6 +165,7 @@ export default [ 'no-return-assign': 'error', 'no-self-compare': 'error', 'no-unused-expressions': 'error', + 'no-unused-vars': 'off', // Using @typescript-eslint/no-unused-vars instead. 'no-useless-call': 'error', 'prefer-arrow-functions/prefer-arrow-functions': [ From 6ef2f5ca263a4d9eb09210fd5184f259ae8dadba Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 12:28:01 +0100 Subject: [PATCH 39/77] fix(lint/types): revert underscore on "unused var" --- lint/types.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lint/types.d.ts b/lint/types.d.ts index 0f81541209300f..efd2fc123492a5 100644 --- a/lint/types.d.ts +++ b/lint/types.d.ts @@ -40,6 +40,6 @@ export interface Linter { name: string; description: string; scope: LinterScope; - check: (_logger: Logger, _data: LinterData) => void | Promise; + check: (logger: Logger, data: LinterData) => void | Promise; exceptions?: string[]; } From 4c20fb8225307743a290b902949228aeca311499 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 12:36:55 +0100 Subject: [PATCH 40/77] chore(schemas): remove examples from type descriptions Move them to `examples` property where appropriate. --- schemas/browsers.schema.json | 19 +++++++++++-------- schemas/public.schema.json | 17 ++++++++++------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/schemas/browsers.schema.json b/schemas/browsers.schema.json index 56f05c485426a4..bdb00a520d403d 100644 --- a/schemas/browsers.schema.json +++ b/schemas/browsers.schema.json @@ -22,7 +22,7 @@ "browser_name": { "type": "string", - "description": "Browser key (e.g. 'firefox', 'chrome_android', or 'webview_ios').", + "description": "Browser key.", "enum": [ "bun", "chrome", @@ -46,7 +46,7 @@ "upstream_browser_name": { "type": "string", - "description": "Upstream browser key (e.g. 'firefox' or 'chrome_android').", + "description": "Upstream browser key.", "enum": ["chrome", "chrome_android", "firefox", "safari", "safari_ios"] }, @@ -77,25 +77,28 @@ "properties": { "name": { "type": "string", - "description": "The browser brand name (e.g. Firefox, Firefox Android, Chrome, etc.)." + "description": "The browser brand name.", + "examples": ["Firefox", "Firefox Android", "Chrome"] }, "type": { "$ref": "#/definitions/browser_type", - "description": "The platform the browser runs on (e.g. desktop, mobile, XR, or server engine).", + "description": "The platform the browser runs on.", "tsType": "BrowserType" }, "upstream": { "type": "string", - "description": "The upstream browser this browser derives from (e.g. Firefox Android is derived from Firefox, Edge is derived from Chrome).", + "description": "The upstream browser this browser derives from.", "tsType": "BrowserName" }, "preview_name": { "type": "string", - "description": "The name of the browser's preview channel (e.g. 'Nightly' for Firefox or 'TP' for Safari)." + "description": "The name of the browser's preview channel.", + "examples": ["Nightly", "TP"] }, "pref_url": { "type": "string", - "description": "URL of the page where feature flags can be changed (e.g. 'about:config' for Firefox or 'chrome://flags' for Chrome)." + "description": "URL of the page where feature flags can be changed.", + "examples": ["about:config", "chrome://flags"] }, "accepts_flags": { "type": "boolean", @@ -138,7 +141,7 @@ }, "status": { "$ref": "#/definitions/browser_status", - "description": "A property indicating where in the lifetime cycle this release is in (e.g. current, retired, beta, nightly).", + "description": "A property indicating where in the lifetime cycle this release is in.", "tsType": "BrowserStatus" }, "engine": { diff --git a/schemas/public.schema.json b/schemas/public.schema.json index b5f8df66c1fc75..4186a94d4c6d7b 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -20,7 +20,7 @@ "browser_type": { "type": "string", - "description": "Platform the browser runs on (e.g. desktop, mobile, XR, or server engine).", + "description": "Platform the browser runs on.", "enum": ["desktop", "mobile", "xr", "server"] }, @@ -40,7 +40,7 @@ "browser_name": { "type": "string", - "description": "Browser key (e.g. 'firefox', 'chrome_android', or 'webview_ios').", + "description": "Browser key.", "enum": [ "bun", "chrome", @@ -64,13 +64,13 @@ "upstream_browser_name": { "type": "string", - "description": "Upstream browser key (e.g. 'firefox' or 'chrome_android').", + "description": "Upstream browser key.", "enum": ["chrome", "chrome_android", "firefox", "safari", "safari_ios"] }, "browser_status": { "type": "string", - "description": "Lifetime cycle status of a browser release (e.g. 'current', 'retired', 'beta', 'nightly').", + "description": "Lifetime cycle status of a browser release.", "enum": ["retired", "current", "beta", "nightly", "esr", "planned"] }, @@ -91,7 +91,8 @@ "properties": { "name": { "type": "string", - "description": "Name of the browser (e.g. 'Firefox', 'Firefox Android', 'Chrome', etc.)." + "description": "Name of the browser.", + "examples": ["Firefox", "Firefox Android", "Chrome"] }, "type": { "$ref": "#/definitions/browser_type" @@ -101,11 +102,13 @@ }, "preview_name": { "type": "string", - "description": "Name of the browser's preview channel (e.g. 'Nightly' for Firefox or 'TP' for Safari)." + "description": "Name of the browser's preview channel.", + "examples": ["Nightly", "TP"] }, "pref_url": { "type": "string", - "description": "URL of the page where feature flags can be changed (e.g. 'about:config' for Firefox or 'chrome://flags' for Chrome)." + "description": "URL of the page where feature flags can be changed.", + "examples": ["about:config", "chrome://flags"] }, "accepts_flags": { "type": "boolean", From 87955243e6e8e48671a9444300cc6acb649f220f Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 12:39:36 +0100 Subject: [PATCH 41/77] fixup! refactor: revert named browsers export --- scripts/migrations/007-experimental-false.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/migrations/007-experimental-false.js b/scripts/migrations/007-experimental-false.js index 788128d8d2a881..a4bdf719f259f0 100644 --- a/scripts/migrations/007-experimental-false.js +++ b/scripts/migrations/007-experimental-false.js @@ -11,6 +11,8 @@ import esMain from 'es-main'; import { walk } from '../../utils/index.js'; import { dataFoldersMinusBrowsers } from '../lib/data-folders.js'; +import bcd from '../../index.js'; +const { browsers } = bcd; const dirname = fileURLToPath(new URL('.', import.meta.url)); @@ -53,7 +55,7 @@ export const fixExperimental = (bcd) => { const engineSupport = new Set(); for (const browser of browserSupport) { - const currentRelease = Object.values(bcd.browsers[browser].releases).find( + const currentRelease = Object.values(browsers[browser].releases).find( (r) => r.status === 'current', ); if (!currentRelease) { From ed665b70a7a4b775c8ccbc44356114bb3f0b8fcd Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 12:49:41 +0100 Subject: [PATCH 42/77] chore(schemas): remove duplicated information from description Removes type information, and required/optional state. --- schemas/compat-data.schema.json | 34 ++++++++++++++++----------------- schemas/public.schema.json | 32 +++++++++++++++---------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index d47a3c2ebf485d..22ea40fd90db5c 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -6,7 +6,7 @@ "type": "object", "properties": { "version_added": { - "description": "A string (indicating which browser version added this feature), or the value false (indicating the feature is not supported).", + "description": "The browser version that added this feature, or false if the feature is not supported.", "anyOf": [ { "type": "string", @@ -19,7 +19,7 @@ "tsType": "VersionValue" }, "version_removed": { - "description": "A string, indicating which browser version removed this feature.", + "description": "The browser version that removed this feature.", "type": "string", "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$", "tsType": "string" @@ -34,7 +34,7 @@ }, "flags": { "type": "array", - "description": "An optional array of objects describing flags that must be configured for this browser to support this feature.", + "description": "Flags that must be configured for this browser to support this feature.", "minItems": 1, "items": { "$ref": "#/definitions/flag_statement" @@ -54,7 +54,7 @@ } } ], - "description": "An optional changeset/commit URL for the revision which implemented the feature in the source code, or the URL to the bug tracking the implementation, for the associated browser.", + "description": "Changeset/commit URL for the revision which implemented the feature in the source code, or the URL to the bug tracking the implementation, for the associated browser.", "tsType": "string | [string, string, ...string[]]", "errorMessage": { "pattern": "impl_url must be a link to a browser commit or bug URL. URLs must be in shortened form (ex. bugs.chromium.org -> crbug.com). Note: `npm run fix` may resolve these issues." @@ -62,10 +62,10 @@ }, "partial_implementation": { "const": true, - "description": "A boolean value indicating whether or not the implementation of the sub-feature deviates from the specification in a way that may cause compatibility problems. It defaults to false (no interoperability problems expected). If set to true, it is recommended that you add a note explaining how it diverges from the standard (such as that it implements an old version of the standard, for example)." + "description": "Whether the implementation of the sub-feature deviates from the specification in a way that may cause compatibility problems. It defaults to false (no interoperability problems expected). If set to true, it is recommended that you add a note explaining how it diverges from the standard (such as that it implements an old version of the standard, for example)." }, "notes": { - "description": "A string or array of strings containing additional information.", + "description": "Additional information about the feature's support.", "anyOf": [ { "type": "string" @@ -98,16 +98,16 @@ "properties": { "type": { "type": "string", - "description": "An enum that indicates the flag type.", + "description": "The flag type.", "enum": ["preference", "runtime_flag"] }, "name": { "type": "string", - "description": "A string giving the name of the flag or preference that must be configured." + "description": "The name of the flag or preference that must be configured." }, "value_to_set": { "type": "string", - "description": "A string giving the value which the specified flag must be set to for this feature to work." + "description": "The value which the specified flag must be set to for this feature to work." } }, "additionalProperties": false, @@ -134,15 +134,15 @@ "experimental": { "type": "boolean", "deprecated": true, - "description": "(This property is deprecated. Prefer using more well-defined stability calculations, such as Baseline, instead.) A boolean value. Usually, this value is true for single-implementer features and false for multiple-implementer features or single-implementer features that are not expected to change." + "description": "(This property is deprecated. Prefer using more well-defined stability calculations, such as Baseline, instead.) Usually, this value is true for single-implementer features and false for multiple-implementer features or single-implementer features that are not expected to change." }, "standard_track": { "type": "boolean", - "description": "A boolean value indicating whether the feature is part of an active specification or specification process." + "description": "Whether the feature is part of an active specification or specification process." }, "deprecated": { "type": "boolean", - "description": "A boolean value that indicates whether the feature is no longer recommended. It might be removed in the future or might only be kept for compatibility purposes. Avoid using this functionality." + "description": "Whether the feature is no longer recommended. It might be removed in the future or might only be kept for compatibility purposes. Avoid using this functionality." } }, "required": ["experimental", "standard_track", "deprecated"], @@ -195,13 +195,13 @@ "properties": { "description": { "type": "string", - "description": "A string containing a human-readable description of the feature." + "description": "Human-readable description of the feature." }, "mdn_url": { "type": "string", "format": "uri", "pattern": "^https://developer\\.mozilla\\.org/docs/", - "description": "A URL that points to an MDN reference page documenting the feature. The URL should be language-agnostic." + "description": "Link to the MDN reference page documenting the feature. The URL should be language-agnostic." }, "spec_url": { "anyOf": [ @@ -216,11 +216,11 @@ } } ], - "description": "An optional URL or array of URLs, each of which is for a specific part of a specification in which this feature is defined. Each URL must contain a fragment identifier.", + "description": "URL(s) for specific parts of a specification in which this feature is defined. Each URL must contain a fragment identifier.", "tsType": "string | [string, string, ...string[]]" }, "tags": { - "description": "An optional array of strings allowing to assign tags to the feature.", + "description": "Tags assigned to the feature.", "type": "array", "minItems": 1, "tsType": "[string, ...string[]]" @@ -231,7 +231,7 @@ }, "status": { "$ref": "#/definitions/internal_status_block", - "description": "An object containing information about the stability of the feature." + "description": "Information about the stability of the feature." } }, "required": ["support"], diff --git a/schemas/public.schema.json b/schemas/public.schema.json index 4186a94d4c6d7b..6dc87dad4cb347 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -174,12 +174,12 @@ "$ref": "#/definitions/version_value" }, "version_removed": { - "description": "A string, indicating which browser version removed this feature.", + "description": "The browser version that removed this feature.", "type": "string", "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$" }, "version_last": { - "description": "A string, indicating the last browser version that supported this feature. This is automatically generated.", + "description": "The last browser version that supported this feature. This is automatically generated.", "type": "string", "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$" }, @@ -193,7 +193,7 @@ }, "flags": { "type": "array", - "description": "An optional array of objects describing flags that must be configured for this browser to support this feature.", + "description": "Flags that must be configured for this browser to support this feature.", "minItems": 1, "items": { "$ref": "#/definitions/flag_statement" @@ -214,17 +214,17 @@ } } ], - "description": "An optional changeset/commit URL for the revision which implemented the feature in the source code, or the URL to the bug tracking the implementation, for the associated browser.", + "description": "Changeset/commit URL for the revision which implemented the feature in the source code, or the URL to the bug tracking the implementation, for the associated browser.", "errorMessage": { "pattern": "impl_url must be a link to a browser commit or bug URL. URLs must be in shortened form (ex. bugs.chromium.org -> crbug.com). Note: `npm run fix` may resolve these issues." } }, "partial_implementation": { "const": true, - "description": "A boolean value indicating whether or not the implementation of the sub-feature deviates from the specification in a way that may cause compatibility problems. It defaults to false (no interoperability problems expected). If set to true, it is recommended that you add a note explaining how it diverges from the standard (such as that it implements an old version of the standard, for example)." + "description": "Whether the implementation of the sub-feature deviates from the specification in a way that may cause compatibility problems. It defaults to false (no interoperability problems expected). If set to true, it is recommended that you add a note explaining how it diverges from the standard (such as that it implements an old version of the standard, for example)." }, "notes": { - "description": "A string or array of strings containing additional information.", + "description": "Additional information about the feature's support.", "anyOf": [ { "type": "string" @@ -255,7 +255,7 @@ }, "version_value": { - "description": "A string (indicating which browser version added this feature), or the value false (indicating the feature is not supported).", + "description": "The browser version that added this feature, or false if the feature is not supported.", "anyOf": [ { "type": "string", @@ -272,16 +272,16 @@ "properties": { "type": { "type": "string", - "description": "An enum that indicates the flag type.", + "description": "The flag type.", "enum": ["preference", "runtime_flag"] }, "name": { "type": "string", - "description": "A string giving the name of the flag or preference that must be configured." + "description": "The name of the flag or preference that must be configured." }, "value_to_set": { "type": "string", - "description": "A string giving the value which the specified flag must be set to for this feature to work." + "description": "The value which the specified flag must be set to for this feature to work." } }, "additionalProperties": false, @@ -303,20 +303,20 @@ "status_block": { "type": "object", - "description": "An object containing information about the stability of the feature.", + "description": "Information about the stability of the feature.", "properties": { "experimental": { "type": "boolean", "deprecated": true, - "description": "(This property is deprecated. Prefer using more well-defined stability calculations, such as Baseline, instead.) A boolean value. Usually, this value is true for single-implementer features and false for multiple-implementer features or single-implementer features that are not expected to change." + "description": "(This property is deprecated. Prefer using more well-defined stability calculations, such as Baseline, instead.) Usually, this value is true for single-implementer features and false for multiple-implementer features or single-implementer features that are not expected to change." }, "standard_track": { "type": "boolean", - "description": "A boolean value indicating whether the feature is part of an active specification or specification process." + "description": "Whether the feature is part of an active specification or specification process." }, "deprecated": { "type": "boolean", - "description": "A boolean value that indicates whether the feature is no longer recommended. It might be removed in the future or might only be kept for compatibility purposes. Avoid using this functionality." + "description": "Whether the feature is no longer recommended. It might be removed in the future or might only be kept for compatibility purposes. Avoid using this functionality." } }, "required": ["experimental", "standard_track", "deprecated"], @@ -364,10 +364,10 @@ } } ], - "description": "An optional URL or array of URLs, each of which is for a specific part of a specification in which this feature is defined. Each URL must contain a fragment identifier." + "description": "URL(s) for specific parts of a specification in which this feature is defined. Each URL must contain a fragment identifier." }, "tags": { - "description": "An optional array of strings allowing to assign tags to the feature.", + "description": "Tags assigned to the feature.", "type": "array", "minItems": 1, "items": { From d9d323d4c574043b5a73a55cc820ff58259ef4fc Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 13:06:06 +0100 Subject: [PATCH 43/77] chore(schemas): revert BrowserName type --- schemas/browsers.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemas/browsers.schema.json b/schemas/browsers.schema.json index bdb00a520d403d..906914224cb564 100644 --- a/schemas/browsers.schema.json +++ b/schemas/browsers.schema.json @@ -69,7 +69,7 @@ "minProperties": "A browser must be described within the file.", "maxProperties": "Each browser JSON file may only describe one browser." }, - "tsType": "{ [browser: BrowserName]: BrowserStatement }" + "tsType": "Record" }, "browser_statement": { From 22e9dbd601493d371a32315932c0596e01dab667 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 13:14:55 +0100 Subject: [PATCH 44/77] chore(schemas): reflect draft-07 in `$schema` value --- schemas/browsers.schema.json | 2 +- schemas/compat-data.schema.json | 2 +- schemas/public.schema.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/schemas/browsers.schema.json b/schemas/browsers.schema.json index 906914224cb564..0d80b2cff3c232 100644 --- a/schemas/browsers.schema.json +++ b/schemas/browsers.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/schema", + "$schema": "https://json-schema.org/draft-07/schema#", "definitions": { "browser_type": { diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index 22ea40fd90db5c..76833d3658de60 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/schema", + "$schema": "https://json-schema.org/draft-07/schema#", "definitions": { "internal_simple_support_statement": { diff --git a/schemas/public.schema.json b/schemas/public.schema.json index 6dc87dad4cb347..12d9ad82834e25 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/schema", + "$schema": "https://json-schema.org/draft-07/schema#", "definitions": { "meta_block": { From a760f581e36852d1bc809053c6b69f5bbbc57717 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 14:55:36 +0100 Subject: [PATCH 45/77] chore(schemas): make descriptions more concise - Remove filler phrases. - Drop unnecessary leading articles. - Trim verbose explanations. - Condense wordy descriptions. - Shorten multi-sentence descriptions with unnecessary elaboration. - Remove "Avoid using this functinoaly" from `deprecated` status. - Replace multi-paragraph descriptions. --- schemas/browsers.schema.json | 20 ++++++++--------- schemas/compat-data.schema.json | 26 ++++++++++----------- schemas/public.schema.json | 40 ++++++++++++++++----------------- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/schemas/browsers.schema.json b/schemas/browsers.schema.json index 0d80b2cff3c232..7e4205659be037 100644 --- a/schemas/browsers.schema.json +++ b/schemas/browsers.schema.json @@ -77,22 +77,22 @@ "properties": { "name": { "type": "string", - "description": "The browser brand name.", + "description": "Browser brand name.", "examples": ["Firefox", "Firefox Android", "Chrome"] }, "type": { "$ref": "#/definitions/browser_type", - "description": "The platform the browser runs on.", + "description": "Platform the browser runs on.", "tsType": "BrowserType" }, "upstream": { "type": "string", - "description": "The upstream browser this browser derives from.", + "description": "Upstream browser this browser derives from.", "tsType": "BrowserName" }, "preview_name": { "type": "string", - "description": "The name of the browser's preview channel.", + "description": "Name of the browser's preview channel.", "examples": ["Nightly", "TP"] }, "pref_url": { @@ -111,7 +111,7 @@ "releases": { "type": "object", "additionalProperties": { "$ref": "#/definitions/release_statement" }, - "description": "The known versions of this browser.", + "description": "Known versions of this browser.", "tsType": "{ [version: string]: ReleaseStatement };" } }, @@ -131,27 +131,27 @@ "release_date": { "type": "string", "format": "date", - "description": "The date on which this version was released, formatted as `YYYY-MM-DD`." + "description": "Date this version was released (`YYYY-MM-DD`)." }, "release_notes": { "type": "string", "format": "uri", "pattern": "^https://", - "description": "A link to the release notes or changelog for a given release." + "description": "Link to the release notes or changelog." }, "status": { "$ref": "#/definitions/browser_status", - "description": "A property indicating where in the lifetime cycle this release is in.", + "description": "Where in the lifetime cycle this release is.", "tsType": "BrowserStatus" }, "engine": { "$ref": "#/definitions/browser_engine", - "description": "Name of the browser's underlying engine.", + "description": "Browser engine name.", "tsType": "BrowserEngine" }, "engine_version": { "type": "string", - "description": "Version of the engine corresponding to the browser version." + "description": "Engine version corresponding to this browser version." } }, "required": ["status"], diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index 76833d3658de60..7477e4f1e84cfa 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -6,7 +6,7 @@ "type": "object", "properties": { "version_added": { - "description": "The browser version that added this feature, or false if the feature is not supported.", + "description": "Browser version that added this feature, or false if not supported.", "anyOf": [ { "type": "string", @@ -19,18 +19,18 @@ "tsType": "VersionValue" }, "version_removed": { - "description": "The browser version that removed this feature.", + "description": "Browser version that removed this feature.", "type": "string", "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$", "tsType": "string" }, "prefix": { "type": "string", - "description": "A prefix to add to the sub-feature name (defaults to empty string). If applicable, leading and trailing '-' must be included." + "description": "Prefix for the sub-feature name. Leading and trailing '-' must be included if applicable." }, "alternative_name": { "type": "string", - "description": "An alternative name for the feature, for cases where a feature is implemented under an entirely different name and not just prefixed." + "description": "Alternative name for the feature, when implemented under an entirely different name." }, "flags": { "type": "array", @@ -54,7 +54,7 @@ } } ], - "description": "Changeset/commit URL for the revision which implemented the feature in the source code, or the URL to the bug tracking the implementation, for the associated browser.", + "description": "URL for the changeset/commit or bug that tracks the implementation of this feature.", "tsType": "string | [string, string, ...string[]]", "errorMessage": { "pattern": "impl_url must be a link to a browser commit or bug URL. URLs must be in shortened form (ex. bugs.chromium.org -> crbug.com). Note: `npm run fix` may resolve these issues." @@ -62,7 +62,7 @@ }, "partial_implementation": { "const": true, - "description": "Whether the implementation of the sub-feature deviates from the specification in a way that may cause compatibility problems. It defaults to false (no interoperability problems expected). If set to true, it is recommended that you add a note explaining how it diverges from the standard (such as that it implements an old version of the standard, for example)." + "description": "Whether the implementation deviates from the specification in a way that may cause compatibility problems. If true, a note explaining the divergence is required." }, "notes": { "description": "Additional information about the feature's support.", @@ -98,16 +98,16 @@ "properties": { "type": { "type": "string", - "description": "The flag type.", + "description": "Flag type.", "enum": ["preference", "runtime_flag"] }, "name": { "type": "string", - "description": "The name of the flag or preference that must be configured." + "description": "Name of the flag or preference to configure." }, "value_to_set": { "type": "string", - "description": "The value which the specified flag must be set to for this feature to work." + "description": "Value to set the flag to." } }, "additionalProperties": false, @@ -142,7 +142,7 @@ }, "deprecated": { "type": "boolean", - "description": "Whether the feature is no longer recommended. It might be removed in the future or might only be kept for compatibility purposes. Avoid using this functionality." + "description": "Whether the feature is no longer recommended. It might be removed in the future or kept only for compatibility purposes." } }, "required": ["experimental", "standard_track", "deprecated"], @@ -201,7 +201,7 @@ "type": "string", "format": "uri", "pattern": "^https://developer\\.mozilla\\.org/docs/", - "description": "Link to the MDN reference page documenting the feature. The URL should be language-agnostic." + "description": "Link to the MDN reference page for this feature. Must be language-agnostic." }, "spec_url": { "anyOf": [ @@ -227,7 +227,7 @@ }, "support": { "$ref": "#/definitions/internal_support_block", - "description": "The data for the support of each browser, containing a `support_statement` object for each browser identifier with information about versions, prefixes, or alternate names, as well as notes." + "description": "Browser support data for this feature." }, "status": { "$ref": "#/definitions/internal_status_block", @@ -245,7 +245,7 @@ "type": "object", "$ref": "#/definitions/internal_compat_statement", "required": ["status"], - "description": "A feature is described by an identifier containing the `__compat` property.\n\nIn other words, identifiers without `__compat` aren't necessarily features, but help to nest the features properly.\n\nWhen an identifier has a `__compat` block, it represents its basic support, indicating that a minimal implementation of a functionality is included.\n\nWhat it represents exactly depends of the evolution of the feature over time, both in terms of specifications and of browser support.", + "description": "The compatibility data for a feature, nested under the `__compat` property of an identifier.", "tsType": "InternalCompatStatement" } }, diff --git a/schemas/public.schema.json b/schemas/public.schema.json index 12d9ad82834e25..6f77a18f98738f 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -4,7 +4,7 @@ "definitions": { "meta_block": { "type": "object", - "description": "Metadata of the present BCD data.", + "description": "Release metadata.", "properties": { "version": { "type": "string" @@ -121,7 +121,7 @@ "releases": { "type": "object", "additionalProperties": { "$ref": "#/definitions/release_statement" }, - "description": "The known versions of this browser.", + "description": "Known versions of this browser.", "tsType": "Record" } }, @@ -141,13 +141,13 @@ "release_date": { "type": "string", "format": "date", - "description": "The date on which this version was released, formatted as `YYYY-MM-DD`." + "description": "Date this version was released (`YYYY-MM-DD`)." }, "release_notes": { "type": "string", "format": "uri", "pattern": "^https://", - "description": "A link to the release notes or changelog for a given release." + "description": "Link to the release notes or changelog." }, "status": { "$ref": "#/definitions/browser_status" @@ -157,7 +157,7 @@ }, "engine_version": { "type": "string", - "description": "Version of the engine corresponding to the browser version." + "description": "Engine version corresponding to this browser version." } }, "required": ["status"], @@ -174,22 +174,22 @@ "$ref": "#/definitions/version_value" }, "version_removed": { - "description": "The browser version that removed this feature.", + "description": "Browser version that removed this feature.", "type": "string", "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$" }, "version_last": { - "description": "The last browser version that supported this feature. This is automatically generated.", + "description": "Last browser version that supported this feature. Automatically generated.", "type": "string", "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$" }, "prefix": { "type": "string", - "description": "A prefix to add to the sub-feature name (defaults to empty string). If applicable, leading and trailing '-' must be included." + "description": "Prefix for the sub-feature name. Leading and trailing '-' must be included if applicable." }, "alternative_name": { "type": "string", - "description": "An alternative name for the feature, for cases where a feature is implemented under an entirely different name and not just prefixed." + "description": "Alternative name for the feature, when implemented under an entirely different name." }, "flags": { "type": "array", @@ -214,14 +214,14 @@ } } ], - "description": "Changeset/commit URL for the revision which implemented the feature in the source code, or the URL to the bug tracking the implementation, for the associated browser.", + "description": "URL for the changeset/commit or bug that tracks the implementation of this feature.", "errorMessage": { "pattern": "impl_url must be a link to a browser commit or bug URL. URLs must be in shortened form (ex. bugs.chromium.org -> crbug.com). Note: `npm run fix` may resolve these issues." } }, "partial_implementation": { "const": true, - "description": "Whether the implementation of the sub-feature deviates from the specification in a way that may cause compatibility problems. It defaults to false (no interoperability problems expected). If set to true, it is recommended that you add a note explaining how it diverges from the standard (such as that it implements an old version of the standard, for example)." + "description": "Whether the implementation deviates from the specification in a way that may cause compatibility problems. If true, a note explaining the divergence is required." }, "notes": { "description": "Additional information about the feature's support.", @@ -255,7 +255,7 @@ }, "version_value": { - "description": "The browser version that added this feature, or false if the feature is not supported.", + "description": "Browser version that added this feature, or false if not supported.", "anyOf": [ { "type": "string", @@ -272,16 +272,16 @@ "properties": { "type": { "type": "string", - "description": "The flag type.", + "description": "Flag type.", "enum": ["preference", "runtime_flag"] }, "name": { "type": "string", - "description": "The name of the flag or preference that must be configured." + "description": "Name of the flag or preference to configure." }, "value_to_set": { "type": "string", - "description": "The value which the specified flag must be set to for this feature to work." + "description": "Value to set the flag to." } }, "additionalProperties": false, @@ -316,7 +316,7 @@ }, "deprecated": { "type": "boolean", - "description": "Whether the feature is no longer recommended. It might be removed in the future or might only be kept for compatibility purposes. Avoid using this functionality." + "description": "Whether the feature is no longer recommended. It might be removed in the future or kept only for compatibility purposes." } }, "required": ["experimental", "standard_track", "deprecated"], @@ -325,7 +325,7 @@ "support_block": { "type": "object", - "description": "The data for the support of each browser, containing a `support_statement` object for each browser identifier with information about versions, prefixes, or alternate names, as well as notes.", + "description": "Browser support data for this feature.", "propertyNames": { "$ref": "#/definitions/browser_name" }, @@ -337,7 +337,7 @@ "compat_statement": { "type": "object", - "description": "A feature is described by an identifier containing the `__compat` property.\n\nIn other words, identifiers without `__compat` aren't necessarily features, but help to nest the features properly.\n\nWhen an identifier has a `__compat` block, it represents its basic support, indicating that a minimal implementation of a functionality is included.\n\nWhat it represents exactly depends of the evolution of the feature over time, both in terms of specifications and of browser support.", + "description": "The compatibility data for a feature, nested under the `__compat` property of an identifier.", "properties": { "description": { "type": "string", @@ -376,7 +376,7 @@ }, "source_file": { "type": "string", - "description": "The path to the file that defines this feature in browser-compat-data, relative to the repository root. Useful for guiding potential contributors towards the correct file to edit. This is automatically generated at build time and should never manually be specified." + "description": "Path to the file defining this feature, relative to the repository root. Automatically generated at build time." }, "support": { "$ref": "#/definitions/support_block" @@ -409,7 +409,7 @@ "title": "CompatData", "type": "object", - "description": "Shape of the `data.json` file in published BCD releases", + "description": "Published BCD data.", "properties": { "__meta": { "$ref": "#/definitions/meta_block" From 662ee5979426786f7dedeb8b182320b8d7eb6443 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 16:09:51 +0100 Subject: [PATCH 46/77] fixup! chore(schemas): reflect draft-07 in `$schema` value --- schemas/browsers.schema.json | 2 +- schemas/compat-data.schema.json | 2 +- schemas/public.schema.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/schemas/browsers.schema.json b/schemas/browsers.schema.json index 7e4205659be037..1966a0c32e6b62 100644 --- a/schemas/browsers.schema.json +++ b/schemas/browsers.schema.json @@ -1,5 +1,5 @@ { - "$schema": "https://json-schema.org/draft-07/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { "browser_type": { diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index 7477e4f1e84cfa..9c9e7c808c7851 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -1,5 +1,5 @@ { - "$schema": "https://json-schema.org/draft-07/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { "internal_simple_support_statement": { diff --git a/schemas/public.schema.json b/schemas/public.schema.json index 6f77a18f98738f..ae18f258e75941 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -1,5 +1,5 @@ { - "$schema": "https://json-schema.org/draft-07/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { "meta_block": { From 883d28803696838fdc402be3a09074928cd9fcef Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 16:42:01 +0100 Subject: [PATCH 47/77] chore(schemas): add description/examples to meta block --- schemas/public.schema.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/schemas/public.schema.json b/schemas/public.schema.json index ae18f258e75941..aa55eea2b55f5e 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -7,11 +7,15 @@ "description": "Release metadata.", "properties": { "version": { - "type": "string" + "type": "string", + "description": "Package version number of this BCD release.", + "examples": ["5.6.14", "6.0.0"] }, "timestamp": { "type": "string", - "format": "date-time" + "format": "date-time", + "description": "Timestamp of when this BCD release was built.", + "examples": ["2025-06-15T14:32:00.000Z"] } }, "required": ["version", "timestamp"], From 30361cdecdde283191d3e1e85a838777b62e5a6f Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 16:45:18 +0100 Subject: [PATCH 48/77] chore(schemas): prefer "subfeature" over "sub-feature" --- schemas/compat-data-schema.md | 12 ++++++------ schemas/compat-data.schema.json | 2 +- schemas/public.schema.json | 2 +- schemas/public.schema.md | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/schemas/compat-data-schema.md b/schemas/compat-data-schema.md index 4434db1e186066..102f8ebf5588cd 100644 --- a/schemas/compat-data-schema.md +++ b/schemas/compat-data-schema.md @@ -72,9 +72,9 @@ A feature is described by an identifier containing the `__compat` property. In o When an identifier has a `__compat` block, it represents its basic support, indicating that a minimal implementation of a functionality is included. What it represents exactly depends on the evolution of the feature over time, both in terms of specifications and of browser support. -#### Sub-features +#### Subfeatures -To add a sub-feature, a new identifier is added below the main feature at the level of a `__compat` object (see the sub-features "start" and "end" above). The same could be done for sub-sub-features. There is no depth limit. +To add a subfeature, a new identifier is added below the main feature at the level of a `__compat` object (see the subfeatures "start" and "end" above). The same could be done for sub-subfeatures. There is no depth limit. See [Data guidelines](../docs/data-guidelines/README.md) for more information about feature naming conventions and other best practices. @@ -269,7 +269,7 @@ The `simple_support_statement` object is the core object containing the compatib #### `version_added` -This is the only mandatory property and it contains a string with the version number indicating when a sub-feature has been added (and is therefore supported), or the value `false` indicating the feature is not supported. Examples: +This is the only mandatory property and it contains a string with the version number indicating when a subfeature has been added (and is therefore supported), or the value `false` indicating the feature is not supported. Examples: - Support from version 3.5 (inclusive): @@ -305,7 +305,7 @@ This is the only mandatory property and it contains a string with the version nu #### `version_removed` -Contains a string with the version number the sub-feature was removed in. If the feature has not been removed from the browser, this property is omitted, rather than being set to `false`. +Contains a string with the version number the subfeature was removed in. If the feature has not been removed from the browser, this property is omitted, rather than being set to `false`. Examples: @@ -334,7 +334,7 @@ Ranged versions should be used sparingly and only when it is impossible or highl #### `prefix` -A prefix to add to the sub-feature name (defaults to empty string). +A prefix to add to the subfeature name (defaults to empty string). If applicable, leading and trailing `-` must be included. Examples: @@ -446,7 +446,7 @@ Notes may be formatted in Markdown. Only links, bold, italics, codeblocks, and ` #### `partial_implementation` -A `boolean` value indicating whether or not the implementation of the sub-feature deviates from the specification in a way that may cause significant compatibility problems. It defaults to `false` (no interoperability problems expected). If set to `true`, it is [required](../docs/data-guidelines/README.md#partial_implementation-requires-a-note) that you add a note explaining how it diverges from the standard (such as that it implements an old version of the standard). +A `boolean` value indicating whether or not the implementation of the subfeature deviates from the specification in a way that may cause significant compatibility problems. It defaults to `false` (no interoperability problems expected). If set to `true`, it is [required](../docs/data-guidelines/README.md#partial_implementation-requires-a-note) that you add a note explaining how it diverges from the standard (such as that it implements an old version of the standard). ```json { diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index 9c9e7c808c7851..fb30fea3beb5e5 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -26,7 +26,7 @@ }, "prefix": { "type": "string", - "description": "Prefix for the sub-feature name. Leading and trailing '-' must be included if applicable." + "description": "Prefix for the subfeature name. Leading and trailing '-' must be included if applicable." }, "alternative_name": { "type": "string", diff --git a/schemas/public.schema.json b/schemas/public.schema.json index aa55eea2b55f5e..33943edf322881 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -189,7 +189,7 @@ }, "prefix": { "type": "string", - "description": "Prefix for the sub-feature name. Leading and trailing '-' must be included if applicable." + "description": "Prefix for the subfeature name. Leading and trailing '-' must be included if applicable." }, "alternative_name": { "type": "string", diff --git a/schemas/public.schema.md b/schemas/public.schema.md index 451a2b381e3940..446841af9b95ba 100644 --- a/schemas/public.schema.md +++ b/schemas/public.schema.md @@ -157,7 +157,7 @@ The `simple_support_statement` object is the core object containing the compatib #### `version_added` -This is the only mandatory property and it contains a string with the version number indicating when a sub-feature has been added (and is therefore supported), or `false` indicating the feature is not supported. Examples: +This is the only mandatory property and it contains a string with the version number indicating when a subfeature has been added (and is therefore supported), or `false` indicating the feature is not supported. Examples: - Support from version 3.5 (inclusive): @@ -193,7 +193,7 @@ This is the only mandatory property and it contains a string with the version nu #### `version_removed` -Contains a string with the version number the sub-feature was removed in. If the feature has not been removed from the browser, this property is omitted. When `version_removed` is present, `version_last` is always present as well. +Contains a string with the version number the subfeature was removed in. If the feature has not been removed from the browser, this property is omitted. When `version_removed` is present, `version_last` is always present as well. Example: @@ -221,7 +221,7 @@ A string indicating the last browser version that supported the feature. This pr #### `prefix` -A prefix to add to the sub-feature name (defaults to empty string). +A prefix to add to the subfeature name (defaults to empty string). If applicable, leading and trailing `-` must be included. Examples: @@ -296,7 +296,7 @@ A string or array of strings containing additional information. In the published #### `partial_implementation` -A `boolean` value indicating whether or not the implementation of the sub-feature deviates from the specification in a way that may cause significant compatibility problems. It defaults to `false` (no interoperability problems expected). If set to `true`, a `notes` field explaining the divergence is always present. +A `boolean` value indicating whether or not the implementation of the subfeature deviates from the specification in a way that may cause significant compatibility problems. It defaults to `false` (no interoperability problems expected). If set to `true`, a `notes` field explaining the divergence is always present. ```json { From 24ccd78e7ac3ee5c3d1f56856e17a978f354a73b Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 16:53:17 +0100 Subject: [PATCH 49/77] chore(schemas): refine Browsers type description --- schemas/public.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemas/public.schema.json b/schemas/public.schema.json index 33943edf322881..74d1bfa7b146a1 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -80,7 +80,7 @@ "browsers": { "type": "object", - "description": "Data for each known and tracked browser/runtime.", + "description": "Data about browsers, such as name, type and releases.", "propertyNames": { "$ref": "#/definitions/browser_name" }, From ea1b0295c2771e7227ceeeebae688bbf7745ba8c Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 17:02:15 +0100 Subject: [PATCH 50/77] chore(schemas): prefer "feature flags" over "[user-toggleable] flags" --- schemas/browsers.schema.json | 2 +- schemas/public.schema.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/schemas/browsers.schema.json b/schemas/browsers.schema.json index 1966a0c32e6b62..1ef5d763771465 100644 --- a/schemas/browsers.schema.json +++ b/schemas/browsers.schema.json @@ -102,7 +102,7 @@ }, "accepts_flags": { "type": "boolean", - "description": "Whether the browser supports user-toggleable flags that enable or disable features." + "description": "Whether the browser supports feature flags that enable or disable features." }, "accepts_webextensions": { "type": "boolean", diff --git a/schemas/public.schema.json b/schemas/public.schema.json index 74d1bfa7b146a1..6eb9f213c283ad 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -116,7 +116,7 @@ }, "accepts_flags": { "type": "boolean", - "description": "Whether the browser supports user-toggleable flags that enable or disable features." + "description": "Whether the browser supports feature flags that enable or disable features." }, "accepts_webextensions": { "type": "boolean", @@ -197,7 +197,7 @@ }, "flags": { "type": "array", - "description": "Flags that must be configured for this browser to support this feature.", + "description": "Feature flags that must be configured for this browser to support this feature.", "minItems": 1, "items": { "$ref": "#/definitions/flag_statement" From d39e07e09e3dba516515d3553a993c722de00edc Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 17:09:45 +0100 Subject: [PATCH 51/77] chore(schemas): refine Browsers type descriptions --- schemas/browsers.schema.json | 6 +++--- schemas/public.schema.json | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/schemas/browsers.schema.json b/schemas/browsers.schema.json index 1ef5d763771465..2e5a07f04bda68 100644 --- a/schemas/browsers.schema.json +++ b/schemas/browsers.schema.json @@ -111,7 +111,7 @@ "releases": { "type": "object", "additionalProperties": { "$ref": "#/definitions/release_statement" }, - "description": "Known versions of this browser.", + "description": "Releases of this browser.", "tsType": "{ [version: string]: ReleaseStatement };" } }, @@ -131,7 +131,7 @@ "release_date": { "type": "string", "format": "date", - "description": "Date this version was released (`YYYY-MM-DD`)." + "description": "When this version was released or will be released (`YYYY-MM-DD`)." }, "release_notes": { "type": "string", @@ -151,7 +151,7 @@ }, "engine_version": { "type": "string", - "description": "Engine version corresponding to this browser version." + "description": "Engine version used in this release." } }, "required": ["status"], diff --git a/schemas/public.schema.json b/schemas/public.schema.json index 6eb9f213c283ad..ecd38246fccea5 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -125,7 +125,7 @@ "releases": { "type": "object", "additionalProperties": { "$ref": "#/definitions/release_statement" }, - "description": "Known versions of this browser.", + "description": "Releases of this browser.", "tsType": "Record" } }, @@ -145,7 +145,7 @@ "release_date": { "type": "string", "format": "date", - "description": "Date this version was released (`YYYY-MM-DD`)." + "description": "When this version was released or will be released (`YYYY-MM-DD`)." }, "release_notes": { "type": "string", @@ -161,7 +161,7 @@ }, "engine_version": { "type": "string", - "description": "Engine version corresponding to this browser version." + "description": "Engine version used in this release." } }, "required": ["status"], From b1bc417476569600a1b658418e099f2a7a7e9815 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 17:17:22 +0100 Subject: [PATCH 52/77] chore(schemas): remove "This is automatically generated" --- schemas/public.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemas/public.schema.json b/schemas/public.schema.json index ecd38246fccea5..15a84e357fa9ab 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -183,7 +183,7 @@ "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$" }, "version_last": { - "description": "Last browser version that supported this feature. Automatically generated.", + "description": "Last browser version that supported this feature.", "type": "string", "pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$" }, From dacc0252289f48c4f0cf4069b3cd6937ea1051af Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 17:25:33 +0100 Subject: [PATCH 53/77] fixup! chore(schemas): prefer "feature flags" over "[user-toggleable] flags" --- schemas/compat-data.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index fb30fea3beb5e5..8bf6a0558bc750 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -34,7 +34,7 @@ }, "flags": { "type": "array", - "description": "Flags that must be configured for this browser to support this feature.", + "description": "Feature flags that must be configured for this browser to support this feature.", "minItems": 1, "items": { "$ref": "#/definitions/flag_statement" From 246c656e02687a5971dcb87e3a637176de074d16 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 17:40:23 +0100 Subject: [PATCH 54/77] chore(schemas): refine `impl_url` description --- schemas/compat-data.schema.json | 2 +- schemas/public.schema.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index 8bf6a0558bc750..a4f509da0d248e 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -54,7 +54,7 @@ } } ], - "description": "URL for the changeset/commit or bug that tracks the implementation of this feature.", + "description": "Link to the bug or issue that tracks the implementation of this feature.", "tsType": "string | [string, string, ...string[]]", "errorMessage": { "pattern": "impl_url must be a link to a browser commit or bug URL. URLs must be in shortened form (ex. bugs.chromium.org -> crbug.com). Note: `npm run fix` may resolve these issues." diff --git a/schemas/public.schema.json b/schemas/public.schema.json index 15a84e357fa9ab..29165ba10cdf49 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -218,7 +218,7 @@ } } ], - "description": "URL for the changeset/commit or bug that tracks the implementation of this feature.", + "description": "Link to the bug or issue that tracks the implementation of this feature.", "errorMessage": { "pattern": "impl_url must be a link to a browser commit or bug URL. URLs must be in shortened form (ex. bugs.chromium.org -> crbug.com). Note: `npm run fix` may resolve these issues." } From c80a51e518e79f4febd4a342d20a8cee8f541987 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 17:42:29 +0100 Subject: [PATCH 55/77] chore(schemas): remove errorMessage from public schema --- schemas/public.schema.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/schemas/public.schema.json b/schemas/public.schema.json index 29165ba10cdf49..2ff884e33595fc 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -218,10 +218,7 @@ } } ], - "description": "Link to the bug or issue that tracks the implementation of this feature.", - "errorMessage": { - "pattern": "impl_url must be a link to a browser commit or bug URL. URLs must be in shortened form (ex. bugs.chromium.org -> crbug.com). Note: `npm run fix` may resolve these issues." - } + "description": "Link to the bug or issue that tracks the implementation of this feature." }, "partial_implementation": { "const": true, @@ -404,9 +401,6 @@ "^(?!__compat)[a-zA-Z_0-9-$@]*$": { "$ref": "#/definitions/identifier" } }, "additionalProperties": false, - "errorMessage": { - "additionalProperties": "Feature names can only contain alphanumerical characters or the following symbols: _-$@" - }, "tsType": "{__compat?: CompatStatement} & {[key: string]: Identifier}" } }, From 549e023ddc92c556546ae3e4590b35ba6407fc89 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 17:48:38 +0100 Subject: [PATCH 56/77] chore(schemas): refine `partial_implementation` description --- schemas/compat-data.schema.json | 2 +- schemas/public.schema.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index a4f509da0d248e..38308faca5a51f 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -62,7 +62,7 @@ }, "partial_implementation": { "const": true, - "description": "Whether the implementation deviates from the specification in a way that may cause compatibility problems. If true, a note explaining the divergence is required." + "description": "Set if the browser's support does not implement mandatory specified behavior, is inconsistent with other browsers, causes confusing feature detection results, and has a demonstrable negative impact on web developers." }, "notes": { "description": "Additional information about the feature's support.", diff --git a/schemas/public.schema.json b/schemas/public.schema.json index 2ff884e33595fc..2378441e33d27b 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -222,7 +222,7 @@ }, "partial_implementation": { "const": true, - "description": "Whether the implementation deviates from the specification in a way that may cause compatibility problems. If true, a note explaining the divergence is required." + "description": "Set if the browser's support does not implement mandatory specified behavior, is inconsistent with other browsers, causes confusing feature detection results, and has a demonstrable negative impact on web developers." }, "notes": { "description": "Additional information about the feature's support.", From 108d46280976e978cebd106a9a256ead35c1fa02 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 17:56:35 +0100 Subject: [PATCH 57/77] docs(schemas): sync docs with changes --- schemas/browsers-schema.md | 4 ++-- schemas/compat-data-schema.md | 20 +++++++++----------- schemas/public.schema.md | 18 +++++++++--------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/schemas/browsers-schema.md b/schemas/browsers-schema.md index 03b71de81cb831..d9d9600238da17 100644 --- a/schemas/browsers-schema.md +++ b/schemas/browsers-schema.md @@ -74,13 +74,13 @@ The `releases` object contains data regarding the browsers' releases, using the - `esr`: This release is an Extended Support Release or Long Term Support release. - `planned`: This release is planned in the future. -- An optional `release_date` property with the `YYYY-MM-DD` release date of the browser's release. +- An optional `release_date` property with the `YYYY-MM-DD` date of when this version was released or will be released. - An optional `release_notes` property which points to release notes. It needs to be a valid URL. - An optional `engine` property which is the name of the browser's engine. Valid values are `"Blink"`, `"EdgeHTML"`, `"Gecko"`, `"Presto"`, `"Trident"`, `"WebKit"`, and `"V8"`. This property is placed on the individual release as a browser may switch to a different engine (e.g. Microsoft Edge switched to Chrome as its base engine). If `engine` is specified, `engine_version` must also be provided. -- An optional `engine_version` property which is the version of the browser's engine. Depending on the browser, this may or may not differ from the browser version. Required when `engine` is specified. +- An optional `engine_version` property indicating the engine version used in this release. Required when `engine` is specified. #### Initial versions diff --git a/schemas/compat-data-schema.md b/schemas/compat-data-schema.md index 102f8ebf5588cd..d4846324f3ba64 100644 --- a/schemas/compat-data-schema.md +++ b/schemas/compat-data-schema.md @@ -185,7 +185,7 @@ The `__compat` object consists of the following: Each URL must either contain a fragment identifier (e.g. `https://tc39.es/proposal-promise-allSettled/#sec-promise.allsettled`), or else must match the regular-expression pattern `^https://registry.khronos.org/webgl/extensions/[^/]+/` (e.g. `https://registry.khronos.org/webgl/extensions/ANGLE_instanced_arrays/`) or `^https://github.com/WebAssembly/.+` for WebAssembly specs. Each URL must link to a specification published by a standards body or a formal proposal that may lead to such publication. -- An optional `tags` property which is an array of strings allowing to assign tags to the feature. +- An optional `tags` property to assign tags to the feature. Each tag in the array must be namespaced. The currently allowed namespaces are: - `web-features`: A namespace to tag features belonging to a web platform feature group as defined by [web-platform-dx/web-features](https://github.com/web-platform-dx/web-features/blob/main/features/README.md). @@ -375,13 +375,13 @@ Note that you can’t have both `prefix` and `alternative_name`. #### `flags` -An optional array of objects describing flags that must be configured for this browser to support this feature. Usually this array will have one item, but there are cases where two or more flags can be required to activate a feature. An object in the `flags` array consists of three properties: +An array of objects describing feature flags that must be configured for this browser to support this feature. Usually this array will have one item, but there are cases where two or more flags can be required to activate a feature. An object in the `flags` array consists of three properties: -- `type` (mandatory): an enum that indicates the flag type: +- `type` (mandatory): the flag type: - `preference` a flag the user can set (like in `about:config` in Firefox). - `runtime_flag` a flag to be set before starting the browser. -- `name` (mandatory): a string giving the value which the specified flag must be set to for this feature to work. -- `value_to_set` (optional): representing the actual value to set the flag to. It is a string, that may be converted to the right type (that is `true` or `false` for Boolean value, or `4` for an integer value). It doesn't need to be enclosed in backticks. +- `name` (mandatory): the name of the flag or preference to configure. +- `value_to_set` (optional): the value to set the flag to. It is a string, that may be converted to the right type (that is `true` or `false` for Boolean value, or `4` for an integer value). Example for one flag required: @@ -420,13 +420,11 @@ Example for two flags required: #### `impl_url` -An optional changeset/commit URL for the revision which implemented the feature in the source code, or the URL to the bug tracking the implementation, for the associated browser. The presence of an `impl_url` value indicates that the associated browser has implemented the feature or intends to implement the feature. - -For changeset/commit URLs, this is typically a https://trac.webkit.org/changeset/, https://hg.mozilla.org/mozilla-central/rev/, or https://crrev.com/ URL for a changeset with a subject line that will typically be something of the form _"Implement [feature]"_, _"Support [feature]"_, or _"Enable [feature]"_. For bug URLs, this is typically a https://webkit.org/b/, https://bugzil.la/, https://crbug.com/, or https://github.com/GoogleChromeLabs/chromium-bidi/issues/ URL indicating an intent to implement and ship the feature. +A URL or array of URLs linking to the bug or issue that tracks the implementation of this feature. #### `notes` -A string or `array` of strings containing additional information. If there is only one entry, the value of `notes` must simply be a string instead of an array. +A string or `array` of strings containing additional information about the feature's support. If there is only one entry, the value of `notes` must simply be a string instead of an array. Example: @@ -446,7 +444,7 @@ Notes may be formatted in Markdown. Only links, bold, italics, codeblocks, and ` #### `partial_implementation` -A `boolean` value indicating whether or not the implementation of the subfeature deviates from the specification in a way that may cause significant compatibility problems. It defaults to `false` (no interoperability problems expected). If set to `true`, it is [required](../docs/data-guidelines/README.md#partial_implementation-requires-a-note) that you add a note explaining how it diverges from the standard (such as that it implements an old version of the standard). +Set to `true` if the browser's support does not implement mandatory specified behavior, is inconsistent with other browsers, causes confusing feature detection results, and has a demonstrable negative impact on web developers. It defaults to `false`. If set to `true`, it is [required](../docs/data-guidelines/README.md#partial_implementation-requires-a-note) that you add a note explaining how it diverges from the standard. ```json { @@ -482,7 +480,7 @@ The mandatory status property contains information about stability of the featur - `deprecated`: a `boolean` value. - If `deprecated` is `true`, then the feature is no longer recommended. It might be removed in the future or might only be kept for compatibility purposes. Avoid using this functionality. + If `deprecated` is `true`, then the feature is no longer recommended. It might be removed in the future or kept only for compatibility purposes. ```json "__compat": { diff --git a/schemas/public.schema.md b/schemas/public.schema.md index 446841af9b95ba..f4ea118ec70c1c 100644 --- a/schemas/public.schema.md +++ b/schemas/public.schema.md @@ -115,7 +115,7 @@ The `__compat` object describes a feature's compatibility data. It consists of t - An optional `spec_url` property as a URL or an array of URLs, each of which is for a specific part of a specification in which this feature is defined. Each URL must contain a fragment identifier. -- An optional `tags` property which is an array of strings allowing to assign tags to the feature. +- An optional `tags` property to assign tags to the feature. - A mandatory `source_file` property containing the path to the source file that defines this feature, relative to the repository root (e.g. `"api/AbortController.json"`). This is automatically generated at build time. @@ -263,13 +263,13 @@ Note that you can't have both `prefix` and `alternative_name`. #### `flags` -An optional array of objects describing flags that must be configured for this browser to support this feature. Usually this array will have one item, but there are cases where two or more flags can be required to activate a feature. An object in the `flags` array consists of three properties: +An array of objects describing feature flags that must be configured for this browser to support this feature. Usually this array will have one item, but there are cases where two or more flags can be required to activate a feature. An object in the `flags` array consists of three properties: -- `type` (mandatory): an enum that indicates the flag type: +- `type` (mandatory): the flag type: - `preference` a flag the user can set (like in `about:config` in Firefox). - `runtime_flag` a flag to be set before starting the browser. -- `name` (mandatory): a string giving the name of the flag or preference that must be configured. -- `value_to_set` (optional): representing the actual value to set the flag to. It is a string, that may be converted to the right type (that is `true` or `false` for Boolean value, or `4` for an integer value). +- `name` (mandatory): the name of the flag or preference to configure. +- `value_to_set` (optional): the value to set the flag to. It is a string, that may be converted to the right type (that is `true` or `false` for Boolean value, or `4` for an integer value). Example for one flag required: @@ -288,15 +288,15 @@ Example for one flag required: #### `impl_url` -An optional changeset/commit URL (or array of URLs) for the revision which implemented the feature in the source code, or the URL to the bug tracking the implementation, for the associated browser. +A URL or array of URLs linking to the bug or issue that tracks the implementation of this feature. #### `notes` -A string or array of strings containing additional information. In the published data, notes are formatted as HTML. +A string or array of strings containing additional information about the feature's support. In the published data, notes are formatted as HTML. #### `partial_implementation` -A `boolean` value indicating whether or not the implementation of the subfeature deviates from the specification in a way that may cause significant compatibility problems. It defaults to `false` (no interoperability problems expected). If set to `true`, a `notes` field explaining the divergence is always present. +Set to `true` if the browser's support does not implement mandatory specified behavior, is inconsistent with other browsers, causes confusing feature detection results, and has a demonstrable negative impact on web developers. It defaults to `false`. If set to `true`, a `notes` field explaining the divergence is always present. ```json { @@ -338,7 +338,7 @@ The status property contains information about stability of the feature. It is a - `deprecated`: a `boolean` value. - If `deprecated` is `true`, then the feature is no longer recommended. It might be removed in the future or might only be kept for compatibility purposes. Avoid using this functionality. + If `deprecated` is `true`, then the feature is no longer recommended. It might be removed in the future or kept only for compatibility purposes. ```json "__compat": { From b7cccc51216643d807d12d8e7a5d71ca7610a6c3 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 18:02:34 +0100 Subject: [PATCH 58/77] refactor(lint/fixer): refine common-errors fix Accept `InternalSupportBlock` instead of `InternalCompatStatement`. --- lint/fixer/common-errors.js | 26 +++++++++++++------------- lint/fixer/common-errors.test.js | 16 ++++++---------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/lint/fixer/common-errors.js b/lint/fixer/common-errors.js index 430d3329fbfb0a..3188c5b033eaed 100644 --- a/lint/fixer/common-errors.js +++ b/lint/fixer/common-errors.js @@ -3,33 +3,33 @@ import { walk } from '../../utils/index.js'; -/** @import {InternalCompatStatement} from '../../types/index.js' */ +/** @import {InternalSupportBlock} from '../../types/index.js' */ /** - * Fixes common errors in InternalCompatStatements. + * Fixes common errors in an InternalSupportBlock. * * - Replaces `browser: { version_added: "mirror" }` with `browser: "mirror"` * - Wraps `browser: false` with `browser: `{ version_added: false }` - * @param {Pick} compat The compat statement to fix + * @param {InternalSupportBlock} support The support block to fix * @returns {void} */ -export const fixCommonErrorsInCompatStatement = (compat) => { - for (const browser of Object.keys(compat.support)) { - if (compat.support[browser] === false) { - compat.support[browser] = { +export const fixCommonErrorsInSupportBlock = (support) => { + for (const browser of Object.keys(support)) { + if (support[browser] === false) { + support[browser] = { version_added: false, }; } else if ( - typeof compat.support[browser] === 'object' && - JSON.stringify(compat.support[browser]) === '{"version_added":"mirror"}' + typeof support[browser] === 'object' && + JSON.stringify(support[browser]) === '{"version_added":"mirror"}' ) { - compat.support[browser] = 'mirror'; + support[browser] = 'mirror'; } if ( browser == 'ie' && - JSON.stringify(compat.support[browser]) === '{"version_added":false}' + JSON.stringify(support[browser]) === '{"version_added":false}' ) { - Reflect.deleteProperty(compat.support, browser); + Reflect.deleteProperty(support, browser); } } }; @@ -48,7 +48,7 @@ const fixCommonErrors = (filename, actual) => { const bcd = JSON.parse(actual); for (const { compat } of walk(undefined, bcd)) { - fixCommonErrorsInCompatStatement(compat); + fixCommonErrorsInSupportBlock(compat.support); } return JSON.stringify(bcd, null, 2); diff --git a/lint/fixer/common-errors.test.js b/lint/fixer/common-errors.test.js index 18d8cafd809d1a..45fb97b0261541 100644 --- a/lint/fixer/common-errors.test.js +++ b/lint/fixer/common-errors.test.js @@ -3,13 +3,13 @@ import assert from 'node:assert/strict'; -import { fixCommonErrorsInCompatStatement } from './common-errors.js'; +import { fixCommonErrorsInSupportBlock } from './common-errors.js'; /** * @import { InternalSupportBlock } from '../../types/index.js' */ -/** @type {{ input: any; output?: Partial }[]} */ +/** @type {{ input: any; output: InternalSupportBlock }[]} */ const tests = [ // Replace unwrapped "false". { @@ -40,14 +40,10 @@ describe('fix -> common errors', () => { let i = 1; for (const test of tests) { it(`Test #${i}`, () => { - const input = { - support: test.input, - }; - const output = { - support: test.output ?? test.input, - }; - - fixCommonErrorsInCompatStatement(input); + const input = test.input; + const output = test.output ?? test.input; + + fixCommonErrorsInSupportBlock(input); assert.deepStrictEqual(input, output); }); From 93f7b14fc99d2d56a4ee559d2178db10eddf32e5 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 18:08:55 +0100 Subject: [PATCH 59/77] chore(lint): remove unnecessary type cast --- lint/fixer/status.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lint/fixer/status.js b/lint/fixer/status.js index b23da351b84fdf..a6cfea1c786fa7 100644 --- a/lint/fixer/status.js +++ b/lint/fixer/status.js @@ -22,7 +22,7 @@ export const fixStatusValue = (value) => { compat.status.standard_track = true; } - if (!checkExperimental(/** @type {InternalCompatStatement} */ (compat))) { + if (!checkExperimental(compat)) { compat.status.experimental = false; } @@ -69,11 +69,7 @@ const fixStatusFromFile = (filename, actual) => { } return JSON.stringify( - JSON.parse( - actual, - (/** @type {string} */ _key, /** @type {InternalIdentifier} */ value) => - fixStatusValue(value), - ), + JSON.parse(actual, (_key, value) => fixStatusValue(value)), null, 2, ); From dd6452b61a310418f3507a3a9712e4af7ff4dd3c Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 18:27:04 +0100 Subject: [PATCH 60/77] fix(schemas): refine [Internal]Identifier tsType Prevent mixed type of `__compat` key. --- schemas/compat-data.schema.json | 2 +- schemas/public.schema.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index 38308faca5a51f..9644541079b80c 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -258,7 +258,7 @@ "errorMessage": { "additionalProperties": "Feature names can only contain alphanumerical characters or the following symbols: _-$@" }, - "tsType": "{[key: string]: InternalIdentifier} & {__compat?: InternalCompatStatement};" + "tsType": "{__compat?: InternalCompatStatement} & Record, InternalIdentifier>" }, "webextensions_internal_identifier": { diff --git a/schemas/public.schema.json b/schemas/public.schema.json index 2378441e33d27b..bcbcf39c8ae65b 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -401,7 +401,7 @@ "^(?!__compat)[a-zA-Z_0-9-$@]*$": { "$ref": "#/definitions/identifier" } }, "additionalProperties": false, - "tsType": "{__compat?: CompatStatement} & {[key: string]: Identifier}" + "tsType": "{__compat?: InternalCompatStatement} & Record, InternalIdentifier>" } }, From 662b144cdda274c440a6a9bb2af432076e76a181 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 18:28:00 +0100 Subject: [PATCH 61/77] chore(lint): replace any type --- lint/fixer/status.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lint/fixer/status.test.js b/lint/fixer/status.test.js index 41df0a75cda3b3..50bcd3467f725a 100644 --- a/lint/fixer/status.test.js +++ b/lint/fixer/status.test.js @@ -5,7 +5,9 @@ import assert from 'node:assert/strict'; import { fixStatusValue } from './status.js'; -/** @type {{ name: string; input: *; output: * }[]} */ +/** @import {InternalIdentifier} from '../../types/internal.js' */ + +/** @type {{ name: string; input: InternalIdentifier; output: InternalIdentifier }[]} */ const tests = [ { name: 'should unset experimental when feature is deprecated', From fa35b2187ec089d6e99357bec23601623f55755a Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Feb 2026 18:39:53 +0100 Subject: [PATCH 62/77] fixup! chore(lint): remove unnecessary type cast --- lint/linter/test-consistency.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/lint/linter/test-consistency.js b/lint/linter/test-consistency.js index ea6941160c7b32..5ee119e2a29b9f 100644 --- a/lint/linter/test-consistency.js +++ b/lint/linter/test-consistency.js @@ -125,15 +125,13 @@ export class ConsistencyChecker { // Test whether sub-features are supported when basic support is not implemented // For all unsupported browsers (basic support == false), sub-features should be set to false - const unsupportedInParent = this.extractUnsupportedBrowsers( - /** @type {InternalCompatStatement} */ (data.__compat), - ); + const unsupportedInParent = this.extractUnsupportedBrowsers(data.__compat); /** @type {Partial>} */ let inconsistentSubfeaturesByBrowser = {}; subfeatures.forEach((subfeature) => { const unsupportedInChild = this.extractUnsupportedBrowsers( - /** @type {InternalIdentifier} */ (query(subfeature, data)).__compat, + query(subfeature, data).__compat, ); const browsers = /** @type {BrowserName[]} */ ( @@ -146,8 +144,7 @@ export class ConsistencyChecker { browser, ); const subfeature_value = this.getVersionAdded( - /** @type {InternalIdentifier} */ (query(subfeature, data)).__compat - ?.support, + query(subfeature, data).__compat?.support, browser, ); if (feature_value === subfeature_value) { @@ -187,9 +184,7 @@ export class ConsistencyChecker { for (const subfeature of subfeatures) { for (const browser of supportInParent) { - const subfeatureData = /** @type {InternalIdentifier} */ ( - query(subfeature, data) - ); + const subfeatureData = query(subfeature, data); if ( subfeatureData.__compat?.support[browser] != undefined && this.isVersionAddedGreater( @@ -423,7 +418,7 @@ export default { */ check: (logger, { data }) => { const checker = new ConsistencyChecker(); - const allErrors = checker.check(/** @type {InternalCompatData} */ (data)); + const allErrors = checker.check(data); for (const { path, errors } of allErrors) { for (const { type, browser, parentValue, subfeatures } of errors) { From 1ba780eafee7ad3346891f9b2cf330dde2a6a45a Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 17 Mar 2026 10:55:25 +0100 Subject: [PATCH 63/77] chore(schemas): IE 6 omits ".0" --- schemas/compat-data-schema.md | 2 +- schemas/public.schema.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/schemas/compat-data-schema.md b/schemas/compat-data-schema.md index d4846324f3ba64..d267876a64d667 100644 --- a/schemas/compat-data-schema.md +++ b/schemas/compat-data-schema.md @@ -242,7 +242,7 @@ Example of a `support` compat object (with 1 entry, array omitted): ```json "support": { - "ie": { "version_added": "6.0" } + "ie": { "version_added": "6" } } ``` diff --git a/schemas/public.schema.md b/schemas/public.schema.md index f4ea118ec70c1c..134ad2a0aab1c8 100644 --- a/schemas/public.schema.md +++ b/schemas/public.schema.md @@ -147,7 +147,7 @@ Example of a `support` compat object (with 1 entry, array omitted): ```json "support": { - "ie": { "version_added": "6.0" } + "ie": { "version_added": "6" } } ``` From 2f2b55696d337abc0b2bb91d70a5eb646a68e6e9 Mon Sep 17 00:00:00 2001 From: Claas Augner <495429+caugner@users.noreply.github.com> Date: Tue, 17 Mar 2026 11:01:19 +0100 Subject: [PATCH 64/77] chore(schemas): fix webextensions tsType --- schemas/compat-data.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index 9644541079b80c..b12dfe5930cb73 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -276,7 +276,7 @@ "errorMessage": { "additionalProperties": "Feature names can only contain alphanumerical characters or the following symbols: _-$@" }, - "tsType": "{[key: string]: InternalIdentifier} & {__compat?: InternalCompatStatement}" + "tsType": "{__compat?: InternalCompatStatement} & Record, InternalIdentifier>" } }, From 467fd0eb0d87a5ea7c8f57b1c429957197052cde Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 17 Mar 2026 14:50:51 +0100 Subject: [PATCH 65/77] fix(lint): revert accidental var rename --- lint/linter/test-consistency.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lint/linter/test-consistency.js b/lint/linter/test-consistency.js index 1099499d583591..7c30243a4961bc 100644 --- a/lint/linter/test-consistency.js +++ b/lint/linter/test-consistency.js @@ -394,31 +394,28 @@ export class ConsistencyChecker { /** * Get all of the browsers within the data and pass the data to the callback. - * @param {InternalCompatStatement | undefined} InternalcompatData The compat data to process + * @param {InternalCompatStatement | undefined} compatData The compat data to process * @param {(browserData: InternalSimpleSupportStatement) => boolean} callback The function to pass the data to * @returns {BrowserName[]} The list of browsers using the callback as a filter */ - extractBrowsers(InternalcompatData, callback) { - if (!InternalcompatData) { + extractBrowsers(compatData, callback) { + if (!compatData) { return []; } return /** @type {BrowserName[]} */ (Object.keys(bcd.browsers)).filter( (browser) => { - if (!(browser in InternalcompatData.support)) { + if (!(browser in compatData.support)) { return callback({ version_added: false }); } let browserData = /** @type {InternalSupportStatement | undefined} */ ( - InternalcompatData.support[browser] + compatData.support[browser] ); if ( /** @type {InternalSupportStatement} */ (browserData) === 'mirror' ) { - browserData = this.#resolveMirror( - browser, - InternalcompatData.support, - ); + browserData = this.#resolveMirror(browser, compatData.support); } if (Array.isArray(browserData)) { From fbd209edb298035cdfa43b716b1c7cddfef9c3e8 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 17 Mar 2026 15:07:01 +0100 Subject: [PATCH 66/77] fix(scripts): add missing await --- scripts/generate-public-types.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate-public-types.js b/scripts/generate-public-types.js index 0f45827ad5ce11..b54c5c6e0fd984 100644 --- a/scripts/generate-public-types.js +++ b/scripts/generate-public-types.js @@ -60,7 +60,7 @@ const compile = async ( }; if (esMain(import.meta)) { - compile(); + await compile(); } export default compile; From 2227be72b329cadd9bd95b1091316dc5d16b128e Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 17 Mar 2026 15:08:22 +0100 Subject: [PATCH 67/77] fix(schema): avoid internal type in public schema --- schemas/public.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemas/public.schema.json b/schemas/public.schema.json index bcbcf39c8ae65b..49b183a2d1bc5b 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -401,7 +401,7 @@ "^(?!__compat)[a-zA-Z_0-9-$@]*$": { "$ref": "#/definitions/identifier" } }, "additionalProperties": false, - "tsType": "{__compat?: InternalCompatStatement} & Record, InternalIdentifier>" + "tsType": "{__compat?: CompatStatement} & Record, Identifier>" } }, From cd09fba157bd1f34fea29b4ce70119615d2d8027 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 17 Mar 2026 15:09:34 +0100 Subject: [PATCH 68/77] fix(lint): revert accidental rename in JSDoc --- lint/linter/test-consistency.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lint/linter/test-consistency.js b/lint/linter/test-consistency.js index 7c30243a4961bc..3b420fd3a5e176 100644 --- a/lint/linter/test-consistency.js +++ b/lint/linter/test-consistency.js @@ -108,8 +108,8 @@ export class ConsistencyChecker { } /** - * Get the subfeatures of an Internalidentifier - * @param {InternalIdentifier} data The Internalidentifier + * Get the subfeatures of an identifier + * @param {InternalIdentifier} data The identifier * @returns {string[]} The subfeatures */ getSubfeatures(data) { From 9193cedf0975a8757ac0981ebeefdd8a0007be00 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 17 Mar 2026 15:22:38 +0100 Subject: [PATCH 69/77] chore(generate-internal-types): sync with generate-public-types Move extra types to `types/index.d.ts`. --- scripts/generate-internal-types.js | 140 ++++------------------------- scripts/lib/data-folders.js | 2 +- types/index.d.ts | 18 ++++ 3 files changed, 36 insertions(+), 124 deletions(-) diff --git a/scripts/generate-internal-types.js b/scripts/generate-internal-types.js index efa8a7ef754774..52c2965bd55c9e 100644 --- a/scripts/generate-internal-types.js +++ b/scripts/generate-internal-types.js @@ -4,140 +4,34 @@ /* c8 ignore start */ import fs from 'node:fs/promises'; -import path from 'node:path'; import { fileURLToPath } from 'node:url'; import esMain from 'es-main'; -import { fdir } from 'fdir'; import { compileFromFile } from 'json-schema-to-typescript'; import { spawn } from '../utils/index.js'; -import extend from './lib/extend.js'; - -const dirname = fileURLToPath(new URL('.', import.meta.url)); +const root = fileURLToPath(new URL('.', import.meta.url)); const opts = { - bannerComment: '', + bannerComment: + '/* This file is a part of @mdn/browser-compat-data\n * See LICENSE file for more information. */\n\n/**\n* This file was automatically generated by json-schema-to-typescript.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source schema files in\n* schemas/*, and run "npm run gentypes" to regenerate this file.\n*/', unreachableDefinitions: true, }; -const header = - '/* This file is a part of @mdn/browser-compat-data\n * See LICENSE file for more information. */\n\n/**\n* This file was automatically generated by json-schema-to-typescript.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source schema files in\n* schemas/*, and run "npm run gentypes" to regenerate this file.\n*/'; - -const compatDataTypes = { - api: 'Contains data for each [Web API](https://developer.mozilla.org/docs/Web/API) interface.', - browsers: 'Contains data for each known and tracked browser/engine.', - css: 'Contains data for [CSS](https://developer.mozilla.org/docs/Web/CSS) properties, selectors, and at-rules.', - html: 'Contains data for [HTML](https://developer.mozilla.org/docs/Web/HTML) elements, attributes, and global attributes.', - http: 'Contains data for [HTTP](https://developer.mozilla.org/docs/Web/HTTP) headers, statuses, and methods.', - javascript: - 'Contains data for [JavaScript](https://developer.mozilla.org/docs/Web/JavaScript) built-in Objects, statement, operators, and other ECMAScript language features.', - manifests: - 'Contains data for various manifests, such as the [Web Application Manifest](https://developer.mozilla.org/docs/Web/Progressive_web_apps/manifest).', - mathml: - 'Contains data for [MathML](https://developer.mozilla.org/docs/Web/MathML) elements, attributes, and global attributes.', - mediatypes: - 'Contains data for [Media types](https://developer.mozilla.org/docs/Web/HTTP/Guides/MIME_types).', - svg: 'Contains data for [SVG](https://developer.mozilla.org/docs/Web/SVG) elements, attributes, and global attributes.', - webassembly: - 'Contains data for [WebAssembly](https://developer.mozilla.org/docs/WebAssembly) features.', - webdriver: - 'Contains data for [WebDriver](https://developer.mozilla.org/docs/Web/WebDriver) commands.', - webextensions: - 'Contains data for [WebExtensions](https://developer.mozilla.org/Add-ons/WebExtensions) JavaScript APIs and manifest keys.', -}; - -/** - * Generate the browser names TypeScript - * @returns {Promise} The stringified TypeScript typedef - */ -const generateBrowserNames = async () => { - // Load browser data independently of index.ts, since index.ts depends - // on the output of this script - const browserData = { browsers: {} }; - - const paths = /** @type {string[]} */ ( - new fdir() - .withBasePath() - .filter((fp) => fp.endsWith('.json')) - .crawl(path.join(dirname, '..', 'browsers')) - .sync() - ); - - for (const fp of paths) { - try { - const contents = await fs.readFile(fp); - extend(browserData, JSON.parse(contents.toString('utf8'))); - } catch { - // Skip invalid JSON. Tests will flag the problem separately. - continue; - } - } - - // Generate BrowserName type - const browsers = Object.keys(browserData.browsers); - return `/**\n * The names of the known browsers.\n */\nexport type BrowserName = ${browsers - .map((b) => `"${b}"`) - .join(' | ')};`; -}; - -/** - * Generate the CompatData TypeScript - * @returns {string} The stringified TypeScript typedef - */ -const generateCompatDataTypes = () => { - const props = Object.entries(compatDataTypes).map( - (t) => - ` /**\n * ${t[1]}\n */\n ${t[0]}: ${ - t[0] === 'browsers' ? 'Browsers' : 'InternalIdentifier' - };`, - ); - - return `export interface InternalCompatData {\n${props.join('\n\n')}\n}\n`; -}; - /** * Transform the TypeScript to remove unneeded bits of typedefs - * @param {string} browserTS Typedefs for BrowserName - * @param {string} compatTS Typedefs for CompatData + * @param {string} ts Typedefs * @returns {string} Updated typedefs */ -const transformTS = (browserTS, compatTS) => { - // XXX Temporary until the following PR is merged and released: - // https://github.com/bcherny/json-schema-to-typescript/pull/456 - let ts = browserTS + '\n\n' + compatTS; - +const transformTS = (ts) => { + // Remove "interface was referenced" comments. ts = ts .replace( - 'export interface BrowserDataFile {\n browsers?: Browsers;\n}', - '', - ) - .replace('export interface CompatDataFile {}', '') - .replace( - /\/\*\*\n \* This interface was referenced by `CompatDataFile`'s JSON-Schema definition\n \* via the `patternProperty` "\^webextensions\*\$"\.\n \*\/\nexport type WebextensionsIdentifier = Identifier;\n/, - '', - ) - .replace( - /\/\*\*\n \* This interface was referenced by `CompatDataFile`'s JSON-Schema\n \* via the `definition` "webextensions_identifier"\.\n \*\/\nexport type WebextensionsIdentifier1 = .*;\n/, - '', - ) - .replace( - /\/\*\*\n \* This interface was referenced by `CompatDataFile`'s JSON-Schema\n \* via the `definition` "spec_url_value"\.\n \*\/\nexport type SpecUrlValue = string;\n/, - '', - ) - .replace( - /\/\*\*\n \* This interface was referenced by `CompatDataFile`'s JSON-Schema\n \* via the `definition` "impl_url_value"\.\n \*\/\nexport type ImplUrlValue = string;\n/, + /( \*\n)? \* This interface was referenced by [^\n]+\n \* via the [^\n]+\n/g, '', ) - .replace( - '/**\n * This interface was referenced by `CompatDataFile`\'s JSON-Schema\n * via the `definition` "support_block".\n */\nexport type SupportBlock1 = Partial>;\n', - '', - ) - .replace( - /\/\*\*\n \* This interface was referenced by `CompatDataFile`'s JSON-Schema\n \* via the `definition` "status_block"\.\n \*\/\nexport interface StatusBlock1 {(.*\n)*}\n/, - '', - ); + .replace(/\/\*\*\n \*\/\n/g, ''); return ts; }; @@ -155,16 +49,16 @@ const compile = async ( opts, ); - const ts = [ - header, - await generateBrowserNames(), - 'export type VersionValue = string | false;', - transformTS(browserTS, compatTS), - generateCompatDataTypes(), - ].join('\n\n'); - await fs.writeFile(destination, ts); + let ts = browserTS + '\n\n' + compatTS; + + ts = transformTS(ts); + + const file = + destination instanceof URL ? destination : new URL(destination, root); + + await fs.writeFile(file, ts); spawn('tsc', ['--skipLibCheck', '../types/internal.d.ts'], { - cwd: dirname, + cwd: root, stdio: 'inherit', }); }; diff --git a/scripts/lib/data-folders.js b/scripts/lib/data-folders.js index da3d82798f204d..adcf5e201e06f0 100644 --- a/scripts/lib/data-folders.js +++ b/scripts/lib/data-folders.js @@ -1,7 +1,7 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -/** @import { InternalCompatData } from "../../types/internal.js" */ +/** @import { InternalCompatData } from "../../types/index.js" */ export const dataFoldersMinusBrowsers = [ 'api', diff --git a/types/index.d.ts b/types/index.d.ts index 15baf46ac8c53e..61beb56d0fdb5e 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -14,3 +14,21 @@ export type InternalDataType = | BrowserStatement | InternalCompatStatement | InternalIdentifier; + +export interface InternalCompatData { + api: InternalIdentifier; + browsers: Browsers; + css: InternalIdentifier; + html: InternalIdentifier; + http: InternalIdentifier; + javascript: InternalIdentifier; + manifests: InternalIdentifier; + mathml: InternalIdentifier; + mediatypes: InternalIdentifier; + svg: InternalIdentifier; + webassembly: InternalIdentifier; + webdriver: InternalIdentifier; + webextensions: InternalIdentifier; +} + +export type VersionValue = string | false; From 6ed2a4bd162a2325781c7dd8697ac17e00fa57e0 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 17 Mar 2026 15:32:41 +0100 Subject: [PATCH 70/77] refacotr(scripts): merge generate-types scripts --- scripts/build/index.js | 4 +- scripts/generate-internal-types.js | 72 ------------------------- scripts/generate-public-types.js | 68 ----------------------- scripts/generate-types.js | 86 +++++++++++++++++++++++++++--- 4 files changed, 82 insertions(+), 148 deletions(-) delete mode 100644 scripts/generate-internal-types.js delete mode 100644 scripts/generate-public-types.js diff --git a/scripts/build/index.js b/scripts/build/index.js index d5129948d755c2..b18904d59b95b1 100644 --- a/scripts/build/index.js +++ b/scripts/build/index.js @@ -11,7 +11,7 @@ import stringify from 'fast-json-stable-stringify'; import { compareVersions } from 'compare-versions'; import { marked } from 'marked'; -import compileTS from '../generate-public-types.js'; +import { compilePublicTypes } from '../generate-types.js'; import schema from '../../schemas/public.schema.json' with { type: 'json' }; import { createAjv } from '../lib/ajv.js'; import { walk } from '../../utils/index.js'; @@ -308,7 +308,7 @@ export * from "./types.js";`; await fs.writeFile(destImport, content); logWrite(destImport, 'ESM types'); - await compileTS('schemas/public.schema.json', destTypes); + await compilePublicTypes('schemas/public.schema.json', destTypes); logWrite(destTypes, 'data types'); }; diff --git a/scripts/generate-internal-types.js b/scripts/generate-internal-types.js deleted file mode 100644 index 52c2965bd55c9e..00000000000000 --- a/scripts/generate-internal-types.js +++ /dev/null @@ -1,72 +0,0 @@ -/* This file is a part of @mdn/browser-compat-data - * See LICENSE file for more information. */ - -/* c8 ignore start */ - -import fs from 'node:fs/promises'; -import { fileURLToPath } from 'node:url'; - -import esMain from 'es-main'; -import { compileFromFile } from 'json-schema-to-typescript'; - -import { spawn } from '../utils/index.js'; - -const root = fileURLToPath(new URL('.', import.meta.url)); - -const opts = { - bannerComment: - '/* This file is a part of @mdn/browser-compat-data\n * See LICENSE file for more information. */\n\n/**\n* This file was automatically generated by json-schema-to-typescript.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source schema files in\n* schemas/*, and run "npm run gentypes" to regenerate this file.\n*/', - unreachableDefinitions: true, -}; - -/** - * Transform the TypeScript to remove unneeded bits of typedefs - * @param {string} ts Typedefs - * @returns {string} Updated typedefs - */ -const transformTS = (ts) => { - // Remove "interface was referenced" comments. - ts = ts - .replace( - /( \*\n)? \* This interface was referenced by [^\n]+\n \* via the [^\n]+\n/g, - '', - ) - .replace(/\/\*\*\n \*\/\n/g, ''); - - return ts; -}; - -/** - * Compile the TypeScript typedefs from the schema JSON - * @param {URL | string} [destination] Output destination - */ -const compile = async ( - destination = new URL('../types/internal.d.ts', import.meta.url), -) => { - const browserTS = await compileFromFile('schemas/browsers.schema.json', opts); - const compatTS = await compileFromFile( - 'schemas/compat-data.schema.json', - opts, - ); - - let ts = browserTS + '\n\n' + compatTS; - - ts = transformTS(ts); - - const file = - destination instanceof URL ? destination : new URL(destination, root); - - await fs.writeFile(file, ts); - spawn('tsc', ['--skipLibCheck', '../types/internal.d.ts'], { - cwd: root, - stdio: 'inherit', - }); -}; - -if (esMain(import.meta)) { - await compile(); -} - -export default compile; - -/* c8 ignore stop */ diff --git a/scripts/generate-public-types.js b/scripts/generate-public-types.js deleted file mode 100644 index b54c5c6e0fd984..00000000000000 --- a/scripts/generate-public-types.js +++ /dev/null @@ -1,68 +0,0 @@ -/* This file is a part of @mdn/browser-compat-data - * See LICENSE file for more information. */ - -/* c8 ignore start */ - -import fs from 'node:fs/promises'; -import { fileURLToPath } from 'node:url'; - -import esMain from 'es-main'; -import { compileFromFile } from 'json-schema-to-typescript'; - -import { spawn } from '../utils/index.js'; - -const root = new URL('..', import.meta.url); - -const opts = { - bannerComment: - '/* This file is a part of @mdn/browser-compat-data\n * See LICENSE file for more information. */\n\n/**\n* This file was automatically generated by json-schema-to-typescript.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source schema files in\n* schemas/*, and run "npm run gentypes" to regenerate this file.\n*/', - unreachableDefinitions: true, -}; - -/** - * Transform the TypeScript to remove unneeded bits of typedefs - * @param {string} ts Typedefs - * @returns {string} Updated typedefs - */ -const transformTS = (ts) => { - // Remove "interface was referenced" comments. - ts = ts - .replace( - /( \*\n)? \* This interface was referenced by [^\n]+\n \* via the [^\n]+\n/g, - '', - ) - .replace(/\/\*\*\n \*\/\n/g, ''); - - return ts; -}; - -/** - * Compile the TypeScript typedefs from the schema JSON - * @param {string} source - JSON schema source - * @param {URL | string} destination - Output destination - */ -const compile = async ( - source = 'schemas/public.schema.json', - destination = 'types/public.d.ts', -) => { - let ts = await compileFromFile(source, opts); - - ts = transformTS(ts); - - const file = - destination instanceof URL ? destination : new URL(destination, root); - - await fs.writeFile(file, ts); - spawn('tsc', ['--skipLibCheck', fileURLToPath(file)], { - cwd: fileURLToPath(root), - stdio: 'inherit', - }); -}; - -if (esMain(import.meta)) { - await compile(); -} - -export default compile; - -/* c8 ignore stop */ diff --git a/scripts/generate-types.js b/scripts/generate-types.js index 3f98f7e65e6346..585a5bf6c3b27b 100644 --- a/scripts/generate-types.js +++ b/scripts/generate-types.js @@ -1,20 +1,90 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -import { rm } from 'node:fs/promises'; +/* c8 ignore start */ + +import fs, { rm } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; import esMain from 'es-main'; +import { compileFromFile } from 'json-schema-to-typescript'; -import compileInternal from './generate-internal-types.js'; -import compilePublic from './generate-public-types.js'; +import { spawn } from '../utils/index.js'; -/* c8 ignore start */ +const root = new URL('..', import.meta.url); + +const opts = { + bannerComment: + '/* This file is a part of @mdn/browser-compat-data\n * See LICENSE file for more information. */\n\n/**\n* This file was automatically generated by json-schema-to-typescript.\n* DO NOT MODIFY IT BY HAND. Instead, modify the source schema files in\n* schemas/*, and run "npm run gentypes" to regenerate this file.\n*/', + unreachableDefinitions: true, +}; + +/** + * Transform the TypeScript to remove unneeded bits of typedefs + * @param {string} ts Typedefs + * @returns {string} Updated typedefs + */ +const transformTS = (ts) => { + // Remove "interface was referenced" comments. + ts = ts + .replace( + /( \*\n)? \* This interface was referenced by [^\n]+\n \* via the [^\n]+\n/g, + '', + ) + .replace(/\/\*\*\n \*\/\n/g, ''); + + return ts; +}; + +/** + * Compile TypeScript typedefs from one or more schema JSON files + * @param {string | string[]} sources - JSON schema source(s) + * @param {URL | string} destination - Output destination + */ +const compileTypesFromSchemas = async (sources, destination) => { + const sourceArray = Array.isArray(sources) ? sources : [sources]; + const parts = await Promise.all( + sourceArray.map((s) => compileFromFile(s, opts)), + ); + const ts = transformTS(parts.join('\n\n')); + + const file = + destination instanceof URL ? destination : new URL(destination, root); + + await fs.writeFile(file, ts); + spawn('tsc', ['--skipLibCheck', fileURLToPath(file)], { + cwd: fileURLToPath(root), + stdio: 'inherit', + }); +}; + +/** + * Compile the public TypeScript typedefs + * @param {string} [source] - JSON schema source + * @param {URL | string} [destination] - Output destination + * @returns {Promise} + */ +export const compilePublicTypes = ( + source = 'schemas/public.schema.json', + destination = 'types/public.d.ts', +) => compileTypesFromSchemas(source, destination); + +/** + * Compile the internal TypeScript typedefs + * @param {URL | string} [destination] - Output destination + * @returns {Promise} + */ +export const compileInternalTypes = (destination = 'types/internal.d.ts') => + compileTypesFromSchemas( + ['schemas/browsers.schema.json', 'schemas/compat-data.schema.json'], + destination, + ); /** * Cleans up old types. */ const cleanupObsolete = async () => { - const path = new URL('../types/types.d.ts', import.meta.url); + const path = new URL('types/types.d.ts', root); try { await rm(path); } catch { @@ -23,7 +93,11 @@ const cleanupObsolete = async () => { }; if (esMain(import.meta)) { - await Promise.all([compileInternal(), compilePublic(), cleanupObsolete()]); + await Promise.all([ + compileInternalTypes(), + compilePublicTypes(), + cleanupObsolete(), + ]); } /* c8 ignore stop */ From 3d9f3e5fcc5c9b1653afada9e5c5d12f95a41d68 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Mar 2026 16:24:48 +0100 Subject: [PATCH 71/77] fix(scripts/build): use CompatData, not InternalCompatData --- scripts/build/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build/index.js b/scripts/build/index.js index b18904d59b95b1..3dd461ae8bd852 100644 --- a/scripts/build/index.js +++ b/scripts/build/index.js @@ -296,9 +296,9 @@ const writeTypeScript = async () => { const content = `/* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ -import { InternalCompatData } from "./types.js"; +import { CompatData } from "./types.js"; -declare var bcd: InternalCompatData; +declare var bcd: CompatData; export default bcd; export * from "./types.js";`; From 353a592d838224e996cc6e04455355faafc54fe8 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Mar 2026 16:28:25 +0100 Subject: [PATCH 72/77] fix(types): add missing imports --- types/index.d.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/types/index.d.ts b/types/index.d.ts index 61beb56d0fdb5e..812333c51f8c32 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,6 +1,14 @@ /* This file is a part of @mdn/browser-compat-data * See LICENSE file for more information. */ +import type { + BrowserStatement, + InternalCompatStatement, + InternalIdentifier, + Browsers, +} from './internal.js'; +import type { CompatData, CompatStatement, Identifier } from './public.js'; + export type * from './internal.js'; export type DataType = From 0aedae90e7dd0b92e8400b93f16cf8a47199f588 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Mar 2026 16:32:50 +0100 Subject: [PATCH 73/77] fix(schemas): avoid VersionValue in compat data --- schemas/compat-data.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index b12dfe5930cb73..9297e7a67f722f 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -16,7 +16,7 @@ "const": false } ], - "tsType": "VersionValue" + "tsType": "string | false" }, "version_removed": { "description": "Browser version that removed this feature.", From d7059c72a9f0e69df35353cb589996a622ea706d Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 19 Mar 2026 18:16:16 +0100 Subject: [PATCH 74/77] chore(schemas): revert Identifier tsType --- lint/fixer/status.test.js | 5 +++-- lint/linter/test-consistency.js | 13 +++++++++---- schemas/compat-data.schema.json | 4 ++-- schemas/public.schema.json | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lint/fixer/status.test.js b/lint/fixer/status.test.js index 50bcd3467f725a..0fbbc134bd5ba8 100644 --- a/lint/fixer/status.test.js +++ b/lint/fixer/status.test.js @@ -7,7 +7,6 @@ import { fixStatusValue } from './status.js'; /** @import {InternalIdentifier} from '../../types/internal.js' */ -/** @type {{ name: string; input: InternalIdentifier; output: InternalIdentifier }[]} */ const tests = [ { name: 'should unset experimental when feature is deprecated', @@ -142,7 +141,9 @@ const tests = [ describe('fixStatus', () => { for (const test of tests) { it(test.name, () => { - const result = fixStatusValue(test.input); + const result = fixStatusValue( + /** @type {InternalIdentifier} */ (test.input), + ); assert.deepStrictEqual(result, test.output); }); diff --git a/lint/linter/test-consistency.js b/lint/linter/test-consistency.js index 3b420fd3a5e176..ca1cd4981b5965 100644 --- a/lint/linter/test-consistency.js +++ b/lint/linter/test-consistency.js @@ -153,8 +153,11 @@ export class ConsistencyChecker { let inconsistentSubfeaturesByBrowser = {}; subfeatures.forEach((subfeature) => { + const subfeatureId = /** @type {InternalIdentifier} */ ( + query(subfeature, data) + ); const unsupportedInChild = this.extractUnsupportedBrowsers( - query(subfeature, data).__compat, + subfeatureId.__compat, ); const browsers = /** @type {BrowserName[]} */ ( @@ -167,7 +170,7 @@ export class ConsistencyChecker { browser, ); const subfeature_value = this.getVersionAdded( - query(subfeature, data).__compat?.support, + subfeatureId.__compat?.support, browser, ); if (feature_value === subfeature_value) { @@ -207,7 +210,9 @@ export class ConsistencyChecker { for (const subfeature of subfeatures) { for (const browser of supportInParent) { - const subfeatureData = query(subfeature, data); + const subfeatureData = /** @type {InternalIdentifier} */ ( + query(subfeature, data) + ); if ( subfeatureData.__compat?.support[browser] != undefined && this.isVersionAddedGreater( @@ -441,7 +446,7 @@ export default { */ check: (logger, { data }) => { const checker = new ConsistencyChecker(); - const allErrors = checker.check(data); + const allErrors = checker.check(/** @type {InternalCompatData} */ (data)); for (const { path, errors } of allErrors) { for (const { type, browser, parentValue, subfeatures } of errors) { diff --git a/schemas/compat-data.schema.json b/schemas/compat-data.schema.json index 9297e7a67f722f..aa3ec06696f556 100644 --- a/schemas/compat-data.schema.json +++ b/schemas/compat-data.schema.json @@ -258,7 +258,7 @@ "errorMessage": { "additionalProperties": "Feature names can only contain alphanumerical characters or the following symbols: _-$@" }, - "tsType": "{__compat?: InternalCompatStatement} & Record, InternalIdentifier>" + "tsType": "{[key: string]: InternalIdentifier} & {__compat?: InternalCompatStatement}" }, "webextensions_internal_identifier": { @@ -276,7 +276,7 @@ "errorMessage": { "additionalProperties": "Feature names can only contain alphanumerical characters or the following symbols: _-$@" }, - "tsType": "{__compat?: InternalCompatStatement} & Record, InternalIdentifier>" + "tsType": "{[key: string]: InternalIdentifier} & {__compat?: InternalCompatStatement}" } }, diff --git a/schemas/public.schema.json b/schemas/public.schema.json index 49b183a2d1bc5b..bc935235161bd9 100644 --- a/schemas/public.schema.json +++ b/schemas/public.schema.json @@ -401,7 +401,7 @@ "^(?!__compat)[a-zA-Z_0-9-$@]*$": { "$ref": "#/definitions/identifier" } }, "additionalProperties": false, - "tsType": "{__compat?: CompatStatement} & Record, Identifier>" + "tsType": "{[key: string]: Identifier} & {__compat?: CompatStatement}" } }, From 82165ddf63d5b58018124f14aa827e36170521dc Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 20 Mar 2026 15:09:52 +0100 Subject: [PATCH 75/77] chore(types): enable skipLibCheck --- .lefthook.yml | 1 + types/tsconfig.json | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 types/tsconfig.json diff --git a/.lefthook.yml b/.lefthook.yml index 44aaba7f363ea5..66937ca5756dc9 100644 --- a/.lefthook.yml +++ b/.lefthook.yml @@ -18,6 +18,7 @@ pre-commit: - .vscode/*.json - browsers/*.json - schemas/*.json + - types/*.json run: npm run lint:fix {staged_files} && git add lint/common/standard-track-exceptions.txt stage_fixed: true diff --git a/types/tsconfig.json b/types/tsconfig.json new file mode 100644 index 00000000000000..7fcf46a791f02b --- /dev/null +++ b/types/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "skipLibCheck": false + } +} From c4332817d02e29f9fcd210b11688b008f63c6b32 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Wed, 25 Mar 2026 13:05:20 +0100 Subject: [PATCH 76/77] chore(build): add tsconfig with skipLibCheck = false --- .gitignore | 2 -- .lefthook.yml | 1 + build/.gitignore | 4 ++++ build/.npmignore | 3 +++ build/tsconfig.json | 6 ++++++ 5 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 build/.gitignore create mode 100644 build/.npmignore create mode 100644 build/tsconfig.json diff --git a/.gitignore b/.gitignore index 403e515d024c13..a8b01fbfa70a99 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,6 @@ __enumerating__*/ .idea *.log /node_modules/ -/build/ -!/scripts/build/ yarn.lock .eslintcache .nyc_output/ diff --git a/.lefthook.yml b/.lefthook.yml index 66937ca5756dc9..ef97f75455aa1c 100644 --- a/.lefthook.yml +++ b/.lefthook.yml @@ -16,6 +16,7 @@ pre-commit: glob: "**/*.json" exclude: - .vscode/*.json + - build/*.json - browsers/*.json - schemas/*.json - types/*.json diff --git a/build/.gitignore b/build/.gitignore new file mode 100644 index 00000000000000..0944627213403a --- /dev/null +++ b/build/.gitignore @@ -0,0 +1,4 @@ +* +!.gitignore +!.npmignore +!tsconfig.json diff --git a/build/.npmignore b/build/.npmignore new file mode 100644 index 00000000000000..6d887ed6f43e19 --- /dev/null +++ b/build/.npmignore @@ -0,0 +1,3 @@ +.gitignore +.npmignore +tsconfig.json diff --git a/build/tsconfig.json b/build/tsconfig.json new file mode 100644 index 00000000000000..5547a1ece1517d --- /dev/null +++ b/build/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "skipLibCheck": false, + } +} From 93c02898d3f8455cb621ce703610e265b2b251a5 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 2 Apr 2026 20:01:04 +0200 Subject: [PATCH 77/77] chore(scripts/generate-types): check types with lib check --- scripts/generate-types.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate-types.js b/scripts/generate-types.js index 762f809baa813c..0e3382d6c8ac52 100644 --- a/scripts/generate-types.js +++ b/scripts/generate-types.js @@ -52,7 +52,7 @@ const compileTypesFromSchemas = async (sources, destination) => { destination instanceof URL ? destination : new URL(destination, root); await fs.writeFile(file, ts); - spawn('tsc', ['--skipLibCheck', '--ignoreConfig', fileURLToPath(file)], { + spawn('tsc', ['--ignoreConfig', fileURLToPath(file)], { cwd: fileURLToPath(root), stdio: 'inherit', });