Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ If planned renames clash, the process is halted and no change is applied to any
| `-A` | **`--void-async`** Handle files in a synchronous flow. Good to limit memory usage when handling large files. [boolean] |
| `-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] |
| `-S` | **`--simulate`** Execute everything during a run except changes to the file system. Names of affected files and content are still printed as in a usual run. [boolean] |
| | **`--sort`** Process the files one at a time, in a sorted order. The order is chosen from 6 values: `natural` (numbers compared as numbers, and case ignored), `ascii` (alphabetic), `modified`, `created`, `size`, or `none` (the order of the input: the files given first, and then the files found by globs). Files that sort the same (like the same size) are ordered by ascii sort. Without --sort the files are processed asynchronously, so the order of the output can not be guaranteed, but the processing is started in natural order. [string] [choices: "natural", "ascii", "modified", "created", "size", "none"] |
| | **`--sort-reverse`** Turn the order of the files around. [boolean] |
| `-F` | **`--expect-files`** Check that at least one file is found. The exit code is 1 if not. [boolean] |
Expand Down
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ const yargs = require('yargs')
.boolean('S')
.describe(
'S',
'Do everything but write the files. The names of the files that would be changed are printed as usual, and no backup is made.'
'Execute everything during a run except changes to the file system. Names of affected files and content are still printed as in a usual run.'
)
.alias('S', 'simulate')

Expand Down
14 changes: 12 additions & 2 deletions src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,16 @@ function dynamicReplacement(
'return eval(__code_rr);'
);

// When the code fails, only the message is told, and not the stack trace with all the code put in front of the code of the user
const runCode = (...args) => {
try {
return dynamicContent(...args);
} catch (e) {
const where = _config_rr.dataIsPiped ? 'the piped data' : _file_rr;
return panic('The code of --js failed for ' + where + ':', String(e));
}
};

const needsByteOrSize = re.byteOrSize.test(_config_rr.replacement);
const betterToReadfromFile = needsByteOrSize && 50000000 < _text.length; // around 50 Mb will lead to reading filezise from file instead of copying into buffer

Expand Down Expand Up @@ -947,7 +957,7 @@ function dynamicReplacement(
!re.matchNumbers.test(_config_rr.replacement) &&
!usesNamedGroups(_config_rr)
) {
const result = dynamicContent(
const result = runCode(
require,
fs,
globs,
Expand Down Expand Up @@ -1042,7 +1052,7 @@ function dynamicReplacement(
}
}

return dynamicContent(
return runCode(
require,
fs,
globs,
Expand Down
19 changes: 12 additions & 7 deletions test/engine/replacement-js.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,18 @@ describe('--replacement-js', () => {
expect(fs.readFileSync('my.file', 'utf8')).toBe('xxxbar');
});

test('throws the errors of the code', async () => {
test('stops the run with the error of the code (#808)', async () => {
fs.writeFileSync('my.file', 'foobar');
const config = {includeGlob: ['my.file'], replacementJs: true};
await expect(rr('foo', 'this is not javascript', config)).rejects.toThrow(SyntaxError);
await expect(rr('foo', 'nope.nope', config)).rejects.toThrow(ReferenceError);
await expect(rr('foo', 'throw new Error("my own")', config)).rejects.toThrow('my own');
for (const [code, message] of [
['this is not javascript', 'SyntaxError'],
['nope.nope', 'ReferenceError'],
['throw new Error("my own")', 'Error: my own'],
]) {
const result = await rr('foo', code, config);
expect(result.err).toContain(message);
expect(result.exit).toBe(3);
}
expect(fs.readFileSync('my.file', 'utf8')).toBe('foobar');
});

Expand Down Expand Up @@ -302,9 +308,8 @@ describe('--replacement-js', () => {
describe('combined', () => {
test('with --void-euro there is only the $ to get to the groups', async () => {
expect(await js('(o)', '$1.toUpperCase()', 'fo', {voidEuro: true})).toBe('fO');
await expect(js('(o)', '€1.toUpperCase()', 'fo', {voidEuro: true})).rejects.toThrow(
SyntaxError
);
const config = {pipedData: 'fo', replacementJs: true, voidEuro: true};
expect((await rr('(o)', '€1.toUpperCase()', config)).err).toContain('SyntaxError');
});

test('with --void-section a § can be used in the code', async () => {
Expand Down
9 changes: 2 additions & 7 deletions test/engine/void-euro.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,8 @@ describe('--void-euro', () => {
expect(
(await rr('(o)', '€1.toUpperCase()', {pipedData: 'fo', replacementJs: true})).out
).toBe('fO');
await expect(
rr('(o)', '€1.toUpperCase()', {
pipedData: 'fo',
replacementJs: true,
voidEuro: true,
})
).rejects.toThrow(SyntaxError);
const voided = {pipedData: 'fo', replacementJs: true, voidEuro: true};
expect((await rr('(o)', '€1.toUpperCase()', voided)).err).toContain('SyntaxError');
});

test('with --replacement-pipe the piped replacement is never converted', async () => {
Expand Down
6 changes: 4 additions & 2 deletions test/fix/705.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,13 @@ describe('#705 the engine returns a promise that resolves when all files are don
});
});

describe('the promise is rejected when the work can not be done', () => {
describe('the run stops when the work can not be done', () => {
test.each(flows)('like with code that fails with %s', async (name, flags) => {
fs.writeFileSync('my.file', 'foo');
const config = {includeGlob: ['my.file'], replacementJs: true, ...flags};
await expect(rr('o', 'nope.nope', config)).rejects.toThrow(ReferenceError);
const result = await rr('o', 'nope.nope', config);
expect(result.err).toContain('ReferenceError');
expect(result.exit).toBe(3);
expect(fs.readdirSync('.')).toEqual(['my.file']);
});
});
Expand Down
52 changes: 52 additions & 0 deletions test/fix/808.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// https://github.com/mathiasrw/rexreplace/issues/808
// When the code of --js failed, the stack trace showed all the code put in front of the code of the user.
import {describe, test, expect} from 'bun:test';
import fs from 'fs';
import {rr, rrStatus} from '../helper.js';

const failed = (where, message) =>
` ❌ The code of --js failed for ${where}: ${message}\n No further processing.\n\n`;

describe('#808 code of --js that fails', () => {
test('stops the run with only the message', async () => {
expect(await rr('o', 'nope()', {pipedData: 'foo', replacementJs: true})).toEqual({
out: '',
err: failed('the piped data', 'ReferenceError: nope is not defined'),
exit: 3,
});
});

test('tells a syntax error the same way', async () => {
const result = await rr('o', '1 +', {pipedData: 'foo', replacementJs: true});
expect(result.err).toStartWith(' ❌ The code of --js failed for the piped data: SyntaxError');
expect(result.exit).toBe(3);
});

test('tells the file, also when the code runs once per match, and changes nothing', async () => {
fs.writeFileSync('my.file', 'foo');
const config = {includeGlob: ['my.file'], replacementJs: true};
expect((await rr('(o)', 'nope($1)', config)).err).toBe(
failed('my.file', 'ReferenceError: nope is not defined')
);
expect(fs.readFileSync('my.file', 'utf8')).toBe('foo');
});

test('stops the run in the async flow too', async () => {
const names = Array.from({length: 20}, (_, i) => `file${i}.txt`);
names.forEach(name => fs.writeFileSync(name, 'foo'));
const result = await rr('o', 'nope()', {
includeGlob: ['*.txt'],
replacementJs: true,
voidAsync: false,
});
expect(result.err).toContain('ReferenceError: nope is not defined');
expect(result.exit).toBe(3);
});

test('is in the errors of the status report', async () => {
const status = await rrStatus('o', 'nope()', {pipedData: 'foo', replacementJs: true});
expect(status.errors).toEqual([
'The code of --js failed for the piped data: ReferenceError: nope is not defined',
]);
});
});
Loading