Skip to content

Commit 7f74e35

Browse files
dylhunnalxhub
authored andcommitted
refactor(language-service): Create helper methods for manipulating object and array ASTs. (#47181)
Create three new helper methods: `addElementToArrayLiteral`, `objectPropertyAssignmentForKey`, and `updateObjectValueForKey`. These methods make interacting with array and object literals easier. These will be useful for the standalone imports feature, which will need to add new terms to import arrays in Component and NgModule decorators. PR Close #47181
1 parent 8e9ab02 commit 7f74e35

File tree

2 files changed

+115
-2
lines changed

2 files changed

+115
-2
lines changed

packages/language-service/src/ts_utils.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,38 @@ export function collectMemberMethods(
9494
}
9595
return members;
9696
}
97+
98+
/**
99+
* Given an existing array literal expression, update it by pushing a new expression.
100+
*/
101+
export function addElementToArrayLiteral(
102+
arr: ts.ArrayLiteralExpression, elem: ts.Expression): ts.ArrayLiteralExpression {
103+
return ts.factory.updateArrayLiteralExpression(arr, [...arr.elements, elem]);
104+
}
105+
106+
/**
107+
* Given an ObjectLiteralExpression node, extract and return the PropertyAssignment corresponding to
108+
* the given key. `null` if no such key exists.
109+
*/
110+
export function objectPropertyAssignmentForKey(
111+
obj: ts.ObjectLiteralExpression, key: string): ts.PropertyAssignment|null {
112+
const matchingProperty = obj.properties.filter(
113+
a => a.name !== undefined && ts.isIdentifier(a.name) && a.name.escapedText === key)[0];
114+
return matchingProperty && ts.isPropertyAssignment(matchingProperty) ? matchingProperty : null;
115+
}
116+
117+
/**
118+
* Given an ObjectLiteralExpression node, create or update the specified key, using the provided
119+
* callback to generate the new value (possibly based on an old value).
120+
*/
121+
export function updateObjectValueForKey(
122+
obj: ts.ObjectLiteralExpression, key: string,
123+
newValueFn: (oldValue?: ts.Expression) => ts.Expression): ts.ObjectLiteralExpression {
124+
const existingProp = objectPropertyAssignmentForKey(obj, key);
125+
const newProp = ts.factory.createPropertyAssignment(
126+
ts.factory.createIdentifier(key), newValueFn(existingProp?.initializer));
127+
return ts.factory.updateObjectLiteralExpression(obj, [
128+
...obj.properties.filter(p => p !== existingProp),
129+
newProp,
130+
]);
131+
}

packages/language-service/test/ts_utils_spec.ts

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
import {initMockFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
1010
import ts from 'typescript';
1111

12-
import {collectMemberMethods, findTightestNode} from '../src/ts_utils';
12+
import {addElementToArrayLiteral, collectMemberMethods, findTightestNode, objectPropertyAssignmentForKey, updateObjectValueForKey} from '../src/ts_utils';
1313
import {LanguageServiceTestEnv, OpenBuffer, Project} from '../testing';
1414

15-
describe('ts utils', () => {
15+
describe('TS util', () => {
1616
describe('collectMemberMethods', () => {
1717
beforeEach(() => {
1818
initMockFileSystem('Native');
@@ -84,4 +84,82 @@ describe('ts utils', () => {
8484
.sort();
8585
}
8686
});
87+
88+
describe('AST method', () => {
89+
let printer: ts.Printer;
90+
let sourceFile: ts.SourceFile;
91+
92+
function print(node: ts.Node): string {
93+
return printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
94+
}
95+
96+
beforeAll(() => {
97+
printer = ts.createPrinter();
98+
sourceFile =
99+
ts.createSourceFile('placeholder.ts', '', ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS);
100+
});
101+
102+
describe('addElementToArrayLiteral', () => {
103+
it('transforms an empty array literal expression', () => {
104+
const oldArr = ts.factory.createArrayLiteralExpression([], false);
105+
const newArr = addElementToArrayLiteral(oldArr, ts.factory.createStringLiteral('a'));
106+
expect(print(newArr)).toEqual('["a"]');
107+
});
108+
109+
it('transforms an existing array literal expression', () => {
110+
const oldArr =
111+
ts.factory.createArrayLiteralExpression([ts.factory.createStringLiteral('a')], false);
112+
const newArr = addElementToArrayLiteral(oldArr, ts.factory.createStringLiteral('b'));
113+
expect(print(newArr)).toEqual('["a", "b"]');
114+
});
115+
});
116+
117+
describe('objectPropertyAssignmentForKey', () => {
118+
let oldObj: ts.ObjectLiteralExpression;
119+
120+
beforeEach(() => {
121+
oldObj = ts.factory.createObjectLiteralExpression(
122+
[ts.factory.createPropertyAssignment(
123+
ts.factory.createIdentifier('foo'), ts.factory.createStringLiteral('bar'))],
124+
false);
125+
});
126+
127+
it('returns null when no property exists', () => {
128+
const prop = objectPropertyAssignmentForKey(oldObj, 'oops');
129+
expect(prop).toBeNull();
130+
});
131+
132+
it('returns the requested property assignment', () => {
133+
const prop = objectPropertyAssignmentForKey(oldObj, 'foo');
134+
expect(print(prop!)).toEqual('foo: "bar"');
135+
});
136+
});
137+
138+
describe('updateObjectValueForKey', () => {
139+
let oldObj: ts.ObjectLiteralExpression;
140+
141+
const valueAppenderFn = (oldValue?: ts.Expression) => {
142+
if (!oldValue) return ts.factory.createStringLiteral('baz');
143+
if (!ts.isStringLiteral(oldValue)) return oldValue;
144+
return ts.factory.createStringLiteral(oldValue.text + 'baz');
145+
};
146+
147+
beforeEach(() => {
148+
oldObj = ts.factory.createObjectLiteralExpression(
149+
[ts.factory.createPropertyAssignment(
150+
ts.factory.createIdentifier('foo'), ts.factory.createStringLiteral('bar'))],
151+
false);
152+
});
153+
154+
it('creates a non-existant property', () => {
155+
const obj = updateObjectValueForKey(oldObj, 'newKey', valueAppenderFn);
156+
expect(print(obj)).toBe('{ foo: "bar", newKey: "baz" }');
157+
});
158+
159+
it('updates an existing property', () => {
160+
const obj = updateObjectValueForKey(oldObj, 'foo', valueAppenderFn);
161+
expect(print(obj)).toBe('{ foo: "barbaz" }');
162+
});
163+
});
164+
});
87165
});

0 commit comments

Comments
 (0)