Skip to content

Commit 481f74c

Browse files
authored
feat(schema)!: align typedefs/linters with schema (#27428)
BREAKING CHANGE: Previously, TypeScript definitions for version fields were inconsistent with the schema. Now, definitions are aligned with the schema: * `VersionValue` is now `string | false` (previously `string | boolean | null`). * `version_added` is still `VersionValue` (without `true` or `null`). * `version_removed` and `version_last` are now `string` (previously `VersionValue`). Impact: Consumers may need to update their code handling these fields.
1 parent 92e8b51 commit 481f74c

File tree

11 files changed

+81
-163
lines changed

11 files changed

+81
-163
lines changed

docs/testing.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,21 @@ For more information on the schema for browser data, see [`browsers-schema.md`](
2424

2525
## Generate statistics
2626

27-
To see how changes will affect the statistics of real (either `false` or a version number, as defined in [issue 3555](https://github.com/mdn/browser-compat-data/issues/3555)), true, and null values, you can run `npm run stats [folder]`. This generates a Markdown-formatted table of the percentages of real, true, and null values for the eight primary browsers that browser-compat-data is focusing on. The script also takes an optional argument regarding a specific folder (such as `api` or `javascript`), which will print statistics result for only that folder. Additionally, you can run the script with `--all` to get statistics for all browsers tracked in BCD, not just the primary eight.
27+
To see how changes will affect the statistics of exact (formerly "real") and ranged values, you can run `npm run stats [folder]`. This generates a Markdown-formatted table of the percentages of exact and ranged values for the eight primary browsers that browser-compat-data is focusing on. The script also takes an optional argument regarding a specific folder (such as `api` or `javascript`), which will print statistics result for only that folder. Additionally, you can run the script with `--all` to get statistics for all browsers tracked in BCD, not just the primary eight.
28+
29+
> [!NOTE]
30+
> Originally, this script was designed to track "real" (either `false` or a version number, as defined in [issue 3555](https://github.com/mdn/browser-compat-data/issues/3555)), ranged, true, and null values in order to track the progress of a project to eliminate "non-real" (true and null) values. Since the project was completed, the script was simplified to display just exact and ranged values.
2831
2932
## Traverse features
3033

31-
To find all the entries that are non-real, or of a specified value, you can run `npm run traverse -- [options] [folder]`.
34+
To find all the entries that are of a specified value, you can run `npm run traverse -- [options] [folder]`.
3235

33-
By default, the script will traverse and print the dotted path to every feature. One or more positional arguments can limit the traversal to a specific folder (for example, `api`). Additional options can limit the features listed to features with data matching specific browser and version values (for example, `--non-real`).
36+
By default, the script will traverse and print the dotted path to every feature. One or more positional arguments can limit the traversal to a specific folder (for example, `api`). Additional options can limit the features listed to features with data matching specific browser and version values.
3437

3538
Run `npm run traverse -- --help` for a complete list of options and examples.
3639

3740
The `-b` or `--browser` argument may be any browser in the [`browsers/` folder](https://github.com/mdn/browser-compat-data/blob/main/browsers/). This argument may be repeated to traverse multiple browsers. By default, the script will traverse all browsers.
3841

39-
The `-f` or `--filter` argument may be any value accepted by `version_added` or `version_removed`. This argument may be repeated to test multiple values. By default, the script will traverse all features regardless of their value. The `-n` or `--non-real` argument may be included as a convenience alias for `-f null -f true`.
42+
The `-f` or `--filter` argument may be any value accepted by `version_added` or `version_removed`. This argument may be repeated to test multiple values. By default, the script will traverse all features regardless of their value.
4043

41-
Examples: to search for all Safari entries that are non-real, run `npm run traverse -- -b safari --non-real`. To search for all WebView entries that are marked as `true` in `api` and `javascript`, run `npm run traverse api,javascript -- -b webview_android -f true`. To search for all Firefox entries supported since `10` across all folders, run `npm run traverse -- -b firefox -f 10`.
44+
Examples: to search for all Safari entries that are not supported, run `npm run traverse -- -b safari -f false`. To search for all WebView entries that are mirroring from upstream in `api` and `javascript`, run `npm run traverse api,javascript -- -b webview_android -f mirror`. To search for all Firefox entries supported since `10` across all folders, run `npm run traverse -- -b firefox -f 10`.

lint/common/overlap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export const checkOverlap = (
8787
if (fix) {
8888
if (
8989
typeof current.version_removed === 'undefined' &&
90+
next.version_added !== false &&
9091
next.version_added !== 'preview'
9192
) {
9293
current.version_removed = next.version_added;

lint/linter/test-consistency.test.ts

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,14 @@ import { ConsistencyChecker } from './test-consistency.js';
88
const check = new ConsistencyChecker();
99

1010
describe('ConsistencyChecker.getVersionAdded()', () => {
11-
it('returns null for non-real values', () => {
12-
assert.equal(
13-
check.getVersionAdded({ chrome: { version_added: null } }, 'chrome'),
14-
null,
15-
);
16-
});
17-
18-
it('returns null for "preview" values', () => {
11+
it('returns false for "preview" values', () => {
1912
assert.equal(
2013
check.getVersionAdded({ chrome: { version_added: 'preview' } }, 'chrome'),
21-
null,
14+
false,
2215
);
2316
});
2417

25-
it('returns the value for real and ranged values', () => {
18+
it('returns the value for exact and ranged values', () => {
2619
assert.equal(
2720
check.getVersionAdded({ chrome: { version_added: '12' } }, 'chrome'),
2821
'12',
@@ -33,7 +26,7 @@ describe('ConsistencyChecker.getVersionAdded()', () => {
3326
);
3427
});
3528

36-
it('returns the earliest real value for an array support statement', () => {
29+
it('returns the earliest value for an array support statement', () => {
3730
assert.equal(
3831
check.getVersionAdded(
3932
{ chrome: [{ version_added: '≤11' }, { version_added: '101' }] },
@@ -51,19 +44,19 @@ describe('ConsistencyChecker.getVersionAdded()', () => {
5144
},
5245
'chrome',
5346
),
54-
null,
47+
false,
5548
);
5649
assert.equal(
5750
check.getVersionAdded(
5851
{
5952
chrome: [
60-
{ version_added: true },
53+
{ version_added: '20' },
6154
{ version_added: '≤11', flags: [] },
6255
],
6356
},
6457
'chrome',
6558
),
66-
null,
59+
'20',
6760
);
6861
assert.equal(
6962
check.getVersionAdded(

lint/linter/test-consistency.ts

Lines changed: 21 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@ import { query } from '../../utils/index.js';
2222
import mirrorSupport from '../../scripts/build/mirror.js';
2323
import bcd from '../../index.js';
2424

25-
type ErrorType =
26-
| 'unsupported'
27-
| 'support_unknown'
28-
| 'subfeature_earlier_implementation';
25+
type ErrorType = 'unsupported' | 'subfeature_earlier_implementation';
2926

3027
interface ConsistencyError {
3128
path: string[];
@@ -181,53 +178,6 @@ export class ConsistencyChecker {
181178
}
182179
});
183180

184-
// Test whether sub-features are supported when basic support is not implemented
185-
// For all unsupported browsers (basic support == false), sub-features should be set to false
186-
const supportUnknownInParent = this.extractSupportUnknownBrowsers(
187-
data.__compat,
188-
);
189-
inconsistentSubfeaturesByBrowser = {};
190-
191-
for (const subfeature of subfeatures) {
192-
const supportUnknownInChild = this.extractSupportNotTrueBrowsers(
193-
query(subfeature, data).__compat,
194-
);
195-
196-
const browsers = supportUnknownInParent.filter(
197-
(x) => !supportUnknownInChild.includes(x),
198-
) as BrowserName[];
199-
200-
for (const browser of browsers) {
201-
inconsistentSubfeaturesByBrowser[browser] =
202-
inconsistentSubfeaturesByBrowser[browser] || [];
203-
const subfeature_value = this.getVersionAdded(
204-
query(subfeature, data).__compat.support,
205-
browser,
206-
);
207-
inconsistentSubfeaturesByBrowser[browser]?.push([
208-
subfeature,
209-
subfeature_value,
210-
]);
211-
}
212-
}
213-
214-
// Add errors
215-
Object.keys(inconsistentSubfeaturesByBrowser).forEach((browser) => {
216-
const subfeatures =
217-
inconsistentSubfeaturesByBrowser[browser as BrowserName];
218-
if (subfeatures) {
219-
errors.push({
220-
type: 'support_unknown',
221-
browser: browser as BrowserName,
222-
parentValue: this.getVersionAdded(
223-
data?.__compat?.support,
224-
browser as BrowserName,
225-
),
226-
subfeatures,
227-
});
228-
}
229-
});
230-
231181
// Test whether sub-features are supported at an earlier version than basic support
232182
const supportInParent = this.extractSupportedBrowsersWithVersion(
233183
data.__compat,
@@ -297,38 +247,10 @@ export class ConsistencyChecker {
297247
compatData,
298248
(data) =>
299249
data.version_added === false ||
300-
(typeof data.version_removed !== 'undefined' &&
301-
data.version_removed !== false),
302-
);
303-
}
304-
305-
/**
306-
* Get all of the browsers with unknown support in a feature
307-
* @param compatData The compat data to process
308-
* @returns The list of browsers with unknown support
309-
*/
310-
extractSupportUnknownBrowsers(compatData?: CompatStatement): BrowserName[] {
311-
return this.extractBrowsers(
312-
compatData,
313-
(data) => data.version_added === null,
250+
typeof data.version_removed !== 'undefined',
314251
);
315252
}
316253

317-
/**
318-
* Get all of the browsers with either unknown or no support in a feature
319-
* @param compatData The compat data to process
320-
* @returns The list of browsers with non-truthy (false or null) support
321-
*/
322-
extractSupportNotTrueBrowsers(compatData?: CompatStatement): BrowserName[] {
323-
return this.extractBrowsers(
324-
compatData,
325-
(data) =>
326-
data.version_added === false ||
327-
data.version_added === null ||
328-
(typeof data.version_removed !== 'undefined' &&
329-
data.version_removed !== false),
330-
);
331-
}
332254
/**
333255
* Get all of the browsers with a version number in a feature.
334256
* @param compatData The compat data to process
@@ -354,12 +276,12 @@ export class ConsistencyChecker {
354276
browser: BrowserName,
355277
): VersionValue {
356278
if (!supportBlock) {
357-
return null;
279+
return false;
358280
}
359281

360282
const supportStatement = supportBlock[browser];
361283
if (!supportStatement) {
362-
return null;
284+
return false;
363285
}
364286

365287
if (supportStatement === 'mirror') {
@@ -370,16 +292,15 @@ export class ConsistencyChecker {
370292
}
371293

372294
/**
373-
* A convenience function to squash non-real values and previews into null
295+
* A convenience function to squash preview and flag support into `false`
374296
* @param statement The statement to use
375-
* @returns The version number or 'null'
297+
* @returns The version number or `false`
376298
*/
377299
const resolveVersionAddedValue = (
378300
statement: SimpleSupportStatement,
379301
): VersionValue =>
380-
[true, false, 'preview', null].includes(statement?.version_added) ||
381-
statement.flags
382-
? null
302+
statement?.version_added == 'preview' || statement.flags
303+
? false
383304
: statement?.version_added;
384305

385306
// Handle simple support statements
@@ -388,35 +309,27 @@ export class ConsistencyChecker {
388309
}
389310

390311
// Handle array support statements
391-
let selectedValue: string | boolean | null = null;
312+
let selectedValue: string | false = false;
392313
for (const statement of supportStatement) {
393314
const resolvedValue = resolveVersionAddedValue(statement);
394315

395-
if (resolvedValue === null) {
316+
if (resolvedValue === false) {
396317
// We're not going to get a more specific version, so bail out now
397318
continue;
398319
}
399320

400-
if (selectedValue !== null) {
401-
if (
402-
typeof resolvedValue === 'string' &&
403-
typeof selectedValue === 'string'
404-
) {
405-
// Earlier value takes precedence
406-
const resolvedIsEarlier = compare(
407-
resolvedValue.replace('≤', ''),
408-
selectedValue.replace('≤', ''),
409-
'<',
410-
);
411-
if (resolvedIsEarlier) {
412-
selectedValue = resolvedValue;
413-
}
414-
} else if (typeof resolvedValue === 'string') {
415-
// If selectedValue is bool/null but resolvedValue is string
321+
if (
322+
typeof resolvedValue === 'string' &&
323+
typeof selectedValue === 'string'
324+
) {
325+
// Earlier value takes precedence
326+
const resolvedIsEarlier = compare(
327+
resolvedValue.replace('≤', ''),
328+
selectedValue.replace('≤', ''),
329+
'<',
330+
);
331+
if (resolvedIsEarlier) {
416332
selectedValue = resolvedValue;
417-
} else {
418-
// If neither are version numbers, assign to the truthiest value
419-
selectedValue = selectedValue || resolvedValue;
420333
}
421334
} else {
422335
selectedValue = resolvedValue;
@@ -519,8 +432,6 @@ export default {
519432
let errorMessage = '';
520433
if (type == 'unsupported') {
521434
errorMessage += chalk`No support in {bold ${browser}}, but support is declared in the following sub-feature(s):`;
522-
} else if (type == 'support_unknown') {
523-
errorMessage += chalk`Unknown support in parent for {bold ${browser}}, but support is declared in the following sub-feature(s):`;
524435
} else if (type == 'subfeature_earlier_implementation') {
525436
errorMessage += chalk`Basic support in {bold ${browser}} was declared implemented in a later version ({bold ${parentValue}}) than the following sub-feature(s):`;
526437
}

schemas/compat-data.schema.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222
"description": "A string, indicating which browser version removed this feature.",
2323
"type": "string",
2424
"pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$",
25-
"tsType": "VersionValue"
25+
"tsType": "string"
2626
},
2727
"version_last": {
2828
"description": "A string, indicating the last browser version that supported this feature. This is automatically generated.",
2929
"type": "string",
3030
"pattern": "^(≤?(\\d+)(\\.\\d+)*|preview)$",
31-
"tsType": "VersionValue"
31+
"tsType": "string"
3232
},
3333
"prefix": {
3434
"type": "string",

scripts/build/mirror.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,13 +236,15 @@ export const bumpSupport = (
236236
sourceData.version_removed &&
237237
typeof sourceData.version_removed === 'string'
238238
) {
239-
newData.version_removed = getMatchingBrowserVersion(
239+
const versionRemoved = getMatchingBrowserVersion(
240240
destination,
241241
sourceData.version_removed,
242242
);
243243

244-
// Ensure that version_removed is not present if it's not applicable, such as when the upstream browser removed the feature in a newer release than a matching downstream browser
245-
if (newData.version_removed === false) {
244+
if (typeof versionRemoved === 'string') {
245+
newData.version_removed = versionRemoved;
246+
} else {
247+
// Ensure that version_removed is not present if it's not applicable, such as when the upstream browser removed the feature in a newer release than a matching downstream browser
246248
delete newData.version_removed;
247249
}
248250
}

scripts/generate-types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ const compile = async (
165165
const ts = [
166166
header,
167167
await generateBrowserNames(),
168-
'export type VersionValue = string | boolean | null;',
168+
'export type VersionValue = string | false;',
169169
transformTS(browserTS, compatTS),
170170
generateCompatDataTypes(),
171171
].join('\n\n');

scripts/statistics.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,24 +43,24 @@ describe('getStats', () => {
4343
it('should return stats for all browsers when allBrowsers is true', () => {
4444
const stats = getStats('api', true, bcd);
4545
assert.deepEqual(stats, {
46-
total: { all: 3, range: 1, real: 2 },
47-
chrome: { all: 1, range: 0, real: 1 },
48-
firefox: { all: 1, range: 1, real: 0 },
49-
safari: { all: 1, range: 0, real: 1 },
46+
total: { all: 3, range: 1, exact: 2 },
47+
chrome: { all: 1, range: 0, exact: 1 },
48+
firefox: { all: 1, range: 1, exact: 0 },
49+
safari: { all: 1, range: 0, exact: 1 },
5050
});
5151
});
5252

5353
it('should return stats for webextensions browsers when folder is "webextensions"', () => {
5454
const stats = getStats('webextensions', false, bcd);
5555
assert.deepEqual(stats, {
56-
total: { all: 7, range: 1, real: 6 },
57-
chrome: { all: 1, range: 1, real: 0 },
58-
edge: { all: 1, range: 0, real: 1 },
59-
firefox: { all: 1, range: 0, real: 1 },
60-
opera: { all: 1, range: 0, real: 1 },
61-
safari: { all: 1, range: 0, real: 1 },
62-
firefox_android: { all: 1, range: 0, real: 1 },
63-
safari_ios: { all: 1, range: 0, real: 1 },
56+
total: { all: 7, range: 1, exact: 6 },
57+
chrome: { all: 1, range: 1, exact: 0 },
58+
edge: { all: 1, range: 0, exact: 1 },
59+
firefox: { all: 1, range: 0, exact: 1 },
60+
opera: { all: 1, range: 0, exact: 1 },
61+
safari: { all: 1, range: 0, exact: 1 },
62+
firefox_android: { all: 1, range: 0, exact: 1 },
63+
safari_ios: { all: 1, range: 0, exact: 1 },
6464
});
6565
});
6666

0 commit comments

Comments
 (0)