Skip to content

Commit

Permalink
getBlockStringIndentation: simplify implementation (graphql#2748)
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanGoncharov authored Aug 15, 2020
1 parent 6e0bc20 commit b83a9fe
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 51 deletions.
29 changes: 14 additions & 15 deletions src/language/__tests__/blockString-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,33 +96,32 @@ describe('dedentBlockStringValue', () => {
});

describe('getBlockStringIndentation', () => {
it('returns zero for an empty array', () => {
expect(getBlockStringIndentation([])).to.equal(0);
it('returns zero for an empty string', () => {
expect(getBlockStringIndentation('')).to.equal(0);
});

it('do not take first line into account', () => {
expect(getBlockStringIndentation([' a'])).to.equal(0);
expect(getBlockStringIndentation([' a', ' b'])).to.equal(2);
expect(getBlockStringIndentation(' a')).to.equal(0);
expect(getBlockStringIndentation(' a\n b')).to.equal(2);
});

it('returns minimal indentation length', () => {
expect(getBlockStringIndentation(['', ' a', ' b'])).to.equal(1);
expect(getBlockStringIndentation(['', ' a', ' b'])).to.equal(1);
expect(getBlockStringIndentation(['', ' a', ' b', 'c'])).to.equal(0);
expect(getBlockStringIndentation('\n a\n b')).to.equal(1);
expect(getBlockStringIndentation('\n a\n b')).to.equal(1);
expect(getBlockStringIndentation('\n a\n b\nc')).to.equal(0);
});

it('count both tab and space as single character', () => {
expect(getBlockStringIndentation(['', '\ta', ' b'])).to.equal(1);
expect(getBlockStringIndentation(['', '\t a', ' b'])).to.equal(2);
expect(getBlockStringIndentation(['', ' \t a', ' b'])).to.equal(3);
expect(getBlockStringIndentation('\n\ta\n b')).to.equal(1);
expect(getBlockStringIndentation('\n\t a\n b')).to.equal(2);
expect(getBlockStringIndentation('\n \t a\n b')).to.equal(3);
});

it('do not take empty lines into account', () => {
expect(getBlockStringIndentation(['a', '\t'])).to.equal(0);
expect(getBlockStringIndentation(['a', ' '])).to.equal(0);
expect(getBlockStringIndentation(['a', ' ', ' b'])).to.equal(2);
expect(getBlockStringIndentation(['a', ' ', ' b'])).to.equal(2);
expect(getBlockStringIndentation(['a', '', ' b'])).to.equal(1);
expect(getBlockStringIndentation('a\n ')).to.equal(0);
expect(getBlockStringIndentation('a\n\t')).to.equal(0);
expect(getBlockStringIndentation('a\n\n b')).to.equal(1);
expect(getBlockStringIndentation('a\n \n b')).to.equal(2);
});
});

Expand Down
2 changes: 1 addition & 1 deletion src/language/blockString.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function dedentBlockStringValue(rawString: string): string;
/**
* @internal
*/
export function getBlockStringIndentation(lines: ReadonlyArray<string>): number;
export function getBlockStringIndentation(body: string): number;

/**
* Print a block string in the indented block form by adding a leading and
Expand Down
82 changes: 49 additions & 33 deletions src/language/blockString.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function dedentBlockStringValue(rawString: string): string {
const lines = rawString.split(/\r\n|[\n\r]/g);

// Remove common indentation from all lines but first.
const commonIndent = getBlockStringIndentation(lines);
const commonIndent = getBlockStringIndentation(rawString);

if (commonIndent !== 0) {
for (let i = 1; i < lines.length; i++) {
Expand All @@ -20,52 +20,68 @@ export function dedentBlockStringValue(rawString: string): string {
}

// Remove leading and trailing blank lines.
while (lines.length > 0 && isBlank(lines[0])) {
lines.shift();
let startLine = 0;
while (startLine < lines.length && isBlank(lines[startLine])) {
++startLine;
}
while (lines.length > 0 && isBlank(lines[lines.length - 1])) {
lines.pop();

let endLine = lines.length;
while (endLine > startLine && isBlank(lines[endLine - 1])) {
--endLine;
}

// Return a string of the lines joined with U+000A.
return lines.join('\n');
return lines.slice(startLine, endLine).join('\n');
}

function isBlank(str) {
for (let i = 0; i < str.length; ++i) {
if (str[i] !== ' ' && str[i] !== '\t') {
return false;
}
}

return true;
}

/**
* @internal
*/
export function getBlockStringIndentation(
lines: $ReadOnlyArray<string>,
): number {
export function getBlockStringIndentation(value: string): number {
let isFirstLine = true;
let isEmptyLine = true;
let indent = 0;
let commonIndent = null;

for (let i = 1; i < lines.length; i++) {
const line = lines[i];
const indent = leadingWhitespace(line);
if (indent === line.length) {
continue; // skip empty lines
}

if (commonIndent === null || indent < commonIndent) {
commonIndent = indent;
if (commonIndent === 0) {
for (let i = 0; i < value.length; ++i) {
switch (value.charCodeAt(i)) {
case 13: // \r
if (value.charCodeAt(i + 1) === 10) {
++i; // skip \r\n as one symbol
}
// falls through
case 10: // \n
isFirstLine = false;
isEmptyLine = true;
indent = 0;
break;
case 9: // \t
case 32: // <space>
++indent;
break;
}
default:
if (
isEmptyLine &&
!isFirstLine &&
(commonIndent === null || indent < commonIndent)
) {
commonIndent = indent;
}
isEmptyLine = false;
}
}

return commonIndent === null ? 0 : commonIndent;
}

function leadingWhitespace(str) {
let i = 0;
while (i < str.length && (str[i] === ' ' || str[i] === '\t')) {
i++;
}
return i;
}

function isBlank(str) {
return leadingWhitespace(str) === str.length;
return commonIndent ?? 0;
}

/**
Expand Down
3 changes: 1 addition & 2 deletions src/utilities/stripIgnoredCharacters.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ function dedentBlockString(blockStr) {
const rawStr = blockStr.slice(3, -3);
let body = dedentBlockStringValue(rawStr);

const lines = body.split(/\r\n|[\n\r]/g);
if (getBlockStringIndentation(lines) > 0) {
if (getBlockStringIndentation(body) > 0) {
body = '\n' + body;
}

Expand Down

0 comments on commit b83a9fe

Please sign in to comment.