From 2fd5572aa770bb5018b325833e2cd9cf6e613eab Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Wed, 15 Apr 2026 18:21:50 +0300 Subject: [PATCH 1/3] feat: allow customizing walker options Add an optional `walker` key to the options object passed to both `precinct()` and `paperwork()`. The value is forwarded directly to the `node-source-walk` constructor, giving callers full control over the underlying parser options. --- README.md | 21 ++++++++++ index.d.ts | 5 +++ index.js | 3 +- test/fixtures/es6ImportInsideBlock.js | 3 ++ test/test.js | 57 +++++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/es6ImportInsideBlock.js diff --git a/README.md b/README.md index 009184a..647d8c4 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,26 @@ Current options: * `es6.mixedImports`: allows for all dependencies to be fetched from a file that contains both CJS and ES6 imports. * Note: This will work for any file format that contains an ES6 import. * `css.url`: tells the CSS detective to include `url()` references to images, fonts, etc. +* `walker`: options passed directly to the underlying [node-source-walk](https://github.com/dependents/node-source-walk) instance. Controls how JavaScript source is parsed. Key sub-options: + * `sourceType`, `plugins`, etc. - forwarded to the default `@babel/parser`. + * `parser` - replace the parser entirely with any object that exposes a `parse(src, options)` method returning an AST. The replacement does not have to be Babel-based. + + Example: + + ```js + precinct(content, { + walker: { + allowImportExportEverywhere: true + } + }); + + // Or supply a fully custom parser: + precinct(content, { + walker: { + parser: myCustomParser // any object with a .parse(src, opts) method + } + }); + ``` Finding non-JavaScript (ex: Sass and Stylus) dependencies: @@ -79,6 +99,7 @@ Supported options: * `includeCore`: (default: `true`) set to `false` to exclude core Node.js dependencies from the list of dependencies. * `fileSystem`: (default: `undefined`) set to an alternative `fs` implementation that will be used to read the file path. +* `walker`: (default: `undefined`) options forwarded to the underlying [node-source-walk](https://github.com/dependents/node-source-walk) instance - same as the top-level `walker` option. * You may also pass detective-specific configuration like you would to `precinct(content, options)`. ### CLI diff --git a/index.d.ts b/index.d.ts index 5a030a4..fac8509 100644 --- a/index.d.ts +++ b/index.d.ts @@ -6,11 +6,14 @@ export = precinct; * @param {String|Object} content - File's content or AST * @param {Object} [options] * @param {String} [options.type] - The type of content being passed in. Useful if you want to use a non-js detective + * @param {Object} [options.walker] - Options to pass to the underlying source walker (node-source-walk) * @return {String[]} */ declare function precinct(content: string | any, options?: { type?: string; + walker?: Record; }): string[]; + declare namespace precinct { /** * Returns the dependencies for the given file path @@ -19,10 +22,12 @@ declare namespace precinct { * @param {Object} [options] * @param {Boolean} [options.includeCore=true] - Whether or not to include core modules in the dependency list * @param {Object} [options.fileSystem=undefined] - An alternative fs implementation to use for reading the file path. + * @param {Object} [options.walker] - Options to pass to the underlying source walker (node-source-walk) * @return {String[]} */ function paperwork(filename: string, options?: { includeCore?: boolean; fileSystem?: any; + walker?: Record; }): string[]; } diff --git a/index.js b/index.js index 545ffa0..dc9e9df 100644 --- a/index.js +++ b/index.js @@ -25,6 +25,7 @@ const debug = debuglog('precinct'); * @param {String|Object} content - File's content or AST * @param {Object} [options] * @param {String} [options.type] - The type of content being passed in. Useful if you want to use a non-js detective + * @param {Object} [options.walker] - Options to pass to the underlying source walker (node-source-walk) * @return {String[]} */ function precinct(content, options = {}) { @@ -35,7 +36,7 @@ function precinct(content, options = {}) { // We assume we're dealing with a JS file if (!options.type && typeof content !== 'object') { debug('we assume this is JS'); - const walker = new Walker(); + const walker = new Walker(options.walker); try { // Parse once and distribute the AST to all detectives diff --git a/test/fixtures/es6ImportInsideBlock.js b/test/fixtures/es6ImportInsideBlock.js new file mode 100644 index 0000000..81b5d24 --- /dev/null +++ b/test/fixtures/es6ImportInsideBlock.js @@ -0,0 +1,3 @@ +if (true) { + import lib from 'lib'; +} diff --git a/test/test.js b/test/test.js index 7969efe..192615d 100644 --- a/test/test.js +++ b/test/test.js @@ -322,6 +322,63 @@ describe('node-precinct', () => { const expected = ['mystyles', 'styles2.styl', 'styles3.styl', 'styles4']; assert.deepEqual(result, expected); }); + + describe('walker options', () => { + it('finds imports inside blocks when allowImportExportEverywhere is enabled', async() => { + // By default babel disallows import/export outside the top level, so + // a file with an import inside an if-block yields no dependencies. + const fixture = await read('es6ImportInsideBlock.js'); + const withoutOption = precinct(fixture); + assert.equal(withoutOption.length, 0); + + // With allowImportExportEverywhere the same file is parsed correctly. + const withOption = precinct(fixture, { + walker: { + allowImportExportEverywhere: true + } + }); + assert.equal(withOption.includes('lib'), true); + assert.equal(withOption.length, 1); + }); + + it('accepts a custom parser via walker options', async() => { + const fixture = await read('commonjs.js'); + + // Parse the AST up-front - the custom parser below returns it directly + // without invoking Babel, demonstrating that any object with a parse() + // method can be supplied, not just @babel/parser. + const prebuiltAst = require('@babel/parser').parse(fixture, { + sourceType: 'module', + allowHashBang: true + }); + + let parseCallCount = 0; + const customParser = { + parse() { + parseCallCount++; + return prebuiltAst; + } + }; + + const result = precinct(fixture, { walker: { parser: customParser } }); + assert.equal(parseCallCount, 1); + assert.equal(result.includes('./a'), true); + }); + + it('passes walker options through paperwork', () => { + const fixture = path.join(__dirname, 'fixtures/es6ImportInsideBlock.js'); + const withoutOption = precinct.paperwork(fixture); + assert.equal(withoutOption.length, 0); + + const withOption = precinct.paperwork(fixture, { + walker: { + allowImportExportEverywhere: true + } + }); + assert.equal(withOption.includes('lib'), true); + assert.equal(withOption.length, 1); + }); + }); }); describe('paperwork', () => { From e4fc1096ddcad50e29764c0f343689f98380de15 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Wed, 15 Apr 2026 18:32:48 +0300 Subject: [PATCH 2/3] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 647d8c4..1131004 100644 --- a/README.md +++ b/README.md @@ -53,9 +53,7 @@ Current options: * `es6.mixedImports`: allows for all dependencies to be fetched from a file that contains both CJS and ES6 imports. * Note: This will work for any file format that contains an ES6 import. * `css.url`: tells the CSS detective to include `url()` references to images, fonts, etc. -* `walker`: options passed directly to the underlying [node-source-walk](https://github.com/dependents/node-source-walk) instance. Controls how JavaScript source is parsed. Key sub-options: - * `sourceType`, `plugins`, etc. - forwarded to the default `@babel/parser`. - * `parser` - replace the parser entirely with any object that exposes a `parse(src, options)` method returning an AST. The replacement does not have to be Babel-based. +* `walker`: options passed directly to the underlying [node-source-walk](https://github.com/dependents/node-source-walk) instance. Example: From b8a69cee8c0e2c5b6020541048f6b7f819309cb1 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Wed, 15 Apr 2026 18:34:35 +0300 Subject: [PATCH 3/3] Update test.js --- test/test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/test.js b/test/test.js index 192615d..db5f689 100644 --- a/test/test.js +++ b/test/test.js @@ -360,7 +360,11 @@ describe('node-precinct', () => { } }; - const result = precinct(fixture, { walker: { parser: customParser } }); + const result = precinct(fixture, { + walker: { + parser: customParser + } + }); assert.equal(parseCallCount, 1); assert.equal(result.includes('./a'), true); });