forked from daren.hsu/line_push
137 lines
3.7 KiB
JavaScript
137 lines
3.7 KiB
JavaScript
'use strict';
|
|
const getDocumentationUrl = require('./utils/get-documentation-url');
|
|
const isValueNotUsable = require('./utils/is-value-not-usable');
|
|
const methodSelector = require('./utils/method-selector');
|
|
|
|
const messages = {
|
|
replaceChildOrInsertBefore:
|
|
'Prefer `{{oldChildNode}}.{{preferredMethod}}({{newChildNode}})` over `{{parentNode}}.{{method}}({{newChildNode}}, {{oldChildNode}})`.',
|
|
insertAdjacentTextOrInsertAdjacentElement:
|
|
'Prefer `{{reference}}.{{preferredMethod}}({{content}})` over `{{reference}}.{{method}}({{position}}, {{content}})`.'
|
|
};
|
|
|
|
const replaceChildOrInsertBeforeSelector = [
|
|
methodSelector({
|
|
names: ['replaceChild', 'insertBefore'],
|
|
length: 2
|
|
}),
|
|
// We only allow Identifier for now
|
|
'[arguments.0.type="Identifier"]',
|
|
'[arguments.0.name!="undefined"]',
|
|
'[arguments.1.type="Identifier"]',
|
|
'[arguments.1.name!="undefined"]',
|
|
// This check makes sure that only the first method of chained methods with same identifier name e.g: parentNode.insertBefore(alfa, beta).insertBefore(charlie, delta); gets reported
|
|
'[callee.object.type="Identifier"]'
|
|
].join('');
|
|
|
|
const forbiddenMethods = new Map([
|
|
['replaceChild', 'replaceWith'],
|
|
['insertBefore', 'before']
|
|
]);
|
|
|
|
const checkForReplaceChildOrInsertBefore = (context, node) => {
|
|
const method = node.callee.property.name;
|
|
const parentNode = node.callee.object.name;
|
|
const [newChildNode, oldChildNode] = node.arguments.map(({name}) => name);
|
|
const preferredMethod = forbiddenMethods.get(method);
|
|
|
|
const fix = isValueNotUsable(node) ?
|
|
fixer => fixer.replaceText(
|
|
node,
|
|
`${oldChildNode}.${preferredMethod}(${newChildNode})`
|
|
) :
|
|
undefined;
|
|
|
|
return context.report({
|
|
node,
|
|
messageId: 'replaceChildOrInsertBefore',
|
|
data: {
|
|
parentNode,
|
|
method,
|
|
preferredMethod,
|
|
newChildNode,
|
|
oldChildNode
|
|
},
|
|
fix
|
|
});
|
|
};
|
|
|
|
const insertAdjacentTextOrInsertAdjacentElementSelector = [
|
|
methodSelector({
|
|
names: ['insertAdjacentText', 'insertAdjacentElement'],
|
|
length: 2
|
|
}),
|
|
// Position argument should be `string`
|
|
'[arguments.0.type="Literal"]',
|
|
// TODO: remove this limits on second argument
|
|
':matches([arguments.1.type="Literal"], [arguments.1.type="Identifier"])',
|
|
// TODO: remove this limits on callee
|
|
'[callee.object.type="Identifier"]'
|
|
].join('');
|
|
|
|
const positionReplacers = new Map([
|
|
['beforebegin', 'before'],
|
|
['afterbegin', 'prepend'],
|
|
['beforeend', 'append'],
|
|
['afterend', 'after']
|
|
]);
|
|
|
|
const checkForInsertAdjacentTextOrInsertAdjacentElement = (context, node) => {
|
|
const method = node.callee.property.name;
|
|
const [positionNode, contentNode] = node.arguments;
|
|
|
|
const position = positionNode.value;
|
|
// Return early when specified position value of first argument is not a recognized value.
|
|
if (!positionReplacers.has(position)) {
|
|
return;
|
|
}
|
|
|
|
const preferredMethod = positionReplacers.get(position);
|
|
const content = context.getSource(contentNode);
|
|
const reference = context.getSource(node.callee.object);
|
|
|
|
const fix = method === 'insertAdjacentElement' && !isValueNotUsable(node) ?
|
|
undefined :
|
|
// TODO: make a better fix, don't touch reference
|
|
fixer => fixer.replaceText(
|
|
node,
|
|
`${reference}.${preferredMethod}(${content})`
|
|
);
|
|
|
|
return context.report({
|
|
node,
|
|
messageId: 'insertAdjacentTextOrInsertAdjacentElement',
|
|
data: {
|
|
reference,
|
|
method,
|
|
preferredMethod,
|
|
position: context.getSource(positionNode),
|
|
content
|
|
},
|
|
fix
|
|
});
|
|
};
|
|
|
|
const create = context => {
|
|
return {
|
|
[replaceChildOrInsertBeforeSelector](node) {
|
|
checkForReplaceChildOrInsertBefore(context, node);
|
|
},
|
|
[insertAdjacentTextOrInsertAdjacentElementSelector](node) {
|
|
checkForInsertAdjacentTextOrInsertAdjacentElement(context, node);
|
|
}
|
|
};
|
|
};
|
|
|
|
module.exports = {
|
|
create,
|
|
meta: {
|
|
type: 'suggestion',
|
|
docs: {
|
|
url: getDocumentationUrl(__filename)
|
|
},
|
|
fixable: 'code',
|
|
messages
|
|
}
|
|
};
|