diff --git a/src/providers/mdn-provider.ts b/src/providers/mdn-provider.ts index a0f48ce0..87168790 100644 --- a/src/providers/mdn-provider.ts +++ b/src/providers/mdn-provider.ts @@ -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[] = [ + // 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 = new Map( - apis.map((e) => [e.protoChainId, e]) + allApis.map((e) => [e.protoChainId, e]) ); interface TargetIdMappings { @@ -147,7 +202,7 @@ function getMetadataName(metadata: ApiMetadata) { } } -const MdnProvider: Array = apis +const MdnProvider: Array = allApis // Create entries for each ast node type .map((metadata) => metadata.astNodeTypes.map((astNodeType) => ({ diff --git a/test/e2e.spec.ts b/test/e2e.spec.ts index f89576a6..1309ea21 100644 --- a/test/e2e.spec.ts +++ b/test/e2e.spec.ts @@ -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: [ { @@ -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", + }, + ], + }, ], }); diff --git a/test/mdn-provider.spec.ts b/test/mdn-provider.spec.ts index 180e4708..dfcb4401 100644 --- a/test/mdn-provider.spec.ts +++ b/test/mdn-provider.spec.ts @@ -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([]); + }); });