From 2db4570cbe13f449ee139781ecab3fc9fd3e9cec Mon Sep 17 00:00:00 2001 From: Elizaveta Egorova Date: Fri, 6 Dec 2024 14:15:30 +0300 Subject: [PATCH] AG-31412 Improve error message during include directives resolving. Squashed commit of the following: commit 9708d216b90c98f09e6f0d4931aa3ba7cdbf7af6 Author: jellizaveta Date: Fri Dec 6 14:11:08 2024 +0300 update changelog commit df3b5b0b758d335d6870437fb720de3589160272 Author: jellizaveta Date: Fri Dec 6 13:42:38 2024 +0300 update CHANGELOG commit 33ccd0c6c12aed10436a518121c13d14ae5674b3 Merge: 52af3a2 6fd1af1 Author: jellizaveta Date: Wed Dec 4 19:37:20 2024 +0300 conflicts commit 52af3a2e5dc2c3fe7e9f54234335a43b2e588818 Author: jellizaveta Date: Wed Dec 4 19:36:34 2024 +0300 update names and context extraction, create a separate file for logger functions commit 6fd1af1072f337b8444c09e9d899850d29a00bb4 Author: Slava Leleka Date: Wed Dec 4 13:37:05 2024 +0300 src/filters-downloader-creator.ts edited online with Bitbucket commit ddd9ac0e7043e2be947e2e2d736020e5c02d2ab4 Author: jellizaveta Date: Wed Dec 4 13:03:31 2024 +0300 fix error logging commit 47fafe8f2ff1bb09bb91e577a420a1a03c028ea4 Author: Slava Leleka Date: Wed Dec 4 12:58:13 2024 +0300 src/filters-downloader-creator.ts edited online with Bitbucket commit bdb6e7c657ecff03ebc681a26d2c649972ba159a Author: Slava Leleka Date: Wed Dec 4 12:58:07 2024 +0300 src/filters-downloader-creator.ts edited online with Bitbucket commit 9a587c2337ad72f9e80283deb7ab57b8a1bbd6eb Author: Slava Leleka Date: Wed Dec 4 12:57:46 2024 +0300 src/filters-downloader-creator.ts edited online with Bitbucket commit 514bfe6cab42074a1ed2ac442acd22285d83d8a5 Author: Slava Leleka Date: Wed Dec 4 12:57:28 2024 +0300 src/filters-downloader-creator.ts edited online with Bitbucket commit 8b5f0cccc27511c3aff956be5b13fbb96c586441 Author: jellizaveta Date: Tue Dec 3 19:55:44 2024 +0300 revert Qunit tests commit f80bb023979a12217a7dcedf7db53a67a59cdfa6 Author: jellizaveta Date: Tue Dec 3 19:53:36 2024 +0300 added error message when possible commit dc7b00adcb2aacc6d942f7628dafab5912595d21 Author: jellizaveta Date: Tue Dec 3 19:10:39 2024 +0300 error handling commit 9ab4725042d6dfa0fc9b4b94d4471a8365eedee2 Author: Slava Leleka Date: Tue Dec 3 18:20:36 2024 +0300 __tests__/filters-downloader.test.ts edited online with Bitbucket commit b3a10e98a5dca98faa48d64f9320f6fe022cc195 Author: jellizaveta Date: Tue Dec 3 18:05:26 2024 +0300 fix error messages commit 3c808c103f2789db10e05da4f6119d2a4cf69512 Author: jellizaveta Date: Tue Dec 3 17:49:00 2024 +0300 Fix cd '/Users/elizavetaegorova/Documents/filters-downloader' commit eae8b8e3615a82accf8f5277a93ab1293fa69eec Author: jellizaveta Date: Tue Dec 3 17:34:46 2024 +0300 update tests commit 569ff8ffbd023a97e918d0f4938d407072b08185 Author: jellizaveta Date: Mon Dec 2 21:03:49 2024 +0300 fix comment commit 073c59b2ab78ac29b12453c92e44fadb387df308 Merge: 5405295 c2cb8ab Author: jellizaveta Date: Mon Dec 2 21:03:04 2024 +0300 Merge branch 'fix/AG-31412' of ssh://bit.int.agrd.dev:7999/extensions/filters-downloader into fix/AG-31412 commit 5405295e893bbc144bd4ce2c72257c60916b652f Author: jellizaveta Date: Mon Dec 2 21:02:44 2024 +0300 update createErrorMessage function ... and 11 more commits --- .eslintrc.js | 3 + CHANGELOG.md | 23 ++--- __tests__/filters-downloader.test.ts | 122 +++++++++++++++++++++++ _qunit_tests_/filters-downloader.test.ts | 17 ++-- package.json | 1 + src/filters-downloader-creator.ts | 73 ++++++++++---- src/helpers/logger.ts | 63 ++++++++++++ 7 files changed, 263 insertions(+), 39 deletions(-) create mode 100644 src/helpers/logger.ts diff --git a/.eslintrc.js b/.eslintrc.js index 3a84040..ce229fc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -33,6 +33,8 @@ module.exports = { 'no-restricted-syntax': ['error', 'LabeledStatement', 'WithStatement'], 'no-constant-condition': ['error', { checkLoops: false }], 'no-param-reassign': 'off', + 'no-unused-vars': ['error', { varsIgnorePattern: '^_' }], + 'no-empty': ['error', { allowEmptyCatch: false }], 'import/prefer-default-export': 'off', 'import/no-extraneous-dependencies': ['error', { devDependencies: true }], @@ -46,6 +48,7 @@ module.exports = { '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/interface-name-prefix': 'off', '@typescript-eslint/explicit-function-return-type': 'error', + '@typescript-eslint/no-unused-vars': 'error', // types described in ts 'jsdoc/require-param-type': 'off', diff --git a/CHANGELOG.md b/CHANGELOG.md index 59beeba..ca23955 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.2.4] - 2024-12-06 + +### Changed + +- Improved error message during `!#include` and condition directives resolving [FiltersCompiler#213]. + +[2.2.4]: https://github.com/AdguardTeam/FiltersDownloader/compare/v2.2.3...v2.2.4 +[FiltersCompiler#213]: https://github.com/AdguardTeam/FiltersCompiler/issues/213 + ## [2.2.3] - 2024-11-25 ### Added - Ability to add filters from Google Drive [AdguardBrowserExtension#2908]. +[2.2.3]: https://github.com/AdguardTeam/FiltersDownloader/compare/v2.2.2...v2.2.3 [AdguardBrowserExtension#2908]: https://github.com/AdguardTeam/AdguardBrowserExtension/issues/2908 ## [2.2.2] - 2024-07-11 @@ -51,7 +61,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [2.1.2]: https://github.com/AdguardTeam/FiltersDownloader/compare/v2.1.1...v2.1.2 [AdguardBrowserExtension#2717]: https://github.com/AdguardTeam/AdguardBrowserExtension/issues/2717 - ## [2.1.1] - 2024-03-13 ### Changed @@ -60,7 +69,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [2.1.1]: https://github.com/AdguardTeam/FiltersDownloader/compare/v2.0.7...v2.1.1 - ## [2.0.7] - 2024-01-17 ### Changed @@ -69,7 +77,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [2.0.7]: https://github.com/AdguardTeam/FiltersDownloader/compare/v2.0.6...v2.0.7 - ## [2.0.6] - 2024-01-17 ### Changed @@ -78,7 +85,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [2.0.6]: https://github.com/AdguardTeam/FiltersDownloader/compare/v2.0.5...v2.0.6 - ## [2.0.5] - 2024-01-11 ### Changed @@ -87,7 +93,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [2.0.5]: https://github.com/AdguardTeam/FiltersDownloader/compare/v2.0.4...v2.0.5 - ## [2.0.4] - 2024-01-11 ### Fixed @@ -96,7 +101,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [2.0.4]: https://github.com/AdguardTeam/FiltersDownloader/compare/v2.0.3...v2.0.4 - ## [2.0.3] - 2023-12-29 ### Fixed @@ -105,7 +109,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [2.0.3]: https://github.com/AdguardTeam/FiltersDownloader/compare/v2.0.2...v2.0.3 - ## [2.0.2] - 2023-12-29 ### Changed @@ -114,7 +117,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [2.0.2]: https://github.com/AdguardTeam/FiltersDownloader/compare/v2.0.1...v2.0.2 - ## [2.0.1] - 2023-12-29 ### Changed @@ -123,7 +125,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [2.0.1]: https://github.com/AdguardTeam/FiltersDownloader/compare/v2.0.0...v2.0.1 - ## [2.0.0] - 2023-12-27 ### Added @@ -133,7 +134,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [2.0.0]: https://github.com/AdguardTeam/FiltersDownloader/compare/v1.1.23...v2.0.0 - ## [1.1.23] - 2023-11-09 ### Fixed @@ -143,7 +143,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [1.1.23]: https://github.com/AdguardTeam/FiltersDownloader/compare/v1.1.21...v1.1.23 [AdguardBrowserExtension#1723]: https://github.com/AdguardTeam/AdguardBrowserExtension/issues/1723 - ## [1.1.21] - 2023-10-20 ### Fixed @@ -152,7 +151,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [1.1.21]: https://github.com/AdguardTeam/FiltersDownloader/compare/v1.1.20...v1.1.21 - ## [1.1.20] - 2023-10-10 ### Added @@ -162,7 +160,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#20]: https://github.com/AdguardTeam/FiltersDownloader/issues/20 [1.1.20]: https://github.com/AdguardTeam/FiltersDownloader/compare/v1.1.15...v1.1.20 - ## [1.1.15] - 2022-11-25 ### Fixed diff --git a/__tests__/filters-downloader.test.ts b/__tests__/filters-downloader.test.ts index 961d159..06b3842 100644 --- a/__tests__/filters-downloader.test.ts +++ b/__tests__/filters-downloader.test.ts @@ -11,8 +11,130 @@ import nock from 'nock'; import { FiltersDownloader } from '../src'; import { server } from './server'; +import { mergeErrorDetails } from '../src/helpers/logger'; + +const FilterCompilerConditionsConstants = { + adguard: true, + adguard_ext_chromium: true, + adguard_ext_firefox: false, + adguard_ext_edge: false, + adguard_ext_safari: false, + adguard_ext_opera: false, + adguard_ext_android_cb: false, +}; describe('FiltersDownloader', () => { + describe('error message during `!#include` and condition directives resolving', () => { + it('unexpected condition else branch', async () => { + const rules = [ + 'always_included_rule', + '!#else', + 'if_adguard_included_rule', + ]; + const errorDetails = [ + 'Found unexpected condition else branch: \'!#else\'', + 'Context:', + '\talways_included_rule', + '\t!#else', + ]; + expect(() => FiltersDownloader.resolveConditions( + rules, + FilterCompilerConditionsConstants, + )).toThrowError(new Error(mergeErrorDetails(errorDetails))); + }); + + it('unexpected condition end', async () => { + const rules = [ + 'always_included_rule', + 'if_adguard_included_rule', + '!#endif', + ]; + const errorDetails = [ + 'Found unexpected condition end: \'!#endif\'', + 'Context:', + '\talways_included_rule', + '\tif_adguard_included_rule', + '\t!#endif', + ]; + expect(() => FiltersDownloader.resolveConditions( + rules, + FilterCompilerConditionsConstants, + )).toThrowError(new Error(mergeErrorDetails(errorDetails))); + }); + + it('failed to resolve the include directive', async () => { + const nonExistentFilePath = path.resolve(__dirname, './resources/not_found_file.txt'); + const rules = [ + 'always_included_rule', + '||example.com', + '||example.org', + `!#include ${nonExistentFilePath}`, + ]; + const errorDetails = [ + `Failed to resolve the include directive '!#include ${nonExistentFilePath}'`, + 'Context:', + '\talways_included_rule', + '\t||example.com', + '\t||example.org', + `\t!#include ${nonExistentFilePath}`, + `\tError: ENOENT: no such file or directory, open '${nonExistentFilePath}'`, + ]; + await expect(FiltersDownloader.resolveIncludes( + rules, + undefined, + FilterCompilerConditionsConstants, + )).rejects.toThrowError(new Error(mergeErrorDetails(errorDetails))); + }); + + it('failed to resolve the include directive without file path', async () => { + const rules = [ + 'always_included_rule', + 'included_rule', + '||example.org^', + '||example.com^', + '!#include', + ]; + const errorDetails = [ + 'Failed to resolve the include directive \'!#include\'', + 'Context:', + '\tincluded_rule', + '\t||example.org^', + '\t||example.com^', + '\t!#include', + '\tError: EISDIR: illegal operation on a directory, read', + ]; + await expect(FiltersDownloader.resolveIncludes( + rules, + undefined, + FilterCompilerConditionsConstants, + )).rejects.toThrowError(new Error(mergeErrorDetails(errorDetails))); + }); + + it('failed to resolve the include directive with 404 path', async () => { + const rules = [ + 'always_included_rule', + '||example.org^', + '!#include https://raw.githubusercontent.com/AdguardTeam/FiltersDownloader/test-resources/__test__/resources/blabla.txt', + 'if_adguard_included_rule', + '||example.com^', + ]; + const errorDetails = [ + 'Failed to resolve the include directive \'!#include https://raw.githubusercontent.com/AdguardTeam/FiltersDownloader/test-resources/__test__/resources/blabla.txt\'', + 'Context:', + '\talways_included_rule', + '\t||example.org^', + '\t!#include https://raw.githubusercontent.com/AdguardTeam/FiltersDownloader/test-resources/__test__/resources/blabla.txt', + '\tResponse status for url https://raw.githubusercontent.com/AdguardTeam/FiltersDownloader/test-resources/__test__/resources/blabla.txt is invalid: 404', + ]; + await expect( + FiltersDownloader.compile( + rules, + undefined, + FilterCompilerConditionsConstants, + ), + ).rejects.toThrowError(new Error(mergeErrorDetails(errorDetails))); + }); + }); describe('downloadWithRaw', () => { describe('applies patches', () => { beforeAll(async () => { diff --git a/_qunit_tests_/filters-downloader.test.ts b/_qunit_tests_/filters-downloader.test.ts index da8a68a..95fa450 100644 --- a/_qunit_tests_/filters-downloader.test.ts +++ b/_qunit_tests_/filters-downloader.test.ts @@ -729,15 +729,14 @@ QUnit.test('Test filter downloader - compile rules with conditional includes', a '!#endif', ]; - try { - await FiltersDownloader.compile(rules, null, FilterCompilerConditionsConstants); - } catch (e) { - assert.equal( - (e as Error).message, - // eslint-disable-next-line max-len - `Failed to resolve the include directive: '!#include ${URL404}'`, - ); - } + assert.rejects( + FiltersDownloader.compile(rules, null, FilterCompilerConditionsConstants), + `Failed to resolve the include directive '!#include https://raw.githubusercontent.com/AdguardTeam/FiltersDownloader/test-resources/__test__/resources/blabla.txt' +Context: + always_included_rule + !#include ${URL404} + Response status for url ${URL404} is invalid: 404`, + ); // case 3: negative condition and include non-existing url rules = [ diff --git a/package.json b/package.json index ccbfaed..940dfb9 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "scripts": { "prebuild": "yarn rimraf dist && yarn build:types", "build": "rollup -c rollup.config.ts --configPlugin typescript && yarn build:version", + "watch": "rollup -c rollup.config.ts --configPlugin typescript --watch", "build:version": "ts-node scripts/build-txt.ts", "build:types": "tsc --project tsconfig.build.json --declaration --emitDeclarationOnly --outdir dist/types", "test": "ts-node _qunit_tests_/testrunner.ts && jest", diff --git a/src/filters-downloader-creator.ts b/src/filters-downloader-creator.ts index 09ec7c4..00b0063 100644 --- a/src/filters-downloader-creator.ts +++ b/src/filters-downloader-creator.ts @@ -17,6 +17,7 @@ import { DiffUpdater, UnacceptableResponseError } from '@adguard/diff-builder/diff-updater'; import { isValidChecksum } from './checksum'; +import { throwError, getContext } from './helpers/logger'; /** * The utility tool resolves preprocessor directives in filter content. @@ -429,6 +430,7 @@ const FiltersDownloaderCreator = (FileDownloadWrapper: IFileDownloader): IFilter * @param rules The list of filtering rules to resolve. * @param definedExpressions An object containing defined expressions for * condition resolution. + * @param urlOrigin The origin of the URL from which the filter was downloaded. * * @returns The resolved filtering rules after processing conditional directives. * @@ -437,6 +439,7 @@ const FiltersDownloaderCreator = (FileDownloadWrapper: IFileDownloader): IFilter const resolveConditions = ( rules: string[], definedExpressions?: DefinedExpressions, + urlOrigin?: string, ): string[] => { if (!definedExpressions) { return rules; @@ -455,7 +458,12 @@ const FiltersDownloaderCreator = (FileDownloadWrapper: IFileDownloader): IFilter rules.length, ); if (endLineIndex === -1) { - throw new Error(`Invalid directives: Condition end not found: ${rule}`); + throwError( + 'Invalid directives: Condition end not found', + rule, + getContext(rules, i), + urlOrigin, + ); } const elseLineIndex = findConditionBlockEnd( @@ -471,22 +479,33 @@ const FiltersDownloaderCreator = (FileDownloadWrapper: IFileDownloader): IFilter if (isConditionMatched) { const rulesUnderCondition = rules.slice(i + 1, endLineIndex); // Resolve inner conditions in recursion - result = result.concat(resolveConditions(rulesUnderCondition, definedExpressions)); + result = result + .concat(resolveConditions(rulesUnderCondition, definedExpressions, urlOrigin)); } } else { // check if there is something after !#else if (rules[elseLineIndex].trim().length !== CONDITION_ELSE_DIRECTIVE_START.length) { - throw new Error(`Invalid directives: Found invalid !#else: ${rule}`); + throwError( + 'Found invalid directive !#else', + rule, + getContext(rules, i), + urlOrigin, + ); } if (isConditionMatched) { const rulesForConditionTrue = rules.slice(i + 1, elseLineIndex); // Resolve inner conditions in recursion - result = result.concat(resolveConditions(rulesForConditionTrue, definedExpressions)); + result = result + .concat(resolveConditions(rulesForConditionTrue, definedExpressions, urlOrigin)); } else { const rulesForConditionFalse = rules.slice(elseLineIndex + 1, endLineIndex); // Resolve inner conditions in recursion - result = result.concat(resolveConditions(rulesForConditionFalse, definedExpressions)); + result = result.concat(resolveConditions( + rulesForConditionFalse, + definedExpressions, + urlOrigin, + )); } } @@ -494,10 +513,20 @@ const FiltersDownloaderCreator = (FileDownloadWrapper: IFileDownloader): IFilter i = endLineIndex; } else if (rule.indexOf(CONDITION_ELSE_DIRECTIVE_START) === 0) { // Found !#else without !#if - throw new Error(`Invalid directives: Found unexpected condition else branch: ${rule}`); + throwError( + 'Found unexpected condition else branch:', + rule, + getContext(rules, i), + urlOrigin, + ); } else if (rule.indexOf(CONDITION_DIRECTIVE_END) === 0) { // Found !#endif without !#if - throw new Error(`Invalid directives: Found unexpected condition end: ${rule}`); + throwError( + 'Found unexpected condition end:', + rule, + getContext(rules, i), + urlOrigin, + ); } else { result.push(rule); } @@ -534,6 +563,7 @@ const FiltersDownloaderCreator = (FileDownloadWrapper: IFileDownloader): IFilter * @param line Line with directive. * @param filterOrigin Filter file URL origin or undefined. * @param definedExpressions An object with the defined properties. + * @param context Previous lines for better error messages. * These properties might be used in pre-processor directives (`#if`, etc.). * * @returns A promise that returns string with rules if resolved and Error if rejected. @@ -543,6 +573,7 @@ const FiltersDownloaderCreator = (FileDownloadWrapper: IFileDownloader): IFilter line: string, filterOrigin?: string, definedExpressions?: DefinedExpressions, + context?: string, ): Promise => { if (line.indexOf(INCLUDE_DIRECTIVE) !== 0) { return Promise.resolve(line); @@ -550,7 +581,7 @@ const FiltersDownloaderCreator = (FileDownloadWrapper: IFileDownloader): IFilter const url = line.substring(INCLUDE_DIRECTIVE.length).trim(); validateUrl(url, filterOrigin); - let filter: string[]; + let filter: string[] = []; try { // eslint-disable-next-line @typescript-eslint/no-use-before-define const downloadResult = await downloadFilterRules(url, { @@ -559,8 +590,14 @@ const FiltersDownloaderCreator = (FileDownloadWrapper: IFileDownloader): IFilter resolveDirectives: true, }); filter = downloadResult.filter; - } catch (error) { - throw new Error(`Failed to resolve the include directive: '${line}'`, { cause: error }); + } catch (error: unknown) { + throwError( + 'Failed to resolve the include directive', + line, + context, + filterOrigin, + error, + ); } const MAX_LINES_TO_SCAN = 50; @@ -584,15 +621,17 @@ const FiltersDownloaderCreator = (FileDownloadWrapper: IFileDownloader): IFilter * @returns A promise that returns {string} with rules when if resolved and {Error} if rejected. */ const resolveIncludes = async ( - rules: string [], + rules: string[], filterOrigin?: string, definedExpressions?: DefinedExpressions, ): Promise => { - const promises = rules.map((rule) => resolveInclude(rule, filterOrigin, definedExpressions)); + const promises = rules.map((rule, index) => { + return resolveInclude(rule, filterOrigin, definedExpressions, getContext(rules, index)); + }); let result: string[] = []; // We do not use here Promise.all because it freezes the Chromium browsers and electron built on it, if there - // are more than 1_100_00 promises. Also, we consider that wa can afford promises to be resolved sequentially. + // are more than 1_100_00 promises. Also, we consider that we can afford promises to be resolved sequentially. for (let i = 0; i < promises.length; i += 1) { // eslint-disable-next-line no-await-in-loop const resolved = await promises[i]; @@ -655,7 +694,7 @@ const FiltersDownloaderCreator = (FileDownloadWrapper: IFileDownloader): IFilter } const urlOrigin = getFilterUrlOrigin(filterUrl); - const conditionsResult = resolveConditions(filter, downloadOptions.definedExpressions); + const conditionsResult = resolveConditions(filter, downloadOptions.definedExpressions, urlOrigin); const includesResult = await resolveIncludes( conditionsResult, urlOrigin, @@ -683,7 +722,7 @@ const FiltersDownloaderCreator = (FileDownloadWrapper: IFileDownloader): IFilter definedProperties?: DefinedExpressions, ): Promise => { // Resolve 'if' conditions - const resolvedConditionsResult = resolveConditions(rules, definedProperties); + const resolvedConditionsResult = resolveConditions(rules, definedProperties, filterOrigin); // Resolve 'includes' directives return resolveIncludes(resolvedConditionsResult, filterOrigin, definedProperties); @@ -727,7 +766,7 @@ const FiltersDownloaderCreator = (FileDownloadWrapper: IFileDownloader): IFilter const urlOrigin = getFilterUrlOrigin(urlToLoad); // Resolve 'if' conditions and 'includes' directives - const conditionsResult = resolveConditions(filterContent, downloadOptions.definedExpressions); + const conditionsResult = resolveConditions(filterContent, downloadOptions.definedExpressions, urlOrigin); const includesResult = await resolveIncludes(conditionsResult, urlOrigin, downloadOptions.definedExpressions); return { filter: includesResult, @@ -810,7 +849,7 @@ const FiltersDownloaderCreator = (FileDownloadWrapper: IFileDownloader): IFilter filterUrlOrigin?: string, ): Promise { const filter = splitFilter(rawFilter); - const resolvedConditionsResult = resolveConditions(filter, options.definedExpressions); + const resolvedConditionsResult = resolveConditions(filter, options.definedExpressions, filterUrlOrigin); return resolveIncludes( resolvedConditionsResult, filterUrlOrigin, diff --git a/src/helpers/logger.ts b/src/helpers/logger.ts new file mode 100644 index 0000000..4696feb --- /dev/null +++ b/src/helpers/logger.ts @@ -0,0 +1,63 @@ +/** + * The number of lines to include before the error line in the error message. + */ +const LINES_BEFORE_DIRECTIVE = 3; + +/** + * Merge an error messages by joining the array strings with a newline character. + * + * @param {string[]} messages - The array of message strings to format. + * @returns {string} The formatted error message. + */ +export const mergeErrorDetails = (messages: string[]): string => `${messages.join('\n')}\n`; + +/** + * Creates and throws a detailed error message with context information. + * + * @param errorDescription The main error message. + * @param errorRule The rule where the error occurred. + * @param contextLines The context string (3 lines before) to include in the error message. + * @param filterUrl The URL of the filter file. + * @param originalError The original error to include in the error message. + * @throws {Error} Throws an error with a detailed error message. + */ +export const throwError = ( + errorDescription: string, + errorRule: string, + contextLines?: string, + filterUrl?: string, + originalError?: unknown, +): void => { + const errorDetails = [`${errorDescription} '${errorRule}'`]; + + if (filterUrl) { + errorDetails.push(`URL: '${filterUrl}'`); + } + + if (contextLines) { + errorDetails.push('Context:'); + errorDetails.push(contextLines); + errorDetails.push(`\t${errorRule}`); + } + + if (originalError) { + const originalErrorMessage = originalError instanceof Error ? originalError.message : String(originalError); + errorDetails.push(`\t${originalErrorMessage}`); + } + + const formattedErrorMessage = mergeErrorDetails(errorDetails); + throw new Error(formattedErrorMessage); +}; + +/** + * Get (3) lines before the error rule for error messages. + * @param rulesList List of rules to get context from. + * @param errorRuleIndex Index of the error line. + * @returns Lines before the error rule. + */ +export const getContext = (rulesList: string[], errorRuleIndex: number): string => { + return rulesList + .slice(Math.max(0, errorRuleIndex - LINES_BEFORE_DIRECTIVE), errorRuleIndex) + .map((line) => `\t${line}`) + .join('\n'); +};