diff --git a/packages/replace/README.md b/packages/replace/README.md index 533e09c9d..bd5ffb26f 100644 --- a/packages/replace/README.md +++ b/packages/replace/README.md @@ -70,6 +70,39 @@ For example, if you pass `typeof window` in `values` to-be-replaced, then you co - `typeof window.document` **will not** be replaced due to `(?!\.)` boundary - `typeof windowSmth` **will not** be replaced due to a `\b` boundary +### `objectGuards` + +Type: `Boolean`
+Default: `false` + +When replacing dot-separated object properties like `process.env.NODE_ENV`, will also replace `typeof process` object guard +checks against the objects with the string `"object"`. + +For example: + +```js +replace({ + values: { + 'process.env.NODE_ENV': '"production"' + } +}); +``` + +```js +// Input +if (typeof process !== 'undefined' && process.env.NODE_ENV === 'production') { + console.log('production'); +} +// Without `objectGuards` +if (typeof process !== 'undefined' && 'production' === 'production') { + console.log('production'); +} +// With `objectGuards` +if ('object' !== 'undefined' && 'production' === 'production') { + console.log('production'); +} +``` + ### `preventAssignment` Type: `Boolean`
diff --git a/packages/replace/src/index.js b/packages/replace/src/index.js index f2085e7e8..dd001b234 100755 --- a/packages/replace/src/index.js +++ b/packages/replace/src/index.js @@ -24,6 +24,7 @@ function getReplacements(options) { delete values.exclude; delete values.sourcemap; delete values.sourceMap; + delete values.objectGuards; return values; } @@ -35,10 +36,42 @@ function mapToFunctions(object) { }, {}); } +const objKeyRegEx = /^([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)(\.([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*))+$/; +function expandTypeofReplacements(replacements) { + Object.keys(replacements).forEach((key) => { + const objMatch = key.match(objKeyRegEx); + if (!objMatch) return; + let dotIndex = objMatch[1].length; + let lastIndex = 0; + do { + // eslint-disable-next-line no-param-reassign + replacements[`typeof ${key.slice(lastIndex, dotIndex)} ===`] = '"object" ==='; + // eslint-disable-next-line no-param-reassign + replacements[`typeof ${key.slice(lastIndex, dotIndex)} !==`] = '"object" !=='; + // eslint-disable-next-line no-param-reassign + replacements[`typeof ${key.slice(lastIndex, dotIndex)}===`] = '"object"==='; + // eslint-disable-next-line no-param-reassign + replacements[`typeof ${key.slice(lastIndex, dotIndex)}!==`] = '"object"!=='; + // eslint-disable-next-line no-param-reassign + replacements[`typeof ${key.slice(lastIndex, dotIndex)} ==`] = '"object" ==='; + // eslint-disable-next-line no-param-reassign + replacements[`typeof ${key.slice(lastIndex, dotIndex)} !=`] = '"object" !=='; + // eslint-disable-next-line no-param-reassign + replacements[`typeof ${key.slice(lastIndex, dotIndex)}==`] = '"object"==='; + // eslint-disable-next-line no-param-reassign + replacements[`typeof ${key.slice(lastIndex, dotIndex)}!=`] = '"object"!=='; + lastIndex = dotIndex + 1; + dotIndex = key.indexOf('.', lastIndex); + } while (dotIndex !== -1); + }); +} + export default function replace(options = {}) { const filter = createFilter(options.include, options.exclude); - const { delimiters, preventAssignment } = options; - const functionValues = mapToFunctions(getReplacements(options)); + const { delimiters, preventAssignment, objectGuards } = options; + const replacements = getReplacements(options); + if (objectGuards) expandTypeofReplacements(replacements); + const functionValues = mapToFunctions(replacements); const keys = Object.keys(functionValues).sort(longest).map(escape); const lookahead = preventAssignment ? '(?!\\s*=[^=])' : ''; const pattern = delimiters diff --git a/packages/replace/test/fixtures/form/process-check/_config.js b/packages/replace/test/fixtures/form/process-check/_config.js new file mode 100644 index 000000000..43c1a7f9d --- /dev/null +++ b/packages/replace/test/fixtures/form/process-check/_config.js @@ -0,0 +1,8 @@ +module.exports = { + description: 'Handles process type guards in replacements', + options: { + 'process.env.NODE_ENV': '"production"', + preventAssignment: true, + objectGuards: true + } +}; diff --git a/packages/replace/test/fixtures/form/process-check/input.js b/packages/replace/test/fixtures/form/process-check/input.js new file mode 100644 index 000000000..dcce0a89f --- /dev/null +++ b/packages/replace/test/fixtures/form/process-check/input.js @@ -0,0 +1,3 @@ +if (typeof process !== 'undefined' && process.env.NODE_ENV === 'production') { + console.log('production'); +} diff --git a/packages/replace/test/fixtures/form/process-check/output.js b/packages/replace/test/fixtures/form/process-check/output.js new file mode 100644 index 000000000..5fa7f8f80 --- /dev/null +++ b/packages/replace/test/fixtures/form/process-check/output.js @@ -0,0 +1 @@ +process.env.DEBUG = 'test'; diff --git a/packages/replace/test/snapshots/form.js.md b/packages/replace/test/snapshots/form.js.md index eaeb3189d..c0d1e0c93 100644 --- a/packages/replace/test/snapshots/form.js.md +++ b/packages/replace/test/snapshots/form.js.md @@ -39,6 +39,14 @@ Generated by [AVA](https://avajs.dev). exclude␊ `);` +## process-check: Handles process type guards in replacements + +> Snapshot 1 + + `if (typeof process !== 'undefined' && "production" === 'production') {␊ + console.log('production');␊ + }` + ## replace-nothing: replaces nothing > Snapshot 1 diff --git a/packages/replace/test/snapshots/form.js.snap b/packages/replace/test/snapshots/form.js.snap index f0d05a94e..f87dfde21 100644 Binary files a/packages/replace/test/snapshots/form.js.snap and b/packages/replace/test/snapshots/form.js.snap differ