diff --git a/packages/schematics/angular/utility/json-utils.ts b/packages/schematics/angular/utility/json-utils.ts deleted file mode 100644 index cf1754a9a4..0000000000 --- a/packages/schematics/angular/utility/json-utils.ts +++ /dev/null @@ -1,231 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { - JsonAstArray, - JsonAstKeyValue, - JsonAstNode, - JsonAstObject, - JsonValue, -} from '@angular-devkit/core'; -import { UpdateRecorder } from '@angular-devkit/schematics'; - -export function appendPropertyInAstObject( - recorder: UpdateRecorder, - node: JsonAstObject, - propertyName: string, - value: JsonValue, - indent: number, -) { - const indentStr = _buildIndent(indent); - let index = node.start.offset + 1; - if (node.properties.length > 0) { - // Insert comma. - const last = node.properties[node.properties.length - 1]; - const { text, end } = last; - const commaIndex = text.endsWith('\n') ? end.offset - 1 : end.offset; - recorder.insertRight(commaIndex, ','); - index = end.offset; - } - const content = _stringifyContent(value, indentStr); - recorder.insertRight( - index, - (node.properties.length === 0 && indent ? '\n' : '') - + ' '.repeat(indent) - + `"${propertyName}":${indent ? ' ' : ''}${content}` - + indentStr.slice(0, -indent), - ); -} - -export function insertPropertyInAstObjectInOrder( - recorder: UpdateRecorder, - node: JsonAstObject, - propertyName: string, - value: JsonValue, - indent: number, -) { - - if (node.properties.length === 0) { - appendPropertyInAstObject(recorder, node, propertyName, value, indent); - - return; - } - - // Find insertion info. - let insertAfterProp: JsonAstKeyValue | null = null; - let prev: JsonAstKeyValue | null = null; - let isLastProp = false; - const last = node.properties[node.properties.length - 1]; - for (const prop of node.properties) { - if (prop.key.value > propertyName) { - if (prev) { - insertAfterProp = prev; - } - break; - } - if (prop === last) { - isLastProp = true; - insertAfterProp = last; - } - prev = prop; - } - - if (isLastProp) { - appendPropertyInAstObject(recorder, node, propertyName, value, indent); - - return; - } - - const indentStr = _buildIndent(indent); - const insertIndex = insertAfterProp === null - ? node.start.offset + 1 - : insertAfterProp.end.offset + 1; - const content = _stringifyContent(value, indentStr); - recorder.insertRight( - insertIndex, - indentStr - + `"${propertyName}":${indent ? ' ' : ''}${content}` - + ',', - ); -} - -export function removePropertyInAstObject( - recorder: UpdateRecorder, - node: JsonAstObject, - propertyName: string, -) { - // Find the property inside the object. - const propIdx = node.properties.findIndex(prop => prop.key.value === propertyName); - - if (propIdx === -1) { - // There's nothing to remove. - return; - } - - if (node.properties.length === 1) { - // This is a special case. Everything should be removed, including indentation. - recorder.remove(node.start.offset, node.end.offset - node.start.offset); - recorder.insertRight(node.start.offset, '{}'); - - return; - } - - // The AST considers commas and indentation to be part of the preceding property. - // To get around messy comma and identation management, we can work over the range between - // two properties instead. - const previousProp = node.properties[propIdx - 1]; - const targetProp = node.properties[propIdx]; - const nextProp = node.properties[propIdx + 1]; - - let start, end; - if (previousProp) { - // Given the object below, and intending to remove the `m` property: - // "{\n \"a\": \"a\",\n \"m\": \"m\",\n \"z\": \"z\"\n}" - // ^---------------^ - // Removing the range above results in: - // "{\n \"a\": \"a\",\n \"z\": \"z\"\n}" - start = previousProp.end; - end = targetProp.end; - } else { - // If there's no previousProp there is a nextProp, since we've specialcased the 1 length case. - // Given the object below, and intending to remove the `a` property: - // "{\n \"a\": \"a\",\n \"m\": \"m\",\n \"z\": \"z\"\n}" - // ^---------------^ - // Removing the range above results in: - // "{\n \"m\": \"m\",\n \"z\": \"z\"\n}" - start = targetProp.start; - end = nextProp.start; - } - - recorder.remove(start.offset, end.offset - start.offset); - if (!nextProp) { - recorder.insertRight(start.offset, '\n'); - } -} - - -export function appendValueInAstArray( - recorder: UpdateRecorder, - node: JsonAstArray, - value: JsonValue, - indent = 4, -) { - let indentStr = _buildIndent(indent); - let index = node.start.offset + 1; - // tslint:disable-next-line: no-any - let newNodes: any[] | undefined; - - if (node.elements.length > 0) { - // Insert comma. - const { end } = node.elements[node.elements.length - 1]; - const isClosingOnSameLine = node.end.offset - end.offset === 1; - - if (isClosingOnSameLine && indent) { - // Reformat the entire array - recorder.remove(node.start.offset, node.end.offset - node.start.offset); - newNodes = [ - ...node.elements.map(({ value }) => value), - value, - ]; - index = node.start.offset; - // In case we are generating the entire node we need to reduce the spacing as - // otherwise we'd end up having incorrect double spacing - indent = indent - 2; - indentStr = _buildIndent(indent); - } else { - recorder.insertRight(end.offset, ','); - index = end.offset; - } - } - - recorder.insertRight( - index, - (newNodes ? '' : indentStr) - + _stringifyContent(newNodes || value, indentStr) - + (node.elements.length === 0 && indent ? indentStr.substr(0, -indent) + '\n' : ''), - ); -} - - -export function findPropertyInAstObject( - node: JsonAstObject, - propertyName: string, -): JsonAstNode | null { - let maybeNode: JsonAstNode | null = null; - for (const property of node.properties) { - if (property.key.value == propertyName) { - maybeNode = property.value; - } - } - - return maybeNode; -} - -function _buildIndent(count: number): string { - return count ? '\n' + ' '.repeat(count) : ''; -} - -function _stringifyContent(value: JsonValue, indentStr: string): string { - // TODO: Add snapshot tests - - // The 'space' value is 2, because we want to add 2 additional - // indents from the 'key' node. - - // If we use the indent provided we will have double indents: - // "budgets": [ - // { - // "type": "initial", - // "maximumWarning": "2mb", - // "maximumError": "5mb" - // }, - // { - // "type": "anyComponentStyle", - // 'maximumWarning": "5kb" - // } - // ] - return JSON.stringify(value, null, 2).replace(/\n/g, indentStr); -} diff --git a/packages/schematics/angular/utility/json-utils_spec.ts b/packages/schematics/angular/utility/json-utils_spec.ts deleted file mode 100644 index 400e4e1dd3..0000000000 --- a/packages/schematics/angular/utility/json-utils_spec.ts +++ /dev/null @@ -1,234 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ -import { JsonAstArray, JsonAstObject, JsonValue, parseJsonAst } from '@angular-devkit/core'; -import { HostTree, UpdateRecorder } from '@angular-devkit/schematics'; -import { UnitTestTree } from '@angular-devkit/schematics/testing'; -import { - appendPropertyInAstObject, - appendValueInAstArray, - insertPropertyInAstObjectInOrder, - removePropertyInAstObject, -} from './json-utils'; - -// tslint:disable-next-line: no-big-function -describe('json-utils', () => { - const a = 'a'; - const b = 'b'; - const m = 'm'; - const z = 'z'; - const filePath = '/temp'; - let tree: UnitTestTree; - - function runTest(testFn: Function, obj: JsonValue, indent: number): string { - const content = JSON.stringify(obj, null, indent); - tree.create(filePath, content); - const ast = parseJsonAst(content); - const rec = tree.beginUpdate(filePath); - testFn(rec, ast); - tree.commitUpdate(rec); - - const result = tree.readContent(filePath); - // Clean up the tree by deleting the file. - tree.delete(filePath); - - return result; - } - - beforeEach(() => { - tree = new UnitTestTree(new HostTree()); - }); - - describe('appendPropertyInAstObject', () => { - it('should insert multiple props with different indentations', () => { - const cases: Array<[{}, string, {}, number]> = [ - // initial | prop | want | indent - [{}, z, {z}, 0], - [{z}, m, {z, m}, 0], - [{m, z}, a, {m, z, a}, 0], - [{a, m, z}, b, {a, m, z, b}, 0], - [{}, z, {z}, 2], - [{z}, m, {z, m}, 2], - [{m, z}, a, {m, z, a}, 2], - [{a, m, z}, b, {a, m, z, b}, 2], - [{}, z, {z}, 4], - [{z}, m, {z, m}, 4], - [{m, z}, a, {m, z, a}, 4], - [{a, m, z}, b, {a, m, z, b}, 4], - ]; - for (const c of cases) { - const [initial, prop, want, indent] = c; - const got = runTest((rec: UpdateRecorder, ast: JsonAstObject) => { - expect(ast.kind).toBe('object'); - appendPropertyInAstObject(rec, ast, prop, prop, indent); - }, initial, indent); - expect(got).toBe(JSON.stringify(want, null, indent)); - expect(JSON.parse(got)).toEqual(want); - } - }); - }); - - describe('insertPropertyInAstObjectInOrder', () => { - it('should insert a first prop', () => { - const indent = 2; - const result = runTest((rec: UpdateRecorder, ast: JsonAstObject) => { - expect(ast.kind).toBe('object'); - insertPropertyInAstObjectInOrder(rec, ast, a, a, indent); - }, {m, z}, indent); - expect(result).toBe(JSON.stringify({a, m, z}, null, indent)); - expect(JSON.parse(result)).toEqual({a, m, z}); - }); - - it('should insert a middle prop', () => { - const indent = 2; - const result = runTest((rec: UpdateRecorder, ast: JsonAstObject) => { - expect(ast.kind).toBe('object'); - insertPropertyInAstObjectInOrder(rec, ast, m, m, indent); - }, {a, z}, indent); - expect(result).toBe(JSON.stringify({a, m, z}, null, indent)); - expect(JSON.parse(result)).toEqual({a, m, z}); - }); - - it('should insert a last prop', () => { - const indent = 2; - const result = runTest((rec: UpdateRecorder, ast: JsonAstObject) => { - expect(ast.kind).toBe('object'); - insertPropertyInAstObjectInOrder(rec, ast, z, z, indent); - }, {a, m}, indent); - expect(result).toBe(JSON.stringify({a, m, z}, null, indent)); - expect(JSON.parse(result)).toEqual({a, m, z}); - }); - - it('should insert multiple props with different indentations', () => { - const cases: Array<[{}, string, {}, number]> = [ - // initial | prop | want | indent - [{}, z, {z}, 0], - [{z}, m, {m, z}, 0], - [{m, z}, a, {a, m, z}, 0], - [{a, m, z}, b, {a, b, m, z}, 0], - [{}, z, {z}, 2], - [{z}, m, {m, z}, 2], - [{m, z}, a, {a, m, z}, 2], - [{a, m, z}, b, {a, b, m, z}, 2], - [{}, z, {z}, 4], - [{z}, m, {m, z}, 4], - [{m, z}, a, {a, m, z}, 4], - [{a, m, z}, b, {a, b, m, z}, 4], - ]; - for (const c of cases) { - const [initial, prop, want, indent] = c; - const got = runTest((rec: UpdateRecorder, ast: JsonAstObject) => { - expect(ast.kind).toBe('object'); - insertPropertyInAstObjectInOrder(rec, ast, prop, prop, indent); - }, initial, indent); - expect(got).toBe(JSON.stringify(want, null, indent)); - expect(JSON.parse(got)).toEqual(want); - } - }); - }); - - describe('removePropertyInAstObject', () => { - it('should remove a first prop', () => { - const indent = 2; - const result = runTest((rec: UpdateRecorder, ast: JsonAstObject) => { - expect(ast.kind).toBe('object'); - removePropertyInAstObject(rec, ast, a); - }, {a, m, z}, indent); - expect(result).toBe(JSON.stringify({m, z}, null, indent)); - expect(JSON.parse(result)).toEqual({m, z}); - }); - - it('should remove a middle prop', () => { - const indent = 2; - const result = runTest((rec: UpdateRecorder, ast: JsonAstObject) => { - expect(ast.kind).toBe('object'); - removePropertyInAstObject(rec, ast, m); - }, {a, m, z}, indent); - expect(result).toBe(JSON.stringify({a, z}, null, indent)); - expect(JSON.parse(result)).toEqual({a, z}); - }); - - it('should remove a last prop', () => { - const indent = 2; - const result = runTest((rec: UpdateRecorder, ast: JsonAstObject) => { - expect(ast.kind).toBe('object'); - removePropertyInAstObject(rec, ast, z); - }, {a, m, z}, indent); - expect(result).toBe(JSON.stringify({a, m}, null, indent)); - expect(JSON.parse(result)).toEqual({a, m}); - }); - - it('should remove only prop', () => { - const indent = 2; - const result = runTest((rec: UpdateRecorder, ast: JsonAstObject) => { - expect(ast.kind).toBe('object'); - removePropertyInAstObject(rec, ast, a); - }, {a}, indent); - expect(result).toBe(JSON.stringify({}, null, indent)); - expect(JSON.parse(result)).toEqual({}); - }); - }); - - describe('appendValueInAstArray', () => { - it('should insert multiple props with different indentations', () => { - const cases: Array<[string[], string, {}, number]> = [ - // initial | value | want | indent - [[], z, [z], 0], - [[z], m, [z, m], 0], - [[m, z], a, [m, z, a], 0], - [[a, m, z], b, [a, m, z, b], 0], - [[], z, [z], 2], - [[z], m, [z, m], 2], - [[m, z], a, [m, z, a], 2], - [[a, m, z], b, [a, m, z, b], 2], - [[], z, [z], 4], - [[z], m, [z, m], 4], - [[m, z], a, [m, z, a], 4], - [[a, m, z], b, [a, m, z, b], 4], - ]; - for (const c of cases) { - const [initial, value, want, indent] = c; - const got = runTest((rec: UpdateRecorder, ast: JsonAstArray) => { - expect(ast.kind).toBe('array'); - appendValueInAstArray(rec, ast, value, indent); - }, initial, indent); - expect(got).toBe(JSON.stringify(want, null, indent)); - expect(JSON.parse(got)).toEqual(want); - } - }); - - it('should insert multiple props with different indentations in Object literal', () => { - const cases: Array<[string[], string, {}, number]> = [ - // initial | value | want | indent - [[], z, [z], 0], - [[z], m, [z, m], 0], - [[m, z], a, [m, z, a], 0], - [[a, m, z], b, [a, m, z, b], 0], - // todo: investigate how to do this this of addition with the correct formatting - // [[], z, [z], 2], - [[z], m, [z, m], 2], - [[m, z], a, [m, z, a], 2], - [[a, m, z], b, [a, m, z, b], 2], - // todo: investigate how to do this this of addition with the correct formatting - // [[], z, [z], 4], - [[z], m, [z, m], 4], - [[m, z], a, [m, z, a], 4], - [[a, m, z], b, [a, m, z, b], 4], - ]; - for (const c of cases) { - const [initial, value, want, indent] = c; - const got = runTest((rec: UpdateRecorder, ast: JsonAstObject) => { - expect(ast.properties[0].value.kind).toBe('array'); - appendValueInAstArray(rec, ast.properties[0].value as unknown as JsonAstArray, value, indent * 2); - }, { data: initial }, indent); - const wantData = { data: want }; - expect(got).toBe(JSON.stringify(wantData, null, indent)); - expect(JSON.parse(got)).toEqual(wantData); - } - }); - }); -});