diff --git a/README.md b/README.md index 2cf3e558..0e3e1a62 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,17 @@ Require has been given the alias `r` and both are expanded to understand relativ > rexreplace 'VERSION' 'r("package").version' -j dist/*.js ``` +Longer code can live in its own file. Let the file export a function, and give it the match: + +```js +// fix.js +module.exports = match => match.toUpperCase(); +``` + +```bash +> rexreplace 'foo' 'r("fix")($0)' --js myfile.md +``` + --- #### Reference a matching group @@ -230,10 +241,10 @@ docs/intro.md docs/setup.md ``` -With `--replacement-js` the name is the `text`: +With `--js` the name is the `text`: ```bash -> rexreplace '^.*$' 'text.toLowerCase()' 'docs/*' --rename --replacement-js +> rexreplace '^.*$' 'text.toLowerCase()' 'docs/*' --rename --js docs/readme.md ``` @@ -312,13 +323,17 @@ If planned renames clash, the process is halted and no change is applied to any | `-B` | **`--void-backup`** Avoid temporary backing up file. Works async (independent of -A flag) and will speed up things but at one point data lives only in memory, and you will lose the content if the process is interrupted. [boolean] | | `-b` | **`--keep-backup`** Keep a backup file of the original content. With --rename the old file is kept. [boolean] | | `-S` | **`--simulate`** Do everything but write the files. The names of the files that would be changed are printed as usual, and no backup is made. [boolean] | +| `-F` | **`--expect-files`** Check that at least one file is found. The exit code is 1 if not. [boolean] | +| `-z` | **`--expect-diff`** Check that at least one file is changed. The exit code is 1 if not. [boolean] | +| `-Z` | **`--expect-zero-diff`** Check that no file is changed. The exit code is 1 if one is. [boolean] | +| | **`--expect-diff-all`** Check that all files found are changed. The exit code is 1 if not. [boolean] | | `-r` | **`--rename`** Search and replace in the name of each file, and rename the file to the result. The folder and the content are not touched. If planned renames clash the process is halted and no change is applied to any file. [boolean] | | | **`--rename-path`** Like --rename, but on the whole path of each file, so a / in the replacement moves the file. Folders that are not there are made. [boolean] | | | **`--rename-force`** With --rename or --rename-path, write over a file that is there already. [boolean] | | `-m` | **`--output-match`** Output each match on a new line. Will not replace any content but you still need to provide a dummy value (like `_`) as replacement parameter. If search pattern does not contain matching groups the full match will be outputted. If search pattern does contain matching groups only matching groups will be outputted (same line with no delimiter). [boolean] | | `-T` | **`--trim-pipe`** Trim piped data before processing. If piped data only consists of chars that can be trimmed (new line, space, tabs...) it will become an empty string. [boolean] | -| `-p` | **`--pipe`** Read the piped data and make it available to --replacement-js as `pipe`. [boolean] | -| | **`--js-pipe`** Shorthand for --replacement-js --pipe (-jp). [boolean] | +| `-p` | **`--pipe`** Read the piped data and make it available to --js as `pipe`. [boolean] | +| | **`--js-pipe`** Shorthand for --js --pipe (-jp). [boolean] | | `-g` | **`--glob-pipe`** Each line of the piped data is a path or a glob of the files to work on. They are used together with the ones given as arguments. [boolean] | | | **`--null-in`** The piped paths and globs of --glob-pipe are separated by null bytes, like from `find -print0` or --null-out. [boolean] | | `-R` | **`--replacement-pipe`** Replacement will be piped in. You still need to provide a dummy value (like `_`) as replacement parameter. [boolean] | @@ -348,6 +363,7 @@ Files identified for potential processing are identified by: - Data can be piped in, both as the text to search and as the replacement - The names of the changed files are printed to stdout, so they can be piped on (to stderr with `--output` and `--output-match`) - Set the delimiter between names and between the content of files +- Check the result in a CI step with `--expect-files`, `--expect-diff`, `--expect-zero-diff` and `--expect-diff-all`. Add `--simulate` to only check - Rename files with a search and replace on their name (`--rename`), or move them with one on their path (`--rename-path`). If planned renames clash the process is halted and no change is applied to any file, and `--simulate` shows each old name next to its new one - See the [release note](https://github.com/mathiasrw/rexreplace/releases) for a log of changes. Descriptions are given in latest patch version. @@ -374,10 +390,11 @@ Files identified for potential processing are identified by: | Code | Meaning | | --- | --- | | 0 | Done. Warnings (🟡) are ok. | +| 1 | A check from `--expect-*` was not met. | | 2 | One or more files gave an error (❌), and the run went on. | -| 3 | The run stopped (❌ … `No further processing.`): a bad pattern, an unknown encoding, wrong flags, or any error with `--halt`. | +| 3 | The run stopped (❌ … `No further processing.`): a bad pattern, an unknown encoding, wrong flags, planned renames that clash, or any error with `--halt`. | -Code 1 is kept for a check that says no, like `grep` does when nothing is found. +Like `grep` and `diff`, code 1 means that RexReplace did its job and the answer is no, and 2 or 3 means that something went wrong. ### Limitations diff --git a/src/cli.ts b/src/cli.ts index 4fd55707..0614ed9f 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -247,6 +247,23 @@ const yargs = require('yargs') ) .alias('S', 'simulate') + // Checks for a CI step. When one is not met, the exit code is 1. + .boolean('F') + .describe('F', 'Check that at least one file is found. The exit code is 1 if not.') + .alias('F', 'expect-files') + + .boolean('z') + .describe('z', 'Check that at least one file is changed. The exit code is 1 if not.') + .alias('z', 'expect-diff') + + .boolean('Z') + .describe('Z', 'Check that no file is changed. The exit code is 1 if one is.') + .alias('Z', 'expect-zero-diff') + + .boolean('expect-diff-all') + .describe('expect-diff-all', 'Check that all files found are changed. The exit code is 1 if not.') + .conflicts('Z', ['z', 'expect-diff-all']) + .boolean('r') .describe( 'r', @@ -297,10 +314,10 @@ const yargs = require('yargs') .boolean('p') .alias('p', 'pipe') - .describe('p', `Read the piped data and make it available to --replacement-js as \`pipe\`.`) + .describe('p', `Read the piped data and make it available to --js as \`pipe\`.`) .boolean('js-pipe') - .describe('js-pipe', `Shorthand for --replacement-js --pipe (-jp).`) + .describe('js-pipe', `Shorthand for --js --pipe (-jp).`) .boolean('g') .alias('g', 'glob-pipe') @@ -372,10 +389,6 @@ All variables, except from module, date objects, \`nl\` and \`_\`, has a corresp `Normally . `+ '' ) - - - -E (Expect there to be no match and return exit 1 if found) - -e (Expect there to be batch and return exit 1 if not found) */ /* .boolean('P') @@ -586,13 +599,17 @@ if (config.contentDelimiter) { } // The exit code is only set, so the process ends by itself when all output is written. -// 0: done, 2: an error in a file and the run went on, 3: a panic (the run stopped) +// 0: done, 1: a check that was not met, 2: an error in a file and the run went on, 3: a panic (the run stopped) const run = () => rexreplace.engine(config).then( status => { printStatus(config, status); - if (status.errors.length) { + // The failed checks are errors too, so 1 is when they are the only errors + const checks = status.checks?.length ?? 0; + if (status.errors.length > checks) { process.exitCode = 2; + } else if (checks) { + process.exitCode = 1; } }, e => exitOnPanic(config, e) diff --git a/src/engine.ts b/src/engine.ts index 08e29d9e..905895af 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -8,7 +8,17 @@ const globs = require('fast-glob'); const now = new Date(); -import {outputConfig, chat, info, print, printDone, warn, error, panic} from './output.ts'; +import { + outputConfig, + chat, + info, + print, + printDone, + warn, + error, + panic, + checkFailed, +} from './output.ts'; import './shim.ts'; @@ -43,6 +53,7 @@ export async function engine(config: any = {engine: 'default'}) { outputConfig(config); try { await run(config); + checkExpectations(config); } catch (e) { if (e?.panic) { e.status = config.statusInfo; @@ -62,6 +73,11 @@ function newStatus(config) { status.renames = []; } + // The checks from --expect-* that are not met + if (config.expectFiles || config.expectDiff || config.expectZeroDiff || config.expectDiffAll) { + status.checks = []; + } + // The status report with --verbose also tells what went into the engine and what it did. // The config is copied before it gets the status, and the piped data is told about in `pipe`. if (config.verbose && (config.status || config.statusCompact)) { @@ -84,6 +100,10 @@ function newStatus(config) { async function run(config) { validateEncoding(config.encoding); + if (config.expectZeroDiff && (config.expectDiff || config.expectDiffAll)) { + panic('--expect-zero-diff can not be used with --expect-diff or --expect-diff-all'); + } + // With --glob-pipe each line of the piped data is a path or a glob, used together with the ones given as arguments if (config.globPipe) { if (config.replacementPipe || config.pipe) { @@ -450,6 +470,39 @@ function renameFile(item, config) { info(item.new); } +// The checks from --expect-* are done when the run is done. Piped data counts as one file. +function checkExpectations(config) { + const status = config.statusInfo; + if (!status.checks) { + return; + } + const piped = Boolean(config.dataIsPiped); + const found = piped ? 1 : status.files.searched.length; + const changed = piped ? Number(status.pipe.updated) : status.files.updated.length; + // Nothing is written with --simulate, --output and --output-match + const would = !piped && (config.simulate || config.output || config.outputMatch); + const files = count => (piped ? 'the piped data' : count + (1 === count ? ' file' : ' files')); + const change = count => + would ? 'would change' : 1 === count || piped ? 'was changed' : 'were changed'; + + if (config.expectFiles && !found) { + checkFailed('Expected files, but 0 files found'); + } + if ((config.expectDiff || config.expectDiffAll) && !changed) { + checkFailed('Expected a diff, but nothing ' + (would ? 'would change' : 'was changed')); + } else if (config.expectDiffAll && changed < found) { + const unchanged = found - changed; + checkFailed( + 'Expected all files to change, but ' + + files(unchanged) + + (would ? ' would not change' : 1 === unchanged ? ' was not changed' : ' were not changed') + ); + } + if (config.expectZeroDiff && changed) { + checkFailed('Expected no diff, but ' + files(changed) + ' ' + change(changed)); + } +} + // How the piped data was used function pipeType(config, piped) { if (config.globPipe) { diff --git a/src/output.ts b/src/output.ts index ff418041..3b77f490 100644 --- a/src/output.ts +++ b/src/output.ts @@ -115,6 +115,13 @@ export const panic = function (msg = '', data = '', displayHelp = false) { throw new Panic(text); }; +// A check from --expect-* that is not met. It is an error, and is also in `checks`, so the exit code can tell it from the other errors. +export const checkFailed = function (text) { + config.statusInfo?.errors.push(text); + config.statusInfo?.checks.push(text); + printError(text, false); +}; + // Something failed, and the run goes on. With --halt it is a panic. export const error = function (msg, data = '') { const text = msg + (data ? ' ' + data : ''); diff --git a/test/cli/expect.test.js b/test/cli/expect.test.js new file mode 100644 index 00000000..d445bb43 --- /dev/null +++ b/test/cli/expect.test.js @@ -0,0 +1,27 @@ +// --expect-* checks the result, so a CI step can stop when it is not as expected +import {describe, test, expect} from 'bun:test'; +import fs from 'fs'; +import {$} from 'bun'; +import {cli, sh} from '../helper.js'; + +describe('--expect-*', () => { + test('gives the exit code 1 when a check is not met', async () => { + fs.writeFileSync('my.file', 'foobar'); + expect(await sh($`bun ${cli} o x my.file -Z -S`)).toEqual({ + out: 'my.file\n', + err: ' ❌ Expected no diff, but 1 file would change\n\n', + exit: 1, + }); + expect((await sh($`bun ${cli} nope x my.file -z`)).exit).toBe(1); + expect((await sh($`bun ${cli} o x nope.file -F`)).exit).toBe(1); + }); + + test.each([['-z'], ['--expect-diff-all']])( + '--expect-zero-diff can not be used with %s', + async flag => { + fs.writeFileSync('my.file', 'foobar'); + expect((await sh($`bun ${cli} o x my.file -Z ${flag}`)).exit).toBe(3); + expect(fs.readFileSync('my.file', 'utf8')).toBe('foobar'); + } + ); +}); diff --git a/test/cli/pipe.test.js b/test/cli/pipe.test.js index c7221078..a763a5ea 100644 --- a/test/cli/pipe.test.js +++ b/test/cli/pipe.test.js @@ -8,18 +8,18 @@ const warning = ' 🟡 Data is piped in, but no flag tells what to do with it. I describe('--pipe and --js-pipe', () => { test('without --pipe the code does not get the piped data', async () => { fs.writeFileSync('my.file', 'foobar\n'); - expect(await sh($`echo -n x | bun ${cli} foo pipe my.file -o --replacement-js`)).toEqual({ + expect(await sh($`echo -n x | bun ${cli} foo pipe my.file -o --js`)).toEqual({ out: 'nullbar\n', err: 'my.file\n', exit: 0, }); }); - test('the code gets the piped data with --replacement-js --pipe', async () => { + test('the code gets the piped data with --js --pipe', async () => { fs.writeFileSync('my.file', 'foobar\n'); - expect( - (await sh($`echo -n x | bun ${cli} foo pipe my.file -o --replacement-js --pipe`)).out - ).toBe('xbar\n'); + expect((await sh($`echo -n x | bun ${cli} foo pipe my.file -o --js --pipe`)).out).toBe( + 'xbar\n' + ); }); test('the code gets the piped data with -jp and --js-pipe', async () => { diff --git a/test/cli/run.sh b/test/cli/run.sh index 30b51a93..ccd46478 100644 --- a/test/cli/run.sh +++ b/test/cli/run.sh @@ -253,6 +253,15 @@ assert_raises "rexreplace '^(my|your)' 'our' my.file your.file -r" 3 assert "cat my.file" 'foobar' +# --expect-* checks the result, so a CI step can stop. When a check is not met, the exit code is 1. +reset +assert_raises "rexreplace 'oo' 'x' my.file --expect-zero-diff --simulate" 1 +assert "cat my.file" 'foobar' +assert_raises "rexreplace 'nope' 'x' my.file --expect-diff" 1 +assert_raises "rexreplace 'oo' 'x' my.file -z" 0 +assert_raises "rexreplace 'oo' 'x' nope.file --expect-files" 1 + + # --full-path prints the full path of each name reset assert "rexreplace 'b' 'x' my.file --full-path" "$(pwd -P)/my.file" diff --git a/test/engine/crossover.test.js b/test/engine/crossover.test.js index ffd705e8..7f5bdeb2 100644 --- a/test/engine/crossover.test.js +++ b/test/engine/crossover.test.js @@ -118,7 +118,7 @@ describe('same result no matter how it is delivered', () => { expect(fs.readdirSync('.')).toHaveLength(files); }); - test('--replacement-pipe and --replacement-js', async () => { + test('--replacement-pipe and --js', async () => { fs.writeFileSync('my.file', text); const config = {includeGlob: ['my.file'], output: true, ...search}; expect((await rr('a.b', '_', {...config, replacementPipe: true, pipedData: '[$&]'})).out).toBe( diff --git a/test/engine/encoding.test.js b/test/engine/encoding.test.js index e18fdd2e..a04f1234 100644 --- a/test/engine/encoding.test.js +++ b/test/engine/encoding.test.js @@ -116,7 +116,7 @@ describe('--encoding', () => { expect(fs.readFileSync('my.file', 'utf16le')).toBe('🦕 🦖'); }); - test('with --replacement-js the text is decoded', async () => { + test('with --js the text is decoded', async () => { fs.writeFileSync('my.file', latin1()); const config = { includeGlob: ['my.file'], @@ -127,7 +127,7 @@ describe('--encoding', () => { expect((await rr('æ', 'text.length + text', config)).out).toBe('f3fæoo\n'); }); - test('with --replacement-js `bytes` and `size` are the size of the file in its encoding (#688)', async () => { + test('with --js `bytes` and `size` are the size of the file in its encoding (#688)', async () => { // 1500 times `æ`: one byte each in latin1, but two in utf8 (that would give 3000 and 2.9 KB) fs.writeFileSync('my.file', Buffer.alloc(1500, 0xe6)); const config = { diff --git a/test/engine/expect-diff-all.test.js b/test/engine/expect-diff-all.test.js new file mode 100644 index 00000000..13ec3562 --- /dev/null +++ b/test/engine/expect-diff-all.test.js @@ -0,0 +1,37 @@ +import {describe, test, expect} from 'bun:test'; +import fs from 'fs'; +import {rr} from '../helper.js'; + +describe('--expect-diff-all', () => { + test('is met when all files found are changed', async () => { + fs.writeFileSync('a.file', 'foo'); + fs.writeFileSync('b.file', 'boo'); + expect((await rr('o', 'x', {includeGlob: ['*.file'], expectDiffAll: true})).exit).toBe(null); + }); + + test('is not met when a file is not changed', async () => { + fs.writeFileSync('a.file', 'foo'); + fs.writeFileSync('b.file', 'bar'); + fs.writeFileSync('c.file', 'baz'); + expect(await rr('o', 'x', {includeGlob: ['*.file'], expectDiffAll: true})).toEqual({ + out: 'a.file\n', + err: ' ❌ Expected all files to change, but 2 files were not changed\n\n', + exit: 1, + }); + }); + + test('is not met when no file is found', async () => { + const result = await rr('o', 'x', {includeGlob: ['nope.file'], expectDiffAll: true}); + expect(result.err).toContain(' ❌ Expected a diff, but nothing was changed'); + expect(result.exit).toBe(1); + }); + + test('says would with --simulate', async () => { + fs.writeFileSync('a.file', 'foo'); + fs.writeFileSync('b.file', 'bar'); + const config = {includeGlob: ['*.file'], expectDiffAll: true, simulate: true}; + expect((await rr('o', 'x', config)).err).toBe( + ' ❌ Expected all files to change, but 1 file would not change\n\n' + ); + }); +}); diff --git a/test/engine/expect-diff.test.js b/test/engine/expect-diff.test.js new file mode 100644 index 00000000..343d116d --- /dev/null +++ b/test/engine/expect-diff.test.js @@ -0,0 +1,56 @@ +import {describe, test, expect} from 'bun:test'; +import fs from 'fs'; +import {rr} from '../helper.js'; + +describe('--expect-diff', () => { + test('is met when a file is changed', async () => { + fs.writeFileSync('a.file', 'foo'); + fs.writeFileSync('b.file', 'bar'); + expect(await rr('o', 'x', {includeGlob: ['*.file'], expectDiff: true})).toEqual({ + out: 'a.file\n', + err: '', + exit: null, + }); + }); + + test('is not met when nothing is changed', async () => { + fs.writeFileSync('my.file', 'foobar'); + expect(await rr('nope', 'x', {includeGlob: ['my.file'], expectDiff: true})).toEqual({ + out: '', + err: ' ❌ Expected a diff, but nothing was changed\n\n', + exit: 1, + }); + }); + + test('is not met when no file is found', async () => { + expect((await rr('o', 'x', {includeGlob: ['nope.file'], expectDiff: true})).exit).toBe(1); + }); + + test('says would with --simulate', async () => { + fs.writeFileSync('my.file', 'foobar'); + const config = {includeGlob: ['my.file'], expectDiff: true, simulate: true}; + expect((await rr('nope', 'x', config)).err).toBe( + ' ❌ Expected a diff, but nothing would change\n\n' + ); + }); + + test('a match that gives the same text is not a diff', async () => { + fs.writeFileSync('my.file', 'foobar'); + expect((await rr('o', 'o', {includeGlob: ['my.file'], expectDiff: true})).exit).toBe(1); + }); + + test('piped data counts as one file', async () => { + expect(await rr('o', 'x', {pipedData: 'foo', expectDiff: true})).toEqual({ + out: 'fxx', + err: '', + exit: null, + }); + expect((await rr('z', 'x', {pipedData: 'foo', expectDiff: true})).exit).toBe(1); + }); + + test('a rename is a diff', async () => { + fs.writeFileSync('a.md', 'foo'); + const config = {includeGlob: ['a.md'], rename: true, expectDiff: true}; + expect((await rr('md', 'txt', config)).exit).toBe(null); + }); +}); diff --git a/test/engine/expect-files.test.js b/test/engine/expect-files.test.js new file mode 100644 index 00000000..93f0309f --- /dev/null +++ b/test/engine/expect-files.test.js @@ -0,0 +1,42 @@ +import {describe, test, expect} from 'bun:test'; +import fs from 'fs'; +import {rr, rrStatus} from '../helper.js'; + +describe('--expect-files', () => { + test('is met when a file is found, also when nothing is changed', async () => { + fs.writeFileSync('my.file', 'foobar'); + expect(await rr('nope', 'x', {includeGlob: ['my.file'], expectFiles: true})).toEqual({ + out: '', + err: '', + exit: null, + }); + }); + + test('is not met when no file is found, so the warning becomes a failed check', async () => { + expect(await rr('o', 'x', {includeGlob: ['nope.file'], expectFiles: true})).toEqual({ + out: '', + err: ' 🟡 0 files found\n\n ❌ Expected files, but 0 files found\n\n', + exit: 1, + }); + }); + + test('is in `errors` and in `checks` in the status report', async () => { + const status = await rrStatus('o', 'x', {includeGlob: ['nope.file'], expectFiles: true}); + expect(status.errors).toEqual(['Expected files, but 0 files found']); + expect(status.checks).toEqual(['Expected files, but 0 files found']); + }); + + test('is still shown with --quiet, but not with --quiet-total', async () => { + const config = {includeGlob: ['nope.file'], expectFiles: true}; + expect((await rr('o', 'x', {...config, quiet: true})).err).toBe( + ' ❌ Expected files, but 0 files found\n\n' + ); + expect(await rr('o', 'x', {...config, quietTotal: true})).toEqual({out: '', err: '', exit: 1}); + }); + + test('an error in a file gives the exit code 2, also when a check is not met', async () => { + fs.writeFileSync('a.file', 'foo'); + fs.chmodSync('a.file', 0); + expect((await rr('o', 'x', {includeGlob: ['a.file'], expectDiff: true})).exit).toBe(2); + }); +}); diff --git a/test/engine/expect-zero-diff.test.js b/test/engine/expect-zero-diff.test.js new file mode 100644 index 00000000..0a82e2eb --- /dev/null +++ b/test/engine/expect-zero-diff.test.js @@ -0,0 +1,56 @@ +import {describe, test, expect} from 'bun:test'; +import fs from 'fs'; +import {rr} from '../helper.js'; + +describe('--expect-zero-diff', () => { + test('is met when nothing is changed, also when no file is found', async () => { + fs.writeFileSync('my.file', 'foobar'); + expect(await rr('nope', 'x', {includeGlob: ['my.file'], expectZeroDiff: true})).toEqual({ + out: '', + err: '', + exit: null, + }); + expect((await rr('o', 'x', {includeGlob: ['nope.file'], expectZeroDiff: true})).exit).toBe( + null + ); + }); + + test('is not met when a file is changed, and the file is still changed', async () => { + fs.writeFileSync('a.file', 'foo'); + fs.writeFileSync('b.file', 'foo'); + expect(await rr('o', 'x', {includeGlob: ['*.file'], expectZeroDiff: true})).toEqual({ + out: 'a.file\nb.file\n', + err: ' ❌ Expected no diff, but 2 files were changed\n\n', + exit: 1, + }); + expect(fs.readFileSync('a.file', 'utf8')).toBe('fxx'); + }); + + test('with --simulate only checks', async () => { + fs.writeFileSync('my.file', 'foobar'); + const config = {includeGlob: ['my.file'], expectZeroDiff: true, simulate: true}; + expect(await rr('o', 'x', config)).toEqual({ + out: 'my.file\n', + err: ' ❌ Expected no diff, but 1 file would change\n\n', + exit: 1, + }); + expect(fs.readFileSync('my.file', 'utf8')).toBe('foobar'); + }); + + test('tells about piped data', async () => { + expect((await rr('o', 'x', {pipedData: 'foo', expectZeroDiff: true})).err).toBe( + ' ❌ Expected no diff, but the piped data was changed\n\n' + ); + }); + + test.each([['expectDiff'], ['expectDiffAll']])('can not be used with --%s', async flag => { + fs.writeFileSync('my.file', 'foobar'); + const result = await rr('o', 'x', { + includeGlob: ['my.file'], + expectZeroDiff: true, + [flag]: true, + }); + expect(result.exit).toBe(3); + expect(fs.readFileSync('my.file', 'utf8')).toBe('foobar'); + }); +}); diff --git a/test/engine/keep-backup.test.js b/test/engine/keep-backup.test.js index 3f7974c4..58960f22 100644 --- a/test/engine/keep-backup.test.js +++ b/test/engine/keep-backup.test.js @@ -63,7 +63,7 @@ describe('--keep-backup', () => { expect(fs.readdirSync('.')).toEqual(['my.file']); }); - test('with --replacement-js', async () => { + test('with --js', async () => { fs.writeFileSync('my.file', 'foobar'); await rr('foo', 'name', { includeGlob: ['my.file'], diff --git a/test/engine/literal.test.js b/test/engine/literal.test.js index 50b96328..52a5d234 100644 --- a/test/engine/literal.test.js +++ b/test/engine/literal.test.js @@ -68,7 +68,7 @@ describe('--literal', () => { ).toBe('o.\no.\n'); }); - test('with --replacement-js', async () => { + test('with --js', async () => { const config = {pipedData: '1+1 = 2', literal: true, replacementJs: true}; expect((await rr('1+1', '$0.length', config)).out).toBe('3 = 2'); }); diff --git a/test/engine/output-match.test.js b/test/engine/output-match.test.js index 868907d1..d7ed3b5e 100644 --- a/test/engine/output-match.test.js +++ b/test/engine/output-match.test.js @@ -89,7 +89,7 @@ describe('--output-match', () => { ).toBe('a.\n'); }); - test('with --replacement-js the code is not run', async () => { + test('with --js the code is not run', async () => { const config = {pipedData: 'foobar', outputMatch: true, replacementJs: true}; expect((await rr('[fb].', 'throw new Error("should not run")', config)).out).toBe('fo\nba\n'); }); diff --git a/test/engine/output.test.js b/test/engine/output.test.js index 07a8f315..a39f4583 100644 --- a/test/engine/output.test.js +++ b/test/engine/output.test.js @@ -69,7 +69,7 @@ describe('--output', () => { expect(result.err).toContain('Output result from: my.file'); }); - test('with --replacement-js and several files', async () => { + test('with --js and several files', async () => { fs.writeFileSync('a.file', 'foo'); fs.writeFileSync('b.file', 'foo'); const config = {includeGlob: ['*.file'], output: true, replacementJs: true}; diff --git a/test/engine/rename.test.js b/test/engine/rename.test.js index 2f7ad927..20cde82a 100644 --- a/test/engine/rename.test.js +++ b/test/engine/rename.test.js @@ -60,7 +60,7 @@ describe('--rename', () => { expect(files()).toEqual(['a.md']); }); - test('runs the code of --replacement-js with the name as the text', async () => { + test('runs the code of --js with the name as the text', async () => { fs.mkdirSync('docs'); fs.writeFileSync('docs/Intro.md', 'foo'); const config = {includeGlob: ['docs/*'], rename: true, replacementJs: true}; diff --git a/test/engine/replacement-pipe.test.js b/test/engine/replacement-pipe.test.js index 6ce19b0e..5c7ffa30 100644 --- a/test/engine/replacement-pipe.test.js +++ b/test/engine/replacement-pipe.test.js @@ -68,7 +68,7 @@ describe('--replacement-pipe', () => { }); describe('combined', () => { - test('with --replacement-js the piped data is the code to run', async () => { + test('with --js the piped data is the code to run', async () => { fs.writeFileSync('my.file', 'foobar'); const config = { includeGlob: ['my.file'], diff --git a/test/engine/unicode.test.js b/test/engine/unicode.test.js index 0b412258..9beea484 100644 --- a/test/engine/unicode.test.js +++ b/test/engine/unicode.test.js @@ -65,7 +65,7 @@ describe('--unicode', () => { expect((await rr('\\w(.)', '_', config)).out).toBe('🦖\n🦕\n'); }); - test('with --replacement-js', async () => { + test('with --js', async () => { const config = {pipedData: 'a🦖b', unicode: true, replacementJs: true}; expect((await rr('a(.)b', '$1.codePointAt(0).toString(16)', config)).out).toBe('1f996'); }); diff --git a/test/engine/utf8.test.js b/test/engine/utf8.test.js index 7070595e..ac2c2e64 100644 --- a/test/engine/utf8.test.js +++ b/test/engine/utf8.test.js @@ -41,7 +41,7 @@ describe('#735 UTF-8 in the replacement', () => { expect(asUtf8((await rr('§d', '🦖', {pipedData: 'a1b'})).out)).toBe('a🦖b'); }); - test('can come from --replacement-js, where an emoji has the length 2', async () => { + test('can come from --js, where an emoji has the length 2', async () => { const config = {pipedData: 'a🦖', replacementJs: true}; expect(asUtf8((await rr('🦖', '$0 + $0.length', config)).out)).toBe('a🦖2'); }); diff --git a/test/engine/verbose.test.js b/test/engine/verbose.test.js index bcb53ce0..8ce551d6 100644 --- a/test/engine/verbose.test.js +++ b/test/engine/verbose.test.js @@ -72,7 +72,7 @@ describe('--verbose', () => { ); }); - test('with --replacement-js piped data counts as used', async () => { + test('with --js piped data counts as used', async () => { fs.writeFileSync('my.file', 'foobar'); const config = { includeGlob: ['my.file'], diff --git a/test/engine/void-async.test.js b/test/engine/void-async.test.js index 592c3eb8..22d0078b 100644 --- a/test/engine/void-async.test.js +++ b/test/engine/void-async.test.js @@ -85,7 +85,7 @@ describe('--void-async', () => { } ); - test('with --replacement-js and several files', async () => { + test('with --js and several files', async () => { ['a', 'b', 'c'].forEach(name => fs.writeFileSync(name + '.file', 'foo')); await rr('foo', 'name + name', { includeGlob: ['*.file'], diff --git a/test/engine/void-backup.test.js b/test/engine/void-backup.test.js index 3ee1ed1e..c01fe33d 100644 --- a/test/engine/void-backup.test.js +++ b/test/engine/void-backup.test.js @@ -51,7 +51,7 @@ describe('--void-backup', () => { expect(fs.readFileSync('my.file', 'utf8')).toBe('foobar'); }); - test('with --replacement-js', async () => { + test('with --js', async () => { fs.writeFileSync('my.file', 'foobar'); const config = {includeGlob: ['my.file'], voidBackup: true, replacementJs: true}; await rr('foo', 'filename', config); diff --git a/test/engine/void-euro.test.js b/test/engine/void-euro.test.js index c0c64234..3477118e 100644 --- a/test/engine/void-euro.test.js +++ b/test/engine/void-euro.test.js @@ -50,7 +50,7 @@ describe('--void-euro', () => { ); }); - test('with --replacement-js the € is how to get to the groups', async () => { + test('with --js the € is how to get to the groups', async () => { expect( (await rr('(o)', '€1.toUpperCase()', {pipedData: 'fo', replacementJs: true})).out ).toBe('fO'); diff --git a/test/engine/void-global.test.js b/test/engine/void-global.test.js index 5893b81b..861eb05b 100644 --- a/test/engine/void-global.test.js +++ b/test/engine/void-global.test.js @@ -51,7 +51,7 @@ describe('--void-global', () => { expect((await rr('a+', 'x', config)).out).toBe('A+ x a+'); }); - test('with --replacement-js the code only runs for the first match', async () => { + test('with --js the code only runs for the first match', async () => { const config = {pipedData: 'a a a', voidGlobal: true, replacementJs: true}; expect((await rr('a', '$0.toUpperCase()', config)).out).toBe('A a a'); }); diff --git a/test/engine/void-ignore-case.test.js b/test/engine/void-ignore-case.test.js index b8f1444f..f881c204 100644 --- a/test/engine/void-ignore-case.test.js +++ b/test/engine/void-ignore-case.test.js @@ -40,7 +40,7 @@ describe('--void-ignore-case', () => { expect((await rr('F.o', '_', config)).out).toBe('Foo\n'); }); - test('with --replacement-js', async () => { + test('with --js', async () => { const config = {pipedData: 'foo Foo', replacementJs: true, voidIgnoreCase: true}; expect((await rr('F(oo)', '$1.toUpperCase()', config)).out).toBe('foo OO'); }); diff --git a/test/engine/void-multiline.test.js b/test/engine/void-multiline.test.js index 7df40933..1cb34b1a 100644 --- a/test/engine/void-multiline.test.js +++ b/test/engine/void-multiline.test.js @@ -53,7 +53,7 @@ describe('--void-multiline', () => { expect((await rr('^.|.$', '_', config)).out).toBe('a\nf\n'); }); - test('with --replacement-js', async () => { + test('with --js', async () => { const config = {pipedData: 'ab\ncd', voidMultiline: true, replacementJs: true}; expect((await rr('^(.)', '$1.toUpperCase()', config)).out).toBe('Ab\ncd'); }); diff --git a/test/engine/void-section.test.js b/test/engine/void-section.test.js index 5bab9a63..88e6e918 100644 --- a/test/engine/void-section.test.js +++ b/test/engine/void-section.test.js @@ -64,7 +64,7 @@ describe('--void-section', () => { ).toBe('x a\\b'); }); - test('with --replacement-js', async () => { + test('with --js', async () => { const config = {pipedData: 'a1', replacementJs: true}; expect((await rr('§d', '"§n".length', config)).out).toBe('a1'); expect((await rr('§d', '"§n".length', {...config, voidSection: true})).out).toBe('a1'); diff --git a/test/fix/699.test.js b/test/fix/699.test.js index 7b3ac504..0c481c9a 100644 --- a/test/fix/699.test.js +++ b/test/fix/699.test.js @@ -129,7 +129,7 @@ describe('#699 € and § are only aliases in the pattern, the replacement and - expect((await rr('(b)', '[€1§]', {pipedData: 'foobar', ...voided})).out).toBe('foo[€1§]ar'); }); - test('the replacement with --replacement-js', async () => { + test('the replacement with --js', async () => { const config = {pipedData: 'foobar', replacementJs: true}; expect((await rr('(b)', '€1 + "§n"', config)).out).toBe('foob\nar'); }); diff --git a/test/fix/700.test.js b/test/fix/700.test.js index 474fa405..79cd47d8 100644 --- a/test/fix/700.test.js +++ b/test/fix/700.test.js @@ -51,8 +51,8 @@ describe('#700 § is a full substitute for \\ in the replacement', () => { expect((await replaceB('§\\n', '-§')).out).toBe('foo§\nar'); }); - test('--replacement-js still gets § as a backslash in the source', async () => { - expect((await replaceB('"a§nb"', '--replacement-js')).out).toBe('fooa\nbar'); - expect((await replaceB('"a§nb"', '--replacement-js', '--void-section')).out).toBe('fooa§nbar'); + test('--js still gets § as a backslash in the source', async () => { + expect((await replaceB('"a§nb"', '--js')).out).toBe('fooa\nbar'); + expect((await replaceB('"a§nb"', '--js', '--void-section')).out).toBe('fooa§nbar'); }); }); diff --git a/test/fix/726.test.js b/test/fix/726.test.js index f02ca3f1..c5b3df2f 100644 --- a/test/fix/726.test.js +++ b/test/fix/726.test.js @@ -67,7 +67,7 @@ describe('#726 a file given with an absolute path is changed', () => { } ); - test('--replacement-js gets the path of the file as it is', async () => { + test('--js gets the path of the file as it is', async () => { fs.mkdirSync('work'); fs.mkdirSync('other'); const file = path.resolve('other', 'my.file'); diff --git a/test/fix/742.test.js b/test/fix/742.test.js index fd346f15..0c9b8ff6 100644 --- a/test/fix/742.test.js +++ b/test/fix/742.test.js @@ -135,7 +135,7 @@ describe('#742 the engine part of the status', () => { test.each([ ['a replacement', {}, 'x', null], - ['--replacement-js', {replacementJs: true}, null, '"x"'], + ['--js', {replacementJs: true}, null, '"x"'], ['--output-match', {outputMatch: true}, null, null], ])( 'tells what the regex engine was asked to do with %s', diff --git a/test/helper.js b/test/helper.js index 2b932fb0..041a53d2 100644 --- a/test/helper.js +++ b/test/helper.js @@ -69,9 +69,12 @@ async function run(pattern, replacement, config) { }; alias(options); status = await engine(options); - // Like the CLI, an error gives the exit code 2 when the run is done - if (status.errors.length) { + // Like the CLI, an error gives the exit code 2 and a check that is not met 1, when the run is done + const checks = status.checks?.length ?? 0; + if (status.errors.length > checks) { result.exit = 2; + } else if (checks) { + result.exit = 1; } } catch (e) { // Like the CLI, a panic stops the run with the exit code 3