UNPKG

8.9 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright Google LLC All Rights Reserved.
5 *
6 * Use of this source code is governed by an MIT-style license that can be
7 * found in the LICENSE file at https://angular.io/license
8 */
9Object.defineProperty(exports, "__esModule", { value: true });
10exports.createVirtualAstObject = exports.unescapeKey = exports.escapeKey = void 0;
11const stableStringify = require('fast-json-stable-stringify');
12function findNode(parent, p) {
13 if (parent.kind === 'object') {
14 const entry = parent.properties.find((entry) => entry.key.value === p);
15 if (entry) {
16 return { node: entry.value, parent: entry };
17 }
18 }
19 else {
20 const index = Number(p);
21 if (!isNaN(index)) {
22 return { node: parent.elements[index], parent };
23 }
24 }
25 return { parent };
26}
27function createPropertyDescriptor(value) {
28 return {
29 configurable: true,
30 enumerable: true,
31 writable: true,
32 value,
33 };
34}
35function escapeKey(key) {
36 if (typeof key === 'number') {
37 return key;
38 }
39 return key.replace('~', '~0').replace('/', '~1');
40}
41exports.escapeKey = escapeKey;
42function unescapeKey(key) {
43 if (typeof key === 'number') {
44 return key;
45 }
46 return key.replace('~1', '/').replace('~0', '~');
47}
48exports.unescapeKey = unescapeKey;
49function createVirtualAstObject(root, options = {}) {
50 const reporter = (path, parent, node, old, current) => {
51 if (options.listener) {
52 if (old === current || stableStringify(old) === stableStringify(current)) {
53 return;
54 }
55 const op = old === undefined ? 'add' : current === undefined ? 'remove' : 'replace';
56 options.listener(op, path, parent, current);
57 }
58 };
59 return create(root, '', reporter, new Set(options.exclude), options.include && options.include.length > 0 ? new Set(options.include) : undefined, options.base);
60}
61exports.createVirtualAstObject = createVirtualAstObject;
62function create(ast, path, reporter, excluded = new Set(), included, base) {
63 const cache = new Map();
64 const alteredNodes = new Set();
65 if (!base) {
66 if (ast.kind === 'object') {
67 base = Object.create(null);
68 }
69 else {
70 base = [];
71 base.length = ast.elements.length;
72 }
73 }
74 return new Proxy(base, {
75 getOwnPropertyDescriptor(target, p) {
76 const descriptor = Reflect.getOwnPropertyDescriptor(target, p);
77 if (descriptor || typeof p === 'symbol') {
78 return descriptor;
79 }
80 else if (excluded.has(p) || (included && !included.has(p))) {
81 return undefined;
82 }
83 const propertyPath = path + '/' + escapeKey(p);
84 const cacheEntry = cache.get(propertyPath);
85 if (cacheEntry) {
86 if (cacheEntry.value !== undefined) {
87 return createPropertyDescriptor(cacheEntry.value);
88 }
89 return undefined;
90 }
91 const { node } = findNode(ast, p);
92 if (node) {
93 return createPropertyDescriptor(node.value);
94 }
95 return undefined;
96 },
97 has(target, p) {
98 if (Reflect.has(target, p)) {
99 return true;
100 }
101 else if (typeof p === 'symbol' || excluded.has(p)) {
102 return false;
103 }
104 return cache.has(path + '/' + escapeKey(p)) || findNode(ast, p) !== undefined;
105 },
106 get(target, p) {
107 if (typeof p === 'symbol' || Reflect.has(target, p)) {
108 return Reflect.get(target, p);
109 }
110 else if (excluded.has(p) || (included && !included.has(p))) {
111 return undefined;
112 }
113 const propertyPath = path + '/' + escapeKey(p);
114 const cacheEntry = cache.get(propertyPath);
115 if (cacheEntry) {
116 return cacheEntry.value;
117 }
118 const { node, parent } = findNode(ast, p);
119 let value;
120 if (node) {
121 if (node.kind === 'object' || node.kind === 'array') {
122 value = create(node, propertyPath, (path, parent, vnode, old, current) => {
123 if (!alteredNodes.has(node)) {
124 reporter(path, parent, vnode, old, current);
125 }
126 });
127 }
128 else {
129 value = node.value;
130 }
131 cache.set(propertyPath, { node, parent, value });
132 }
133 return value;
134 },
135 set(target, p, value) {
136 if (value === undefined) {
137 // setting to undefined is equivalent to a delete
138 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
139 return this.deleteProperty(target, p);
140 }
141 if (typeof p === 'symbol' || Reflect.has(target, p)) {
142 return Reflect.set(target, p, value);
143 }
144 else if (excluded.has(p) || (included && !included.has(p))) {
145 return false;
146 }
147 // TODO: Check if is JSON value
148 const jsonValue = value;
149 const propertyPath = path + '/' + escapeKey(p);
150 const cacheEntry = cache.get(propertyPath);
151 if (cacheEntry) {
152 const oldValue = cacheEntry.value;
153 cacheEntry.value = value;
154 if (cacheEntry.node && oldValue !== value) {
155 alteredNodes.add(cacheEntry.node);
156 }
157 reporter(propertyPath, cacheEntry.parent, cacheEntry.node, oldValue, jsonValue);
158 }
159 else {
160 const { node, parent } = findNode(ast, p);
161 cache.set(propertyPath, { node, parent, value: value });
162 if (node && node.value !== value) {
163 alteredNodes.add(node);
164 }
165 reporter(propertyPath, parent, node, node && node.value, value);
166 }
167 return true;
168 },
169 deleteProperty(target, p) {
170 if (typeof p === 'symbol' || Reflect.has(target, p)) {
171 return Reflect.deleteProperty(target, p);
172 }
173 else if (excluded.has(p) || (included && !included.has(p))) {
174 return false;
175 }
176 const propertyPath = path + '/' + escapeKey(p);
177 const cacheEntry = cache.get(propertyPath);
178 if (cacheEntry) {
179 const oldValue = cacheEntry.value;
180 cacheEntry.value = undefined;
181 if (cacheEntry.node) {
182 alteredNodes.add(cacheEntry.node);
183 }
184 if (cacheEntry.parent.kind === 'keyvalue') {
185 // Remove the entire key/value pair from this JSON object
186 reporter(propertyPath, ast, cacheEntry.node, oldValue, undefined);
187 }
188 else {
189 reporter(propertyPath, cacheEntry.parent, cacheEntry.node, oldValue, undefined);
190 }
191 }
192 else {
193 const { node, parent } = findNode(ast, p);
194 if (node) {
195 cache.set(propertyPath, { node, parent, value: undefined });
196 alteredNodes.add(node);
197 if (parent.kind === 'keyvalue') {
198 // Remove the entire key/value pair from this JSON object
199 reporter(propertyPath, ast, node, node && node.value, undefined);
200 }
201 else {
202 reporter(propertyPath, parent, node, node && node.value, undefined);
203 }
204 }
205 }
206 return true;
207 },
208 defineProperty(target, p, attributes) {
209 if (typeof p === 'symbol') {
210 return Reflect.defineProperty(target, p, attributes);
211 }
212 return false;
213 },
214 ownKeys(target) {
215 let keys;
216 if (ast.kind === 'object') {
217 keys = ast.properties
218 .map((entry) => entry.key.value)
219 .filter((p) => !excluded.has(p) && (!included || included.has(p)));
220 }
221 else {
222 keys = [];
223 }
224 for (const key of cache.keys()) {
225 const relativeKey = key.substr(path.length + 1);
226 if (relativeKey.length > 0 && !relativeKey.includes('/')) {
227 keys.push(`${unescapeKey(relativeKey)}`);
228 }
229 }
230 return [...new Set([...keys, ...Reflect.ownKeys(target)])];
231 },
232 });
233}