Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions src/providers/mdn-provider.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,70 @@
import apiMetadata from "ast-metadata-inferer";
import semver from "semver";
import { ApiMetadata } from "ast-metadata-inferer/lib/types";
import {
ApiMetadata,
AstNodeTypes as MetadataAstNodeTypes,
Language,
APIKind,
} from "ast-metadata-inferer/lib/types";
import mdnBrowserCompat from "@mdn/browser-compat-data";
import { reverseTargetMappings } from "../helpers";
import { STANDARD_TARGET_NAME_MAPPING } from "../constants";
import { AstMetadataApiWithTargetsResolver, Target } from "../types";

const apis = apiMetadata as ApiMetadata[];

/**
* APIs that are not yet in ast-metadata-inferer but are available in @mdn/browser-compat-data.
* These are manually added until ast-metadata-inferer is updated.
*/
const additionalApis: ApiMetadata[] = [
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I draft a PR to ast-metadata-inferer?

// AbortSignal.abort() static method
{
id: "AbortSignal.abort",
name: "abort",
language: Language.JS,
protoChain: ["AbortSignal", "abort"],
protoChainId: "AbortSignal.abort",
kind: APIKind.Web,
compat: mdnBrowserCompat.api.AbortSignal.abort_static
.__compat as ApiMetadata["compat"],
astNodeTypes: [MetadataAstNodeTypes.MemberExpression],
isBoolean: false,
},
// AbortSignal.any() static method
{
id: "AbortSignal.any",
name: "any",
language: Language.JS,
protoChain: ["AbortSignal", "any"],
protoChainId: "AbortSignal.any",
kind: APIKind.Web,
compat: mdnBrowserCompat.api.AbortSignal.any_static
.__compat as ApiMetadata["compat"],
astNodeTypes: [MetadataAstNodeTypes.MemberExpression],
isBoolean: false,
},
// AbortSignal.timeout() static method
{
id: "AbortSignal.timeout",
name: "timeout",
language: Language.JS,
protoChain: ["AbortSignal", "timeout"],
protoChainId: "AbortSignal.timeout",
kind: APIKind.Web,
compat: mdnBrowserCompat.api.AbortSignal.timeout_static
.__compat as ApiMetadata["compat"],
astNodeTypes: [MetadataAstNodeTypes.MemberExpression],
isBoolean: false,
},
];

// Combine apis from ast-metadata-inferer with additional manually-added APIs
const allApis = [...apis, ...additionalApis];

// @TODO Import this type from ast-metadata-inferer after migrating this project to TypeScript
const mdnRecords: Map<string, ApiMetadata> = new Map(
apis.map((e) => [e.protoChainId, e])
allApis.map((e) => [e.protoChainId, e])
);

interface TargetIdMappings {
Expand Down Expand Up @@ -147,7 +202,7 @@ function getMetadataName(metadata: ApiMetadata) {
}
}

const MdnProvider: Array<AstMetadataApiWithTargetsResolver> = apis
const MdnProvider: Array<AstMetadataApiWithTargetsResolver> = allApis
// Create entries for each ast node type
.map((metadata) =>
metadata.astNodeTypes.map((astNodeType) => ({
Expand Down
63 changes: 63 additions & 0 deletions test/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,27 @@ ruleTester.run("compat", rule, {
browsers: ["chrome 49", "safari 10.1", "firefox 44"],
},
},
// AbortSignal.any() - valid when browsers support it
{
code: "AbortSignal.any([signal1, signal2])",
settings: {
browsers: ["chrome 116", "safari 17.4", "firefox 124"],
},
},
// AbortSignal.abort() - valid when browsers support it
{
code: "AbortSignal.abort()",
settings: {
browsers: ["chrome 93", "safari 15", "firefox 88"],
},
},
// AbortSignal.timeout() - valid when browsers support it
{
code: "AbortSignal.timeout(5000)",
settings: {
browsers: ["chrome 124", "safari 16", "firefox 100"],
},
},
],
invalid: [
{
Expand Down Expand Up @@ -729,5 +750,47 @@ ruleTester.run("compat", rule, {
},
],
},
// AbortSignal.any() static method
{
code: "AbortSignal.any([signal1, signal2])",
settings: {
browsers: ["chrome 115", "safari 17"],
},
errors: [
{
message:
"AbortSignal.any() is not supported in Safari 17.0, Chrome 115",
type: "MemberExpression",
},
],
},
// AbortSignal.abort() static method
{
code: "AbortSignal.abort()",
settings: {
browsers: ["chrome 92", "safari 14"],
},
errors: [
{
message:
"AbortSignal.abort() is not supported in Safari 14, Chrome 92",
type: "MemberExpression",
},
],
},
// AbortSignal.timeout() static method
{
code: "AbortSignal.timeout(5000)",
settings: {
browsers: ["chrome 102", "safari 15"],
},
errors: [
{
message:
"AbortSignal.timeout() is not supported in Safari 15, Chrome 102",
type: "MemberExpression",
},
],
},
],
});
84 changes: 84 additions & 0 deletions test/mdn-provider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,88 @@ describe("MdnProvider", () => {
const result = getUnsupportedTargets(node, targets);
expect(result).toEqual([]);
});

it("should return unsupported targets for AbortSignal.any in older browsers", () => {
const node = {
protoChainId: "AbortSignal.any",
} as AstMetadataApiWithTargetsResolver;
const config = determineTargetsFromConfig(".", [
"chrome 115",
"safari 17",
"firefox 123",
]);
const targets = parseBrowsersListVersion(config);
const result = getUnsupportedTargets(node, targets);
expect(result).toEqual(["Safari 17.0", "Firefox 123", "Chrome 115"]);
});

it("should return empty for AbortSignal.any in supported browsers", () => {
const node = {
protoChainId: "AbortSignal.any",
} as AstMetadataApiWithTargetsResolver;
const config = determineTargetsFromConfig(".", [
"chrome 116",
"safari 17.4",
"firefox 124",
]);
const targets = parseBrowsersListVersion(config);
const result = getUnsupportedTargets(node, targets);
expect(result).toEqual([]);
});

it("should return unsupported targets for AbortSignal.abort in older browsers", () => {
const node = {
protoChainId: "AbortSignal.abort",
} as AstMetadataApiWithTargetsResolver;
const config = determineTargetsFromConfig(".", [
"chrome 92",
"safari 14",
"firefox 87",
]);
const targets = parseBrowsersListVersion(config);
const result = getUnsupportedTargets(node, targets);
expect(result).toEqual(["Safari 14", "Firefox 87", "Chrome 92"]);
});

it("should return empty for AbortSignal.abort in supported browsers", () => {
const node = {
protoChainId: "AbortSignal.abort",
} as AstMetadataApiWithTargetsResolver;
const config = determineTargetsFromConfig(".", [
"chrome 93",
"safari 15",
"firefox 88",
]);
const targets = parseBrowsersListVersion(config);
const result = getUnsupportedTargets(node, targets);
expect(result).toEqual([]);
});

it("should return unsupported targets for AbortSignal.timeout in older browsers", () => {
const node = {
protoChainId: "AbortSignal.timeout",
} as AstMetadataApiWithTargetsResolver;
const config = determineTargetsFromConfig(".", [
"chrome 102",
"safari 15",
"firefox 99",
]);
const targets = parseBrowsersListVersion(config);
const result = getUnsupportedTargets(node, targets);
expect(result).toEqual(["Safari 15", "Firefox 99", "Chrome 102"]);
});

it("should return empty for AbortSignal.timeout in supported browsers", () => {
const node = {
protoChainId: "AbortSignal.timeout",
} as AstMetadataApiWithTargetsResolver;
const config = determineTargetsFromConfig(".", [
"chrome 124",
"safari 16",
"firefox 100",
]);
const targets = parseBrowsersListVersion(config);
const result = getUnsupportedTargets(node, targets);
expect(result).toEqual([]);
});
});