Skip to content

Commit

Permalink
Allow specifying provided polyfills (#2) (#50)
Browse files Browse the repository at this point in the history
* use polyfill option

* add pollyfils option

* cleanup

* add Promise.prototype.any polyfill

* rename forbiddenFeatures to unsupportedFeatures

* no need to use optional chaining when accessing options on context

* prettier fix

* change nonPolyfilledFeatures(features to isPolifilled

* fix typo

* 💄💃

* get rid of separate polyfilling file

* ensure code quality

* rename forbidden > unsupported in test descriptions and local vars

* mistake != typo

* docs

* get rid of delegateeConfigs variable

* update polyfills schema

* prettier fix

* get rid of intermediate vars in rule create()

* update specs with valid polyfill cases

* improve specs

Co-authored-by: Przemysław Sobstel <XPRZS@saxobank.com>
Co-authored-by: Robat Williams <robatwilliams@gmail.com>
  • Loading branch information
3 people authored Nov 15, 2022
1 parent 502f669 commit 1626022
Show file tree
Hide file tree
Showing 16 changed files with 229 additions and 60 deletions.
14 changes: 13 additions & 1 deletion packages/eslint-plugin-ecmascript-compat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,15 @@ npm install --save-dev eslint-plugin-ecmascript-compat
{
"plugins": ["ecmascript-compat"],
"rules": {
"ecmascript-compat/compat": "error"
"ecmascript-compat/compat": [
"error",
{
// Optionally, specify provided polyfills
"polyfills": [
"Array.prototype.includes"
]
}
]
}
}
```
Expand All @@ -32,6 +40,10 @@ Chrome >= 64
Firefox >= 58
```

<!--- Absolute link, in order to work from NPM website --->

The optional `polyfills` option is used to specify polyfills that your application loads. These features are therefore considered supported in all browsers. Features that are polyfillable and can be specified here can be found in the [rule schema](https://github.com/robatwilliams/es-compat/blob/master/packages/eslint-plugin-ecmascript-compat/lib/rule.js).

For example usage, see sibling directory: `eslint-plugin-ecmascript-compat-example`

<!--- Absolute link, in order to work from NPM website --->
Expand Down
4 changes: 2 additions & 2 deletions packages/eslint-plugin-ecmascript-compat/lib/compatibility.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable camelcase, no-underscore-dangle */

function forbiddenFeatures(features, targets) {
function unsupportedFeatures(features, targets) {
return features.filter((feature) => !isFeatureSupportedByTargets(feature, targets));
}

Expand Down Expand Up @@ -54,4 +54,4 @@ function interpretSupport(versionAdded) {
};
}

module.exports = { forbiddenFeatures };
module.exports = { unsupportedFeatures };
54 changes: 27 additions & 27 deletions packages/eslint-plugin-ecmascript-compat/lib/compatibility.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable camelcase */
const { forbiddenFeatures } = require('./compatibility');
const { unsupportedFeatures } = require('./compatibility');

it('allows feature in version introduced', () => {
it('supports feature in version introduced', () => {
const feature = {
compatFeatures: [
{
Expand All @@ -14,11 +14,11 @@ it('allows feature in version introduced', () => {
],
};

const forbidden = forbiddenFeatures([feature], [{ name: 'chrome', version: '73' }]);
expect(forbidden).toHaveLength(0);
const unsupported = unsupportedFeatures([feature], [{ name: 'chrome', version: '73' }]);
expect(unsupported).toHaveLength(0);
});

it('forbids feature in version before introduced', () => {
it('doesnt support feature in version before introduced', () => {
const feature = {
compatFeatures: [
{
Expand All @@ -31,11 +31,11 @@ it('forbids feature in version before introduced', () => {
],
};

const forbidden = forbiddenFeatures([feature], [{ name: 'chrome', version: '72' }]);
expect(forbidden[0]).toBe(feature);
const unsupported = unsupportedFeatures([feature], [{ name: 'chrome', version: '72' }]);
expect(unsupported[0]).toBe(feature);
});

it('allows feature supported by family in unknown version', () => {
it('supports feature supported by family in unknown version', () => {
const feature = {
compatFeatures: [
{
Expand All @@ -48,11 +48,11 @@ it('allows feature supported by family in unknown version', () => {
],
};

const forbidden = forbiddenFeatures([feature], [{ name: 'chrome', version: '73' }]);
expect(forbidden).toHaveLength(0);
const unsupported = unsupportedFeatures([feature], [{ name: 'chrome', version: '73' }]);
expect(unsupported).toHaveLength(0);
});

it('forbids feature not supported in any version of family', () => {
it('doesnt support feature not supported in any version of family', () => {
const feature = {
compatFeatures: [
{
Expand All @@ -65,11 +65,11 @@ it('forbids feature not supported in any version of family', () => {
],
};

const forbidden = forbiddenFeatures([feature], [{ name: 'chrome', version: '73' }]);
expect(forbidden[0]).toBe(feature);
const unsupported = unsupportedFeatures([feature], [{ name: 'chrome', version: '73' }]);
expect(unsupported[0]).toBe(feature);
});

it('allows feature with unknown support by family', () => {
it('supports feature with unknown support by family', () => {
const feature = {
compatFeatures: [
{
Expand All @@ -82,11 +82,11 @@ it('allows feature with unknown support by family', () => {
],
};

const forbidden = forbiddenFeatures([feature], [{ name: 'chrome', version: '73' }]);
expect(forbidden).toHaveLength(0);
const unsupported = unsupportedFeatures([feature], [{ name: 'chrome', version: '73' }]);
expect(unsupported).toHaveLength(0);
});

it('allows feature with omitted support entry for mobile target', () => {
it('supports feature with omitted support entry for mobile target', () => {
const feature = {
compatFeatures: [
{
Expand All @@ -99,14 +99,14 @@ it('allows feature with omitted support entry for mobile target', () => {
],
};

const forbidden = forbiddenFeatures(
const unsupported = unsupportedFeatures(
[feature],
[{ name: 'chrome_android', version: '73' }]
);
expect(forbidden).toHaveLength(0);
expect(unsupported).toHaveLength(0);
});

it('forbids feature supported by one target but not another', () => {
it('doesnt support feature supported by one target but not another', () => {
const feature = {
compatFeatures: [
{
Expand All @@ -120,14 +120,14 @@ it('forbids feature supported by one target but not another', () => {
],
};

const forbidden = forbiddenFeatures(
const unsupported = unsupportedFeatures(
[feature],
[
{ name: 'chrome', version: '73' },
{ name: 'firefox', version: '50' },
]
);
expect(forbidden[0]).toBe(feature);
expect(unsupported[0]).toBe(feature);
});

it('uses primary support record where multiple ones exist', () => {
Expand All @@ -154,17 +154,17 @@ it('uses primary support record where multiple ones exist', () => {
],
};

const primaryForbidden = forbiddenFeatures(
const primaryUnsupported = unsupportedFeatures(
[feature],
[{ name: 'nodejs', version: '7.0.0' }]
);
expect(primaryForbidden).toHaveLength(0);
expect(primaryUnsupported).toHaveLength(0);

const secondaryForbidden = forbiddenFeatures(
const secondaryUnsupported = unsupportedFeatures(
[feature],
[{ name: 'nodejs', version: '6.7.0' }]
);
expect(secondaryForbidden[0]).toBe(feature);
expect(secondaryUnsupported[0]).toBe(feature);
});

it('explains what the problem is when compat feature not found in MDN data', () => {
Expand All @@ -191,6 +191,6 @@ it('explains what the problem is when compat feature not found in MDN data', ()
};

expect(() => {
forbiddenFeatures([feature], [{ name: 'chrome', version: '73' }]);
unsupportedFeatures([feature], [{ name: 'chrome', version: '73' }]);
}).toThrow("Sparse compatFeatures for rule 'some rule': object,undefined");
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = [
options: noRestrictedSyntaxPrototypeMethod('Array.prototype.includes', 'ES2016'),
},
compatFeatures: [compatData.javascript.builtins.Array.includes],
polyfill: 'Array.prototype.includes',
},
{
ruleConfig: { definition: esPlugin.rules['no-exponential-operators'] },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ const ruleTester = new RuleTester({
});

ruleTester.run('compat', require('../rule'), {
valid: [],
valid: [
{
code: 'foo.includes();',
options: [{ polyfills: ['Array.prototype.includes'] }],
},
],
invalid: [
{
code: 'foo.includes();',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,17 @@ module.exports = [
{
ruleConfig: { definition: esPlugin.rules['no-object-getownpropertydescriptors'] },
compatFeatures: [compatData.javascript.builtins.Object.getOwnPropertyDescriptors],
polyfill: 'Object.getOwnPropertyDescriptors',
},
{
ruleConfig: { definition: esPlugin.rules['no-object-entries'] },
compatFeatures: [compatData.javascript.builtins.Object.entries],
polyfill: 'Object.entries',
},
{
ruleConfig: { definition: esPlugin.rules['no-object-values'] },
compatFeatures: [compatData.javascript.builtins.Object.values],
polyfill: 'Object.values',
},
{
// Rule requires the ES2017 global, SharedArrayBuffer
Expand All @@ -41,13 +44,15 @@ module.exports = [
options: noRestrictedSyntaxPrototypeMethod('String.prototype.padStart', 'ES2017'),
},
compatFeatures: [compatData.javascript.builtins.String.padStart],
polyfill: 'String.prototype.padStart',
},
{
ruleConfig: {
definition: coreRules.get('no-restricted-syntax'),
options: noRestrictedSyntaxPrototypeMethod('String.prototype.padEnd', 'ES2017'),
},
compatFeatures: [compatData.javascript.builtins.String.padEnd],
polyfill: 'String.prototype.padEnd',
},
{
ruleConfig: { definition: esPlugin.rules['no-trailing-function-commas'] },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,28 @@ const ruleTester = new RuleTester({
});

ruleTester.run('compat', require('../rule'), {
valid: [],
valid: [
{
code: 'Object.getOwnPropertyDescriptors();',
options: [{ polyfills: ['Object.getOwnPropertyDescriptors'] }],
},
{
code: 'Object.entries();',
options: [{ polyfills: ['Object.entries'] }],
},
{
code: 'Object.values();',
options: [{ polyfills: ['Object.values'] }],
},
{
code: 'str.padStart();',
options: [{ polyfills: ['String.prototype.padStart'] }],
},
{
code: 'str.padEnd();',
options: [{ polyfills: ['String.prototype.padEnd'] }],
},
],
invalid: [
{
code: 'async function foo() {}',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = [
options: noRestrictedSyntaxPrototypeMethod('Promise.prototype.finally', 'ES2018'),
},
compatFeatures: [compatData.javascript.builtins.Promise.finally],
polyfill: 'Promise.prototype.finally',
},
{
ruleConfig: { definition: esPlugin.rules['no-regexp-lookbehind-assertions'] },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ const ruleTester = new RuleTester({
});

ruleTester.run('compat', require('../rule'), {
valid: [],
valid: [
{
code: 'foo.finally();',
options: [{ polyfills: ['Promise.prototype.finally'] }],
},
],
invalid: [
{
code: 'async function* asyncGenerator() {}',
Expand Down
58 changes: 39 additions & 19 deletions packages/eslint-plugin-ecmascript-compat/lib/features/es2019.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@ module.exports = [
{
ruleConfig: {
definition: coreRules.get('no-restricted-syntax'),
options: [
...noRestrictedSyntaxPrototypeMethod('Array.prototype.flat', 'ES2019'),
...noRestrictedSyntaxPrototypeMethod('Array.prototype.flatMap', 'ES2019'),
],
options: noRestrictedSyntaxPrototypeMethod('Array.prototype.flat', 'ES2019'),
},
compatFeatures: [
compatData.javascript.builtins.Array.flat,
compatData.javascript.builtins.Array.flatMap,
],
compatFeatures: [compatData.javascript.builtins.Array.flat],
polyfill: 'Array.prototype.flat',
},
{
ruleConfig: {
definition: coreRules.get('no-restricted-syntax'),
options: noRestrictedSyntaxPrototypeMethod('Array.prototype.flatMap', 'ES2019'),
},
compatFeatures: [compatData.javascript.builtins.Array.flatMap],
polyfill: 'Array.prototype.flatMap',
},
{
ruleConfig: { definition: esPlugin.rules['no-json-superset'] },
Expand All @@ -26,6 +29,7 @@ module.exports = [
{
ruleConfig: { definition: esPlugin.rules['no-object-fromentries'] },
compatFeatures: [compatData.javascript.builtins.Object.fromEntries],
polyfill: 'Object.fromEntries',
},
{
ruleConfig: { definition: esPlugin.rules['no-optional-catch-binding'] },
Expand All @@ -34,17 +38,33 @@ module.exports = [
{
ruleConfig: {
definition: coreRules.get('no-restricted-syntax'),
options: [
...noRestrictedSyntaxPrototypeMethod('String.prototype.trimLeft', 'ES2019'),
...noRestrictedSyntaxPrototypeMethod('String.prototype.trimRight', 'ES2019'),
...noRestrictedSyntaxPrototypeMethod('String.prototype.trimStart', 'ES2019'),
...noRestrictedSyntaxPrototypeMethod('String.prototype.trimEnd', 'ES2019'),
],
options: noRestrictedSyntaxPrototypeMethod('String.prototype.trimStart', 'ES2019'),
},
compatFeatures: [compatData.javascript.builtins.String.trimStart],
polyfill: 'String.prototype.trimStart',
},
{
ruleConfig: {
definition: coreRules.get('no-restricted-syntax'),
options: noRestrictedSyntaxPrototypeMethod('String.prototype.trimLeft', 'ES2019'),
},
compatFeatures: [compatData.javascript.builtins.String.trimStart], // not a mistake; trimLeft is an alias for trimStart
polyfill: 'String.prototype.trimLeft',
},
{
ruleConfig: {
definition: coreRules.get('no-restricted-syntax'),
options: noRestrictedSyntaxPrototypeMethod('String.prototype.trimEnd', 'ES2019'),
},
compatFeatures: [compatData.javascript.builtins.String.trimEnd],
polyfill: 'String.prototype.trimEnd',
},
{
ruleConfig: {
definition: coreRules.get('no-restricted-syntax'),
options: noRestrictedSyntaxPrototypeMethod('String.prototype.trimRight', 'ES2019'),
},
compatFeatures: [
// trimRight and trimLeft are alternates of these
compatData.javascript.builtins.String.trimEnd,
compatData.javascript.builtins.String.trimStart,
],
compatFeatures: [compatData.javascript.builtins.String.trimEnd], // not a mistake; trimRight is an alias for trimEnd
polyfill: 'String.prototype.trimRight',
},
];
Loading

0 comments on commit 1626022

Please sign in to comment.