Skip to content
Open
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
6 changes: 5 additions & 1 deletion lively.lang/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ function print (object, quote = '\"') {
if (object && Array.isArray(object)) { return '[' + object.map(print) + ']'; }
if (typeof object !== 'string') { return String(object); }
let result = String(object);
result = result.replace(/\n/g, '\\n\\\n');
result = result.replace(/\\/g, '\\\\');
result = result.replace(/\r/g, '\\r');
result = result.replace(/\n/g, '\\n');
result = result.replace(/\u2028/g, '\\u2028');
result = result.replace(/\u2029/g, '\\u2029');
result = result.replace(/(")/g, '\\$1');
result = result.replace(/(')/g, '\\$1');
result = quote + result + quote;
Expand Down
6 changes: 1 addition & 5 deletions lively.lang/string.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,7 @@ function print (obj) {
// // => [[1,2,3],"string",[object Object]]
if (obj && Array.isArray(obj)) return '[' + obj.map(print) + ']';
if (typeof obj !== 'string') return String(obj);
let result = String(obj);
result = result.replace(/\n/g, '\\n\\\n');
result = result.replace(/(")/g, '\\$1');
result = '\"' + result + '\"';
return result;
return inspectObject(obj);
}

function printNested (list, depth) {
Expand Down
22 changes: 22 additions & 0 deletions lively.lang/tests/object-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,28 @@ describe('object', function () {
'}');
});

it('escapes backslashes in string literals', function () {
const value = 'path\\backslash\nnext line';
const inspected = inspect({ value }, { quote: "'" });
expect(Function(`return (${inspected})`)().value).to.equal(value); // eslint-disable-line no-new-func
});

for (const [name, value] of [
['carriage returns', 'a\rb'],
['CRLF', 'a\r\nb'],
['Unicode line separators', 'a\u2028b'],
['Unicode paragraph separators', 'a\u2029b']
]) {
it(`escapes ${name} in string literals`, () => {
for (const quote of ["'", '"']) {
const inspected = inspect({ value }, { quote });
expect(inspected, `inspect must escape ${name}`).not.match(/[\r\u2028\u2029]/);
expect(Function(`return (${inspected})`)().value, // eslint-disable-line no-new-func
`inspect must round-trip ${name} with ${quote} quoting`).equals(value);
}
});
}

it('observes maxDepth when printing', function () {
expect(inspect(obj1, { maxDepth: 1 })).to.equal(
'{\n' +
Expand Down
27 changes: 27 additions & 0 deletions lively.lang/tests/string-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,37 @@ import {
md5,
changeIndent,
applyChange,
print,
lineIndexComputer
} from '../string.js';

describe('string', function () {
describe('print', () => {
for (const [name, value] of [
['backslashes', 'C:\\temp\\file.txt'],
['newlines', 'a\nb'],
['carriage returns', 'a\rb'],
['CRLF', 'a\r\nb'],
['Unicode line separators', 'a\u2028b'],
['Unicode paragraph separators', 'a\u2029b'],
['quotes', '"single\' and double"']
]) {
it(`round-trips ${name}`, () => {
const printed = print(value);
expect(printed, `string.print must escape ${name}`).not.match(/[\r\u2028\u2029]/);
expect(Function(`return (${printed})`)(), // eslint-disable-line no-new-func
`string.print must round-trip ${name}`).equals(value);
});
}

it('keeps array recursion and non-string printing', () => {
const values = [1, ['C:\\temp', null, true]];
expect(Function(`return (${print(values)})`)(), // eslint-disable-line no-new-func
'nested array strings must round-trip').deep.equals(values);
expect(print({ foo: 23 }), 'non-string objects keep their existing representation').equals('[object Object]');
});
});

it('tableize', function () {
expect([['a', 'b', 'c'], ['d', 'e', 'f']]).to.eql(tableize('a b c\nd e f'));
expect([['a', 1, 'c'], ['d', 2, 'f']]).to.eql(tableize('a 1 c\nd 2 f'));
Expand Down
9 changes: 6 additions & 3 deletions lively.serializer2/plugins/expression-serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,9 +461,12 @@ function handleTextAndAttributes (aMorph, exported, styleProto, path, masterInSc
// all of the above work is then discarded basically...
if (asExpression && exported.textAndAttributes) {
// properly serialize some of the attributes such as fontColor
exported.textAndAttributes = getArrayExpression('textAndAttributes', aMorph.textAndAttributes.map(attr => {
return typeof attr === 'string' ? attr.replaceAll('\n', '\\n') : attr;
}), path, opts);
exported.textAndAttributes = getArrayExpression(
'textAndAttributes',
aMorph.textAndAttributes,
path,
opts
);
}
}

Expand Down
30 changes: 30 additions & 0 deletions lively.serializer2/tests/text-expression-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* global describe, it */
import { expect } from 'mocha-es6';
import { morph } from 'lively.morphic';
import ExpressionSerializer, { serializeSpec } from '../plugins/expression-serializer.js';

describe('text expression serialization', () => {
for (const text of ['line1\nline2', 'a\r\nb', 'C:\\temp\nnext']) {
it('round-trips line terminators ' + JSON.stringify(text), () => {
const label = morph({ type: 'label', textAndAttributes: [text, { fontWeight: 'bold' }] });
const expression = serializeSpec(label, { asExpression: true });
const restored = new ExpressionSerializer().deserializeExprObj(expression);

expect(restored.textString, 'serializeSpec/deserializeExprObj must preserve ' + JSON.stringify(text))
.equals(text);
expect(restored.textAndAttributes, 'line terminators and formatting must round-trip together')
.deep.equals(label.textAndAttributes);
});
}

it('round-trips a label containing backslashes without pre-escaping its text', () => {
const text = 'C:\\temp\\file.txt';
const label = morph({ type: 'label', textAndAttributes: [text, { fontWeight: 'bold' }] });
const expression = serializeSpec(label, { asExpression: true });
const restored = new ExpressionSerializer().deserializeExprObj(expression);

expect(restored.textString, 'label text must survive expression serialization exactly').equals(text);
expect(restored.textAndAttributes, 'text attributes must survive with the backslash text')
.deep.equals(label.textAndAttributes);
});
});
Loading