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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"axios": "^0.19.2",
"better-ajv-errors": "^0.6.7",
"consola": "^2.11.3",
"lodash": "^4.17.15",
"fast-escape-regexp": "^1.0.1",
"tldts": "^5.6.10",
"yargs": "^15.3.0"
}
Expand Down
7 changes: 3 additions & 4 deletions src/filter.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const _ = require('lodash');
const { FiltersDownloader } = require('@adguard/filters-downloader');
const utils = require('./utils');
const ruleUtils = require('./rule');
Expand All @@ -12,7 +11,7 @@ const ruleUtils = require('./rule');
*/
async function downloadAll(sources) {
let list = [];
if (_.isEmpty(sources)) {
if (!sources?.length) {
return list;
}

Expand All @@ -36,12 +35,12 @@ async function downloadAll(sources) {
*/
async function prepareWildcards(rules, sources) {
let list = [];
if (!_.isEmpty(rules)) {
if (rules.length > 0) {
list = list.concat(rules);
}
const loadedList = await downloadAll(sources);
list = list.concat(loadedList);
list = _.compact(_.uniq(list));
list = Array.from(new Set(list)).filter(Boolean); // remove duplicates

return list.map((str) => new utils.Wildcard(str));
}
Expand Down
11 changes: 5 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const _ = require('lodash');
const consola = require('consola');
const config = require('./configuration');
const compileSource = require('./compile-source');
Expand All @@ -18,16 +17,16 @@ function prepareHeader(configuration) {
`! Title: ${configuration.name}`,
];

if (!_.isEmpty(configuration.description)) {
if (configuration.description?.length) {
lines.push(`! Description: ${configuration.description}`);
}
if (!_.isEmpty(configuration.version)) {
if (configuration.version?.length) {
lines.push(`! Version: ${configuration.version}`);
}
if (!_.isEmpty(configuration.homepage)) {
if (configuration.homepage?.length) {
lines.push(`! Homepage: ${configuration.homepage}`);
}
if (!_.isEmpty(configuration.license)) {
if (configuration.license?.length) {
lines.push(`! License: ${configuration.license}`);
}

Expand Down Expand Up @@ -55,7 +54,7 @@ function prepareSourceHeader(source) {
'!',
];

if (!_.isEmpty(source.name)) {
if (source.name.length > 0) {
lines.push(`! Source name: ${source.name}`);
}
lines.push(`! Source: ${source.source}`);
Expand Down
29 changes: 14 additions & 15 deletions src/rule.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const _ = require('lodash');
const { domainToASCII } = require('url');
const utils = require('./utils');

Expand All @@ -18,26 +17,26 @@ const nonAsciiRegexp = /[^\x00-\x7F]/;
* @returns {Boolean} true if rule is a comment
*/
function isComment(ruleText) {
return _.startsWith(ruleText, '!')
|| _.startsWith(ruleText, '# ')
return ruleText.startsWith('!')
|| ruleText.startsWith('# ')
|| ruleText === '#'
|| _.startsWith(ruleText, '####');
|| ruleText.startsWith('####');
}

/**
* @param {String} ruleText - rule to check
* @returns {Boolean} true if the rule is "allowing"
*/
function isAllowRule(ruleText) {
return _.startsWith(ruleText, '@@');
return ruleText.startsWith('@@');
}

/**
* @param {String} rule - rule to check
* @returns {Boolean} true if the rule is just the domain name
*/
function isJustDomain(ruleText) {
return _.includes(ruleText, '.')
return ruleText.includes('.')
&& domainRegex.test(ruleText);
}

Expand Down Expand Up @@ -100,7 +99,7 @@ function parseRuleTokens(ruleText) {
};

let startIndex = 0;
if (_.startsWith(ruleText, '@@')) {
if (ruleText.startsWith('@@')) {
tokens.whitelist = true;
startIndex = 2;
}
Expand All @@ -113,8 +112,8 @@ function parseRuleTokens(ruleText) {
tokens.pattern = ruleText.substring(startIndex);

// Avoid parsing options inside of a regex rule
if (_.startsWith(tokens.pattern, '/')
&& _.endsWith(tokens.pattern, '/')
if (tokens.pattern.startsWith('/')
&& tokens.pattern.endsWith('/')
&& tokens.pattern.indexOf('replace=') === -1) {
return tokens;
}
Expand Down Expand Up @@ -150,12 +149,12 @@ function parseRuleTokens(ruleText) {
* @throws {TypeError} thrown if it is not a valid /etc/hosts rule
*/
function loadEtcHostsRuleProperties(ruleText) {
let rule = _.trim(ruleText);
let rule = ruleText.trim();
if (rule.indexOf('#') > 0) {
rule = rule.substring(0, rule.indexOf('#'));
}

const [, ...hostnames] = _.trim(rule).split(/\s+/);
const [, ...hostnames] = rule.trim().split(/\s+/);
if (hostnames.length < 1) {
throw new TypeError(`Invalid /etc/hosts rule: ${ruleText}`);
}
Expand All @@ -171,7 +170,7 @@ function loadEtcHostsRuleProperties(ruleText) {
* @typedef {Object} AdblockRule
* @property {String} ruleText - original rule text
* @property {String} pattern - matching pattern
* @property {Array<{{name: string, value: string}}>} options - list of rule modifiers
* @property {Array<{name: string, value: string}>} [options] - list of rule modifiers
* @property {Boolean} whitelist - whether this is an exception rule or not
* @property {String} hostname - hostname can only be extracted from the rules
* that look like `||[a-z0-9-.]^.*`
Expand All @@ -197,7 +196,7 @@ function extractHostname(pattern) {
* @returns {AdblockRule} - rule properties
*/
function loadAdblockRuleProperties(ruleText) {
const tokens = parseRuleTokens(_.trim(ruleText));
const tokens = parseRuleTokens(ruleText.trim());
const rule = {
ruleText,
pattern: tokens.pattern,
Expand All @@ -213,7 +212,7 @@ function loadAdblockRuleProperties(ruleText) {

// eslint-disable-next-line no-restricted-syntax
for (const option of optionParts) {
const parts = _.split(option, '=', 2);
const parts = option.split('=', 2);
const name = parts[0];
const value = parts[1] ? parts[1] : null;
rule.options.push({
Expand Down Expand Up @@ -285,7 +284,7 @@ function adblockRuleToString(ruleProps) {
ruleText = '@@';
}
ruleText += ruleProps.pattern;
if (!_.isEmpty(ruleProps.options)) {
if (ruleProps.options && ruleProps.options.length > 0) {
ruleText += '$';
for (let i = 0; i < ruleProps.options.length; i += 1) {
const option = ruleProps.options[i];
Expand Down
3 changes: 1 addition & 2 deletions src/transformations/compress.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const _ = require('lodash');
const consola = require('consola');
const ruleUtils = require('../rule');

Expand Down Expand Up @@ -71,7 +70,7 @@ function toAdblockRules(ruleText) {
// try parsing an adblock rule and check if it can be compressed
try {
const props = ruleUtils.loadAdblockRuleProperties(ruleText);
if (props.hostname && !props.whitelist && _.isEmpty(props.options)) {
if (props.hostname && !props.whitelist && props.options.length === 0) {
adblockRules.push({
ruleText,
canCompress: true,
Expand Down
7 changes: 3 additions & 4 deletions src/transformations/deduplicate.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const _ = require('lodash');
const consola = require('consola');
const ruleUtils = require('../rule');

Expand All @@ -14,7 +13,7 @@ const ruleUtils = require('../rule');
* @returns {Array<String>} "deduplicated" array of rules
*/
function deduplicate(rules) {
if (_.isEmpty(rules)) {
if (rules.length === 0) {
consola.info('Empty rules array, nothing to deduplicate');
return rules;
}
Expand All @@ -32,11 +31,11 @@ function deduplicate(rules) {
rulesIndex[ruleText] = true;
}

if (dup && !ruleUtils.isComment(ruleText) && !_.isEmpty(ruleText)) {
if (dup && !ruleUtils.isComment(ruleText) && ruleText.length > 0) {
prevRuleRemoved = true;
consola.debug(`Removing duplicate: ${ruleText}`);
filtered.splice(iFiltered, 1);
} else if (prevRuleRemoved && (ruleUtils.isComment(ruleText) || _.isEmpty(ruleText))) {
} else if (prevRuleRemoved && (ruleUtils.isComment(ruleText) || ruleText.length === 0)) {
// Remove preceding comments and empty lines
consola.debug(`Removing a comment preceding duplicate: ${ruleText}`);
filtered.splice(iFiltered, 1);
Expand Down
5 changes: 2 additions & 3 deletions src/transformations/exclude.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const _ = require('lodash');
const consola = require('consola');
const filterUtils = require('../filter');

Expand All @@ -12,13 +11,13 @@ const filterUtils = require('../filter');
* @returns {Promise<Array<String>>} filtered array of rules
*/
async function exclude(rules, exclusions, exclusionsSources) {
if (_.isEmpty(exclusions) && _.isEmpty(exclusionsSources)) {
if ((!exclusions || exclusions.length === 0) && (!exclusionsSources || exclusionsSources.length === 0)) {
// Nothing to filter here
return rules;
}

const wildcards = await filterUtils.prepareWildcards(exclusions, exclusionsSources);
if (_.isEmpty(wildcards)) {
if (wildcards.length === 0) {
return rules;
}
consola.info(`Filtering the list of rules using ${wildcards.length} exclusion rules`);
Expand Down
5 changes: 2 additions & 3 deletions src/transformations/include.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const _ = require('lodash');
const consola = require('consola');
const filterUtils = require('../filter');

Expand All @@ -12,13 +11,13 @@ const filterUtils = require('../filter');
* @returns {Promise<Array<String>>} filtered array of rules
*/
async function include(rules, inclusions, inclusionsSources) {
if (_.isEmpty(inclusions) && _.isEmpty(inclusionsSources)) {
if ((!inclusions || inclusions.length === 0) && (!inclusionsSources || inclusionsSources.length === 0)) {
// Nothing to filter here
return rules;
}

const wildcards = await filterUtils.prepareWildcards(inclusions, inclusionsSources);
if (_.isEmpty(wildcards)) {
if (wildcards.length === 0) {
return rules;
}
consola.info(`Filtering the list of rules using ${wildcards.length} inclusion rules`);
Expand Down
3 changes: 1 addition & 2 deletions src/transformations/remove-modifiers.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const _ = require('lodash');
const consola = require('consola');
const ruleUtils = require('../rule');

Expand All @@ -15,7 +14,7 @@ function removeModifiers(rules) {
let modifiedCount = 0;
rules.forEach((rawRuleText) => {
const ruleText = rawRuleText.trim();
if (_.isEmpty(ruleText) || ruleUtils.isComment(ruleText)) {
if (ruleText.length === 0 || ruleUtils.isComment(ruleText)) {
filtered.push(ruleText);
return;
}
Expand Down
3 changes: 1 addition & 2 deletions src/transformations/trim-lines.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const consola = require('consola');
const _ = require('lodash');

/**
* This is a very simple transformation that removes leading and trailing spaces/tabs.
Expand All @@ -8,7 +7,7 @@ const _ = require('lodash');
* @returns {Array<string>} filtered lines/rules
*/
function trimLines(lines) {
const transformed = lines.map((line) => _.trim(line, ' \t'));
const transformed = lines.map((line) => line.replace(/^[ \t]+|[ \t]+$/g, ''));
consola.info('Lines trimmed.');
return transformed;
}
Expand Down
19 changes: 8 additions & 11 deletions src/transformations/validate.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const _ = require('lodash');
const consola = require('consola');
const tldts = require('tldts');
const utils = require('../utils');
Expand All @@ -9,8 +8,6 @@ const DOMAIN_SEPARATOR = '^';
const WILDCARD = '*';
const WILDCARD_DOMAIN_PART = '*.';

// TODO: Remove lodash from the project if possible

/**
* The list of modifiers that limit the rule for specific domains.
*/
Expand Down Expand Up @@ -91,7 +88,7 @@ function validEtcHostsRule(ruleText, allowIP) {
consola.error(`Unexpected incorrect /etc/hosts rule: ${ruleText}: ${ex}`);
return false;
}
if (_.isEmpty(props.hostnames)) {
if (!props.hostnames?.length) {
consola.info(`The rule has no hostnames: ${ruleText}`);
return false;
}
Expand Down Expand Up @@ -153,8 +150,8 @@ function validAdblockRule(ruleText, allowedIP) {

// 3.1. Special case: regex rules
// Do nothing with regex rules -- they may contain all kinds of special chars
if (_.startsWith(props.pattern, '/')
&& _.endsWith(props.pattern, '/')) {
if (props.pattern.startsWith('/')
&& props.pattern.endsWith('/')) {
return true;
}

Expand All @@ -163,8 +160,8 @@ function validAdblockRule(ruleText, allowedIP) {
// *|^ -- special characters used by adblock-style rules
// One more special case is rules starting with ://s
let toTest = props.pattern;
if (_.startsWith(toTest, '://')) {
toTest = _.trimStart(toTest, '://');
if (toTest.startsWith('://')) {
toTest = toTest.replace('://', '');
Copy link

Choose a reason for hiding this comment

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

The replacement of _.trimStart(toTest, '://') with toTest.replace('://', '') might not be equivalent in all cases. trimStart removes all leading occurrences of the specified characters, while replace only replaces the first occurrence. Consider using toTest.substring(3) instead for a more direct replacement.

Copy link
Author

@SukkaW SukkaW Jun 20, 2025

Choose a reason for hiding this comment

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

This is equivalent. We already know that :// is always at the start (that's what .startsWith(://) for). So, it will always replace the leading one.

Also, unlike .slice(3), .replace() prevents string CoW inside Node.js v8, which helps release memory pressure by allowing old strings to be GCed from the memory (.slice() holds the reference of the original string, preventing that from being GCed).

}

const checkChars = /^[a-zA-Z0-9-.*|^]+$/.test(toTest);
Expand All @@ -183,7 +180,7 @@ function validAdblockRule(ruleText, allowedIP) {
}

// Check if the pattern does not start with the domain prefix and does not contain a domain separator
if (!_.startsWith(props.pattern, DOMAIN_PREFIX) || sepIdx === -1) {
if (!props.pattern.startsWith(DOMAIN_PREFIX) || sepIdx === -1) {
return true;
}

Expand Down Expand Up @@ -233,7 +230,7 @@ function validAdblockRule(ruleText, allowedIP) {
* @returns {boolean} - true if the rule is valid, false otherwise
*/
function valid(ruleText, allowedIP) {
if (ruleUtils.isComment(ruleText) || _.isEmpty(_.trim(ruleText))) {
if (ruleUtils.isComment(ruleText) || ruleText.trim().length === 0) {
Copy link

Choose a reason for hiding this comment

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

The replacement of _.isEmpty(_.trim(ruleText)) with ruleText.trim().length === 0 might throw an error if ruleText is undefined or null. Consider using a null check: !ruleText || ruleText.trim().length === 0.

Copy link
Author

Choose a reason for hiding this comment

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

We already know that ruleText is a string and will never be falsy.

return true;
}

Expand Down Expand Up @@ -280,7 +277,7 @@ class Validator {
// preceding comments/empty lines if needed
for (let i = filtered.length - 1; i >= 0; i -= 1) {
const isValidRule = valid(filtered[i], this.allowedIP);
const isCommentOrEmptyLine = ruleUtils.isComment(filtered[i]) || _.isEmpty(filtered[i]);
const isCommentOrEmptyLine = ruleUtils.isComment(filtered[i]) || filtered[i].length === 0;
Copy link

Choose a reason for hiding this comment

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

The replacement of _.isEmpty(filtered[i]) with filtered[i].length === 0 might not handle undefined or null values correctly. Consider using !filtered[i] || filtered[i].length === 0 to maintain the same behavior as lodash's isEmpty.

Copy link
Author

Choose a reason for hiding this comment

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

This is not necessary. We already know that the filtered is string[], so filtered[i] will never be nullish.


if (!isValidRule) {
// If the rule is invalid, remove it and set the flag to remove preceding lines
Expand Down
4 changes: 2 additions & 2 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const _ = require('lodash');
const { escapeRegexp } = require('fast-escape-regexp');

/**
* Extracts a substring between two tags.
Expand Down Expand Up @@ -106,7 +106,7 @@ class Wildcard {
} else if (str.includes('*')) {
// Creates a RegExp from the given string, converting asterisks to .* expressions,
// and escaping all other characters.
this.regex = new RegExp(`^${str.split(/\*+/).map(_.escapeRegExp).join('[\\s\\S]*')}$`, 'i');
this.regex = new RegExp(`^${str.split(/\*+/).map(escapeRegexp).join('[\\s\\S]*')}$`, 'i');
}
}

Expand Down
3 changes: 1 addition & 2 deletions test/transformations/trim-lines.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const _ = require('lodash');
const trimLines = require('../../src/transformations/trim-lines');

describe('Trim lines', () => {
Expand Down Expand Up @@ -52,7 +51,7 @@ describe('Trim lines', () => {
});
it('test with three rules and comments', () => {
const rules = [
_.repeat(' ', _.random(1, 10)), // ' ', ' ', ..., ' ' etc.
' '.repeat(Math.floor(Math.random() * 10) + 1), // ' ', ' ', ..., ' ' etc.
' ! comment multiple words , ',
'',
];
Expand Down
Loading