diff --git a/api/GPUDevice.json b/api/GPUDevice.json
index beed6402f2003b..59d885775ffd91 100644
--- a/api/GPUDevice.json
+++ b/api/GPUDevice.json
@@ -384,7 +384,7 @@
},
"storageTexture_access_read-write_read-only": {
"__compat": {
- "description": "read-write and read-only storageTexture.access",
+ "description": "`read-write` and `read-only` `storageTexture.access`",
"spec_url": "https://gpuweb.github.io/gpuweb/wgsl/#memory-access-mode",
"tags": [
"web-features:webgpu"
@@ -427,7 +427,7 @@
},
"texture_rgb10a2uint": {
"__compat": {
- "description": "rgb10a2uint texture format",
+ "description": "`rgb10a2uint` texture format",
"spec_url": "https://gpuweb.github.io/gpuweb/#dom-gputextureformat-rgb10a2uint",
"tags": [
"web-features:webgpu"
@@ -1320,7 +1320,7 @@
},
"texture_rgb10a2uint": {
"__compat": {
- "description": "rgb10a2uint texture format",
+ "description": "`rgb10a2uint` texture format",
"spec_url": "https://gpuweb.github.io/gpuweb/#dom-gputextureformat-rgb10a2uint",
"tags": [
"web-features:webgpu"
@@ -1408,7 +1408,7 @@
},
"vertex_unorm10-10-10-2": {
"__compat": {
- "description": "unorm10-10-10-2 vertex format",
+ "description": "`unorm10-10-10-2` vertex format",
"spec_url": "https://gpuweb.github.io/gpuweb/#dom-gpuvertexformat-unorm10-10-10-2",
"tags": [
"web-features:webgpu"
@@ -1652,7 +1652,7 @@
},
"texture_rgb10a2uint": {
"__compat": {
- "description": "rgb10a2uint texture format",
+ "description": "`rgb10a2uint` texture format",
"spec_url": "https://gpuweb.github.io/gpuweb/#dom-gputextureformat-rgb10a2uint",
"tags": [
"web-features:webgpu"
@@ -1740,7 +1740,7 @@
},
"vertex_unorm10-10-10-2": {
"__compat": {
- "description": "unorm10-10-10-2 vertex format",
+ "description": "`unorm10-10-10-2` vertex format",
"spec_url": "https://gpuweb.github.io/gpuweb/#dom-gpuvertexformat-unorm10-10-10-2",
"tags": [
"web-features:webgpu"
@@ -2100,7 +2100,7 @@
},
"texture_rgb10a2uint": {
"__compat": {
- "description": "rgb10a2uint texture format",
+ "description": "`rgb10a2uint` texture format",
"spec_url": "https://gpuweb.github.io/gpuweb/#dom-gputextureformat-rgb10a2uint",
"tags": [
"web-features:webgpu"
@@ -2330,7 +2330,7 @@
},
"color_space_display-p3": {
"__compat": {
- "description": "display-p3 color space",
+ "description": "`display-p3` color space",
"spec_url": "https://html.spec.whatwg.org/multipage/canvas.html#dom-predefinedcolorspace-display-p3",
"tags": [
"web-features:webgpu"
diff --git a/api/GPUTexture.json b/api/GPUTexture.json
index 0b5757ca682aaf..85c784e767b755 100644
--- a/api/GPUTexture.json
+++ b/api/GPUTexture.json
@@ -582,7 +582,7 @@
},
"texture_rgb10a2uint": {
"__compat": {
- "description": "rgb10a2uint texture format",
+ "description": "`rgb10a2uint` texture format",
"spec_url": "https://gpuweb.github.io/gpuweb/#dom-gputextureformat-rgb10a2uint",
"tags": [
"web-features:webgpu"
diff --git a/api/Notification.json b/api/Notification.json
index 63adc0f24e85c4..109d62a5afe365 100644
--- a/api/Notification.json
+++ b/api/Notification.json
@@ -18,7 +18,7 @@
"chrome_android": {
"version_added": "42",
"partial_implementation": true,
- "notes": "A notification can only be sent from a service worker. To show a notification, see ServiceWorkerRegistration.showNotification()."
+ "notes": "A notification can only be sent from a service worker. To show a notification, see `ServiceWorkerRegistration.showNotification()`."
},
"edge": {
"version_added": "14"
@@ -56,8 +56,8 @@
"version_added": "16.4",
"partial_implementation": true,
"notes": [
- "The Notification interface is undefined, unless the page is a web app saved to the home screen. The app's manifest must have a non-default display value.",
- "A notification can only be sent from a service worker. To show a notification, see ServiceWorkerRegistration.showNotification()."
+ "The `Notification` interface is undefined, unless the page is a web app saved to the home screen. The app's manifest must have a non-default `display` value.",
+ "A notification can only be sent from a service worker. To show a notification, see `ServiceWorkerRegistration.showNotification()`."
]
},
"samsunginternet_android": {
@@ -98,8 +98,8 @@
"version_added": "42",
"partial_implementation": true,
"notes": [
- "A notification can only be sent from a service worker. To show a notification, see ServiceWorkerRegistration.showNotification().",
- "This constructor always throws a TypeError exception."
+ "A notification can only be sent from a service worker. To show a notification, see `ServiceWorkerRegistration.showNotification()`.",
+ "This constructor always throws a `TypeError` exception."
]
},
"edge": {
@@ -127,8 +127,8 @@
"version_added": "16.4",
"partial_implementation": true,
"notes": [
- "This constructor throws a ReferenceError exception, unless the page is a web app saved to the home screen. The app's manifest must have a non-default display value.",
- "A notification can only be sent from a service worker. To show a notification, see ServiceWorkerRegistration.showNotification()."
+ "This constructor throws a `ReferenceError` exception, unless the page is a web app saved to the home screen. The app's manifest must have a non-default `display` value.",
+ "A notification can only be sent from a service worker. To show a notification, see `ServiceWorkerRegistration.showNotification()`."
]
},
"samsunginternet_android": "mirror",
@@ -893,7 +893,7 @@
"safari_ios": {
"version_added": "16.4",
"partial_implementation": true,
- "notes": "The parent Notification interface is undefined unless the page is a web app saved to the home screen. The app's manifest must have a non-default display value."
+ "notes": "The parent `Notification` interface is undefined unless the page is a web app saved to the home screen. The app's manifest must have a non-default `display` value."
},
"samsunginternet_android": "mirror",
"webview_android": {
@@ -1000,7 +1000,7 @@
"safari_ios": {
"version_added": "16.4",
"partial_implementation": true,
- "notes": "The parent Notification interface is undefined unless the page is a web app saved to the home screen. The app's manifest must have a non-default display value."
+ "notes": "The parent `Notification` interface is undefined unless the page is a web app saved to the home screen. The app's manifest must have a non-default `display` value."
},
"samsunginternet_android": "mirror",
"webview_android": {
diff --git a/api/_globals/performance.json b/api/_globals/performance.json
index d512477e91fc8d..dec96f5a428151 100644
--- a/api/_globals/performance.json
+++ b/api/_globals/performance.json
@@ -36,7 +36,7 @@
"version_added": "8.5.0",
"version_removed": "16.0.0",
"partial_implementation": true,
- "notes": "Available as a part of the perf_hooks module."
+ "notes": "Available as a part of the `perf_hooks` module."
}
],
"oculus": "mirror",
@@ -88,7 +88,7 @@
"version_added": "11.7.0",
"version_removed": "16.0.0",
"partial_implementation": true,
- "notes": "Available as a part of the perf_hooks module."
+ "notes": "Available as a part of the `perf_hooks` module."
}
],
"oculus": "mirror",
diff --git a/lint/fix.js b/lint/fix.js
index a6a23cba1d3787..7512de002d0456 100644
--- a/lint/fix.js
+++ b/lint/fix.js
@@ -18,6 +18,7 @@ import fixFeatureOrder from './fixer/feature-order.js';
import fixPropertyOrder from './fixer/property-order.js';
import fixStatementOrder from './fixer/statement-order.js';
import fixDescriptions from './fixer/descriptions.js';
+import fixNotes from './fixer/notes.js';
import fixFlags from './fixer/flags.js';
import fixLinks from './fixer/links.js';
import fixMDNURLs from './fixer/mdn-urls.js';
@@ -35,6 +36,7 @@ const dirname = fileURLToPath(new URL('.', import.meta.url));
/** @type {Readonly | string>>} */
const FIXES = Object.freeze({
descriptions: fixDescriptions,
+ notes: fixNotes,
common_errors: fixCommonErrors,
flags: fixFlags,
links: fixLinks,
diff --git a/lint/fixer/notes.js b/lint/fixer/notes.js
new file mode 100644
index 00000000000000..84452f52aa75ce
--- /dev/null
+++ b/lint/fixer/notes.js
@@ -0,0 +1,48 @@
+/* This file is a part of @mdn/browser-compat-data
+ * See LICENSE file for more information. */
+
+import { replaceCodeTagsWithBackticks } from '../utils.js';
+import walk from '../../utils/walk.js';
+
+/**
+ * @param {string | string[]} notes
+ * @returns {string | string[]}
+ */
+export const fixNotes = (notes) => {
+ if (Array.isArray(notes)) {
+ return notes.map(replaceCodeTagsWithBackticks);
+ }
+ return replaceCodeTagsWithBackticks(notes);
+};
+
+/**
+ * Fixes HTML in notes that should use Markdown syntax instead.
+ * @param {string} filename The filename containing compatibility info
+ * @param {string} actual The current content of the file
+ * @returns {string} expected content of the file
+ */
+const fixNotesFixer = (filename, actual) => {
+ if (filename.includes('/browsers/')) {
+ return actual;
+ }
+
+ const data = JSON.parse(actual);
+ const walker = walk(undefined, data);
+
+ for (const feature of walker) {
+ for (const support of Object.values(feature.compat.support)) {
+ for (const statement of Array.isArray(support) ? support : [support]) {
+ if (statement.notes) {
+ statement.notes =
+ /** @type {string | [string, string, ...string[]]} */ (
+ fixNotes(statement.notes)
+ );
+ }
+ }
+ }
+ }
+
+ return JSON.stringify(data, null, 2);
+};
+
+export default fixNotesFixer;
diff --git a/lint/fixer/notes.test.js b/lint/fixer/notes.test.js
new file mode 100644
index 00000000000000..b55241527fc854
--- /dev/null
+++ b/lint/fixer/notes.test.js
@@ -0,0 +1,36 @@
+/* This file is a part of @mdn/browser-compat-data
+ * See LICENSE file for more information. */
+
+import assert from 'node:assert/strict';
+
+import { fixNotes } from './notes.js';
+
+describe('fix -> notes', () => {
+ it('replaces tags with backticks in a string note', () => {
+ assert.equal(
+ fixNotes('Before version 90, foo was required.'),
+ 'Before version 90, `foo` was required.',
+ );
+ });
+
+ it('replaces tags in each note in an array', () => {
+ assert.deepEqual(
+ fixNotes([
+ 'Before version 90, foo was required.',
+ 'Use `bar` instead.',
+ ]),
+ ['Before version 90, `foo` was required.', 'Use `bar` instead.'],
+ );
+ });
+
+ it('leaves notes without tags unchanged', () => {
+ assert.equal(fixNotes('Use `foo` instead.'), 'Use `foo` instead.');
+ });
+
+ it('does not replace inside backticks', () => {
+ assert.equal(
+ fixNotes('The `` element is not supported.'),
+ 'The `` element is not supported.',
+ );
+ });
+});
diff --git a/lint/linter/test-descriptions.js b/lint/linter/test-descriptions.js
index 81178416cd02be..f2149e9211254a 100644
--- a/lint/linter/test-descriptions.js
+++ b/lint/linter/test-descriptions.js
@@ -3,8 +3,12 @@
import { styleText } from 'node:util';
+import { replaceCodeTagsWithBackticks } from '../utils.js';
+
import { validateHTML } from './test-notes.js';
+export { replaceCodeTagsWithBackticks };
+
/** @import {Linter, LinterData} from '../types.js' */
/** @import {Logger} from '../utils.js' */
/** @import {CompatStatement} from '../../types/types.js' */
@@ -117,6 +121,16 @@ export const processData = (data, category, path) => {
}
if (data.description) {
+ const converted = replaceCodeTagsWithBackticks(data.description);
+ if (converted !== data.description) {
+ errors.push({
+ ruleName: 'no_code_tag_in_description',
+ path,
+ actual: data.description,
+ expected: converted,
+ });
+ }
+
errors.push(...validateHTML(data.description));
}
diff --git a/lint/linter/test-descriptions.test.js b/lint/linter/test-descriptions.test.js
index ba4449e8adcd14..e7f8ecddac5c69 100644
--- a/lint/linter/test-descriptions.test.js
+++ b/lint/linter/test-descriptions.test.js
@@ -113,4 +113,41 @@ describe('test-descriptions', () => {
assert.equal(errors.length, 1);
});
});
+
+ describe('HTML in descriptions', () => {
+ it('flags tags as no_code_tag_in_description', () => {
+ /** @type {CompatStatement} */
+ const data = {
+ description: 'transient_attachment usage',
+ support: {},
+ };
+ const errors = processData(data, 'api', 'api.Foo.bar');
+ const err = /** @type {DescriptionError} */ (
+ errors.find(
+ (e) =>
+ typeof e !== 'string' &&
+ e.ruleName === 'no_code_tag_in_description',
+ )
+ );
+ assert.ok(err);
+ assert.equal(err.actual, 'transient_attachment usage');
+ assert.equal(err.expected, '`transient_attachment` usage');
+ });
+
+ it('does not flag descriptions without HTML', () => {
+ /** @type {CompatStatement} */
+ const data = {
+ description: '`transient_attachment` usage',
+ support: {},
+ };
+ const errors = processData(data, 'api', 'api.Foo.bar');
+ assert.ok(
+ !errors.some(
+ (e) =>
+ typeof e !== 'string' &&
+ e.ruleName === 'no_code_tag_in_description',
+ ),
+ );
+ });
+ });
});
diff --git a/lint/linter/test-notes.js b/lint/linter/test-notes.js
index 070832e914ac2b..2488392d880eae 100644
--- a/lint/linter/test-notes.js
+++ b/lint/linter/test-notes.js
@@ -6,7 +6,7 @@ import { styleText } from 'node:util';
import HTMLParser from '@desertnet/html-parser';
import { marked } from 'marked';
-import { VALID_ELEMENTS } from '../utils.js';
+import { replaceCodeTagsWithBackticks, VALID_ELEMENTS } from '../utils.js';
/** @import {Linter, LinterData} from '../types.js' */
/** @import {Logger} from '../utils.js' */
@@ -112,6 +112,21 @@ const checkNotes = (notes, browser, feature, logger) => {
logger.error(`Notes for ${styleText('bold', browser)} → ${error}`);
}
}
+
+ for (const note of Array.isArray(notes) ? notes : [notes]) {
+ const converted = replaceCodeTagsWithBackticks(note);
+ if (converted !== note) {
+ logger.error(
+ styleText(
+ 'red',
+ `Notes for ${styleText('bold', browser)} use HTML code tags instead of backtick-quoted Markdown code
+ Actual: ${styleText('yellow', `"${note}"`)}
+ Expected: ${styleText('green', `"${converted}"`)}`,
+ ),
+ { fixable: true },
+ );
+ }
+ }
};
/**
diff --git a/lint/linter/test-notes.test.js b/lint/linter/test-notes.test.js
new file mode 100644
index 00000000000000..620092edc16cca
--- /dev/null
+++ b/lint/linter/test-notes.test.js
@@ -0,0 +1,88 @@
+/* This file is a part of @mdn/browser-compat-data
+ * See LICENSE file for more information. */
+
+/** @import {CompatStatement} from '../../types/types.js' */
+/** @import {LinterMessage} from '../types.js' */
+
+import assert from 'node:assert/strict';
+
+import { Logger } from '../utils.js';
+
+import testNotes from './test-notes.js';
+
+/**
+ * Run the notes linter check and return logged messages.
+ * @param {CompatStatement} data
+ * @returns {Promise}
+ */
+const check = async (data) => {
+ const logger = new Logger('Notes', 'test.feature');
+ await testNotes.check(logger, {
+ data,
+ path: { full: 'test.feature', category: 'api' },
+ });
+ return logger.messages;
+};
+
+describe('test-notes', () => {
+ describe('code tag in notes', () => {
+ it('flags a note with a tag', async () => {
+ /** @type {CompatStatement} */
+ const data = {
+ support: {
+ chrome: {
+ version_added: '80',
+ notes: 'Before version 90, foo was required.',
+ },
+ },
+ };
+ const messages = await check(data);
+ assert.ok(messages.some((m) => m.fixable));
+ });
+
+ it('flags each note in an array with a tag', async () => {
+ /** @type {CompatStatement} */
+ const data = {
+ support: {
+ chrome: {
+ version_added: '80',
+ notes: [
+ 'Before version 90, foo was required.',
+ 'Use `bar` instead.',
+ ],
+ },
+ },
+ };
+ const messages = await check(data);
+ assert.equal(messages.filter((m) => m.fixable).length, 1);
+ });
+
+ it('does not flag a note using backtick Markdown', async () => {
+ /** @type {CompatStatement} */
+ const data = {
+ support: {
+ chrome: {
+ version_added: '80',
+ notes: 'Before version 90, `foo` was required.',
+ },
+ },
+ };
+ const messages = await check(data);
+ assert.ok(!messages.some((m) => m.fixable));
+ });
+
+ it('does not flag a tag inside backticks', async () => {
+ /** @type {CompatStatement} */
+ const data = {
+ support: {
+ chrome: {
+ version_added: '80',
+ notes: 'The `` element is not supported.',
+ },
+ },
+ };
+ const messages = await check(data);
+ assert.ok(!messages.some((m) => m.fixable));
+ });
+ });
+});
diff --git a/lint/utils.js b/lint/utils.js
index 9f68684be88f17..f130f9513eb033 100644
--- a/lint/utils.js
+++ b/lint/utils.js
@@ -31,6 +31,14 @@ export const IS_WINDOWS = platform() === 'win32';
export const VALID_ELEMENTS = ['code', 'kbd', 'em', 'strong', 'a'];
+/**
+ * Replace tags with backtick-quoted Markdown.
+ * @param {string} str The string to process
+ * @returns {string} The string with tags replaced by backticks
+ */
+export const replaceCodeTagsWithBackticks = (str) =>
+ str.replace(/([^<]*)<\/code>/g, '`$1`');
+
/**
* Escapes common invisible characters.
* @param {string} str The string to escape invisibles for
diff --git a/lint/utils.test.js b/lint/utils.test.js
index 3f331b9564aee2..c01ab372d9ff28 100644
--- a/lint/utils.test.js
+++ b/lint/utils.test.js
@@ -9,6 +9,7 @@ import {
createStatementGroupKey,
escapeInvisibles,
jsonDiff,
+ replaceCodeTagsWithBackticks,
} from './utils.js';
describe('utils', () => {
@@ -79,6 +80,25 @@ describe('utils', () => {
);
});
+ it('`replaceCodeTagsWithBackticks()` works correctly', () => {
+ assert.equal(
+ replaceCodeTagsWithBackticks('transient_attachment usage'),
+ '`transient_attachment` usage',
+ );
+ assert.equal(
+ replaceCodeTagsWithBackticks('foo and bar'),
+ '`foo` and `bar`',
+ );
+ assert.equal(
+ replaceCodeTagsWithBackticks('`already` markdown'),
+ '`already` markdown',
+ );
+ assert.equal(
+ replaceCodeTagsWithBackticks('Use `` element'),
+ 'Use `` element',
+ );
+ });
+
it('createStatementGroupKey() works correctly', () => {
/** @type {Record} */
const tests = {
diff --git a/webextensions/api/webRequest.json b/webextensions/api/webRequest.json
index ed629cbdaa0c2c..7f30cba0a30022 100644
--- a/webextensions/api/webRequest.json
+++ b/webextensions/api/webRequest.json
@@ -2047,7 +2047,7 @@
"edge": "mirror",
"firefox": {
"version_added": "74",
- "notes": "Classification flags emailtracking and emailtracking_content added in Firefox 104."
+ "notes": "Classification flags `emailtracking` and `emailtracking_content` added in Firefox 104."
},
"firefox_android": {
"version_added": false
@@ -2598,7 +2598,7 @@
"edge": "mirror",
"firefox": {
"version_added": "74",
- "notes": "Classification flags emailtracking and emailtracking_content added in Firefox 104."
+ "notes": "Classification flags `emailtracking` and `emailtracking_content` added in Firefox 104."
},
"firefox_android": {
"version_added": false
@@ -3051,7 +3051,7 @@
"edge": "mirror",
"firefox": {
"version_added": "74",
- "notes": "Classification flags emailtracking and emailtracking_content added in Firefox 104."
+ "notes": "Classification flags `emailtracking` and `emailtracking_content` added in Firefox 104."
},
"firefox_android": {
"version_added": false
@@ -3506,7 +3506,7 @@
"edge": "mirror",
"firefox": {
"version_added": "74",
- "notes": "Classification flags emailtracking and emailtracking_content added in Firefox 104."
+ "notes": "Classification flags `emailtracking` and `emailtracking_content` added in Firefox 104."
},
"firefox_android": {
"version_added": false
@@ -4040,7 +4040,7 @@
"edge": "mirror",
"firefox": {
"version_added": "74",
- "notes": "Classification flags emailtracking and emailtracking_content added in Firefox 104."
+ "notes": "Classification flags `emailtracking` and `emailtracking_content` added in Firefox 104."
},
"firefox_android": {
"version_added": false
@@ -4508,7 +4508,7 @@
"edge": "mirror",
"firefox": {
"version_added": "74",
- "notes": "Classification flags emailtracking and emailtracking_content added in Firefox 104."
+ "notes": "Classification flags `emailtracking` and `emailtracking_content` added in Firefox 104."
},
"firefox_android": {
"version_added": false
@@ -5065,7 +5065,7 @@
"edge": "mirror",
"firefox": {
"version_added": "74",
- "notes": "Classification flags emailtracking and emailtracking_content added in Firefox 104."
+ "notes": "Classification flags `emailtracking` and `emailtracking_content` added in Firefox 104."
},
"firefox_android": {
"version_added": false
@@ -5595,7 +5595,7 @@
"edge": "mirror",
"firefox": {
"version_added": "74",
- "notes": "Classification flags emailtracking and emailtracking_content added in Firefox 104."
+ "notes": "Classification flags `emailtracking` and `emailtracking_content` added in Firefox 104."
},
"firefox_android": {
"version_added": false
@@ -6029,7 +6029,7 @@
"edge": "mirror",
"firefox": {
"version_added": "74",
- "notes": "Classification flags emailtracking and emailtracking_content added in Firefox 104."
+ "notes": "Classification flags `emailtracking` and `emailtracking_content` added in Firefox 104."
},
"firefox_android": {
"version_added": false