Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[kbn/optimizer] extract string diffing logic #127394

Merged
merged 1 commit into from
Mar 9, 2022
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
4 changes: 4 additions & 0 deletions packages/kbn-dev-utils/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,13 @@ RUNTIME_DEPS = [
"@npm//exit-hook",
"@npm//getopts",
"@npm//globby",
"@npm//jest-diff",
"@npm//load-json-file",
"@npm//markdown-it",
"@npm//normalize-path",
"@npm//prettier",
"@npm//rxjs",
"@npm//strip-ansi",
"@npm//sort-package-json",
"@npm//tar",
"@npm//tree-kill",
Expand Down Expand Up @@ -90,8 +92,10 @@ TYPES_DEPS = [
"@npm//execa",
"@npm//exit-hook",
"@npm//getopts",
"@npm//jest-diff",
"@npm//rxjs",
"@npm//sort-package-json",
"@npm//strip-ansi",
"@npm//tree-kill",
]

Expand Down
106 changes: 106 additions & 0 deletions packages/kbn-dev-utils/src/diff_strings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { diffStrings } from './diff_strings';

const json = (x: any) => JSON.stringify(x, null, 2);

describe('diffStrings()', () => {
it('returns undefined if values are equal', () => {
expect(diffStrings('1', '1')).toBe(undefined);
expect(diffStrings(json(['1', '2', { a: 'b' }]), json(['1', '2', { a: 'b' }]))).toBe(undefined);
expect(
diffStrings(
json({
a: '1',
b: '2',
}),
json({
a: '1',
b: '2',
})
)
).toBe(undefined);
});

it('returns a diff if the values are different', () => {
const diff = diffStrings(json(['1', '2', { a: 'b' }]), json(['1', '2', { b: 'a' }]));

expect(diff).toMatchInlineSnapshot(`
"- Expected
+ Received

 [
 \\"1\\",
 \\"2\\",
 {
- \\"a\\": \\"b\\"
+ \\"b\\": \\"a\\"
 }
 ]"
`);

const diff2 = diffStrings(
json({
a: '1',
b: '1',
}),
json({
b: '2',
a: '2',
})
);

expect(diff2).toMatchInlineSnapshot(`
"- Expected
+ Received

 {
- \\"a\\": \\"1\\",
- \\"b\\": \\"1\\"
+ \\"b\\": \\"2\\",
+ \\"a\\": \\"2\\"
 }"
`);
});

it('formats large diffs to focus on the changed lines', () => {
const diff = diffStrings(
json({
a: ['1', '1', '1', '1', '1', '1', '1', '2', '1', '1', '1', '1', '1', '1', '1', '1', '1'],
}),
json({
b: ['1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '2', '1', '1', '1', '1'],
})
);

expect(diff).toMatchInlineSnapshot(`
"- Expected
+ Received

 {
- \\"a\\": [
+ \\"b\\": [
 \\"1\\",
 \\"1\\",
 ...
 \\"1\\",
 \\"1\\",
- \\"2\\",
 \\"1\\",
 \\"1\\",
 ...
 \\"1\\",
 \\"1\\",
+ \\"2\\",
 \\"1\\",
 \\"1\\",
 ..."
`);
});
});
97 changes: 97 additions & 0 deletions packages/kbn-dev-utils/src/diff_strings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import jestDiff from 'jest-diff';
import stripAnsi from 'strip-ansi';
import Chalk from 'chalk';

function reformatJestDiff(diff: string) {
const diffLines = diff.split('\n');

if (
diffLines.length < 4 ||
stripAnsi(diffLines[0]) !== '- Expected' ||
stripAnsi(diffLines[1]) !== '+ Received'
) {
throw new Error(`unexpected diff format: ${diff}`);
}

const outputLines = [diffLines.shift(), diffLines.shift(), diffLines.shift()];

/**
* buffer which contains between 0 and 5 lines from the diff which aren't additions or
* deletions. The first three are the first three lines seen since the buffer was cleared
* and the last two lines are the last two lines seen.
*
* When flushContext() is called we write the first two lines to output, an elipses if there
* are five lines, and then the last two lines.
*
* At the very end we will write the last two lines of context if they're defined
*/
const contextBuffer: string[] = [];

/**
* Convert a line to an empty line with elipses placed where the text on that line starts
*/
const toElipses = (line: string) => {
return stripAnsi(line).replace(/^(\s*).*/, '$1...');
};

while (diffLines.length) {
const line = diffLines.shift()!;
const plainLine = stripAnsi(line);
if (plainLine.startsWith('+ ') || plainLine.startsWith('- ')) {
// write contextBuffer to the outputLines
if (contextBuffer.length) {
outputLines.push(
...contextBuffer.slice(0, 2),
...(contextBuffer.length === 5
? [Chalk.dim(toElipses(contextBuffer[2])), ...contextBuffer.slice(3, 5)]
: contextBuffer.slice(2, 4))
);

contextBuffer.length = 0;
}

// add this line to the outputLines
outputLines.push(line);
} else {
// update the contextBuffer with this line which doesn't represent a change
if (contextBuffer.length === 5) {
contextBuffer[3] = contextBuffer[4];
contextBuffer[4] = line;
} else {
contextBuffer.push(line);
}
}
}

if (contextBuffer.length) {
outputLines.push(
...contextBuffer.slice(0, 2),
...(contextBuffer.length > 2 ? [Chalk.dim(toElipses(contextBuffer[2]))] : [])
);
}

return outputLines.join('\n');
}

/**
* Produces a diff string which is nicely formatted to show the differences between two strings. This will
* be a multi-line string so it's generally a good idea to include a `\n` before this first line of the diff
* if you are concatenating it with another message.
*/
export function diffStrings(expected: string, received: string) {
const diff = jestDiff(expected, received);

if (!diff || stripAnsi(diff) === 'Compared values have no visual difference.') {
return undefined;
}

return reformatJestDiff(diff);
}
1 change: 1 addition & 0 deletions packages/kbn-dev-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ export * from './babel';
export * from './extract';
export * from './vscode_config';
export * from './sort_package_json';
export * from './diff_strings';
2 changes: 0 additions & 2 deletions packages/kbn-optimizer/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ RUNTIME_DEPS = [
"@npm//dedent",
"@npm//del",
"@npm//execa",
"@npm//jest-diff",
"@npm//json-stable-stringify",
"@npm//js-yaml",
"@npm//lmdb-store",
Expand Down Expand Up @@ -76,7 +75,6 @@ TYPES_DEPS = [
"@npm//cpy",
"@npm//del",
"@npm//execa",
"@npm//jest-diff",
"@npm//lmdb-store",
"@npm//pirates",
"@npm//rxjs",
Expand Down
98 changes: 1 addition & 97 deletions packages/kbn-optimizer/src/optimizer/cache_keys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@

import Path from 'path';

import jestDiff from 'jest-diff';
import { REPO_ROOT } from '@kbn/utils';
import { createAbsolutePathSerializer } from '@kbn/dev-utils';

import { reformatJestDiff, getOptimizerCacheKey, diffCacheKey } from './cache_keys';
import { getOptimizerCacheKey } from './cache_keys';
import { OptimizerConfig } from './optimizer_config';

jest.mock('./get_changes.ts', () => ({
Expand Down Expand Up @@ -101,98 +100,3 @@ describe('getOptimizerCacheKey()', () => {
`);
});
});

describe('diffCacheKey()', () => {
it('returns undefined if values are equal', () => {
expect(diffCacheKey('1', '1')).toBe(undefined);
expect(diffCacheKey(1, 1)).toBe(undefined);
expect(diffCacheKey(['1', '2', { a: 'b' }], ['1', '2', { a: 'b' }])).toBe(undefined);
expect(
diffCacheKey(
{
a: '1',
b: '2',
},
{
b: '2',
a: '1',
}
)
).toBe(undefined);
});

it('returns a diff if the values are different', () => {
expect(diffCacheKey(['1', '2', { a: 'b' }], ['1', '2', { b: 'a' }])).toMatchInlineSnapshot(`
"- Expected
+ Received

 [
 \\"1\\",
 \\"2\\",
 {
- \\"a\\": \\"b\\"
+ \\"b\\": \\"a\\"
 }
 ]"
`);
expect(
diffCacheKey(
{
a: '1',
b: '1',
},
{
b: '2',
a: '2',
}
)
).toMatchInlineSnapshot(`
"- Expected
+ Received

 {
- \\"a\\": \\"1\\",
- \\"b\\": \\"1\\"
+ \\"a\\": \\"2\\",
+ \\"b\\": \\"2\\"
 }"
`);
});
});

describe('reformatJestDiff()', () => {
it('reformats large jestDiff output to focus on the changed lines', () => {
const diff = jestDiff(
{
a: ['1', '1', '1', '1', '1', '1', '1', '2', '1', '1', '1', '1', '1', '1', '1', '1', '1'],
},
{
b: ['1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '2', '1', '1', '1', '1'],
}
);

expect(reformatJestDiff(diff)).toMatchInlineSnapshot(`
"- Expected
+ Received

 Object {
- \\"a\\": Array [
+ \\"b\\": Array [
 \\"1\\",
 \\"1\\",
 ...
 \\"1\\",
 \\"1\\",
- \\"2\\",
 \\"1\\",
 \\"1\\",
 ...
 \\"1\\",
 \\"1\\",
+ \\"2\\",
 \\"1\\",
 \\"1\\",
 ..."
`);
});
});
Loading