Skip to content

Commit

Permalink
support detecting locally defined polyfills (#207)
Browse files Browse the repository at this point in the history
* support detecting locally defined polyfills

* handle commonjs imports
  • Loading branch information
amilajack committed Mar 24, 2019
1 parent 6373ab5 commit bb3be6e
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 7 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ eslint-plugin-compat
[![NPM version](https://badge.fury.io/js/eslint-plugin-compat.svg)](http://badge.fury.io/js/eslint-plugin-compat)
[![Dependency Status](https://img.shields.io/david/amilajack/eslint-plugin-compat.svg)](https://david-dm.org/amilajack/eslint-plugin-compat)
[![npm](https://img.shields.io/npm/dm/eslint-plugin-compat.svg)](https://npm-stat.com/charts.html?package=eslint-plugin-compat)
[![Backers on Open Collective](https://opencollective.com/eslint-plugin-compat/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/eslint-plugin-compat/sponsors/badge.svg)](#sponsors)

Lint the browser compatibility of your code

Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,10 @@
"engines": {
"node": ">=8.x",
"npm": ">=6.8.0"
},
"collective": {
"type": "opencollective",
"url": "https://opencollective.com/eslint-plugin-compat",
"logo": "https://opencollective.com/opencollective/logo.txt"
}
}
59 changes: 53 additions & 6 deletions src/rules/compat.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@ type Context = {
report: () => void
};

function getName(node) {
switch (node.type) {
case 'NewExpression': {
return node.callee.name;
}
case 'MemberExpression': {
return node.object.name;
}
case 'CallExpression': {
return node.callee.name;
}
default:
throw new Error('not found');
}
}

export type BrowserListConfig =
| Array<string>
| {
Expand Down Expand Up @@ -47,6 +63,8 @@ export default {
DetermineTargetsFromConfig(browserslistConfig)
);

const errors = [];

function lint(node: ESLintNode) {
const { isValid, rule, unsupportedTargets } = Lint(
node,
Expand All @@ -55,7 +73,7 @@ export default {
);

if (!isValid) {
context.report({
errors.push({
node,
message: [
generateErrorName(rule),
Expand All @@ -66,14 +84,43 @@ export default {
}
}

const identifiers = new Set();

return {
// HACK: Ideally, rules will be generated at runtime. Each rule will have
// have the ability to register itself to run on specific AST
// nodes. For now, we're using the `CallExpression` node since
// its what most rules will run on
CallExpression: lint,
MemberExpression: lint,
NewExpression: lint
NewExpression: lint,
// Keep track of all the defined variables. Do not report errors for nodes that are not defined
Identifier(node) {
if (node.parent) {
const { type } = node.parent;
if (
// ex. const { Set } = require('immutable');
type === 'Property' ||
// ex. function Set() {}
type === 'FunctionDeclaration' ||
// ex. const Set = () => {}
type === 'VariableDeclarator' ||
// ex. class Set {}
type === 'ClassDeclaration' ||
// ex. import Set from 'set';
type === 'ImportDefaultSpecifier' ||
// ex. import {Set} from 'set';
type === 'ImportSpecifier' ||
// ex. import {Set} from 'set';
type === 'ImportDeclaration'
) {
identifiers.add(node.name);
}
}
},
'Program:exit': () => {
// Get a map of all the variables defined in the root scope (not the global scope)
// const variablesMap = context.getScope().childScopes.map(e => e.set)[0];
errors
.filter(error => !identifiers.has(getName(error.node)))
.forEach(node => context.report(node));
}
};
}
};
112 changes: 111 additions & 1 deletion test/e2e.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,98 @@ import { RuleTester } from 'eslint';
import rule from '../src/rules/compat';

const ruleTester = new RuleTester({
parserOptions: { ecmaVersion: 2015 }
parserOptions: { ecmaVersion: 2015, sourceType: 'module' }
});

ruleTester.run('compat', rule, {
valid: [
{
code: `
import { Set } from 'immutable';
new Set();
`,
settings: { browsers: ['ie 9'] }
},
{
code: `
const { Set } = require('immutable');
new Set();
`,
settings: { browsers: ['ie 9'] }
},
{
code: `
const Set = require('immutable').Set;
new Set();
`,
settings: { browsers: ['ie 9'] }
},
{
code: `
const { Set } = require('immutable');
(() => {
new Set();
})();
`,
settings: { browsers: ['ie 9'] }
},
{
code: `
import Set from 'immutable';
new Set();
`,
settings: { browsers: ['ie 9'] }
},
{
code: `
function Set() {}
new Set();
`,
settings: { browsers: ['ie 9'] }
},
{
code: `
const Set = () => {};
new Set();
`,
settings: { browsers: ['ie 9'] }
},
{
code: `
const bar = () => {
const Set = () => {};
new Set();
}
`,
settings: { browsers: ['ie 9'] }
},
{
code: `
const bar = () => {
class Set {}
new Set()
}
`,
settings: { browsers: ['ie 9'] }
},
{
code: `
const bar = () => {
const Set = {}
new Set()
}
`,
settings: { browsers: ['ie 9'] }
},
{
code: `
const bar = () => {
function Set() {}
new Set()
}
`,
settings: { browsers: ['ie 9'] }
},
{
code: 'document.documentElement()',
settings: { browsers: ['Safari 11', 'Opera 57', 'Edge 17'] }
Expand Down Expand Up @@ -70,6 +157,29 @@ ruleTester.run('compat', rule, {
}
],
invalid: [
{
code: `
import { Map } from 'immutable';
new Set()
`,
settings: { browsers: ['ie 9'] },
errors: [
{
message: 'Set is not supported in IE 9',
type: 'NewExpression'
}
]
},
{
code: 'new Set()',
settings: { browsers: ['ie 9'] },
errors: [
{
message: 'Set is not supported in IE 9',
type: 'NewExpression'
}
]
},
{
code: 'new TypedArray()',
settings: { browsers: ['ie 9'] },
Expand Down

0 comments on commit bb3be6e

Please sign in to comment.