diff --git a/packages/strip/README.md b/packages/strip/README.md index ef06a5cb9..43e8dd4e0 100755 --- a/packages/strip/README.md +++ b/packages/strip/README.md @@ -51,42 +51,52 @@ Then call `rollup` either via the [CLI](https://www.rollupjs.org/guide/en/#comma ### `include` Type: `String | RegExp | Array[...String|RegExp]`
-Default: `['**/*.js']` +Default: `['**/*.js']`
+Example: `include: '**/*.(mjs|js)',`
A pattern, or array of patterns, which specify the files in the build the plugin should operate on. ### `exclude` Type: `String | RegExp | Array[...String|RegExp]`
-Default: `[]` +Default: `[]`
+Example: `exlude: 'tests/**/*',`
A pattern, or array of patterns, which specify the files in the build the plugin should _ignore_. ### `debugger` Type: `Boolean`
-Default: `true` +Default: `true`
+Example: `debugger: false,`
-If `true`, instructs the plugin to remove debugger statements. +If `true` instructs the plugin to remove debugger statements. ### `functions` Type: `Array[...String]`
-Default: `[ 'console.*', 'assert.*' ]` +Default: `[ 'console.*', 'assert.*' ]`
+Example: `functions: [ 'console.log', 'MyClass.Test' ],`
Specifies the functions that the plugin will target and remove. +_Note: specifying functions that are used at the begining of a chain, such as 'a().b().c()', will result in '(void 0).b().c()' which will generate an error at runtime._ + ### `labels` Type: `Array[...String]`
-Default: `[]` +Default: `[]`
+Example: `labels: ['unittest'],`
+ +Specifies the [labeled blocks or statements](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label) that the plugin will target and remove. -Specifies the [labeled blocks](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label) that the plugin will target and remove. +_Note: the '**:**' is implied and should not be specified in the config._ ### `sourceMap` Type: `Boolean`
-Default: `true` +Default: `true`
+Example: `sourceMap: false,`
If `true`, instructs the plugin to update source maps accordingly after removing configured targets from the bundle. diff --git a/packages/strip/src/index.js b/packages/strip/src/index.js index 0e8d01833..0759a7491 100755 --- a/packages/strip/src/index.js +++ b/packages/strip/src/index.js @@ -39,20 +39,30 @@ export default function strip(options = {}) { const removeDebuggerStatements = options.debugger !== false; const functions = (options.functions || ['console.*', 'assert.*']).map((keypath) => - keypath.replace(/\./g, '\\.').replace(/\*/g, '\\w+') + keypath.replace(/\*/g, '\\w+').replace(/\./g, '\\s*\\.\\s*') ); const labels = options.labels || []; - const firstpass = new RegExp(`\\b(?:${functions.join('|')}|debugger)\\b`); - const pattern = new RegExp(`^(?:${functions.join('|')})$`); + const labelsPatterns = labels.map((l) => `${l}\\s*:`); + + const firstPass = [...functions, ...labelsPatterns]; + if (removeDebuggerStatements) { + firstPass.push('debugger\\b'); + } + + const reFunctions = new RegExp(`^(?:${functions.join('|')})$`); + const reFirstpass = new RegExp(`\\b(?:${firstPass.join('|')})`); + const firstPassFilter = firstPass.length > 0 ? (code) => reFirstpass.test(code) : () => false; + const UNCHANGED = null; return { name: 'strip', transform(code, id) { - if (!filter(id)) return null; - if (functions.length > 0 && !firstpass.test(code)) return null; + if (!filter(id) || !firstPassFilter(code)) { + return UNCHANGED; + } let ast; @@ -81,7 +91,7 @@ export default function strip(options = {}) { if (parent.type === 'ExpressionStatement') { removeStatement(parent); } else { - magicString.overwrite(node.start, node.end, 'void 0'); + magicString.overwrite(node.start, node.end, '(void 0)'); } edited = true; @@ -93,7 +103,7 @@ export default function strip(options = {}) { if (isBlock(parent)) { remove(node.start, node.end); } else { - magicString.overwrite(node.start, node.end, 'void 0;'); + magicString.overwrite(node.start, node.end, '(void 0);'); } edited = true; @@ -114,13 +124,15 @@ export default function strip(options = {}) { if (removeDebuggerStatements && node.type === 'DebuggerStatement') { removeStatement(node); + this.skip(); } else if (node.type === 'LabeledStatement') { if (node.label && labels.includes(node.label.name)) { removeStatement(node); + this.skip(); } } else if (node.type === 'CallExpression') { const keypath = flatten(node.callee); - if (keypath && pattern.test(keypath)) { + if (keypath && reFunctions.test(keypath)) { removeExpression(node); this.skip(); } @@ -128,7 +140,9 @@ export default function strip(options = {}) { } }); - if (!edited) return null; + if (!edited) { + return UNCHANGED; + } code = magicString.toString(); const map = sourceMap ? magicString.generateMap() : null; diff --git a/packages/strip/test/fixtures/excluded-not-changed/input.js b/packages/strip/test/fixtures/excluded-not-changed/input.js new file mode 100644 index 000000000..bd4e4ddba --- /dev/null +++ b/packages/strip/test/fixtures/excluded-not-changed/input.js @@ -0,0 +1,9 @@ +/* eslint-disable */ +export default function foo() { + before(); + debugger; + logging: + console.log('a'); + console.error('b'); + after(); +} diff --git a/packages/strip/test/fixtures/functions-direct/input.js b/packages/strip/test/fixtures/functions-direct/input.js new file mode 100644 index 000000000..d60875bdc --- /dev/null +++ b/packages/strip/test/fixtures/functions-direct/input.js @@ -0,0 +1,4 @@ +/* eslint-disable */ + before(); + f().t(); + after(); diff --git a/packages/strip/test/fixtures/functions-spaced/input.js b/packages/strip/test/fixtures/functions-spaced/input.js new file mode 100644 index 000000000..d5e4655ba --- /dev/null +++ b/packages/strip/test/fixtures/functions-spaced/input.js @@ -0,0 +1,20 @@ +/* eslint-disable */ + before(); + function f() { + return { + g: function () { + return { + hello: function () { + console.log('hello'); + } + }; + } + }; + } + + Test + . + f() + . g() . + hello(); + after(); diff --git a/packages/strip/test/fixtures/label-awkward-spacing/input.js b/packages/strip/test/fixtures/label-awkward-spacing/input.js new file mode 100644 index 000000000..70af38461 --- /dev/null +++ b/packages/strip/test/fixtures/label-awkward-spacing/input.js @@ -0,0 +1,10 @@ +/* eslint-disable */ + before(); + unittest + : { + test('some test', (assert) => { + }); + } + again(); + unittest:console.log(); + after(); diff --git a/packages/strip/test/fixtures/label-expression/input.js b/packages/strip/test/fixtures/label-expression/input.js new file mode 100644 index 000000000..a8a10d760 --- /dev/null +++ b/packages/strip/test/fixtures/label-expression/input.js @@ -0,0 +1,2 @@ +/* eslint-disable */ + before();unittest:console.log();after(); diff --git a/packages/strip/test/fixtures/label-multiple-times/input.js b/packages/strip/test/fixtures/label-multiple-times/input.js new file mode 100644 index 000000000..70af38461 --- /dev/null +++ b/packages/strip/test/fixtures/label-multiple-times/input.js @@ -0,0 +1,10 @@ +/* eslint-disable */ + before(); + unittest + : { + test('some test', (assert) => { + }); + } + again(); + unittest:console.log(); + after(); diff --git a/packages/strip/test/fixtures/label-whitespace/input.js b/packages/strip/test/fixtures/label-whitespace/input.js new file mode 100644 index 000000000..5e95eca7b --- /dev/null +++ b/packages/strip/test/fixtures/label-whitespace/input.js @@ -0,0 +1,7 @@ +/* eslint-disable */ + before(); + unittest : { + test('some test', (assert) => { + }); + } + after(); diff --git a/packages/strip/test/fixtures/lambda-void/input.js b/packages/strip/test/fixtures/lambda-void/input.js new file mode 100644 index 000000000..f370859ff --- /dev/null +++ b/packages/strip/test/fixtures/lambda-void/input.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +console.log(['h', 'e', 'y'].forEach((letter) => console.warn(letter))) diff --git a/packages/strip/test/fixtures/no-changes/input.js b/packages/strip/test/fixtures/no-changes/input.js new file mode 100644 index 000000000..bd4e4ddba --- /dev/null +++ b/packages/strip/test/fixtures/no-changes/input.js @@ -0,0 +1,9 @@ +/* eslint-disable */ +export default function foo() { + before(); + debugger; + logging: + console.log('a'); + console.error('b'); + after(); +} diff --git a/packages/strip/test/snapshots/test.js.md b/packages/strip/test/snapshots/test.js.md index 76bed0fd5..0fde251ad 100644 --- a/packages/strip/test/snapshots/test.js.md +++ b/packages/strip/test/snapshots/test.js.md @@ -4,6 +4,21 @@ The actual snapshot is saved in `test.js.snap`. Generated by [AVA](https://ava.li). +## can be configured to make no changes + +> Snapshot 1 + + `/* eslint-disable */␊ + export default function foo() {␊ + before();␊ + debugger;␊ + logging:␊ + console.log('a');␊ + console.error('b');␊ + after();␊ + }␊ + ` + ## does not remove debugger statements with debugger: false > Snapshot 1 @@ -15,6 +30,57 @@ Generated by [AVA](https://ava.li). }␊ ` +## empty functions list leaves assert statements + +> Snapshot 1 + + `/* eslint-disable */␊ + function foo(message) {␊ + assert.equal(arguments.length, 1);␊ + assert.equal(typeof arguments[0], 'string');␊ + bar(message);␊ + }␊ + ` + +## empty functions list leaves console statements + +> Snapshot 1 + + `/* eslint-disable */␊ + export default function foo() {␊ + before();␊ + logging:␊ + console.log('a');␊ + console.error('b');␊ + after();␊ + }␊ + ` + +## excluded files do not get changed + +> Snapshot 1 + + `/* eslint-disable */␊ + export default function foo() {␊ + before();␊ + debugger;␊ + logging:␊ + console.log('a');␊ + console.error('b');␊ + after();␊ + }␊ + ` + +## function calls without object are replaced with (void 0) + +> Snapshot 1 + + `/* eslint-disable */␊ + before();␊ + (void 0).t();␊ + after();␊ + ` + ## leaves console statements if custom functions are provided > Snapshot 1 @@ -93,6 +159,32 @@ Generated by [AVA](https://ava.li). after();␊ ` +## removes labeled blocks when filtered function is present + +> Snapshot 1 + + `before();␊ + after();␊ + ` + +## removes labeled blocks when functions list is empty + +> Snapshot 1 + + `before();␊ + after();␊ + ` + +## removes labeled even with awkward spacing + +> Snapshot 1 + + `/* eslint-disable */␊ + before();␊ + again();␊ + after();␊ + ` + ## removes methods of this > Snapshot 1 @@ -123,42 +215,101 @@ Generated by [AVA](https://ava.li). }␊ ` +## removing a lable also works for expressions + +> Snapshot 1 + + `/* eslint-disable */␊ + before();after();␊ + ` + ## replaces case body with void 0 > Snapshot 1 `switch (a) {␊ case 1:␊ - void 0;␊ + (void 0);␊ }␊ ` +## rewrites expressions as void 0 in lambdas + +> Snapshot 1 + + `/* eslint-disable */␊ + console.log(['h', 'e', 'y'].forEach((letter) => (void 0)))␊ + ` + ## rewrtestes inline call expressions (not expression statements) as void 0 > Snapshot 1 - `DEBUG && void 0;␊ + `DEBUG && (void 0);␊ ` ## rewrtestes inline if expessions as void 0 > Snapshot 1 - `if (DEBUG) void 0;␊ + `if (DEBUG) (void 0);␊ ` ## rewrtestes inline while expressions as void 0 > Snapshot 1 - `while (test()) void 0;␊ + `while (test()) (void 0);␊ + ` + +## spaces around . in function calls are accepted + +> Snapshot 1 + + `/* eslint-disable */␊ + before();␊ + function f() {␊ + return {␊ + g: function () {␊ + return {␊ + hello: function () {␊ + console.log('hello');␊ + }␊ + };␊ + }␊ + };␊ + }␊ + ␊ + (void 0)␊ + . g() .␊ + hello();␊ + after();␊ ` ## supports object destructuring assignments with default values > Snapshot 1 - `export function fn({ foo = void 0, bar } = {}) {␊ - const { baz = void 0 } = bar;␊ + `export function fn({ foo = (void 0), bar } = {}) {␊ + const { baz = (void 0) } = bar;␊ }␊ ` + +## the same label can occur multiple times and all are removed + +> Snapshot 1 + + `/* eslint-disable */␊ + before();␊ + again();␊ + after();␊ + ` + +## whitespace between label and colon is accepted + +> Snapshot 1 + + `/* eslint-disable */␊ + before();␊ + after();␊ + ` diff --git a/packages/strip/test/snapshots/test.js.snap b/packages/strip/test/snapshots/test.js.snap index 5c1e641a4..e828bef81 100644 Binary files a/packages/strip/test/snapshots/test.js.snap and b/packages/strip/test/snapshots/test.js.snap differ diff --git a/packages/strip/test/test.js b/packages/strip/test/test.js index 27fa54b87..c279a14a7 100755 --- a/packages/strip/test/test.js +++ b/packages/strip/test/test.js @@ -25,6 +25,18 @@ const compare = (t, fixture, options) => { t.snapshot(output ? output.code : input); }; +test('can be configured to make no changes', (t) => { + compare(t, 'no-changes', { + labels: [], + functions: [], + debugger: false + }); +}); + +test('excluded files do not get changed', (t) => { + compare(t, 'excluded-not-changed', { exclude: `**/excluded-not-changed/input.js` }); +}); + test('removes debugger statements', (t) => { compare(t, 'debugger'); }); @@ -33,6 +45,10 @@ test('does not remove debugger statements with debugger: false', (t) => { compare(t, 'debugger-false', { debugger: false }); }); +test('empty functions list leaves console statements', (t) => { + compare(t, 'no-changes', { functions: [] }); +}); + test('removes console statements', (t) => { compare(t, 'console'); }); @@ -41,6 +57,10 @@ test('removes assert statements', (t) => { compare(t, 'assert'); }); +test('empty functions list leaves assert statements', (t) => { + compare(t, 'assert', { functions: [] }); +}); + test('leaves console statements if custom functions are provided', (t) => { compare(t, 'console-custom', { functions: ['console.log'] }); }); @@ -57,6 +77,10 @@ test('rewrtestes inline if expessions as void 0', (t) => { compare(t, 'inline-if'); }); +test('rewrites expressions as void 0 in lambdas', (t) => { + compare(t, 'lambda-void', { functions: ['console.warn'] }); +}); + test('removes expressions in if blocks', (t) => { compare(t, 'if-block'); }); @@ -92,3 +116,35 @@ test('removes multiple labeled blocks', (t) => { test('only removes specified blocks', (t) => { compare(t, 'label-block-discriminate', { labels: ['second'] }); }); + +test('removes labeled blocks when filtered function is present', (t) => { + compare(t, 'label-block', { labels: ['unittest'], functions: ['console.*'] }); +}); + +test('removes labeled blocks when functions list is empty', (t) => { + compare(t, 'label-block', { labels: ['unittest'], functions: [] }); +}); + +test('whitespace between label and colon is accepted', (t) => { + compare(t, 'label-whitespace', { labels: ['unittest'], functions: ['console.*'] }); +}); + +test('removing a lable also works for expressions', (t) => { + compare(t, 'label-expression', { labels: ['unittest'] }); +}); + +test('the same label can occur multiple times and all are removed', (t) => { + compare(t, 'label-multiple-times', { labels: ['unittest'] }); +}); + +test('removes labeled even with awkward spacing', (t) => { + compare(t, 'label-awkward-spacing', { labels: ['unittest'], functions: ['console.*'] }); +}); + +test('spaces around . in function calls are accepted', (t) => { + compare(t, 'functions-spaced', { labels: ['unittest'], functions: ['Test.f'] }); +}); + +test('function calls without object are replaced with (void 0)', (t) => { + compare(t, 'functions-direct', { labels: ['unittest'], functions: ['f'] }); +});