1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | let $deferred;
|
8 | function define(modules, callback) {
|
9 | $deferred = {modules, callback};
|
10 | }
|
11 | module.exports = function(provided) {
|
12 | const ts = provided['typescript'];
|
13 | if (!ts) {
|
14 | throw new Error('Caller does not provide typescript module');
|
15 | }
|
16 | const results = {};
|
17 | const resolvedModules = $deferred.modules.map(m => {
|
18 | if (m === 'exports') {
|
19 | return results;
|
20 | }
|
21 | if (m === 'typescript' || m === 'typescript/lib/tsserverlibrary') {
|
22 | return ts;
|
23 | }
|
24 | return require(m);
|
25 | });
|
26 | $deferred.callback(...resolvedModules);
|
27 | return results;
|
28 | };
|
29 |
|
30 | define(['exports', 'typescript/lib/tsserverlibrary', 'os', 'typescript', 'fs', 'constants', 'stream', 'util', 'assert', 'path'], function (exports, ts, os, ts$1, fs$2, constants, stream, util, assert, path) { 'use strict';
|
31 |
|
32 | var os__default = 'default' in os ? os['default'] : os;
|
33 | var fs$2__default = 'default' in fs$2 ? fs$2['default'] : fs$2;
|
34 | constants = constants && Object.prototype.hasOwnProperty.call(constants, 'default') ? constants['default'] : constants;
|
35 | stream = stream && Object.prototype.hasOwnProperty.call(stream, 'default') ? stream['default'] : stream;
|
36 | util = util && Object.prototype.hasOwnProperty.call(util, 'default') ? util['default'] : util;
|
37 | assert = assert && Object.prototype.hasOwnProperty.call(assert, 'default') ? assert['default'] : assert;
|
38 | var path__default = 'default' in path ? path['default'] : path;
|
39 |
|
40 | |
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 | class InvalidFileSystem {
|
49 | exists(path) {
|
50 | throw makeError();
|
51 | }
|
52 | readFile(path) {
|
53 | throw makeError();
|
54 | }
|
55 | readFileBuffer(path) {
|
56 | throw makeError();
|
57 | }
|
58 | writeFile(path, data, exclusive) {
|
59 | throw makeError();
|
60 | }
|
61 | removeFile(path) {
|
62 | throw makeError();
|
63 | }
|
64 | symlink(target, path) {
|
65 | throw makeError();
|
66 | }
|
67 | readdir(path) {
|
68 | throw makeError();
|
69 | }
|
70 | lstat(path) {
|
71 | throw makeError();
|
72 | }
|
73 | stat(path) {
|
74 | throw makeError();
|
75 | }
|
76 | pwd() {
|
77 | throw makeError();
|
78 | }
|
79 | chdir(path) {
|
80 | throw makeError();
|
81 | }
|
82 | extname(path) {
|
83 | throw makeError();
|
84 | }
|
85 | copyFile(from, to) {
|
86 | throw makeError();
|
87 | }
|
88 | moveFile(from, to) {
|
89 | throw makeError();
|
90 | }
|
91 | ensureDir(path) {
|
92 | throw makeError();
|
93 | }
|
94 | removeDeep(path) {
|
95 | throw makeError();
|
96 | }
|
97 | isCaseSensitive() {
|
98 | throw makeError();
|
99 | }
|
100 | resolve(...paths) {
|
101 | throw makeError();
|
102 | }
|
103 | dirname(file) {
|
104 | throw makeError();
|
105 | }
|
106 | join(basePath, ...paths) {
|
107 | throw makeError();
|
108 | }
|
109 | isRoot(path) {
|
110 | throw makeError();
|
111 | }
|
112 | isRooted(path) {
|
113 | throw makeError();
|
114 | }
|
115 | relative(from, to) {
|
116 | throw makeError();
|
117 | }
|
118 | basename(filePath, extension) {
|
119 | throw makeError();
|
120 | }
|
121 | realpath(filePath) {
|
122 | throw makeError();
|
123 | }
|
124 | getDefaultLibLocation() {
|
125 | throw makeError();
|
126 | }
|
127 | normalize(path) {
|
128 | throw makeError();
|
129 | }
|
130 | }
|
131 | function makeError() {
|
132 | return new Error('FileSystem has not been configured. Please call `setFileSystem()` before calling this method.');
|
133 | }
|
134 |
|
135 | const TS_DTS_JS_EXTENSION = /(?:\.d)?\.ts$|\.js$/;
|
136 | |
137 |
|
138 |
|
139 | function stripExtension(path) {
|
140 | return path.replace(TS_DTS_JS_EXTENSION, '');
|
141 | }
|
142 | function getSourceFileOrError(program, fileName) {
|
143 | const sf = program.getSourceFile(fileName);
|
144 | if (sf === undefined) {
|
145 | throw new Error(`Program does not contain "${fileName}" - available files are ${program.getSourceFiles().map(sf => sf.fileName).join(', ')}`);
|
146 | }
|
147 | return sf;
|
148 | }
|
149 |
|
150 | let fs = new InvalidFileSystem();
|
151 | function getFileSystem() {
|
152 | return fs;
|
153 | }
|
154 | function setFileSystem(fileSystem) {
|
155 | fs = fileSystem;
|
156 | }
|
157 | |
158 |
|
159 |
|
160 | function absoluteFrom(path) {
|
161 | if (!fs.isRooted(path)) {
|
162 | throw new Error(`Internal Error: absoluteFrom(${path}): path is not absolute`);
|
163 | }
|
164 | return fs.resolve(path);
|
165 | }
|
166 | |
167 |
|
168 |
|
169 | function absoluteFromSourceFile(sf) {
|
170 | return fs.resolve(sf.fileName);
|
171 | }
|
172 | |
173 |
|
174 |
|
175 | function dirname(file) {
|
176 | return fs.dirname(file);
|
177 | }
|
178 | |
179 |
|
180 |
|
181 | function join(basePath, ...paths) {
|
182 | return fs.join(basePath, ...paths);
|
183 | }
|
184 | |
185 |
|
186 |
|
187 | function resolve(basePath, ...paths) {
|
188 | return fs.resolve(basePath, ...paths);
|
189 | }
|
190 | |
191 |
|
192 |
|
193 | function isRooted(path) {
|
194 | return fs.isRooted(path);
|
195 | }
|
196 | |
197 |
|
198 |
|
199 | function relative(from, to) {
|
200 | return fs.relative(from, to);
|
201 | }
|
202 | |
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 | function isLocalRelativePath(relativePath) {
|
209 | return !isRooted(relativePath) && !relativePath.startsWith('..');
|
210 | }
|
211 | |
212 |
|
213 |
|
214 |
|
215 |
|
216 | function toRelativeImport(relativePath) {
|
217 | return isLocalRelativePath(relativePath) ? `./${relativePath}` : relativePath;
|
218 | }
|
219 |
|
220 | const LogicalProjectPath = {
|
221 | |
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 | relativePathBetween: function (from, to) {
|
228 | const relativePath = relative(dirname(resolve(from)), resolve(to));
|
229 | return toRelativeImport(relativePath);
|
230 | },
|
231 | };
|
232 | |
233 |
|
234 |
|
235 |
|
236 | class LogicalFileSystem {
|
237 | constructor(rootDirs, compilerHost) {
|
238 | this.compilerHost = compilerHost;
|
239 | |
240 |
|
241 |
|
242 |
|
243 | this.cache = new Map();
|
244 |
|
245 |
|
246 | this.rootDirs = rootDirs.concat([]).sort((a, b) => b.length - a.length);
|
247 | this.canonicalRootDirs =
|
248 | this.rootDirs.map(dir => this.compilerHost.getCanonicalFileName(dir));
|
249 | }
|
250 | |
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 | logicalPathOfSf(sf) {
|
257 | return this.logicalPathOfFile(absoluteFrom(sf.fileName));
|
258 | }
|
259 | |
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 | logicalPathOfFile(physicalFile) {
|
266 | const canonicalFilePath = this.compilerHost.getCanonicalFileName(physicalFile);
|
267 | if (!this.cache.has(canonicalFilePath)) {
|
268 | let logicalFile = null;
|
269 | for (let i = 0; i < this.rootDirs.length; i++) {
|
270 | const rootDir = this.rootDirs[i];
|
271 | const canonicalRootDir = this.canonicalRootDirs[i];
|
272 | if (isWithinBasePath(canonicalRootDir, canonicalFilePath)) {
|
273 |
|
274 |
|
275 | logicalFile = this.createLogicalProjectPath(physicalFile, rootDir);
|
276 |
|
277 | if (logicalFile.indexOf('/node_modules/') !== -1) {
|
278 | logicalFile = null;
|
279 | }
|
280 | else {
|
281 | break;
|
282 | }
|
283 | }
|
284 | }
|
285 | this.cache.set(canonicalFilePath, logicalFile);
|
286 | }
|
287 | return this.cache.get(canonicalFilePath);
|
288 | }
|
289 | createLogicalProjectPath(file, rootDir) {
|
290 | const logicalPath = stripExtension(file.substr(rootDir.length));
|
291 | return (logicalPath.startsWith('/') ? logicalPath : '/' + logicalPath);
|
292 | }
|
293 | }
|
294 | |
295 |
|
296 |
|
297 |
|
298 | function isWithinBasePath(base, path) {
|
299 | return isLocalRelativePath(relative(base, path));
|
300 | }
|
301 |
|
302 |
|
303 | function assign () {
|
304 | const args = [].slice.call(arguments).filter(i => i);
|
305 | const dest = args.shift();
|
306 | args.forEach(src => {
|
307 | Object.keys(src).forEach(key => {
|
308 | dest[key] = src[key];
|
309 | });
|
310 | });
|
311 |
|
312 | return dest
|
313 | }
|
314 |
|
315 | var assign_1 = assign;
|
316 |
|
317 | function createCommonjsModule(fn, module) {
|
318 | return module = { exports: {} }, fn(module, module.exports), module.exports;
|
319 | }
|
320 |
|
321 | var fromCallback = function (fn) {
|
322 | return Object.defineProperty(function () {
|
323 | if (typeof arguments[arguments.length - 1] === 'function') fn.apply(this, arguments);
|
324 | else {
|
325 | return new Promise((resolve, reject) => {
|
326 | arguments[arguments.length] = (err, res) => {
|
327 | if (err) return reject(err)
|
328 | resolve(res);
|
329 | };
|
330 | arguments.length++;
|
331 | fn.apply(this, arguments);
|
332 | })
|
333 | }
|
334 | }, 'name', { value: fn.name })
|
335 | };
|
336 |
|
337 | var fromPromise = function (fn) {
|
338 | return Object.defineProperty(function () {
|
339 | const cb = arguments[arguments.length - 1];
|
340 | if (typeof cb !== 'function') return fn.apply(this, arguments)
|
341 | else fn.apply(this, arguments).then(r => cb(null, r), cb);
|
342 | }, 'name', { value: fn.name })
|
343 | };
|
344 |
|
345 | var universalify = {
|
346 | fromCallback: fromCallback,
|
347 | fromPromise: fromPromise
|
348 | };
|
349 |
|
350 | var origCwd = process.cwd;
|
351 | var cwd = null;
|
352 |
|
353 | var platform = process.env.GRACEFUL_FS_PLATFORM || process.platform;
|
354 |
|
355 | process.cwd = function() {
|
356 | if (!cwd)
|
357 | cwd = origCwd.call(process);
|
358 | return cwd
|
359 | };
|
360 | try {
|
361 | process.cwd();
|
362 | } catch (er) {}
|
363 |
|
364 | var chdir = process.chdir;
|
365 | process.chdir = function(d) {
|
366 | cwd = null;
|
367 | chdir.call(process, d);
|
368 | };
|
369 |
|
370 | var polyfills = patch;
|
371 |
|
372 | function patch (fs) {
|
373 |
|
374 |
|
375 |
|
376 |
|
377 | if (constants.hasOwnProperty('O_SYMLINK') &&
|
378 | process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
|
379 | patchLchmod(fs);
|
380 | }
|
381 |
|
382 |
|
383 | if (!fs.lutimes) {
|
384 | patchLutimes(fs);
|
385 | }
|
386 |
|
387 |
|
388 |
|
389 |
|
390 |
|
391 |
|
392 | fs.chown = chownFix(fs.chown);
|
393 | fs.fchown = chownFix(fs.fchown);
|
394 | fs.lchown = chownFix(fs.lchown);
|
395 |
|
396 | fs.chmod = chmodFix(fs.chmod);
|
397 | fs.fchmod = chmodFix(fs.fchmod);
|
398 | fs.lchmod = chmodFix(fs.lchmod);
|
399 |
|
400 | fs.chownSync = chownFixSync(fs.chownSync);
|
401 | fs.fchownSync = chownFixSync(fs.fchownSync);
|
402 | fs.lchownSync = chownFixSync(fs.lchownSync);
|
403 |
|
404 | fs.chmodSync = chmodFixSync(fs.chmodSync);
|
405 | fs.fchmodSync = chmodFixSync(fs.fchmodSync);
|
406 | fs.lchmodSync = chmodFixSync(fs.lchmodSync);
|
407 |
|
408 | fs.stat = statFix(fs.stat);
|
409 | fs.fstat = statFix(fs.fstat);
|
410 | fs.lstat = statFix(fs.lstat);
|
411 |
|
412 | fs.statSync = statFixSync(fs.statSync);
|
413 | fs.fstatSync = statFixSync(fs.fstatSync);
|
414 | fs.lstatSync = statFixSync(fs.lstatSync);
|
415 |
|
416 |
|
417 | if (!fs.lchmod) {
|
418 | fs.lchmod = function (path, mode, cb) {
|
419 | if (cb) process.nextTick(cb);
|
420 | };
|
421 | fs.lchmodSync = function () {};
|
422 | }
|
423 | if (!fs.lchown) {
|
424 | fs.lchown = function (path, uid, gid, cb) {
|
425 | if (cb) process.nextTick(cb);
|
426 | };
|
427 | fs.lchownSync = function () {};
|
428 | }
|
429 |
|
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 | if (platform === "win32") {
|
440 | fs.rename = (function (fs$rename) { return function (from, to, cb) {
|
441 | var start = Date.now();
|
442 | var backoff = 0;
|
443 | fs$rename(from, to, function CB (er) {
|
444 | if (er
|
445 | && (er.code === "EACCES" || er.code === "EPERM")
|
446 | && Date.now() - start < 60000) {
|
447 | setTimeout(function() {
|
448 | fs.stat(to, function (stater, st) {
|
449 | if (stater && stater.code === "ENOENT")
|
450 | fs$rename(from, to, CB);
|
451 | else
|
452 | cb(er);
|
453 | });
|
454 | }, backoff);
|
455 | if (backoff < 100)
|
456 | backoff += 10;
|
457 | return;
|
458 | }
|
459 | if (cb) cb(er);
|
460 | });
|
461 | }})(fs.rename);
|
462 | }
|
463 |
|
464 |
|
465 | fs.read = (function (fs$read) {
|
466 | function read (fd, buffer, offset, length, position, callback_) {
|
467 | var callback;
|
468 | if (callback_ && typeof callback_ === 'function') {
|
469 | var eagCounter = 0;
|
470 | callback = function (er, _, __) {
|
471 | if (er && er.code === 'EAGAIN' && eagCounter < 10) {
|
472 | eagCounter ++;
|
473 | return fs$read.call(fs, fd, buffer, offset, length, position, callback)
|
474 | }
|
475 | callback_.apply(this, arguments);
|
476 | };
|
477 | }
|
478 | return fs$read.call(fs, fd, buffer, offset, length, position, callback)
|
479 | }
|
480 |
|
481 |
|
482 | read.__proto__ = fs$read;
|
483 | return read
|
484 | })(fs.read);
|
485 |
|
486 | fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) {
|
487 | var eagCounter = 0;
|
488 | while (true) {
|
489 | try {
|
490 | return fs$readSync.call(fs, fd, buffer, offset, length, position)
|
491 | } catch (er) {
|
492 | if (er.code === 'EAGAIN' && eagCounter < 10) {
|
493 | eagCounter ++;
|
494 | continue
|
495 | }
|
496 | throw er
|
497 | }
|
498 | }
|
499 | }})(fs.readSync);
|
500 |
|
501 | function patchLchmod (fs) {
|
502 | fs.lchmod = function (path, mode, callback) {
|
503 | fs.open( path
|
504 | , constants.O_WRONLY | constants.O_SYMLINK
|
505 | , mode
|
506 | , function (err, fd) {
|
507 | if (err) {
|
508 | if (callback) callback(err);
|
509 | return
|
510 | }
|
511 |
|
512 |
|
513 | fs.fchmod(fd, mode, function (err) {
|
514 | fs.close(fd, function(err2) {
|
515 | if (callback) callback(err || err2);
|
516 | });
|
517 | });
|
518 | });
|
519 | };
|
520 |
|
521 | fs.lchmodSync = function (path, mode) {
|
522 | var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode);
|
523 |
|
524 |
|
525 |
|
526 | var threw = true;
|
527 | var ret;
|
528 | try {
|
529 | ret = fs.fchmodSync(fd, mode);
|
530 | threw = false;
|
531 | } finally {
|
532 | if (threw) {
|
533 | try {
|
534 | fs.closeSync(fd);
|
535 | } catch (er) {}
|
536 | } else {
|
537 | fs.closeSync(fd);
|
538 | }
|
539 | }
|
540 | return ret
|
541 | };
|
542 | }
|
543 |
|
544 | function patchLutimes (fs) {
|
545 | if (constants.hasOwnProperty("O_SYMLINK")) {
|
546 | fs.lutimes = function (path, at, mt, cb) {
|
547 | fs.open(path, constants.O_SYMLINK, function (er, fd) {
|
548 | if (er) {
|
549 | if (cb) cb(er);
|
550 | return
|
551 | }
|
552 | fs.futimes(fd, at, mt, function (er) {
|
553 | fs.close(fd, function (er2) {
|
554 | if (cb) cb(er || er2);
|
555 | });
|
556 | });
|
557 | });
|
558 | };
|
559 |
|
560 | fs.lutimesSync = function (path, at, mt) {
|
561 | var fd = fs.openSync(path, constants.O_SYMLINK);
|
562 | var ret;
|
563 | var threw = true;
|
564 | try {
|
565 | ret = fs.futimesSync(fd, at, mt);
|
566 | threw = false;
|
567 | } finally {
|
568 | if (threw) {
|
569 | try {
|
570 | fs.closeSync(fd);
|
571 | } catch (er) {}
|
572 | } else {
|
573 | fs.closeSync(fd);
|
574 | }
|
575 | }
|
576 | return ret
|
577 | };
|
578 |
|
579 | } else {
|
580 | fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb); };
|
581 | fs.lutimesSync = function () {};
|
582 | }
|
583 | }
|
584 |
|
585 | function chmodFix (orig) {
|
586 | if (!orig) return orig
|
587 | return function (target, mode, cb) {
|
588 | return orig.call(fs, target, mode, function (er) {
|
589 | if (chownErOk(er)) er = null;
|
590 | if (cb) cb.apply(this, arguments);
|
591 | })
|
592 | }
|
593 | }
|
594 |
|
595 | function chmodFixSync (orig) {
|
596 | if (!orig) return orig
|
597 | return function (target, mode) {
|
598 | try {
|
599 | return orig.call(fs, target, mode)
|
600 | } catch (er) {
|
601 | if (!chownErOk(er)) throw er
|
602 | }
|
603 | }
|
604 | }
|
605 |
|
606 |
|
607 | function chownFix (orig) {
|
608 | if (!orig) return orig
|
609 | return function (target, uid, gid, cb) {
|
610 | return orig.call(fs, target, uid, gid, function (er) {
|
611 | if (chownErOk(er)) er = null;
|
612 | if (cb) cb.apply(this, arguments);
|
613 | })
|
614 | }
|
615 | }
|
616 |
|
617 | function chownFixSync (orig) {
|
618 | if (!orig) return orig
|
619 | return function (target, uid, gid) {
|
620 | try {
|
621 | return orig.call(fs, target, uid, gid)
|
622 | } catch (er) {
|
623 | if (!chownErOk(er)) throw er
|
624 | }
|
625 | }
|
626 | }
|
627 |
|
628 | function statFix (orig) {
|
629 | if (!orig) return orig
|
630 |
|
631 |
|
632 | return function (target, options, cb) {
|
633 | if (typeof options === 'function') {
|
634 | cb = options;
|
635 | options = null;
|
636 | }
|
637 | function callback (er, stats) {
|
638 | if (stats) {
|
639 | if (stats.uid < 0) stats.uid += 0x100000000;
|
640 | if (stats.gid < 0) stats.gid += 0x100000000;
|
641 | }
|
642 | if (cb) cb.apply(this, arguments);
|
643 | }
|
644 | return options ? orig.call(fs, target, options, callback)
|
645 | : orig.call(fs, target, callback)
|
646 | }
|
647 | }
|
648 |
|
649 | function statFixSync (orig) {
|
650 | if (!orig) return orig
|
651 |
|
652 |
|
653 | return function (target, options) {
|
654 | var stats = options ? orig.call(fs, target, options)
|
655 | : orig.call(fs, target);
|
656 | if (stats.uid < 0) stats.uid += 0x100000000;
|
657 | if (stats.gid < 0) stats.gid += 0x100000000;
|
658 | return stats;
|
659 | }
|
660 | }
|
661 |
|
662 |
|
663 |
|
664 |
|
665 |
|
666 |
|
667 |
|
668 |
|
669 |
|
670 |
|
671 |
|
672 |
|
673 |
|
674 | function chownErOk (er) {
|
675 | if (!er)
|
676 | return true
|
677 |
|
678 | if (er.code === "ENOSYS")
|
679 | return true
|
680 |
|
681 | var nonroot = !process.getuid || process.getuid() !== 0;
|
682 | if (nonroot) {
|
683 | if (er.code === "EINVAL" || er.code === "EPERM")
|
684 | return true
|
685 | }
|
686 |
|
687 | return false
|
688 | }
|
689 | }
|
690 |
|
691 | var Stream = stream.Stream;
|
692 |
|
693 | var legacyStreams = legacy;
|
694 |
|
695 | function legacy (fs) {
|
696 | return {
|
697 | ReadStream: ReadStream,
|
698 | WriteStream: WriteStream
|
699 | }
|
700 |
|
701 | function ReadStream (path, options) {
|
702 | if (!(this instanceof ReadStream)) return new ReadStream(path, options);
|
703 |
|
704 | Stream.call(this);
|
705 |
|
706 | var self = this;
|
707 |
|
708 | this.path = path;
|
709 | this.fd = null;
|
710 | this.readable = true;
|
711 | this.paused = false;
|
712 |
|
713 | this.flags = 'r';
|
714 | this.mode = 438;
|
715 | this.bufferSize = 64 * 1024;
|
716 |
|
717 | options = options || {};
|
718 |
|
719 |
|
720 | var keys = Object.keys(options);
|
721 | for (var index = 0, length = keys.length; index < length; index++) {
|
722 | var key = keys[index];
|
723 | this[key] = options[key];
|
724 | }
|
725 |
|
726 | if (this.encoding) this.setEncoding(this.encoding);
|
727 |
|
728 | if (this.start !== undefined) {
|
729 | if ('number' !== typeof this.start) {
|
730 | throw TypeError('start must be a Number');
|
731 | }
|
732 | if (this.end === undefined) {
|
733 | this.end = Infinity;
|
734 | } else if ('number' !== typeof this.end) {
|
735 | throw TypeError('end must be a Number');
|
736 | }
|
737 |
|
738 | if (this.start > this.end) {
|
739 | throw new Error('start must be <= end');
|
740 | }
|
741 |
|
742 | this.pos = this.start;
|
743 | }
|
744 |
|
745 | if (this.fd !== null) {
|
746 | process.nextTick(function() {
|
747 | self._read();
|
748 | });
|
749 | return;
|
750 | }
|
751 |
|
752 | fs.open(this.path, this.flags, this.mode, function (err, fd) {
|
753 | if (err) {
|
754 | self.emit('error', err);
|
755 | self.readable = false;
|
756 | return;
|
757 | }
|
758 |
|
759 | self.fd = fd;
|
760 | self.emit('open', fd);
|
761 | self._read();
|
762 | });
|
763 | }
|
764 |
|
765 | function WriteStream (path, options) {
|
766 | if (!(this instanceof WriteStream)) return new WriteStream(path, options);
|
767 |
|
768 | Stream.call(this);
|
769 |
|
770 | this.path = path;
|
771 | this.fd = null;
|
772 | this.writable = true;
|
773 |
|
774 | this.flags = 'w';
|
775 | this.encoding = 'binary';
|
776 | this.mode = 438;
|
777 | this.bytesWritten = 0;
|
778 |
|
779 | options = options || {};
|
780 |
|
781 |
|
782 | var keys = Object.keys(options);
|
783 | for (var index = 0, length = keys.length; index < length; index++) {
|
784 | var key = keys[index];
|
785 | this[key] = options[key];
|
786 | }
|
787 |
|
788 | if (this.start !== undefined) {
|
789 | if ('number' !== typeof this.start) {
|
790 | throw TypeError('start must be a Number');
|
791 | }
|
792 | if (this.start < 0) {
|
793 | throw new Error('start must be >= zero');
|
794 | }
|
795 |
|
796 | this.pos = this.start;
|
797 | }
|
798 |
|
799 | this.busy = false;
|
800 | this._queue = [];
|
801 |
|
802 | if (this.fd === null) {
|
803 | this._open = fs.open;
|
804 | this._queue.push([this._open, this.path, this.flags, this.mode, undefined]);
|
805 | this.flush();
|
806 | }
|
807 | }
|
808 | }
|
809 |
|
810 | var clone_1 = clone;
|
811 |
|
812 | function clone (obj) {
|
813 | if (obj === null || typeof obj !== 'object')
|
814 | return obj
|
815 |
|
816 | if (obj instanceof Object)
|
817 | var copy = { __proto__: obj.__proto__ };
|
818 | else
|
819 | var copy = Object.create(null);
|
820 |
|
821 | Object.getOwnPropertyNames(obj).forEach(function (key) {
|
822 | Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key));
|
823 | });
|
824 |
|
825 | return copy
|
826 | }
|
827 |
|
828 | var gracefulFs = createCommonjsModule(function (module) {
|
829 |
|
830 | var gracefulQueue;
|
831 | var previousSymbol;
|
832 |
|
833 |
|
834 | if (typeof Symbol === 'function' && typeof Symbol.for === 'function') {
|
835 | gracefulQueue = Symbol.for('graceful-fs.queue');
|
836 |
|
837 | previousSymbol = Symbol.for('graceful-fs.previous');
|
838 | } else {
|
839 | gracefulQueue = '___graceful-fs.queue';
|
840 | previousSymbol = '___graceful-fs.previous';
|
841 | }
|
842 |
|
843 | function noop () {}
|
844 |
|
845 | function publishQueue(context, queue) {
|
846 | Object.defineProperty(context, gracefulQueue, {
|
847 | get: function() {
|
848 | return queue
|
849 | }
|
850 | });
|
851 | }
|
852 |
|
853 | var debug = noop;
|
854 | if (util.debuglog)
|
855 | debug = util.debuglog('gfs4');
|
856 | else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || ''))
|
857 | debug = function() {
|
858 | var m = util.format.apply(util, arguments);
|
859 | m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ');
|
860 | console.error(m);
|
861 | };
|
862 |
|
863 |
|
864 | if (!fs$2__default[gracefulQueue]) {
|
865 |
|
866 | var queue = global[gracefulQueue] || [];
|
867 | publishQueue(fs$2__default, queue);
|
868 |
|
869 |
|
870 |
|
871 |
|
872 |
|
873 | fs$2__default.close = (function (fs$close) {
|
874 | function close (fd, cb) {
|
875 | return fs$close.call(fs$2__default, fd, function (err) {
|
876 |
|
877 | if (!err) {
|
878 | retry();
|
879 | }
|
880 |
|
881 | if (typeof cb === 'function')
|
882 | cb.apply(this, arguments);
|
883 | })
|
884 | }
|
885 |
|
886 | Object.defineProperty(close, previousSymbol, {
|
887 | value: fs$close
|
888 | });
|
889 | return close
|
890 | })(fs$2__default.close);
|
891 |
|
892 | fs$2__default.closeSync = (function (fs$closeSync) {
|
893 | function closeSync (fd) {
|
894 |
|
895 | fs$closeSync.apply(fs$2__default, arguments);
|
896 | retry();
|
897 | }
|
898 |
|
899 | Object.defineProperty(closeSync, previousSymbol, {
|
900 | value: fs$closeSync
|
901 | });
|
902 | return closeSync
|
903 | })(fs$2__default.closeSync);
|
904 |
|
905 | if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) {
|
906 | process.on('exit', function() {
|
907 | debug(fs$2__default[gracefulQueue]);
|
908 | assert.equal(fs$2__default[gracefulQueue].length, 0);
|
909 | });
|
910 | }
|
911 | }
|
912 |
|
913 | if (!global[gracefulQueue]) {
|
914 | publishQueue(global, fs$2__default[gracefulQueue]);
|
915 | }
|
916 |
|
917 | module.exports = patch(clone_1(fs$2__default));
|
918 | if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs$2__default.__patched) {
|
919 | module.exports = patch(fs$2__default);
|
920 | fs$2__default.__patched = true;
|
921 | }
|
922 |
|
923 | function patch (fs) {
|
924 |
|
925 | polyfills(fs);
|
926 | fs.gracefulify = patch;
|
927 |
|
928 | fs.createReadStream = createReadStream;
|
929 | fs.createWriteStream = createWriteStream;
|
930 | var fs$readFile = fs.readFile;
|
931 | fs.readFile = readFile;
|
932 | function readFile (path, options, cb) {
|
933 | if (typeof options === 'function')
|
934 | cb = options, options = null;
|
935 |
|
936 | return go$readFile(path, options, cb)
|
937 |
|
938 | function go$readFile (path, options, cb) {
|
939 | return fs$readFile(path, options, function (err) {
|
940 | if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
|
941 | enqueue([go$readFile, [path, options, cb]]);
|
942 | else {
|
943 | if (typeof cb === 'function')
|
944 | cb.apply(this, arguments);
|
945 | retry();
|
946 | }
|
947 | })
|
948 | }
|
949 | }
|
950 |
|
951 | var fs$writeFile = fs.writeFile;
|
952 | fs.writeFile = writeFile;
|
953 | function writeFile (path, data, options, cb) {
|
954 | if (typeof options === 'function')
|
955 | cb = options, options = null;
|
956 |
|
957 | return go$writeFile(path, data, options, cb)
|
958 |
|
959 | function go$writeFile (path, data, options, cb) {
|
960 | return fs$writeFile(path, data, options, function (err) {
|
961 | if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
|
962 | enqueue([go$writeFile, [path, data, options, cb]]);
|
963 | else {
|
964 | if (typeof cb === 'function')
|
965 | cb.apply(this, arguments);
|
966 | retry();
|
967 | }
|
968 | })
|
969 | }
|
970 | }
|
971 |
|
972 | var fs$appendFile = fs.appendFile;
|
973 | if (fs$appendFile)
|
974 | fs.appendFile = appendFile;
|
975 | function appendFile (path, data, options, cb) {
|
976 | if (typeof options === 'function')
|
977 | cb = options, options = null;
|
978 |
|
979 | return go$appendFile(path, data, options, cb)
|
980 |
|
981 | function go$appendFile (path, data, options, cb) {
|
982 | return fs$appendFile(path, data, options, function (err) {
|
983 | if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
|
984 | enqueue([go$appendFile, [path, data, options, cb]]);
|
985 | else {
|
986 | if (typeof cb === 'function')
|
987 | cb.apply(this, arguments);
|
988 | retry();
|
989 | }
|
990 | })
|
991 | }
|
992 | }
|
993 |
|
994 | var fs$readdir = fs.readdir;
|
995 | fs.readdir = readdir;
|
996 | function readdir (path, options, cb) {
|
997 | var args = [path];
|
998 | if (typeof options !== 'function') {
|
999 | args.push(options);
|
1000 | } else {
|
1001 | cb = options;
|
1002 | }
|
1003 | args.push(go$readdir$cb);
|
1004 |
|
1005 | return go$readdir(args)
|
1006 |
|
1007 | function go$readdir$cb (err, files) {
|
1008 | if (files && files.sort)
|
1009 | files.sort();
|
1010 |
|
1011 | if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
|
1012 | enqueue([go$readdir, [args]]);
|
1013 |
|
1014 | else {
|
1015 | if (typeof cb === 'function')
|
1016 | cb.apply(this, arguments);
|
1017 | retry();
|
1018 | }
|
1019 | }
|
1020 | }
|
1021 |
|
1022 | function go$readdir (args) {
|
1023 | return fs$readdir.apply(fs, args)
|
1024 | }
|
1025 |
|
1026 | if (process.version.substr(0, 4) === 'v0.8') {
|
1027 | var legStreams = legacyStreams(fs);
|
1028 | ReadStream = legStreams.ReadStream;
|
1029 | WriteStream = legStreams.WriteStream;
|
1030 | }
|
1031 |
|
1032 | var fs$ReadStream = fs.ReadStream;
|
1033 | if (fs$ReadStream) {
|
1034 | ReadStream.prototype = Object.create(fs$ReadStream.prototype);
|
1035 | ReadStream.prototype.open = ReadStream$open;
|
1036 | }
|
1037 |
|
1038 | var fs$WriteStream = fs.WriteStream;
|
1039 | if (fs$WriteStream) {
|
1040 | WriteStream.prototype = Object.create(fs$WriteStream.prototype);
|
1041 | WriteStream.prototype.open = WriteStream$open;
|
1042 | }
|
1043 |
|
1044 | Object.defineProperty(fs, 'ReadStream', {
|
1045 | get: function () {
|
1046 | return ReadStream
|
1047 | },
|
1048 | set: function (val) {
|
1049 | ReadStream = val;
|
1050 | },
|
1051 | enumerable: true,
|
1052 | configurable: true
|
1053 | });
|
1054 | Object.defineProperty(fs, 'WriteStream', {
|
1055 | get: function () {
|
1056 | return WriteStream
|
1057 | },
|
1058 | set: function (val) {
|
1059 | WriteStream = val;
|
1060 | },
|
1061 | enumerable: true,
|
1062 | configurable: true
|
1063 | });
|
1064 |
|
1065 |
|
1066 | var FileReadStream = ReadStream;
|
1067 | Object.defineProperty(fs, 'FileReadStream', {
|
1068 | get: function () {
|
1069 | return FileReadStream
|
1070 | },
|
1071 | set: function (val) {
|
1072 | FileReadStream = val;
|
1073 | },
|
1074 | enumerable: true,
|
1075 | configurable: true
|
1076 | });
|
1077 | var FileWriteStream = WriteStream;
|
1078 | Object.defineProperty(fs, 'FileWriteStream', {
|
1079 | get: function () {
|
1080 | return FileWriteStream
|
1081 | },
|
1082 | set: function (val) {
|
1083 | FileWriteStream = val;
|
1084 | },
|
1085 | enumerable: true,
|
1086 | configurable: true
|
1087 | });
|
1088 |
|
1089 | function ReadStream (path, options) {
|
1090 | if (this instanceof ReadStream)
|
1091 | return fs$ReadStream.apply(this, arguments), this
|
1092 | else
|
1093 | return ReadStream.apply(Object.create(ReadStream.prototype), arguments)
|
1094 | }
|
1095 |
|
1096 | function ReadStream$open () {
|
1097 | var that = this;
|
1098 | open(that.path, that.flags, that.mode, function (err, fd) {
|
1099 | if (err) {
|
1100 | if (that.autoClose)
|
1101 | that.destroy();
|
1102 |
|
1103 | that.emit('error', err);
|
1104 | } else {
|
1105 | that.fd = fd;
|
1106 | that.emit('open', fd);
|
1107 | that.read();
|
1108 | }
|
1109 | });
|
1110 | }
|
1111 |
|
1112 | function WriteStream (path, options) {
|
1113 | if (this instanceof WriteStream)
|
1114 | return fs$WriteStream.apply(this, arguments), this
|
1115 | else
|
1116 | return WriteStream.apply(Object.create(WriteStream.prototype), arguments)
|
1117 | }
|
1118 |
|
1119 | function WriteStream$open () {
|
1120 | var that = this;
|
1121 | open(that.path, that.flags, that.mode, function (err, fd) {
|
1122 | if (err) {
|
1123 | that.destroy();
|
1124 | that.emit('error', err);
|
1125 | } else {
|
1126 | that.fd = fd;
|
1127 | that.emit('open', fd);
|
1128 | }
|
1129 | });
|
1130 | }
|
1131 |
|
1132 | function createReadStream (path, options) {
|
1133 | return new fs.ReadStream(path, options)
|
1134 | }
|
1135 |
|
1136 | function createWriteStream (path, options) {
|
1137 | return new fs.WriteStream(path, options)
|
1138 | }
|
1139 |
|
1140 | var fs$open = fs.open;
|
1141 | fs.open = open;
|
1142 | function open (path, flags, mode, cb) {
|
1143 | if (typeof mode === 'function')
|
1144 | cb = mode, mode = null;
|
1145 |
|
1146 | return go$open(path, flags, mode, cb)
|
1147 |
|
1148 | function go$open (path, flags, mode, cb) {
|
1149 | return fs$open(path, flags, mode, function (err, fd) {
|
1150 | if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
|
1151 | enqueue([go$open, [path, flags, mode, cb]]);
|
1152 | else {
|
1153 | if (typeof cb === 'function')
|
1154 | cb.apply(this, arguments);
|
1155 | retry();
|
1156 | }
|
1157 | })
|
1158 | }
|
1159 | }
|
1160 |
|
1161 | return fs
|
1162 | }
|
1163 |
|
1164 | function enqueue (elem) {
|
1165 | debug('ENQUEUE', elem[0].name, elem[1]);
|
1166 | fs$2__default[gracefulQueue].push(elem);
|
1167 | }
|
1168 |
|
1169 | function retry () {
|
1170 | var elem = fs$2__default[gracefulQueue].shift();
|
1171 | if (elem) {
|
1172 | debug('RETRY', elem[0].name, elem[1]);
|
1173 | elem[0].apply(null, elem[1]);
|
1174 | }
|
1175 | }
|
1176 | });
|
1177 |
|
1178 | var fs_1 = createCommonjsModule(function (module, exports) {
|
1179 |
|
1180 |
|
1181 | const u = universalify.fromCallback;
|
1182 |
|
1183 |
|
1184 | const api = [
|
1185 | 'access',
|
1186 | 'appendFile',
|
1187 | 'chmod',
|
1188 | 'chown',
|
1189 | 'close',
|
1190 | 'fchmod',
|
1191 | 'fchown',
|
1192 | 'fdatasync',
|
1193 | 'fstat',
|
1194 | 'fsync',
|
1195 | 'ftruncate',
|
1196 | 'futimes',
|
1197 | 'lchown',
|
1198 | 'link',
|
1199 | 'lstat',
|
1200 | 'mkdir',
|
1201 | 'open',
|
1202 | 'readFile',
|
1203 | 'readdir',
|
1204 | 'readlink',
|
1205 | 'realpath',
|
1206 | 'rename',
|
1207 | 'rmdir',
|
1208 | 'stat',
|
1209 | 'symlink',
|
1210 | 'truncate',
|
1211 | 'unlink',
|
1212 | 'utimes',
|
1213 | 'writeFile'
|
1214 | ];
|
1215 |
|
1216 |
|
1217 | typeof gracefulFs.copyFile === 'function' && api.push('copyFile');
|
1218 |
|
1219 | typeof gracefulFs.mkdtemp === 'function' && api.push('mkdtemp');
|
1220 |
|
1221 |
|
1222 | Object.keys(gracefulFs).forEach(key => {
|
1223 | exports[key] = gracefulFs[key];
|
1224 | });
|
1225 |
|
1226 |
|
1227 | api.forEach(method => {
|
1228 | exports[method] = u(gracefulFs[method]);
|
1229 | });
|
1230 |
|
1231 |
|
1232 |
|
1233 | exports.exists = function (filename, callback) {
|
1234 | if (typeof callback === 'function') {
|
1235 | return gracefulFs.exists(filename, callback)
|
1236 | }
|
1237 | return new Promise(resolve => {
|
1238 | return gracefulFs.exists(filename, resolve)
|
1239 | })
|
1240 | };
|
1241 |
|
1242 |
|
1243 |
|
1244 | exports.read = function (fd, buffer, offset, length, position, callback) {
|
1245 | if (typeof callback === 'function') {
|
1246 | return gracefulFs.read(fd, buffer, offset, length, position, callback)
|
1247 | }
|
1248 | return new Promise((resolve, reject) => {
|
1249 | gracefulFs.read(fd, buffer, offset, length, position, (err, bytesRead, buffer) => {
|
1250 | if (err) return reject(err)
|
1251 | resolve({ bytesRead, buffer });
|
1252 | });
|
1253 | })
|
1254 | };
|
1255 |
|
1256 |
|
1257 |
|
1258 |
|
1259 |
|
1260 |
|
1261 | exports.write = function (fd, buffer, a, b, c, callback) {
|
1262 | if (typeof arguments[arguments.length - 1] === 'function') {
|
1263 | return gracefulFs.write(fd, buffer, a, b, c, callback)
|
1264 | }
|
1265 |
|
1266 |
|
1267 | if (typeof buffer === 'string') {
|
1268 | return new Promise((resolve, reject) => {
|
1269 | gracefulFs.write(fd, buffer, a, b, (err, bytesWritten, buffer) => {
|
1270 | if (err) return reject(err)
|
1271 | resolve({ bytesWritten, buffer });
|
1272 | });
|
1273 | })
|
1274 | }
|
1275 |
|
1276 | return new Promise((resolve, reject) => {
|
1277 | gracefulFs.write(fd, buffer, a, b, c, (err, bytesWritten, buffer) => {
|
1278 | if (err) return reject(err)
|
1279 | resolve({ bytesWritten, buffer });
|
1280 | });
|
1281 | })
|
1282 | };
|
1283 | });
|
1284 | var fs_2 = fs_1.exists;
|
1285 | var fs_3 = fs_1.read;
|
1286 | var fs_4 = fs_1.write;
|
1287 |
|
1288 |
|
1289 | function hasMillisResSync () {
|
1290 | let tmpfile = path__default.join('millis-test-sync' + Date.now().toString() + Math.random().toString().slice(2));
|
1291 | tmpfile = path__default.join(os__default.tmpdir(), tmpfile);
|
1292 |
|
1293 |
|
1294 | const d = new Date(1435410243862);
|
1295 | gracefulFs.writeFileSync(tmpfile, 'https://github.com/jprichardson/node-fs-extra/pull/141');
|
1296 | const fd = gracefulFs.openSync(tmpfile, 'r+');
|
1297 | gracefulFs.futimesSync(fd, d, d);
|
1298 | gracefulFs.closeSync(fd);
|
1299 | return gracefulFs.statSync(tmpfile).mtime > 1435410243000
|
1300 | }
|
1301 |
|
1302 | function hasMillisRes (callback) {
|
1303 | let tmpfile = path__default.join('millis-test' + Date.now().toString() + Math.random().toString().slice(2));
|
1304 | tmpfile = path__default.join(os__default.tmpdir(), tmpfile);
|
1305 |
|
1306 |
|
1307 | const d = new Date(1435410243862);
|
1308 | gracefulFs.writeFile(tmpfile, 'https://github.com/jprichardson/node-fs-extra/pull/141', err => {
|
1309 | if (err) return callback(err)
|
1310 | gracefulFs.open(tmpfile, 'r+', (err, fd) => {
|
1311 | if (err) return callback(err)
|
1312 | gracefulFs.futimes(fd, d, d, err => {
|
1313 | if (err) return callback(err)
|
1314 | gracefulFs.close(fd, err => {
|
1315 | if (err) return callback(err)
|
1316 | gracefulFs.stat(tmpfile, (err, stats) => {
|
1317 | if (err) return callback(err)
|
1318 | callback(null, stats.mtime > 1435410243000);
|
1319 | });
|
1320 | });
|
1321 | });
|
1322 | });
|
1323 | });
|
1324 | }
|
1325 |
|
1326 | function timeRemoveMillis (timestamp) {
|
1327 | if (typeof timestamp === 'number') {
|
1328 | return Math.floor(timestamp / 1000) * 1000
|
1329 | } else if (timestamp instanceof Date) {
|
1330 | return new Date(Math.floor(timestamp.getTime() / 1000) * 1000)
|
1331 | } else {
|
1332 | throw new Error('fs-extra: timeRemoveMillis() unknown parameter type')
|
1333 | }
|
1334 | }
|
1335 |
|
1336 | function utimesMillis (path, atime, mtime, callback) {
|
1337 |
|
1338 | gracefulFs.open(path, 'r+', (err, fd) => {
|
1339 | if (err) return callback(err)
|
1340 | gracefulFs.futimes(fd, atime, mtime, futimesErr => {
|
1341 | gracefulFs.close(fd, closeErr => {
|
1342 | if (callback) callback(futimesErr || closeErr);
|
1343 | });
|
1344 | });
|
1345 | });
|
1346 | }
|
1347 |
|
1348 | var utimes = {
|
1349 | hasMillisRes,
|
1350 | hasMillisResSync,
|
1351 | timeRemoveMillis,
|
1352 | utimesMillis
|
1353 | };
|
1354 |
|
1355 |
|
1356 |
|
1357 |
|
1358 |
|
1359 |
|
1360 |
|
1361 | function ncp (source, dest, options, callback) {
|
1362 | if (!callback) {
|
1363 | callback = options;
|
1364 | options = {};
|
1365 | }
|
1366 |
|
1367 | var basePath = process.cwd();
|
1368 | var currentPath = path__default.resolve(basePath, source);
|
1369 | var targetPath = path__default.resolve(basePath, dest);
|
1370 |
|
1371 | var filter = options.filter;
|
1372 | var transform = options.transform;
|
1373 | var overwrite = options.overwrite;
|
1374 |
|
1375 | if (overwrite === undefined) overwrite = options.clobber;
|
1376 | if (overwrite === undefined) overwrite = true;
|
1377 | var errorOnExist = options.errorOnExist;
|
1378 | var dereference = options.dereference;
|
1379 | var preserveTimestamps = options.preserveTimestamps === true;
|
1380 |
|
1381 | var started = 0;
|
1382 | var finished = 0;
|
1383 | var running = 0;
|
1384 |
|
1385 | var errored = false;
|
1386 |
|
1387 | startCopy(currentPath);
|
1388 |
|
1389 | function startCopy (source) {
|
1390 | started++;
|
1391 | if (filter) {
|
1392 | if (filter instanceof RegExp) {
|
1393 | console.warn('Warning: fs-extra: Passing a RegExp filter is deprecated, use a function');
|
1394 | if (!filter.test(source)) {
|
1395 | return doneOne(true)
|
1396 | }
|
1397 | } else if (typeof filter === 'function') {
|
1398 | if (!filter(source, dest)) {
|
1399 | return doneOne(true)
|
1400 | }
|
1401 | }
|
1402 | }
|
1403 | return getStats(source)
|
1404 | }
|
1405 |
|
1406 | function getStats (source) {
|
1407 | var stat = dereference ? gracefulFs.stat : gracefulFs.lstat;
|
1408 | running++;
|
1409 | stat(source, function (err, stats) {
|
1410 | if (err) return onError(err)
|
1411 |
|
1412 |
|
1413 | var item = {
|
1414 | name: source,
|
1415 | mode: stats.mode,
|
1416 | mtime: stats.mtime,
|
1417 | atime: stats.atime,
|
1418 | stats: stats
|
1419 | };
|
1420 |
|
1421 | if (stats.isDirectory()) {
|
1422 | return onDir(item)
|
1423 | } else if (stats.isFile() || stats.isCharacterDevice() || stats.isBlockDevice()) {
|
1424 | return onFile(item)
|
1425 | } else if (stats.isSymbolicLink()) {
|
1426 |
|
1427 | return onLink(source)
|
1428 | }
|
1429 | });
|
1430 | }
|
1431 |
|
1432 | function onFile (file) {
|
1433 | var target = file.name.replace(currentPath, targetPath.replace('$', '$$$$'));
|
1434 | isWritable(target, function (writable) {
|
1435 | if (writable) {
|
1436 | copyFile(file, target);
|
1437 | } else {
|
1438 | if (overwrite) {
|
1439 | rmFile(target, function () {
|
1440 | copyFile(file, target);
|
1441 | });
|
1442 | } else if (errorOnExist) {
|
1443 | onError(new Error(target + ' already exists'));
|
1444 | } else {
|
1445 | doneOne();
|
1446 | }
|
1447 | }
|
1448 | });
|
1449 | }
|
1450 |
|
1451 | function copyFile (file, target) {
|
1452 | var readStream = gracefulFs.createReadStream(file.name);
|
1453 | var writeStream = gracefulFs.createWriteStream(target, { mode: file.mode });
|
1454 |
|
1455 | readStream.on('error', onError);
|
1456 | writeStream.on('error', onError);
|
1457 |
|
1458 | if (transform) {
|
1459 | transform(readStream, writeStream, file);
|
1460 | } else {
|
1461 | writeStream.on('open', function () {
|
1462 | readStream.pipe(writeStream);
|
1463 | });
|
1464 | }
|
1465 |
|
1466 | writeStream.once('close', function () {
|
1467 | gracefulFs.chmod(target, file.mode, function (err) {
|
1468 | if (err) return onError(err)
|
1469 | if (preserveTimestamps) {
|
1470 | utimes.utimesMillis(target, file.atime, file.mtime, function (err) {
|
1471 | if (err) return onError(err)
|
1472 | return doneOne()
|
1473 | });
|
1474 | } else {
|
1475 | doneOne();
|
1476 | }
|
1477 | });
|
1478 | });
|
1479 | }
|
1480 |
|
1481 | function rmFile (file, done) {
|
1482 | gracefulFs.unlink(file, function (err) {
|
1483 | if (err) return onError(err)
|
1484 | return done()
|
1485 | });
|
1486 | }
|
1487 |
|
1488 | function onDir (dir) {
|
1489 | var target = dir.name.replace(currentPath, targetPath.replace('$', '$$$$'));
|
1490 | isWritable(target, function (writable) {
|
1491 | if (writable) {
|
1492 | return mkDir(dir, target)
|
1493 | }
|
1494 | copyDir(dir.name);
|
1495 | });
|
1496 | }
|
1497 |
|
1498 | function mkDir (dir, target) {
|
1499 | gracefulFs.mkdir(target, dir.mode, function (err) {
|
1500 | if (err) return onError(err)
|
1501 |
|
1502 |
|
1503 | gracefulFs.chmod(target, dir.mode, function (err) {
|
1504 | if (err) return onError(err)
|
1505 | copyDir(dir.name);
|
1506 | });
|
1507 | });
|
1508 | }
|
1509 |
|
1510 | function copyDir (dir) {
|
1511 | gracefulFs.readdir(dir, function (err, items) {
|
1512 | if (err) return onError(err)
|
1513 | items.forEach(function (item) {
|
1514 | startCopy(path__default.join(dir, item));
|
1515 | });
|
1516 | return doneOne()
|
1517 | });
|
1518 | }
|
1519 |
|
1520 | function onLink (link) {
|
1521 | var target = link.replace(currentPath, targetPath);
|
1522 | gracefulFs.readlink(link, function (err, resolvedPath) {
|
1523 | if (err) return onError(err)
|
1524 | checkLink(resolvedPath, target);
|
1525 | });
|
1526 | }
|
1527 |
|
1528 | function checkLink (resolvedPath, target) {
|
1529 | if (dereference) {
|
1530 | resolvedPath = path__default.resolve(basePath, resolvedPath);
|
1531 | }
|
1532 | isWritable(target, function (writable) {
|
1533 | if (writable) {
|
1534 | return makeLink(resolvedPath, target)
|
1535 | }
|
1536 | gracefulFs.readlink(target, function (err, targetDest) {
|
1537 | if (err) return onError(err)
|
1538 |
|
1539 | if (dereference) {
|
1540 | targetDest = path__default.resolve(basePath, targetDest);
|
1541 | }
|
1542 | if (targetDest === resolvedPath) {
|
1543 | return doneOne()
|
1544 | }
|
1545 | return rmFile(target, function () {
|
1546 | makeLink(resolvedPath, target);
|
1547 | })
|
1548 | });
|
1549 | });
|
1550 | }
|
1551 |
|
1552 | function makeLink (linkPath, target) {
|
1553 | gracefulFs.symlink(linkPath, target, function (err) {
|
1554 | if (err) return onError(err)
|
1555 | return doneOne()
|
1556 | });
|
1557 | }
|
1558 |
|
1559 | function isWritable (path, done) {
|
1560 | gracefulFs.lstat(path, function (err) {
|
1561 | if (err) {
|
1562 | if (err.code === 'ENOENT') return done(true)
|
1563 | return done(false)
|
1564 | }
|
1565 | return done(false)
|
1566 | });
|
1567 | }
|
1568 |
|
1569 | function onError (err) {
|
1570 |
|
1571 | if (!errored && callback !== undefined) {
|
1572 | errored = true;
|
1573 | return callback(err)
|
1574 | }
|
1575 | }
|
1576 |
|
1577 | function doneOne (skipped) {
|
1578 | if (!skipped) running--;
|
1579 | finished++;
|
1580 | if ((started === finished) && (running === 0)) {
|
1581 | if (callback !== undefined) {
|
1582 | return callback(null)
|
1583 | }
|
1584 | }
|
1585 | }
|
1586 | }
|
1587 |
|
1588 | var ncp_1 = ncp;
|
1589 |
|
1590 |
|
1591 | function getRootPath (p) {
|
1592 | p = path__default.normalize(path__default.resolve(p)).split(path__default.sep);
|
1593 | if (p.length > 0) return p[0]
|
1594 | return null
|
1595 | }
|
1596 |
|
1597 |
|
1598 |
|
1599 | const INVALID_PATH_CHARS = /[<>:"|?*]/;
|
1600 |
|
1601 | function invalidWin32Path (p) {
|
1602 | const rp = getRootPath(p);
|
1603 | p = p.replace(rp, '');
|
1604 | return INVALID_PATH_CHARS.test(p)
|
1605 | }
|
1606 |
|
1607 | var win32 = {
|
1608 | getRootPath,
|
1609 | invalidWin32Path
|
1610 | };
|
1611 |
|
1612 | const invalidWin32Path$1 = win32.invalidWin32Path;
|
1613 |
|
1614 | const o777 = parseInt('0777', 8);
|
1615 |
|
1616 | function mkdirs (p, opts, callback, made) {
|
1617 | if (typeof opts === 'function') {
|
1618 | callback = opts;
|
1619 | opts = {};
|
1620 | } else if (!opts || typeof opts !== 'object') {
|
1621 | opts = { mode: opts };
|
1622 | }
|
1623 |
|
1624 | if (process.platform === 'win32' && invalidWin32Path$1(p)) {
|
1625 | const errInval = new Error(p + ' contains invalid WIN32 path characters.');
|
1626 | errInval.code = 'EINVAL';
|
1627 | return callback(errInval)
|
1628 | }
|
1629 |
|
1630 | let mode = opts.mode;
|
1631 | const xfs = opts.fs || gracefulFs;
|
1632 |
|
1633 | if (mode === undefined) {
|
1634 | mode = o777 & (~process.umask());
|
1635 | }
|
1636 | if (!made) made = null;
|
1637 |
|
1638 | callback = callback || function () {};
|
1639 | p = path__default.resolve(p);
|
1640 |
|
1641 | xfs.mkdir(p, mode, er => {
|
1642 | if (!er) {
|
1643 | made = made || p;
|
1644 | return callback(null, made)
|
1645 | }
|
1646 | switch (er.code) {
|
1647 | case 'ENOENT':
|
1648 | if (path__default.dirname(p) === p) return callback(er)
|
1649 | mkdirs(path__default.dirname(p), opts, (er, made) => {
|
1650 | if (er) callback(er, made);
|
1651 | else mkdirs(p, opts, callback, made);
|
1652 | });
|
1653 | break
|
1654 |
|
1655 |
|
1656 |
|
1657 |
|
1658 | default:
|
1659 | xfs.stat(p, (er2, stat) => {
|
1660 |
|
1661 |
|
1662 | if (er2 || !stat.isDirectory()) callback(er, made);
|
1663 | else callback(null, made);
|
1664 | });
|
1665 | break
|
1666 | }
|
1667 | });
|
1668 | }
|
1669 |
|
1670 | var mkdirs_1 = mkdirs;
|
1671 |
|
1672 | const invalidWin32Path$2 = win32.invalidWin32Path;
|
1673 |
|
1674 | const o777$1 = parseInt('0777', 8);
|
1675 |
|
1676 | function mkdirsSync (p, opts, made) {
|
1677 | if (!opts || typeof opts !== 'object') {
|
1678 | opts = { mode: opts };
|
1679 | }
|
1680 |
|
1681 | let mode = opts.mode;
|
1682 | const xfs = opts.fs || gracefulFs;
|
1683 |
|
1684 | if (process.platform === 'win32' && invalidWin32Path$2(p)) {
|
1685 | const errInval = new Error(p + ' contains invalid WIN32 path characters.');
|
1686 | errInval.code = 'EINVAL';
|
1687 | throw errInval
|
1688 | }
|
1689 |
|
1690 | if (mode === undefined) {
|
1691 | mode = o777$1 & (~process.umask());
|
1692 | }
|
1693 | if (!made) made = null;
|
1694 |
|
1695 | p = path__default.resolve(p);
|
1696 |
|
1697 | try {
|
1698 | xfs.mkdirSync(p, mode);
|
1699 | made = made || p;
|
1700 | } catch (err0) {
|
1701 | switch (err0.code) {
|
1702 | case 'ENOENT':
|
1703 | if (path__default.dirname(p) === p) throw err0
|
1704 | made = mkdirsSync(path__default.dirname(p), opts, made);
|
1705 | mkdirsSync(p, opts, made);
|
1706 | break
|
1707 |
|
1708 |
|
1709 |
|
1710 |
|
1711 | default:
|
1712 | let stat;
|
1713 | try {
|
1714 | stat = xfs.statSync(p);
|
1715 | } catch (err1) {
|
1716 | throw err0
|
1717 | }
|
1718 | if (!stat.isDirectory()) throw err0
|
1719 | break
|
1720 | }
|
1721 | }
|
1722 |
|
1723 | return made
|
1724 | }
|
1725 |
|
1726 | var mkdirsSync_1 = mkdirsSync;
|
1727 |
|
1728 | const u = universalify.fromCallback;
|
1729 | const mkdirs$1 = u(mkdirs_1);
|
1730 |
|
1731 |
|
1732 | var mkdirs_1$1 = {
|
1733 | mkdirs: mkdirs$1,
|
1734 | mkdirsSync: mkdirsSync_1,
|
1735 |
|
1736 | mkdirp: mkdirs$1,
|
1737 | mkdirpSync: mkdirsSync_1,
|
1738 | ensureDir: mkdirs$1,
|
1739 | ensureDirSync: mkdirsSync_1
|
1740 | };
|
1741 |
|
1742 | const u$1 = universalify.fromPromise;
|
1743 |
|
1744 |
|
1745 | function pathExists (path) {
|
1746 | return fs_1.access(path).then(() => true).catch(() => false)
|
1747 | }
|
1748 |
|
1749 | var pathExists_1 = {
|
1750 | pathExists: u$1(pathExists),
|
1751 | pathExistsSync: fs_1.existsSync
|
1752 | };
|
1753 |
|
1754 | const pathExists$1 = pathExists_1.pathExists;
|
1755 |
|
1756 | function copy (src, dest, options, callback) {
|
1757 | if (typeof options === 'function' && !callback) {
|
1758 | callback = options;
|
1759 | options = {};
|
1760 | } else if (typeof options === 'function' || options instanceof RegExp) {
|
1761 | options = {filter: options};
|
1762 | }
|
1763 | callback = callback || function () {};
|
1764 | options = options || {};
|
1765 |
|
1766 |
|
1767 | if (options.preserveTimestamps && process.arch === 'ia32') {
|
1768 | console.warn(`fs-extra: Using the preserveTimestamps option in 32-bit node is not recommended;\n
|
1769 | see https://github.com/jprichardson/node-fs-extra/issues/269`);
|
1770 | }
|
1771 |
|
1772 |
|
1773 | const basePath = process.cwd();
|
1774 | const currentPath = path__default.resolve(basePath, src);
|
1775 | const targetPath = path__default.resolve(basePath, dest);
|
1776 | if (currentPath === targetPath) return callback(new Error('Source and destination must not be the same.'))
|
1777 |
|
1778 | gracefulFs.lstat(src, (err, stats) => {
|
1779 | if (err) return callback(err)
|
1780 |
|
1781 | let dir = null;
|
1782 | if (stats.isDirectory()) {
|
1783 | const parts = dest.split(path__default.sep);
|
1784 | parts.pop();
|
1785 | dir = parts.join(path__default.sep);
|
1786 | } else {
|
1787 | dir = path__default.dirname(dest);
|
1788 | }
|
1789 |
|
1790 | pathExists$1(dir, (err, dirExists) => {
|
1791 | if (err) return callback(err)
|
1792 | if (dirExists) return ncp_1(src, dest, options, callback)
|
1793 | mkdirs_1$1.mkdirs(dir, err => {
|
1794 | if (err) return callback(err)
|
1795 | ncp_1(src, dest, options, callback);
|
1796 | });
|
1797 | });
|
1798 | });
|
1799 | }
|
1800 |
|
1801 | var copy_1 = copy;
|
1802 |
|
1803 | const u$2 = universalify.fromCallback;
|
1804 | var copy$1 = {
|
1805 | copy: u$2(copy_1)
|
1806 | };
|
1807 |
|
1808 |
|
1809 | var buffer = function (size) {
|
1810 | if (typeof Buffer.allocUnsafe === 'function') {
|
1811 | try {
|
1812 | return Buffer.allocUnsafe(size)
|
1813 | } catch (e) {
|
1814 | return new Buffer(size)
|
1815 | }
|
1816 | }
|
1817 | return new Buffer(size)
|
1818 | };
|
1819 |
|
1820 | const BUF_LENGTH = 64 * 1024;
|
1821 | const _buff = buffer(BUF_LENGTH);
|
1822 |
|
1823 | function copyFileSync (srcFile, destFile, options) {
|
1824 | const overwrite = options.overwrite;
|
1825 | const errorOnExist = options.errorOnExist;
|
1826 | const preserveTimestamps = options.preserveTimestamps;
|
1827 |
|
1828 | if (gracefulFs.existsSync(destFile)) {
|
1829 | if (overwrite) {
|
1830 | gracefulFs.unlinkSync(destFile);
|
1831 | } else if (errorOnExist) {
|
1832 | throw new Error(`${destFile} already exists`)
|
1833 | } else return
|
1834 | }
|
1835 |
|
1836 | const fdr = gracefulFs.openSync(srcFile, 'r');
|
1837 | const stat = gracefulFs.fstatSync(fdr);
|
1838 | const fdw = gracefulFs.openSync(destFile, 'w', stat.mode);
|
1839 | let bytesRead = 1;
|
1840 | let pos = 0;
|
1841 |
|
1842 | while (bytesRead > 0) {
|
1843 | bytesRead = gracefulFs.readSync(fdr, _buff, 0, BUF_LENGTH, pos);
|
1844 | gracefulFs.writeSync(fdw, _buff, 0, bytesRead);
|
1845 | pos += bytesRead;
|
1846 | }
|
1847 |
|
1848 | if (preserveTimestamps) {
|
1849 | gracefulFs.futimesSync(fdw, stat.atime, stat.mtime);
|
1850 | }
|
1851 |
|
1852 | gracefulFs.closeSync(fdr);
|
1853 | gracefulFs.closeSync(fdw);
|
1854 | }
|
1855 |
|
1856 | var copyFileSync_1 = copyFileSync;
|
1857 |
|
1858 | function copySync (src, dest, options) {
|
1859 | if (typeof options === 'function' || options instanceof RegExp) {
|
1860 | options = {filter: options};
|
1861 | }
|
1862 |
|
1863 | options = options || {};
|
1864 | options.recursive = !!options.recursive;
|
1865 |
|
1866 |
|
1867 | options.clobber = 'clobber' in options ? !!options.clobber : true;
|
1868 |
|
1869 | options.overwrite = 'overwrite' in options ? !!options.overwrite : options.clobber;
|
1870 | options.dereference = 'dereference' in options ? !!options.dereference : false;
|
1871 | options.preserveTimestamps = 'preserveTimestamps' in options ? !!options.preserveTimestamps : false;
|
1872 |
|
1873 | options.filter = options.filter || function () { return true };
|
1874 |
|
1875 |
|
1876 | if (options.preserveTimestamps && process.arch === 'ia32') {
|
1877 | console.warn(`fs-extra: Using the preserveTimestamps option in 32-bit node is not recommended;\n
|
1878 | see https://github.com/jprichardson/node-fs-extra/issues/269`);
|
1879 | }
|
1880 |
|
1881 | const stats = (options.recursive && !options.dereference) ? gracefulFs.lstatSync(src) : gracefulFs.statSync(src);
|
1882 | const destFolder = path__default.dirname(dest);
|
1883 | const destFolderExists = gracefulFs.existsSync(destFolder);
|
1884 | let performCopy = false;
|
1885 |
|
1886 | if (options.filter instanceof RegExp) {
|
1887 | console.warn('Warning: fs-extra: Passing a RegExp filter is deprecated, use a function');
|
1888 | performCopy = options.filter.test(src);
|
1889 | } else if (typeof options.filter === 'function') performCopy = options.filter(src, dest);
|
1890 |
|
1891 | if (stats.isFile() && performCopy) {
|
1892 | if (!destFolderExists) mkdirs_1$1.mkdirsSync(destFolder);
|
1893 | copyFileSync_1(src, dest, {
|
1894 | overwrite: options.overwrite,
|
1895 | errorOnExist: options.errorOnExist,
|
1896 | preserveTimestamps: options.preserveTimestamps
|
1897 | });
|
1898 | } else if (stats.isDirectory() && performCopy) {
|
1899 | if (!gracefulFs.existsSync(dest)) mkdirs_1$1.mkdirsSync(dest);
|
1900 | const contents = gracefulFs.readdirSync(src);
|
1901 | contents.forEach(content => {
|
1902 | const opts = options;
|
1903 | opts.recursive = true;
|
1904 | copySync(path__default.join(src, content), path__default.join(dest, content), opts);
|
1905 | });
|
1906 | } else if (options.recursive && stats.isSymbolicLink() && performCopy) {
|
1907 | const srcPath = gracefulFs.readlinkSync(src);
|
1908 | gracefulFs.symlinkSync(srcPath, dest);
|
1909 | }
|
1910 | }
|
1911 |
|
1912 | var copySync_1 = copySync;
|
1913 |
|
1914 | var copySync$1 = {
|
1915 | copySync: copySync_1
|
1916 | };
|
1917 |
|
1918 | const isWindows = (process.platform === 'win32');
|
1919 |
|
1920 | function defaults (options) {
|
1921 | const methods = [
|
1922 | 'unlink',
|
1923 | 'chmod',
|
1924 | 'stat',
|
1925 | 'lstat',
|
1926 | 'rmdir',
|
1927 | 'readdir'
|
1928 | ];
|
1929 | methods.forEach(m => {
|
1930 | options[m] = options[m] || gracefulFs[m];
|
1931 | m = m + 'Sync';
|
1932 | options[m] = options[m] || gracefulFs[m];
|
1933 | });
|
1934 |
|
1935 | options.maxBusyTries = options.maxBusyTries || 3;
|
1936 | }
|
1937 |
|
1938 | function rimraf (p, options, cb) {
|
1939 | let busyTries = 0;
|
1940 |
|
1941 | if (typeof options === 'function') {
|
1942 | cb = options;
|
1943 | options = {};
|
1944 | }
|
1945 |
|
1946 | assert(p, 'rimraf: missing path');
|
1947 | assert.equal(typeof p, 'string', 'rimraf: path should be a string');
|
1948 | assert.equal(typeof cb, 'function', 'rimraf: callback function required');
|
1949 | assert(options, 'rimraf: invalid options argument provided');
|
1950 | assert.equal(typeof options, 'object', 'rimraf: options should be object');
|
1951 |
|
1952 | defaults(options);
|
1953 |
|
1954 | rimraf_(p, options, function CB (er) {
|
1955 | if (er) {
|
1956 | if ((er.code === 'EBUSY' || er.code === 'ENOTEMPTY' || er.code === 'EPERM') &&
|
1957 | busyTries < options.maxBusyTries) {
|
1958 | busyTries++;
|
1959 | let time = busyTries * 100;
|
1960 |
|
1961 | return setTimeout(() => rimraf_(p, options, CB), time)
|
1962 | }
|
1963 |
|
1964 |
|
1965 | if (er.code === 'ENOENT') er = null;
|
1966 | }
|
1967 |
|
1968 | cb(er);
|
1969 | });
|
1970 | }
|
1971 |
|
1972 |
|
1973 |
|
1974 |
|
1975 |
|
1976 |
|
1977 |
|
1978 |
|
1979 |
|
1980 |
|
1981 |
|
1982 |
|
1983 | function rimraf_ (p, options, cb) {
|
1984 | assert(p);
|
1985 | assert(options);
|
1986 | assert(typeof cb === 'function');
|
1987 |
|
1988 |
|
1989 |
|
1990 | options.lstat(p, (er, st) => {
|
1991 | if (er && er.code === 'ENOENT') {
|
1992 | return cb(null)
|
1993 | }
|
1994 |
|
1995 |
|
1996 | if (er && er.code === 'EPERM' && isWindows) {
|
1997 | return fixWinEPERM(p, options, er, cb)
|
1998 | }
|
1999 |
|
2000 | if (st && st.isDirectory()) {
|
2001 | return rmdir(p, options, er, cb)
|
2002 | }
|
2003 |
|
2004 | options.unlink(p, er => {
|
2005 | if (er) {
|
2006 | if (er.code === 'ENOENT') {
|
2007 | return cb(null)
|
2008 | }
|
2009 | if (er.code === 'EPERM') {
|
2010 | return (isWindows)
|
2011 | ? fixWinEPERM(p, options, er, cb)
|
2012 | : rmdir(p, options, er, cb)
|
2013 | }
|
2014 | if (er.code === 'EISDIR') {
|
2015 | return rmdir(p, options, er, cb)
|
2016 | }
|
2017 | }
|
2018 | return cb(er)
|
2019 | });
|
2020 | });
|
2021 | }
|
2022 |
|
2023 | function fixWinEPERM (p, options, er, cb) {
|
2024 | assert(p);
|
2025 | assert(options);
|
2026 | assert(typeof cb === 'function');
|
2027 | if (er) {
|
2028 | assert(er instanceof Error);
|
2029 | }
|
2030 |
|
2031 | options.chmod(p, 666, er2 => {
|
2032 | if (er2) {
|
2033 | cb(er2.code === 'ENOENT' ? null : er);
|
2034 | } else {
|
2035 | options.stat(p, (er3, stats) => {
|
2036 | if (er3) {
|
2037 | cb(er3.code === 'ENOENT' ? null : er);
|
2038 | } else if (stats.isDirectory()) {
|
2039 | rmdir(p, options, er, cb);
|
2040 | } else {
|
2041 | options.unlink(p, cb);
|
2042 | }
|
2043 | });
|
2044 | }
|
2045 | });
|
2046 | }
|
2047 |
|
2048 | function fixWinEPERMSync (p, options, er) {
|
2049 | let stats;
|
2050 |
|
2051 | assert(p);
|
2052 | assert(options);
|
2053 | if (er) {
|
2054 | assert(er instanceof Error);
|
2055 | }
|
2056 |
|
2057 | try {
|
2058 | options.chmodSync(p, 666);
|
2059 | } catch (er2) {
|
2060 | if (er2.code === 'ENOENT') {
|
2061 | return
|
2062 | } else {
|
2063 | throw er
|
2064 | }
|
2065 | }
|
2066 |
|
2067 | try {
|
2068 | stats = options.statSync(p);
|
2069 | } catch (er3) {
|
2070 | if (er3.code === 'ENOENT') {
|
2071 | return
|
2072 | } else {
|
2073 | throw er
|
2074 | }
|
2075 | }
|
2076 |
|
2077 | if (stats.isDirectory()) {
|
2078 | rmdirSync(p, options, er);
|
2079 | } else {
|
2080 | options.unlinkSync(p);
|
2081 | }
|
2082 | }
|
2083 |
|
2084 | function rmdir (p, options, originalEr, cb) {
|
2085 | assert(p);
|
2086 | assert(options);
|
2087 | if (originalEr) {
|
2088 | assert(originalEr instanceof Error);
|
2089 | }
|
2090 | assert(typeof cb === 'function');
|
2091 |
|
2092 |
|
2093 |
|
2094 |
|
2095 | options.rmdir(p, er => {
|
2096 | if (er && (er.code === 'ENOTEMPTY' || er.code === 'EEXIST' || er.code === 'EPERM')) {
|
2097 | rmkids(p, options, cb);
|
2098 | } else if (er && er.code === 'ENOTDIR') {
|
2099 | cb(originalEr);
|
2100 | } else {
|
2101 | cb(er);
|
2102 | }
|
2103 | });
|
2104 | }
|
2105 |
|
2106 | function rmkids (p, options, cb) {
|
2107 | assert(p);
|
2108 | assert(options);
|
2109 | assert(typeof cb === 'function');
|
2110 |
|
2111 | options.readdir(p, (er, files) => {
|
2112 | if (er) return cb(er)
|
2113 |
|
2114 | let n = files.length;
|
2115 | let errState;
|
2116 |
|
2117 | if (n === 0) return options.rmdir(p, cb)
|
2118 |
|
2119 | files.forEach(f => {
|
2120 | rimraf(path__default.join(p, f), options, er => {
|
2121 | if (errState) {
|
2122 | return
|
2123 | }
|
2124 | if (er) return cb(errState = er)
|
2125 | if (--n === 0) {
|
2126 | options.rmdir(p, cb);
|
2127 | }
|
2128 | });
|
2129 | });
|
2130 | });
|
2131 | }
|
2132 |
|
2133 |
|
2134 |
|
2135 |
|
2136 | function rimrafSync (p, options) {
|
2137 | let st;
|
2138 |
|
2139 | options = options || {};
|
2140 | defaults(options);
|
2141 |
|
2142 | assert(p, 'rimraf: missing path');
|
2143 | assert.equal(typeof p, 'string', 'rimraf: path should be a string');
|
2144 | assert(options, 'rimraf: missing options');
|
2145 | assert.equal(typeof options, 'object', 'rimraf: options should be object');
|
2146 |
|
2147 | try {
|
2148 | st = options.lstatSync(p);
|
2149 | } catch (er) {
|
2150 | if (er.code === 'ENOENT') {
|
2151 | return
|
2152 | }
|
2153 |
|
2154 |
|
2155 | if (er.code === 'EPERM' && isWindows) {
|
2156 | fixWinEPERMSync(p, options, er);
|
2157 | }
|
2158 | }
|
2159 |
|
2160 | try {
|
2161 |
|
2162 | if (st && st.isDirectory()) {
|
2163 | rmdirSync(p, options, null);
|
2164 | } else {
|
2165 | options.unlinkSync(p);
|
2166 | }
|
2167 | } catch (er) {
|
2168 | if (er.code === 'ENOENT') {
|
2169 | return
|
2170 | } else if (er.code === 'EPERM') {
|
2171 | return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er)
|
2172 | } else if (er.code !== 'EISDIR') {
|
2173 | throw er
|
2174 | }
|
2175 | rmdirSync(p, options, er);
|
2176 | }
|
2177 | }
|
2178 |
|
2179 | function rmdirSync (p, options, originalEr) {
|
2180 | assert(p);
|
2181 | assert(options);
|
2182 | if (originalEr) {
|
2183 | assert(originalEr instanceof Error);
|
2184 | }
|
2185 |
|
2186 | try {
|
2187 | options.rmdirSync(p);
|
2188 | } catch (er) {
|
2189 | if (er.code === 'ENOTDIR') {
|
2190 | throw originalEr
|
2191 | } else if (er.code === 'ENOTEMPTY' || er.code === 'EEXIST' || er.code === 'EPERM') {
|
2192 | rmkidsSync(p, options);
|
2193 | } else if (er.code !== 'ENOENT') {
|
2194 | throw er
|
2195 | }
|
2196 | }
|
2197 | }
|
2198 |
|
2199 | function rmkidsSync (p, options) {
|
2200 | assert(p);
|
2201 | assert(options);
|
2202 | options.readdirSync(p).forEach(f => rimrafSync(path__default.join(p, f), options));
|
2203 |
|
2204 |
|
2205 |
|
2206 |
|
2207 |
|
2208 |
|
2209 |
|
2210 | const retries = isWindows ? 100 : 1;
|
2211 | let i = 0;
|
2212 | do {
|
2213 | let threw = true;
|
2214 | try {
|
2215 | const ret = options.rmdirSync(p, options);
|
2216 | threw = false;
|
2217 | return ret
|
2218 | } finally {
|
2219 | if (++i < retries && threw) continue
|
2220 | }
|
2221 | } while (true)
|
2222 | }
|
2223 |
|
2224 | var rimraf_1 = rimraf;
|
2225 | rimraf.sync = rimrafSync;
|
2226 |
|
2227 | const u$3 = universalify.fromCallback;
|
2228 |
|
2229 |
|
2230 | var remove = {
|
2231 | remove: u$3(rimraf_1),
|
2232 | removeSync: rimraf_1.sync
|
2233 | };
|
2234 |
|
2235 | var _fs;
|
2236 | try {
|
2237 | _fs = gracefulFs;
|
2238 | } catch (_) {
|
2239 | _fs = fs$2__default;
|
2240 | }
|
2241 |
|
2242 | function readFile (file, options, callback) {
|
2243 | if (callback == null) {
|
2244 | callback = options;
|
2245 | options = {};
|
2246 | }
|
2247 |
|
2248 | if (typeof options === 'string') {
|
2249 | options = {encoding: options};
|
2250 | }
|
2251 |
|
2252 | options = options || {};
|
2253 | var fs = options.fs || _fs;
|
2254 |
|
2255 | var shouldThrow = true;
|
2256 | if ('throws' in options) {
|
2257 | shouldThrow = options.throws;
|
2258 | }
|
2259 |
|
2260 | fs.readFile(file, options, function (err, data) {
|
2261 | if (err) return callback(err)
|
2262 |
|
2263 | data = stripBom(data);
|
2264 |
|
2265 | var obj;
|
2266 | try {
|
2267 | obj = JSON.parse(data, options ? options.reviver : null);
|
2268 | } catch (err2) {
|
2269 | if (shouldThrow) {
|
2270 | err2.message = file + ': ' + err2.message;
|
2271 | return callback(err2)
|
2272 | } else {
|
2273 | return callback(null, null)
|
2274 | }
|
2275 | }
|
2276 |
|
2277 | callback(null, obj);
|
2278 | });
|
2279 | }
|
2280 |
|
2281 | function readFileSync (file, options) {
|
2282 | options = options || {};
|
2283 | if (typeof options === 'string') {
|
2284 | options = {encoding: options};
|
2285 | }
|
2286 |
|
2287 | var fs = options.fs || _fs;
|
2288 |
|
2289 | var shouldThrow = true;
|
2290 | if ('throws' in options) {
|
2291 | shouldThrow = options.throws;
|
2292 | }
|
2293 |
|
2294 | try {
|
2295 | var content = fs.readFileSync(file, options);
|
2296 | content = stripBom(content);
|
2297 | return JSON.parse(content, options.reviver)
|
2298 | } catch (err) {
|
2299 | if (shouldThrow) {
|
2300 | err.message = file + ': ' + err.message;
|
2301 | throw err
|
2302 | } else {
|
2303 | return null
|
2304 | }
|
2305 | }
|
2306 | }
|
2307 |
|
2308 | function stringify (obj, options) {
|
2309 | var spaces;
|
2310 | var EOL = '\n';
|
2311 | if (typeof options === 'object' && options !== null) {
|
2312 | if (options.spaces) {
|
2313 | spaces = options.spaces;
|
2314 | }
|
2315 | if (options.EOL) {
|
2316 | EOL = options.EOL;
|
2317 | }
|
2318 | }
|
2319 |
|
2320 | var str = JSON.stringify(obj, options ? options.replacer : null, spaces);
|
2321 |
|
2322 | return str.replace(/\n/g, EOL) + EOL
|
2323 | }
|
2324 |
|
2325 | function writeFile (file, obj, options, callback) {
|
2326 | if (callback == null) {
|
2327 | callback = options;
|
2328 | options = {};
|
2329 | }
|
2330 | options = options || {};
|
2331 | var fs = options.fs || _fs;
|
2332 |
|
2333 | var str = '';
|
2334 | try {
|
2335 | str = stringify(obj, options);
|
2336 | } catch (err) {
|
2337 |
|
2338 | if (callback) callback(err, null);
|
2339 | return
|
2340 | }
|
2341 |
|
2342 | fs.writeFile(file, str, options, callback);
|
2343 | }
|
2344 |
|
2345 | function writeFileSync (file, obj, options) {
|
2346 | options = options || {};
|
2347 | var fs = options.fs || _fs;
|
2348 |
|
2349 | var str = stringify(obj, options);
|
2350 |
|
2351 | return fs.writeFileSync(file, str, options)
|
2352 | }
|
2353 |
|
2354 | function stripBom (content) {
|
2355 |
|
2356 | if (Buffer.isBuffer(content)) content = content.toString('utf8');
|
2357 | content = content.replace(/^\uFEFF/, '');
|
2358 | return content
|
2359 | }
|
2360 |
|
2361 | var jsonfile = {
|
2362 | readFile: readFile,
|
2363 | readFileSync: readFileSync,
|
2364 | writeFile: writeFile,
|
2365 | writeFileSync: writeFileSync
|
2366 | };
|
2367 |
|
2368 | var jsonfile_1 = jsonfile;
|
2369 |
|
2370 | const u$4 = universalify.fromCallback;
|
2371 |
|
2372 |
|
2373 | var jsonfile$1 = {
|
2374 |
|
2375 | readJson: u$4(jsonfile_1.readFile),
|
2376 | readJsonSync: jsonfile_1.readFileSync,
|
2377 | writeJson: u$4(jsonfile_1.writeFile),
|
2378 | writeJsonSync: jsonfile_1.writeFileSync
|
2379 | };
|
2380 |
|
2381 | const pathExists$2 = pathExists_1.pathExists;
|
2382 |
|
2383 |
|
2384 | function outputJson (file, data, options, callback) {
|
2385 | if (typeof options === 'function') {
|
2386 | callback = options;
|
2387 | options = {};
|
2388 | }
|
2389 |
|
2390 | const dir = path__default.dirname(file);
|
2391 |
|
2392 | pathExists$2(dir, (err, itDoes) => {
|
2393 | if (err) return callback(err)
|
2394 | if (itDoes) return jsonfile$1.writeJson(file, data, options, callback)
|
2395 |
|
2396 | mkdirs_1$1.mkdirs(dir, err => {
|
2397 | if (err) return callback(err)
|
2398 | jsonfile$1.writeJson(file, data, options, callback);
|
2399 | });
|
2400 | });
|
2401 | }
|
2402 |
|
2403 | var outputJson_1 = outputJson;
|
2404 |
|
2405 | function outputJsonSync (file, data, options) {
|
2406 | const dir = path__default.dirname(file);
|
2407 |
|
2408 | if (!gracefulFs.existsSync(dir)) {
|
2409 | mkdirs_1$1.mkdirsSync(dir);
|
2410 | }
|
2411 |
|
2412 | jsonfile$1.writeJsonSync(file, data, options);
|
2413 | }
|
2414 |
|
2415 | var outputJsonSync_1 = outputJsonSync;
|
2416 |
|
2417 | const u$5 = universalify.fromCallback;
|
2418 |
|
2419 |
|
2420 | jsonfile$1.outputJson = u$5(outputJson_1);
|
2421 | jsonfile$1.outputJsonSync = outputJsonSync_1;
|
2422 |
|
2423 | jsonfile$1.outputJSON = jsonfile$1.outputJson;
|
2424 | jsonfile$1.outputJSONSync = jsonfile$1.outputJsonSync;
|
2425 | jsonfile$1.writeJSON = jsonfile$1.writeJson;
|
2426 | jsonfile$1.writeJSONSync = jsonfile$1.writeJsonSync;
|
2427 | jsonfile$1.readJSON = jsonfile$1.readJson;
|
2428 | jsonfile$1.readJSONSync = jsonfile$1.readJsonSync;
|
2429 |
|
2430 | var json = jsonfile$1;
|
2431 |
|
2432 |
|
2433 |
|
2434 |
|
2435 |
|
2436 |
|
2437 |
|
2438 | const u$6 = universalify.fromCallback;
|
2439 |
|
2440 |
|
2441 |
|
2442 | const remove$1 = remove.remove;
|
2443 | const mkdirp = mkdirs_1$1.mkdirs;
|
2444 |
|
2445 | function move (src, dest, options, callback) {
|
2446 | if (typeof options === 'function') {
|
2447 | callback = options;
|
2448 | options = {};
|
2449 | }
|
2450 |
|
2451 | const overwrite = options.overwrite || options.clobber || false;
|
2452 |
|
2453 | isSrcSubdir(src, dest, (err, itIs) => {
|
2454 | if (err) return callback(err)
|
2455 | if (itIs) return callback(new Error(`Cannot move '${src}' to a subdirectory of itself, '${dest}'.`))
|
2456 | mkdirp(path__default.dirname(dest), err => {
|
2457 | if (err) return callback(err)
|
2458 | doRename();
|
2459 | });
|
2460 | });
|
2461 |
|
2462 | function doRename () {
|
2463 | if (path__default.resolve(src) === path__default.resolve(dest)) {
|
2464 | gracefulFs.access(src, callback);
|
2465 | } else if (overwrite) {
|
2466 | gracefulFs.rename(src, dest, err => {
|
2467 | if (!err) return callback()
|
2468 |
|
2469 | if (err.code === 'ENOTEMPTY' || err.code === 'EEXIST') {
|
2470 | remove$1(dest, err => {
|
2471 | if (err) return callback(err)
|
2472 | options.overwrite = false;
|
2473 | move(src, dest, options, callback);
|
2474 | });
|
2475 | return
|
2476 | }
|
2477 |
|
2478 |
|
2479 | if (err.code === 'EPERM') {
|
2480 | setTimeout(() => {
|
2481 | remove$1(dest, err => {
|
2482 | if (err) return callback(err)
|
2483 | options.overwrite = false;
|
2484 | move(src, dest, options, callback);
|
2485 | });
|
2486 | }, 200);
|
2487 | return
|
2488 | }
|
2489 |
|
2490 | if (err.code !== 'EXDEV') return callback(err)
|
2491 | moveAcrossDevice(src, dest, overwrite, callback);
|
2492 | });
|
2493 | } else {
|
2494 | gracefulFs.link(src, dest, err => {
|
2495 | if (err) {
|
2496 | if (err.code === 'EXDEV' || err.code === 'EISDIR' || err.code === 'EPERM' || err.code === 'ENOTSUP') {
|
2497 | return moveAcrossDevice(src, dest, overwrite, callback)
|
2498 | }
|
2499 | return callback(err)
|
2500 | }
|
2501 | return gracefulFs.unlink(src, callback)
|
2502 | });
|
2503 | }
|
2504 | }
|
2505 | }
|
2506 |
|
2507 | function moveAcrossDevice (src, dest, overwrite, callback) {
|
2508 | gracefulFs.stat(src, (err, stat) => {
|
2509 | if (err) return callback(err)
|
2510 |
|
2511 | if (stat.isDirectory()) {
|
2512 | moveDirAcrossDevice(src, dest, overwrite, callback);
|
2513 | } else {
|
2514 | moveFileAcrossDevice(src, dest, overwrite, callback);
|
2515 | }
|
2516 | });
|
2517 | }
|
2518 |
|
2519 | function moveFileAcrossDevice (src, dest, overwrite, callback) {
|
2520 | const flags = overwrite ? 'w' : 'wx';
|
2521 | const ins = gracefulFs.createReadStream(src);
|
2522 | const outs = gracefulFs.createWriteStream(dest, { flags });
|
2523 |
|
2524 | ins.on('error', err => {
|
2525 | ins.destroy();
|
2526 | outs.destroy();
|
2527 | outs.removeListener('close', onClose);
|
2528 |
|
2529 |
|
2530 |
|
2531 |
|
2532 | gracefulFs.unlink(dest, () => {
|
2533 |
|
2534 | if (err.code === 'EISDIR' || err.code === 'EPERM') {
|
2535 | moveDirAcrossDevice(src, dest, overwrite, callback);
|
2536 | } else {
|
2537 | callback(err);
|
2538 | }
|
2539 | });
|
2540 | });
|
2541 |
|
2542 | outs.on('error', err => {
|
2543 | ins.destroy();
|
2544 | outs.destroy();
|
2545 | outs.removeListener('close', onClose);
|
2546 | callback(err);
|
2547 | });
|
2548 |
|
2549 | outs.once('close', onClose);
|
2550 | ins.pipe(outs);
|
2551 |
|
2552 | function onClose () {
|
2553 | gracefulFs.unlink(src, callback);
|
2554 | }
|
2555 | }
|
2556 |
|
2557 | function moveDirAcrossDevice (src, dest, overwrite, callback) {
|
2558 | const options = {
|
2559 | overwrite: false
|
2560 | };
|
2561 |
|
2562 | if (overwrite) {
|
2563 | remove$1(dest, err => {
|
2564 | if (err) return callback(err)
|
2565 | startNcp();
|
2566 | });
|
2567 | } else {
|
2568 | startNcp();
|
2569 | }
|
2570 |
|
2571 | function startNcp () {
|
2572 | ncp_1(src, dest, options, err => {
|
2573 | if (err) return callback(err)
|
2574 | remove$1(src, callback);
|
2575 | });
|
2576 | }
|
2577 | }
|
2578 |
|
2579 |
|
2580 |
|
2581 | function isSrcSubdir (src, dest, cb) {
|
2582 | gracefulFs.stat(src, (err, st) => {
|
2583 | if (err) return cb(err)
|
2584 | if (st.isDirectory()) {
|
2585 | const baseDir = dest.split(path__default.dirname(src) + path__default.sep)[1];
|
2586 | if (baseDir) {
|
2587 | const destBasename = baseDir.split(path__default.sep)[0];
|
2588 | if (destBasename) return cb(null, src !== dest && dest.indexOf(src) > -1 && destBasename === path__default.basename(src))
|
2589 | return cb(null, false)
|
2590 | }
|
2591 | return cb(null, false)
|
2592 | }
|
2593 | return cb(null, false)
|
2594 | });
|
2595 | }
|
2596 |
|
2597 | var move_1 = {
|
2598 | move: u$6(move)
|
2599 | };
|
2600 |
|
2601 | const copySync$2 = copySync$1.copySync;
|
2602 | const removeSync = remove.removeSync;
|
2603 | const mkdirpSync = mkdirs_1$1.mkdirsSync;
|
2604 |
|
2605 |
|
2606 | function moveSync (src, dest, options) {
|
2607 | options = options || {};
|
2608 | const overwrite = options.overwrite || options.clobber || false;
|
2609 |
|
2610 | src = path__default.resolve(src);
|
2611 | dest = path__default.resolve(dest);
|
2612 |
|
2613 | if (src === dest) return gracefulFs.accessSync(src)
|
2614 |
|
2615 | if (isSrcSubdir$1(src, dest)) throw new Error(`Cannot move '${src}' into itself '${dest}'.`)
|
2616 |
|
2617 | mkdirpSync(path__default.dirname(dest));
|
2618 | tryRenameSync();
|
2619 |
|
2620 | function tryRenameSync () {
|
2621 | if (overwrite) {
|
2622 | try {
|
2623 | return gracefulFs.renameSync(src, dest)
|
2624 | } catch (err) {
|
2625 | if (err.code === 'ENOTEMPTY' || err.code === 'EEXIST' || err.code === 'EPERM') {
|
2626 | removeSync(dest);
|
2627 | options.overwrite = false;
|
2628 | return moveSync(src, dest, options)
|
2629 | }
|
2630 |
|
2631 | if (err.code !== 'EXDEV') throw err
|
2632 | return moveSyncAcrossDevice(src, dest, overwrite)
|
2633 | }
|
2634 | } else {
|
2635 | try {
|
2636 | gracefulFs.linkSync(src, dest);
|
2637 | return gracefulFs.unlinkSync(src)
|
2638 | } catch (err) {
|
2639 | if (err.code === 'EXDEV' || err.code === 'EISDIR' || err.code === 'EPERM' || err.code === 'ENOTSUP') {
|
2640 | return moveSyncAcrossDevice(src, dest, overwrite)
|
2641 | }
|
2642 | throw err
|
2643 | }
|
2644 | }
|
2645 | }
|
2646 | }
|
2647 |
|
2648 | function moveSyncAcrossDevice (src, dest, overwrite) {
|
2649 | const stat = gracefulFs.statSync(src);
|
2650 |
|
2651 | if (stat.isDirectory()) {
|
2652 | return moveDirSyncAcrossDevice(src, dest, overwrite)
|
2653 | } else {
|
2654 | return moveFileSyncAcrossDevice(src, dest, overwrite)
|
2655 | }
|
2656 | }
|
2657 |
|
2658 | function moveFileSyncAcrossDevice (src, dest, overwrite) {
|
2659 | const BUF_LENGTH = 64 * 1024;
|
2660 | const _buff = buffer(BUF_LENGTH);
|
2661 |
|
2662 | const flags = overwrite ? 'w' : 'wx';
|
2663 |
|
2664 | const fdr = gracefulFs.openSync(src, 'r');
|
2665 | const stat = gracefulFs.fstatSync(fdr);
|
2666 | const fdw = gracefulFs.openSync(dest, flags, stat.mode);
|
2667 | let bytesRead = 1;
|
2668 | let pos = 0;
|
2669 |
|
2670 | while (bytesRead > 0) {
|
2671 | bytesRead = gracefulFs.readSync(fdr, _buff, 0, BUF_LENGTH, pos);
|
2672 | gracefulFs.writeSync(fdw, _buff, 0, bytesRead);
|
2673 | pos += bytesRead;
|
2674 | }
|
2675 |
|
2676 | gracefulFs.closeSync(fdr);
|
2677 | gracefulFs.closeSync(fdw);
|
2678 | return gracefulFs.unlinkSync(src)
|
2679 | }
|
2680 |
|
2681 | function moveDirSyncAcrossDevice (src, dest, overwrite) {
|
2682 | const options = {
|
2683 | overwrite: false
|
2684 | };
|
2685 |
|
2686 | if (overwrite) {
|
2687 | removeSync(dest);
|
2688 | tryCopySync();
|
2689 | } else {
|
2690 | tryCopySync();
|
2691 | }
|
2692 |
|
2693 | function tryCopySync () {
|
2694 | copySync$2(src, dest, options);
|
2695 | return removeSync(src)
|
2696 | }
|
2697 | }
|
2698 |
|
2699 |
|
2700 |
|
2701 | function isSrcSubdir$1 (src, dest) {
|
2702 | try {
|
2703 | return gracefulFs.statSync(src).isDirectory() &&
|
2704 | src !== dest &&
|
2705 | dest.indexOf(src) > -1 &&
|
2706 | dest.split(path__default.dirname(src) + path__default.sep)[1].split(path__default.sep)[0] === path__default.basename(src)
|
2707 | } catch (e) {
|
2708 | return false
|
2709 | }
|
2710 | }
|
2711 |
|
2712 | var moveSync_1 = {
|
2713 | moveSync
|
2714 | };
|
2715 |
|
2716 | const u$7 = universalify.fromCallback;
|
2717 |
|
2718 |
|
2719 |
|
2720 |
|
2721 |
|
2722 | const emptyDir = u$7(function emptyDir (dir, callback) {
|
2723 | callback = callback || function () {};
|
2724 | fs$2__default.readdir(dir, (err, items) => {
|
2725 | if (err) return mkdirs_1$1.mkdirs(dir, callback)
|
2726 |
|
2727 | items = items.map(item => path__default.join(dir, item));
|
2728 |
|
2729 | deleteItem();
|
2730 |
|
2731 | function deleteItem () {
|
2732 | const item = items.pop();
|
2733 | if (!item) return callback()
|
2734 | remove.remove(item, err => {
|
2735 | if (err) return callback(err)
|
2736 | deleteItem();
|
2737 | });
|
2738 | }
|
2739 | });
|
2740 | });
|
2741 |
|
2742 | function emptyDirSync (dir) {
|
2743 | let items;
|
2744 | try {
|
2745 | items = fs$2__default.readdirSync(dir);
|
2746 | } catch (err) {
|
2747 | return mkdirs_1$1.mkdirsSync(dir)
|
2748 | }
|
2749 |
|
2750 | items.forEach(item => {
|
2751 | item = path__default.join(dir, item);
|
2752 | remove.removeSync(item);
|
2753 | });
|
2754 | }
|
2755 |
|
2756 | var empty = {
|
2757 | emptyDirSync,
|
2758 | emptydirSync: emptyDirSync,
|
2759 | emptyDir,
|
2760 | emptydir: emptyDir
|
2761 | };
|
2762 |
|
2763 | const u$8 = universalify.fromCallback;
|
2764 |
|
2765 |
|
2766 |
|
2767 | const pathExists$3 = pathExists_1.pathExists;
|
2768 |
|
2769 | function createFile (file, callback) {
|
2770 | function makeFile () {
|
2771 | gracefulFs.writeFile(file, '', err => {
|
2772 | if (err) return callback(err)
|
2773 | callback();
|
2774 | });
|
2775 | }
|
2776 |
|
2777 | gracefulFs.stat(file, (err, stats) => {
|
2778 | if (!err && stats.isFile()) return callback()
|
2779 | const dir = path__default.dirname(file);
|
2780 | pathExists$3(dir, (err, dirExists) => {
|
2781 | if (err) return callback(err)
|
2782 | if (dirExists) return makeFile()
|
2783 | mkdirs_1$1.mkdirs(dir, err => {
|
2784 | if (err) return callback(err)
|
2785 | makeFile();
|
2786 | });
|
2787 | });
|
2788 | });
|
2789 | }
|
2790 |
|
2791 | function createFileSync (file) {
|
2792 | let stats;
|
2793 | try {
|
2794 | stats = gracefulFs.statSync(file);
|
2795 | } catch (e) {}
|
2796 | if (stats && stats.isFile()) return
|
2797 |
|
2798 | const dir = path__default.dirname(file);
|
2799 | if (!gracefulFs.existsSync(dir)) {
|
2800 | mkdirs_1$1.mkdirsSync(dir);
|
2801 | }
|
2802 |
|
2803 | gracefulFs.writeFileSync(file, '');
|
2804 | }
|
2805 |
|
2806 | var file = {
|
2807 | createFile: u$8(createFile),
|
2808 | createFileSync
|
2809 | };
|
2810 |
|
2811 | const u$9 = universalify.fromCallback;
|
2812 |
|
2813 |
|
2814 |
|
2815 | const pathExists$4 = pathExists_1.pathExists;
|
2816 |
|
2817 | function createLink (srcpath, dstpath, callback) {
|
2818 | function makeLink (srcpath, dstpath) {
|
2819 | gracefulFs.link(srcpath, dstpath, err => {
|
2820 | if (err) return callback(err)
|
2821 | callback(null);
|
2822 | });
|
2823 | }
|
2824 |
|
2825 | pathExists$4(dstpath, (err, destinationExists) => {
|
2826 | if (err) return callback(err)
|
2827 | if (destinationExists) return callback(null)
|
2828 | gracefulFs.lstat(srcpath, (err, stat) => {
|
2829 | if (err) {
|
2830 | err.message = err.message.replace('lstat', 'ensureLink');
|
2831 | return callback(err)
|
2832 | }
|
2833 |
|
2834 | const dir = path__default.dirname(dstpath);
|
2835 | pathExists$4(dir, (err, dirExists) => {
|
2836 | if (err) return callback(err)
|
2837 | if (dirExists) return makeLink(srcpath, dstpath)
|
2838 | mkdirs_1$1.mkdirs(dir, err => {
|
2839 | if (err) return callback(err)
|
2840 | makeLink(srcpath, dstpath);
|
2841 | });
|
2842 | });
|
2843 | });
|
2844 | });
|
2845 | }
|
2846 |
|
2847 | function createLinkSync (srcpath, dstpath, callback) {
|
2848 | const destinationExists = gracefulFs.existsSync(dstpath);
|
2849 | if (destinationExists) return undefined
|
2850 |
|
2851 | try {
|
2852 | gracefulFs.lstatSync(srcpath);
|
2853 | } catch (err) {
|
2854 | err.message = err.message.replace('lstat', 'ensureLink');
|
2855 | throw err
|
2856 | }
|
2857 |
|
2858 | const dir = path__default.dirname(dstpath);
|
2859 | const dirExists = gracefulFs.existsSync(dir);
|
2860 | if (dirExists) return gracefulFs.linkSync(srcpath, dstpath)
|
2861 | mkdirs_1$1.mkdirsSync(dir);
|
2862 |
|
2863 | return gracefulFs.linkSync(srcpath, dstpath)
|
2864 | }
|
2865 |
|
2866 | var link = {
|
2867 | createLink: u$9(createLink),
|
2868 | createLinkSync
|
2869 | };
|
2870 |
|
2871 | const pathExists$5 = pathExists_1.pathExists;
|
2872 |
|
2873 | |
2874 |
|
2875 |
|
2876 |
|
2877 |
|
2878 |
|
2879 |
|
2880 |
|
2881 |
|
2882 |
|
2883 |
|
2884 |
|
2885 |
|
2886 |
|
2887 |
|
2888 |
|
2889 |
|
2890 |
|
2891 |
|
2892 |
|
2893 |
|
2894 |
|
2895 | function symlinkPaths (srcpath, dstpath, callback) {
|
2896 | if (path__default.isAbsolute(srcpath)) {
|
2897 | return gracefulFs.lstat(srcpath, (err, stat) => {
|
2898 | if (err) {
|
2899 | err.message = err.message.replace('lstat', 'ensureSymlink');
|
2900 | return callback(err)
|
2901 | }
|
2902 | return callback(null, {
|
2903 | 'toCwd': srcpath,
|
2904 | 'toDst': srcpath
|
2905 | })
|
2906 | })
|
2907 | } else {
|
2908 | const dstdir = path__default.dirname(dstpath);
|
2909 | const relativeToDst = path__default.join(dstdir, srcpath);
|
2910 | return pathExists$5(relativeToDst, (err, exists) => {
|
2911 | if (err) return callback(err)
|
2912 | if (exists) {
|
2913 | return callback(null, {
|
2914 | 'toCwd': relativeToDst,
|
2915 | 'toDst': srcpath
|
2916 | })
|
2917 | } else {
|
2918 | return gracefulFs.lstat(srcpath, (err, stat) => {
|
2919 | if (err) {
|
2920 | err.message = err.message.replace('lstat', 'ensureSymlink');
|
2921 | return callback(err)
|
2922 | }
|
2923 | return callback(null, {
|
2924 | 'toCwd': srcpath,
|
2925 | 'toDst': path__default.relative(dstdir, srcpath)
|
2926 | })
|
2927 | })
|
2928 | }
|
2929 | })
|
2930 | }
|
2931 | }
|
2932 |
|
2933 | function symlinkPathsSync (srcpath, dstpath) {
|
2934 | let exists;
|
2935 | if (path__default.isAbsolute(srcpath)) {
|
2936 | exists = gracefulFs.existsSync(srcpath);
|
2937 | if (!exists) throw new Error('absolute srcpath does not exist')
|
2938 | return {
|
2939 | 'toCwd': srcpath,
|
2940 | 'toDst': srcpath
|
2941 | }
|
2942 | } else {
|
2943 | const dstdir = path__default.dirname(dstpath);
|
2944 | const relativeToDst = path__default.join(dstdir, srcpath);
|
2945 | exists = gracefulFs.existsSync(relativeToDst);
|
2946 | if (exists) {
|
2947 | return {
|
2948 | 'toCwd': relativeToDst,
|
2949 | 'toDst': srcpath
|
2950 | }
|
2951 | } else {
|
2952 | exists = gracefulFs.existsSync(srcpath);
|
2953 | if (!exists) throw new Error('relative srcpath does not exist')
|
2954 | return {
|
2955 | 'toCwd': srcpath,
|
2956 | 'toDst': path__default.relative(dstdir, srcpath)
|
2957 | }
|
2958 | }
|
2959 | }
|
2960 | }
|
2961 |
|
2962 | var symlinkPaths_1 = {
|
2963 | symlinkPaths,
|
2964 | symlinkPathsSync
|
2965 | };
|
2966 |
|
2967 | function symlinkType (srcpath, type, callback) {
|
2968 | callback = (typeof type === 'function') ? type : callback;
|
2969 | type = (typeof type === 'function') ? false : type;
|
2970 | if (type) return callback(null, type)
|
2971 | gracefulFs.lstat(srcpath, (err, stats) => {
|
2972 | if (err) return callback(null, 'file')
|
2973 | type = (stats && stats.isDirectory()) ? 'dir' : 'file';
|
2974 | callback(null, type);
|
2975 | });
|
2976 | }
|
2977 |
|
2978 | function symlinkTypeSync (srcpath, type) {
|
2979 | let stats;
|
2980 |
|
2981 | if (type) return type
|
2982 | try {
|
2983 | stats = gracefulFs.lstatSync(srcpath);
|
2984 | } catch (e) {
|
2985 | return 'file'
|
2986 | }
|
2987 | return (stats && stats.isDirectory()) ? 'dir' : 'file'
|
2988 | }
|
2989 |
|
2990 | var symlinkType_1 = {
|
2991 | symlinkType,
|
2992 | symlinkTypeSync
|
2993 | };
|
2994 |
|
2995 | const u$a = universalify.fromCallback;
|
2996 |
|
2997 |
|
2998 |
|
2999 | const mkdirs$2 = mkdirs_1$1.mkdirs;
|
3000 | const mkdirsSync$1 = mkdirs_1$1.mkdirsSync;
|
3001 |
|
3002 |
|
3003 | const symlinkPaths$1 = symlinkPaths_1.symlinkPaths;
|
3004 | const symlinkPathsSync$1 = symlinkPaths_1.symlinkPathsSync;
|
3005 |
|
3006 |
|
3007 | const symlinkType$1 = symlinkType_1.symlinkType;
|
3008 | const symlinkTypeSync$1 = symlinkType_1.symlinkTypeSync;
|
3009 |
|
3010 | const pathExists$6 = pathExists_1.pathExists;
|
3011 |
|
3012 | function createSymlink (srcpath, dstpath, type, callback) {
|
3013 | callback = (typeof type === 'function') ? type : callback;
|
3014 | type = (typeof type === 'function') ? false : type;
|
3015 |
|
3016 | pathExists$6(dstpath, (err, destinationExists) => {
|
3017 | if (err) return callback(err)
|
3018 | if (destinationExists) return callback(null)
|
3019 | symlinkPaths$1(srcpath, dstpath, (err, relative) => {
|
3020 | if (err) return callback(err)
|
3021 | srcpath = relative.toDst;
|
3022 | symlinkType$1(relative.toCwd, type, (err, type) => {
|
3023 | if (err) return callback(err)
|
3024 | const dir = path__default.dirname(dstpath);
|
3025 | pathExists$6(dir, (err, dirExists) => {
|
3026 | if (err) return callback(err)
|
3027 | if (dirExists) return gracefulFs.symlink(srcpath, dstpath, type, callback)
|
3028 | mkdirs$2(dir, err => {
|
3029 | if (err) return callback(err)
|
3030 | gracefulFs.symlink(srcpath, dstpath, type, callback);
|
3031 | });
|
3032 | });
|
3033 | });
|
3034 | });
|
3035 | });
|
3036 | }
|
3037 |
|
3038 | function createSymlinkSync (srcpath, dstpath, type, callback) {
|
3039 | type = (typeof type === 'function') ? false : type;
|
3040 |
|
3041 | const destinationExists = gracefulFs.existsSync(dstpath);
|
3042 | if (destinationExists) return undefined
|
3043 |
|
3044 | const relative = symlinkPathsSync$1(srcpath, dstpath);
|
3045 | srcpath = relative.toDst;
|
3046 | type = symlinkTypeSync$1(relative.toCwd, type);
|
3047 | const dir = path__default.dirname(dstpath);
|
3048 | const exists = gracefulFs.existsSync(dir);
|
3049 | if (exists) return gracefulFs.symlinkSync(srcpath, dstpath, type)
|
3050 | mkdirsSync$1(dir);
|
3051 | return gracefulFs.symlinkSync(srcpath, dstpath, type)
|
3052 | }
|
3053 |
|
3054 | var symlink = {
|
3055 | createSymlink: u$a(createSymlink),
|
3056 | createSymlinkSync
|
3057 | };
|
3058 |
|
3059 | var ensure = {
|
3060 |
|
3061 | createFile: file.createFile,
|
3062 | createFileSync: file.createFileSync,
|
3063 | ensureFile: file.createFile,
|
3064 | ensureFileSync: file.createFileSync,
|
3065 |
|
3066 | createLink: link.createLink,
|
3067 | createLinkSync: link.createLinkSync,
|
3068 | ensureLink: link.createLink,
|
3069 | ensureLinkSync: link.createLinkSync,
|
3070 |
|
3071 | createSymlink: symlink.createSymlink,
|
3072 | createSymlinkSync: symlink.createSymlinkSync,
|
3073 | ensureSymlink: symlink.createSymlink,
|
3074 | ensureSymlinkSync: symlink.createSymlinkSync
|
3075 | };
|
3076 |
|
3077 | const u$b = universalify.fromCallback;
|
3078 |
|
3079 |
|
3080 |
|
3081 | const pathExists$7 = pathExists_1.pathExists;
|
3082 |
|
3083 | function outputFile (file, data, encoding, callback) {
|
3084 | if (typeof encoding === 'function') {
|
3085 | callback = encoding;
|
3086 | encoding = 'utf8';
|
3087 | }
|
3088 |
|
3089 | const dir = path__default.dirname(file);
|
3090 | pathExists$7(dir, (err, itDoes) => {
|
3091 | if (err) return callback(err)
|
3092 | if (itDoes) return gracefulFs.writeFile(file, data, encoding, callback)
|
3093 |
|
3094 | mkdirs_1$1.mkdirs(dir, err => {
|
3095 | if (err) return callback(err)
|
3096 |
|
3097 | gracefulFs.writeFile(file, data, encoding, callback);
|
3098 | });
|
3099 | });
|
3100 | }
|
3101 |
|
3102 | function outputFileSync (file, data, encoding) {
|
3103 | const dir = path__default.dirname(file);
|
3104 | if (gracefulFs.existsSync(dir)) {
|
3105 | return gracefulFs.writeFileSync.apply(gracefulFs, arguments)
|
3106 | }
|
3107 | mkdirs_1$1.mkdirsSync(dir);
|
3108 | gracefulFs.writeFileSync.apply(gracefulFs, arguments);
|
3109 | }
|
3110 |
|
3111 | var output = {
|
3112 | outputFile: u$b(outputFile),
|
3113 | outputFileSync
|
3114 | };
|
3115 |
|
3116 | const fs$1 = {};
|
3117 |
|
3118 |
|
3119 | assign_1(fs$1, fs_1);
|
3120 |
|
3121 | assign_1(fs$1, copy$1);
|
3122 | assign_1(fs$1, copySync$1);
|
3123 | assign_1(fs$1, mkdirs_1$1);
|
3124 | assign_1(fs$1, remove);
|
3125 | assign_1(fs$1, json);
|
3126 | assign_1(fs$1, move_1);
|
3127 | assign_1(fs$1, moveSync_1);
|
3128 | assign_1(fs$1, empty);
|
3129 | assign_1(fs$1, ensure);
|
3130 | assign_1(fs$1, output);
|
3131 | assign_1(fs$1, pathExists_1);
|
3132 |
|
3133 | var lib = fs$1;
|
3134 |
|
3135 | var fsExtra = Object.freeze({
|
3136 | __proto__: null,
|
3137 | 'default': lib,
|
3138 | __moduleExports: lib
|
3139 | });
|
3140 |
|
3141 | |
3142 |
|
3143 |
|
3144 |
|
3145 |
|
3146 |
|
3147 |
|
3148 | |
3149 |
|
3150 |
|
3151 | class NodeJSPathManipulation {
|
3152 | pwd() {
|
3153 | return this.normalize(process.cwd());
|
3154 | }
|
3155 | chdir(dir) {
|
3156 | process.chdir(dir);
|
3157 | }
|
3158 | resolve(...paths) {
|
3159 | return this.normalize(path.resolve(...paths));
|
3160 | }
|
3161 | dirname(file) {
|
3162 | return this.normalize(path.dirname(file));
|
3163 | }
|
3164 | join(basePath, ...paths) {
|
3165 | return this.normalize(path.join(basePath, ...paths));
|
3166 | }
|
3167 | isRoot(path) {
|
3168 | return this.dirname(path) === this.normalize(path);
|
3169 | }
|
3170 | isRooted(path$1) {
|
3171 | return path.isAbsolute(path$1);
|
3172 | }
|
3173 | relative(from, to) {
|
3174 | return this.normalize(path.relative(from, to));
|
3175 | }
|
3176 | basename(filePath, extension) {
|
3177 | return path.basename(filePath, extension);
|
3178 | }
|
3179 | extname(path$1) {
|
3180 | return path.extname(path$1);
|
3181 | }
|
3182 | normalize(path) {
|
3183 |
|
3184 | return path.replace(/\\/g, '/');
|
3185 | }
|
3186 | }
|
3187 | |
3188 |
|
3189 |
|
3190 | class NodeJSReadonlyFileSystem extends NodeJSPathManipulation {
|
3191 | constructor() {
|
3192 | super(...arguments);
|
3193 | this._caseSensitive = undefined;
|
3194 | }
|
3195 | isCaseSensitive() {
|
3196 | if (this._caseSensitive === undefined) {
|
3197 |
|
3198 |
|
3199 | this._caseSensitive = !fs$2.existsSync(this.normalize(toggleCase(__filename)));
|
3200 | }
|
3201 | return this._caseSensitive;
|
3202 | }
|
3203 | exists(path) {
|
3204 | return fs$2.existsSync(path);
|
3205 | }
|
3206 | readFile(path) {
|
3207 | return fs$2.readFileSync(path, 'utf8');
|
3208 | }
|
3209 | readFileBuffer(path) {
|
3210 | return fs$2.readFileSync(path);
|
3211 | }
|
3212 | readdir(path) {
|
3213 | return fs$2.readdirSync(path);
|
3214 | }
|
3215 | lstat(path) {
|
3216 | return fs$2.lstatSync(path);
|
3217 | }
|
3218 | stat(path) {
|
3219 | return fs$2.statSync(path);
|
3220 | }
|
3221 | realpath(path) {
|
3222 | return this.resolve(fs$2.realpathSync(path));
|
3223 | }
|
3224 | getDefaultLibLocation() {
|
3225 | return this.resolve(require.resolve('typescript'), '..');
|
3226 | }
|
3227 | }
|
3228 | |
3229 |
|
3230 |
|
3231 | class NodeJSFileSystem extends NodeJSReadonlyFileSystem {
|
3232 | writeFile(path, data, exclusive = false) {
|
3233 | fs$2.writeFileSync(path, data, exclusive ? { flag: 'wx' } : undefined);
|
3234 | }
|
3235 | removeFile(path) {
|
3236 | fs$2.unlinkSync(path);
|
3237 | }
|
3238 | symlink(target, path) {
|
3239 | fs$2.symlinkSync(target, path);
|
3240 | }
|
3241 | copyFile(from, to) {
|
3242 | fs$2.copyFileSync(from, to);
|
3243 | }
|
3244 | moveFile(from, to) {
|
3245 | fs$2.renameSync(from, to);
|
3246 | }
|
3247 | ensureDir(path) {
|
3248 | const parents = [];
|
3249 | while (!this.isRoot(path) && !this.exists(path)) {
|
3250 | parents.push(path);
|
3251 | path = this.dirname(path);
|
3252 | }
|
3253 | while (parents.length) {
|
3254 | this.safeMkdir(parents.pop());
|
3255 | }
|
3256 | }
|
3257 | removeDeep(path) {
|
3258 | undefined(path);
|
3259 | }
|
3260 | safeMkdir(path) {
|
3261 | try {
|
3262 | fs$2.mkdirSync(path);
|
3263 | }
|
3264 | catch (err) {
|
3265 |
|
3266 |
|
3267 | if (!this.exists(path) || !this.stat(path).isDirectory()) {
|
3268 | throw err;
|
3269 | }
|
3270 | }
|
3271 | }
|
3272 | }
|
3273 | |
3274 |
|
3275 |
|
3276 | function toggleCase(str) {
|
3277 | return str.replace(/\w/g, ch => ch.toUpperCase() === ch ? ch.toLowerCase() : ch.toUpperCase());
|
3278 | }
|
3279 |
|
3280 | |
3281 |
|
3282 |
|
3283 |
|
3284 |
|
3285 |
|
3286 |
|
3287 | var TagContentType;
|
3288 | (function (TagContentType) {
|
3289 | TagContentType[TagContentType["RAW_TEXT"] = 0] = "RAW_TEXT";
|
3290 | TagContentType[TagContentType["ESCAPABLE_RAW_TEXT"] = 1] = "ESCAPABLE_RAW_TEXT";
|
3291 | TagContentType[TagContentType["PARSABLE_DATA"] = 2] = "PARSABLE_DATA";
|
3292 | })(TagContentType || (TagContentType = {}));
|
3293 | function splitNsName(elementName) {
|
3294 | if (elementName[0] != ':') {
|
3295 | return [null, elementName];
|
3296 | }
|
3297 | const colonIndex = elementName.indexOf(':', 1);
|
3298 | if (colonIndex == -1) {
|
3299 | throw new Error(`Unsupported format "${elementName}" expecting ":namespace:name"`);
|
3300 | }
|
3301 | return [elementName.slice(1, colonIndex), elementName.slice(colonIndex + 1)];
|
3302 | }
|
3303 |
|
3304 | function isNgContainer(tagName) {
|
3305 | return splitNsName(tagName)[1] === 'ng-container';
|
3306 | }
|
3307 |
|
3308 | function isNgContent(tagName) {
|
3309 | return splitNsName(tagName)[1] === 'ng-content';
|
3310 | }
|
3311 |
|
3312 | function isNgTemplate(tagName) {
|
3313 | return splitNsName(tagName)[1] === 'ng-template';
|
3314 | }
|
3315 | function getNsPrefix(fullName) {
|
3316 | return fullName === null ? null : splitNsName(fullName)[0];
|
3317 | }
|
3318 | function mergeNsAndName(prefix, localName) {
|
3319 | return prefix ? `:${prefix}:${localName}` : localName;
|
3320 | }
|
3321 |
|
3322 |
|
3323 |
|
3324 |
|
3325 |
|
3326 | const NAMED_ENTITIES = {
|
3327 | 'Aacute': '\u00C1',
|
3328 | 'aacute': '\u00E1',
|
3329 | 'Acirc': '\u00C2',
|
3330 | 'acirc': '\u00E2',
|
3331 | 'acute': '\u00B4',
|
3332 | 'AElig': '\u00C6',
|
3333 | 'aelig': '\u00E6',
|
3334 | 'Agrave': '\u00C0',
|
3335 | 'agrave': '\u00E0',
|
3336 | 'alefsym': '\u2135',
|
3337 | 'Alpha': '\u0391',
|
3338 | 'alpha': '\u03B1',
|
3339 | 'amp': '&',
|
3340 | 'and': '\u2227',
|
3341 | 'ang': '\u2220',
|
3342 | 'apos': '\u0027',
|
3343 | 'Aring': '\u00C5',
|
3344 | 'aring': '\u00E5',
|
3345 | 'asymp': '\u2248',
|
3346 | 'Atilde': '\u00C3',
|
3347 | 'atilde': '\u00E3',
|
3348 | 'Auml': '\u00C4',
|
3349 | 'auml': '\u00E4',
|
3350 | 'bdquo': '\u201E',
|
3351 | 'Beta': '\u0392',
|
3352 | 'beta': '\u03B2',
|
3353 | 'brvbar': '\u00A6',
|
3354 | 'bull': '\u2022',
|
3355 | 'cap': '\u2229',
|
3356 | 'Ccedil': '\u00C7',
|
3357 | 'ccedil': '\u00E7',
|
3358 | 'cedil': '\u00B8',
|
3359 | 'cent': '\u00A2',
|
3360 | 'Chi': '\u03A7',
|
3361 | 'chi': '\u03C7',
|
3362 | 'circ': '\u02C6',
|
3363 | 'clubs': '\u2663',
|
3364 | 'cong': '\u2245',
|
3365 | 'copy': '\u00A9',
|
3366 | 'crarr': '\u21B5',
|
3367 | 'cup': '\u222A',
|
3368 | 'curren': '\u00A4',
|
3369 | 'dagger': '\u2020',
|
3370 | 'Dagger': '\u2021',
|
3371 | 'darr': '\u2193',
|
3372 | 'dArr': '\u21D3',
|
3373 | 'deg': '\u00B0',
|
3374 | 'Delta': '\u0394',
|
3375 | 'delta': '\u03B4',
|
3376 | 'diams': '\u2666',
|
3377 | 'divide': '\u00F7',
|
3378 | 'Eacute': '\u00C9',
|
3379 | 'eacute': '\u00E9',
|
3380 | 'Ecirc': '\u00CA',
|
3381 | 'ecirc': '\u00EA',
|
3382 | 'Egrave': '\u00C8',
|
3383 | 'egrave': '\u00E8',
|
3384 | 'empty': '\u2205',
|
3385 | 'emsp': '\u2003',
|
3386 | 'ensp': '\u2002',
|
3387 | 'Epsilon': '\u0395',
|
3388 | 'epsilon': '\u03B5',
|
3389 | 'equiv': '\u2261',
|
3390 | 'Eta': '\u0397',
|
3391 | 'eta': '\u03B7',
|
3392 | 'ETH': '\u00D0',
|
3393 | 'eth': '\u00F0',
|
3394 | 'Euml': '\u00CB',
|
3395 | 'euml': '\u00EB',
|
3396 | 'euro': '\u20AC',
|
3397 | 'exist': '\u2203',
|
3398 | 'fnof': '\u0192',
|
3399 | 'forall': '\u2200',
|
3400 | 'frac12': '\u00BD',
|
3401 | 'frac14': '\u00BC',
|
3402 | 'frac34': '\u00BE',
|
3403 | 'frasl': '\u2044',
|
3404 | 'Gamma': '\u0393',
|
3405 | 'gamma': '\u03B3',
|
3406 | 'ge': '\u2265',
|
3407 | 'gt': '>',
|
3408 | 'harr': '\u2194',
|
3409 | 'hArr': '\u21D4',
|
3410 | 'hearts': '\u2665',
|
3411 | 'hellip': '\u2026',
|
3412 | 'Iacute': '\u00CD',
|
3413 | 'iacute': '\u00ED',
|
3414 | 'Icirc': '\u00CE',
|
3415 | 'icirc': '\u00EE',
|
3416 | 'iexcl': '\u00A1',
|
3417 | 'Igrave': '\u00CC',
|
3418 | 'igrave': '\u00EC',
|
3419 | 'image': '\u2111',
|
3420 | 'infin': '\u221E',
|
3421 | 'int': '\u222B',
|
3422 | 'Iota': '\u0399',
|
3423 | 'iota': '\u03B9',
|
3424 | 'iquest': '\u00BF',
|
3425 | 'isin': '\u2208',
|
3426 | 'Iuml': '\u00CF',
|
3427 | 'iuml': '\u00EF',
|
3428 | 'Kappa': '\u039A',
|
3429 | 'kappa': '\u03BA',
|
3430 | 'Lambda': '\u039B',
|
3431 | 'lambda': '\u03BB',
|
3432 | 'lang': '\u27E8',
|
3433 | 'laquo': '\u00AB',
|
3434 | 'larr': '\u2190',
|
3435 | 'lArr': '\u21D0',
|
3436 | 'lceil': '\u2308',
|
3437 | 'ldquo': '\u201C',
|
3438 | 'le': '\u2264',
|
3439 | 'lfloor': '\u230A',
|
3440 | 'lowast': '\u2217',
|
3441 | 'loz': '\u25CA',
|
3442 | 'lrm': '\u200E',
|
3443 | 'lsaquo': '\u2039',
|
3444 | 'lsquo': '\u2018',
|
3445 | 'lt': '<',
|
3446 | 'macr': '\u00AF',
|
3447 | 'mdash': '\u2014',
|
3448 | 'micro': '\u00B5',
|
3449 | 'middot': '\u00B7',
|
3450 | 'minus': '\u2212',
|
3451 | 'Mu': '\u039C',
|
3452 | 'mu': '\u03BC',
|
3453 | 'nabla': '\u2207',
|
3454 | 'nbsp': '\u00A0',
|
3455 | 'ndash': '\u2013',
|
3456 | 'ne': '\u2260',
|
3457 | 'ni': '\u220B',
|
3458 | 'not': '\u00AC',
|
3459 | 'notin': '\u2209',
|
3460 | 'nsub': '\u2284',
|
3461 | 'Ntilde': '\u00D1',
|
3462 | 'ntilde': '\u00F1',
|
3463 | 'Nu': '\u039D',
|
3464 | 'nu': '\u03BD',
|
3465 | 'Oacute': '\u00D3',
|
3466 | 'oacute': '\u00F3',
|
3467 | 'Ocirc': '\u00D4',
|
3468 | 'ocirc': '\u00F4',
|
3469 | 'OElig': '\u0152',
|
3470 | 'oelig': '\u0153',
|
3471 | 'Ograve': '\u00D2',
|
3472 | 'ograve': '\u00F2',
|
3473 | 'oline': '\u203E',
|
3474 | 'Omega': '\u03A9',
|
3475 | 'omega': '\u03C9',
|
3476 | 'Omicron': '\u039F',
|
3477 | 'omicron': '\u03BF',
|
3478 | 'oplus': '\u2295',
|
3479 | 'or': '\u2228',
|
3480 | 'ordf': '\u00AA',
|
3481 | 'ordm': '\u00BA',
|
3482 | 'Oslash': '\u00D8',
|
3483 | 'oslash': '\u00F8',
|
3484 | 'Otilde': '\u00D5',
|
3485 | 'otilde': '\u00F5',
|
3486 | 'otimes': '\u2297',
|
3487 | 'Ouml': '\u00D6',
|
3488 | 'ouml': '\u00F6',
|
3489 | 'para': '\u00B6',
|
3490 | 'permil': '\u2030',
|
3491 | 'perp': '\u22A5',
|
3492 | 'Phi': '\u03A6',
|
3493 | 'phi': '\u03C6',
|
3494 | 'Pi': '\u03A0',
|
3495 | 'pi': '\u03C0',
|
3496 | 'piv': '\u03D6',
|
3497 | 'plusmn': '\u00B1',
|
3498 | 'pound': '\u00A3',
|
3499 | 'prime': '\u2032',
|
3500 | 'Prime': '\u2033',
|
3501 | 'prod': '\u220F',
|
3502 | 'prop': '\u221D',
|
3503 | 'Psi': '\u03A8',
|
3504 | 'psi': '\u03C8',
|
3505 | 'quot': '\u0022',
|
3506 | 'radic': '\u221A',
|
3507 | 'rang': '\u27E9',
|
3508 | 'raquo': '\u00BB',
|
3509 | 'rarr': '\u2192',
|
3510 | 'rArr': '\u21D2',
|
3511 | 'rceil': '\u2309',
|
3512 | 'rdquo': '\u201D',
|
3513 | 'real': '\u211C',
|
3514 | 'reg': '\u00AE',
|
3515 | 'rfloor': '\u230B',
|
3516 | 'Rho': '\u03A1',
|
3517 | 'rho': '\u03C1',
|
3518 | 'rlm': '\u200F',
|
3519 | 'rsaquo': '\u203A',
|
3520 | 'rsquo': '\u2019',
|
3521 | 'sbquo': '\u201A',
|
3522 | 'Scaron': '\u0160',
|
3523 | 'scaron': '\u0161',
|
3524 | 'sdot': '\u22C5',
|
3525 | 'sect': '\u00A7',
|
3526 | 'shy': '\u00AD',
|
3527 | 'Sigma': '\u03A3',
|
3528 | 'sigma': '\u03C3',
|
3529 | 'sigmaf': '\u03C2',
|
3530 | 'sim': '\u223C',
|
3531 | 'spades': '\u2660',
|
3532 | 'sub': '\u2282',
|
3533 | 'sube': '\u2286',
|
3534 | 'sum': '\u2211',
|
3535 | 'sup': '\u2283',
|
3536 | 'sup1': '\u00B9',
|
3537 | 'sup2': '\u00B2',
|
3538 | 'sup3': '\u00B3',
|
3539 | 'supe': '\u2287',
|
3540 | 'szlig': '\u00DF',
|
3541 | 'Tau': '\u03A4',
|
3542 | 'tau': '\u03C4',
|
3543 | 'there4': '\u2234',
|
3544 | 'Theta': '\u0398',
|
3545 | 'theta': '\u03B8',
|
3546 | 'thetasym': '\u03D1',
|
3547 | 'thinsp': '\u2009',
|
3548 | 'THORN': '\u00DE',
|
3549 | 'thorn': '\u00FE',
|
3550 | 'tilde': '\u02DC',
|
3551 | 'times': '\u00D7',
|
3552 | 'trade': '\u2122',
|
3553 | 'Uacute': '\u00DA',
|
3554 | 'uacute': '\u00FA',
|
3555 | 'uarr': '\u2191',
|
3556 | 'uArr': '\u21D1',
|
3557 | 'Ucirc': '\u00DB',
|
3558 | 'ucirc': '\u00FB',
|
3559 | 'Ugrave': '\u00D9',
|
3560 | 'ugrave': '\u00F9',
|
3561 | 'uml': '\u00A8',
|
3562 | 'upsih': '\u03D2',
|
3563 | 'Upsilon': '\u03A5',
|
3564 | 'upsilon': '\u03C5',
|
3565 | 'Uuml': '\u00DC',
|
3566 | 'uuml': '\u00FC',
|
3567 | 'weierp': '\u2118',
|
3568 | 'Xi': '\u039E',
|
3569 | 'xi': '\u03BE',
|
3570 | 'Yacute': '\u00DD',
|
3571 | 'yacute': '\u00FD',
|
3572 | 'yen': '\u00A5',
|
3573 | 'yuml': '\u00FF',
|
3574 | 'Yuml': '\u0178',
|
3575 | 'Zeta': '\u0396',
|
3576 | 'zeta': '\u03B6',
|
3577 | 'zwj': '\u200D',
|
3578 | 'zwnj': '\u200C',
|
3579 | };
|
3580 |
|
3581 |
|
3582 | const NGSP_UNICODE = '\uE500';
|
3583 | NAMED_ENTITIES['ngsp'] = NGSP_UNICODE;
|
3584 |
|
3585 | |
3586 |
|
3587 |
|
3588 |
|
3589 |
|
3590 |
|
3591 |
|
3592 | class HtmlTagDefinition {
|
3593 | constructor({ closedByChildren, implicitNamespacePrefix, contentType = TagContentType.PARSABLE_DATA, closedByParent = false, isVoid = false, ignoreFirstLf = false, preventNamespaceInheritance = false } = {}) {
|
3594 | this.closedByChildren = {};
|
3595 | this.closedByParent = false;
|
3596 | this.canSelfClose = false;
|
3597 | if (closedByChildren && closedByChildren.length > 0) {
|
3598 | closedByChildren.forEach(tagName => this.closedByChildren[tagName] = true);
|
3599 | }
|
3600 | this.isVoid = isVoid;
|
3601 | this.closedByParent = closedByParent || isVoid;
|
3602 | this.implicitNamespacePrefix = implicitNamespacePrefix || null;
|
3603 | this.contentType = contentType;
|
3604 | this.ignoreFirstLf = ignoreFirstLf;
|
3605 | this.preventNamespaceInheritance = preventNamespaceInheritance;
|
3606 | }
|
3607 | isClosedByChild(name) {
|
3608 | return this.isVoid || name.toLowerCase() in this.closedByChildren;
|
3609 | }
|
3610 | getContentType(prefix) {
|
3611 | if (typeof this.contentType === 'object') {
|
3612 | const overrideType = prefix == null ? undefined : this.contentType[prefix];
|
3613 | return overrideType !== null && overrideType !== void 0 ? overrideType : this.contentType.default;
|
3614 | }
|
3615 | return this.contentType;
|
3616 | }
|
3617 | }
|
3618 | let _DEFAULT_TAG_DEFINITION;
|
3619 |
|
3620 |
|
3621 | let TAG_DEFINITIONS;
|
3622 | function getHtmlTagDefinition(tagName) {
|
3623 | var _a, _b;
|
3624 | if (!TAG_DEFINITIONS) {
|
3625 | _DEFAULT_TAG_DEFINITION = new HtmlTagDefinition();
|
3626 | TAG_DEFINITIONS = {
|
3627 | 'base': new HtmlTagDefinition({ isVoid: true }),
|
3628 | 'meta': new HtmlTagDefinition({ isVoid: true }),
|
3629 | 'area': new HtmlTagDefinition({ isVoid: true }),
|
3630 | 'embed': new HtmlTagDefinition({ isVoid: true }),
|
3631 | 'link': new HtmlTagDefinition({ isVoid: true }),
|
3632 | 'img': new HtmlTagDefinition({ isVoid: true }),
|
3633 | 'input': new HtmlTagDefinition({ isVoid: true }),
|
3634 | 'param': new HtmlTagDefinition({ isVoid: true }),
|
3635 | 'hr': new HtmlTagDefinition({ isVoid: true }),
|
3636 | 'br': new HtmlTagDefinition({ isVoid: true }),
|
3637 | 'source': new HtmlTagDefinition({ isVoid: true }),
|
3638 | 'track': new HtmlTagDefinition({ isVoid: true }),
|
3639 | 'wbr': new HtmlTagDefinition({ isVoid: true }),
|
3640 | 'p': new HtmlTagDefinition({
|
3641 | closedByChildren: [
|
3642 | 'address', 'article', 'aside', 'blockquote', 'div', 'dl', 'fieldset',
|
3643 | 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5',
|
3644 | 'h6', 'header', 'hgroup', 'hr', 'main', 'nav', 'ol',
|
3645 | 'p', 'pre', 'section', 'table', 'ul'
|
3646 | ],
|
3647 | closedByParent: true
|
3648 | }),
|
3649 | 'thead': new HtmlTagDefinition({ closedByChildren: ['tbody', 'tfoot'] }),
|
3650 | 'tbody': new HtmlTagDefinition({ closedByChildren: ['tbody', 'tfoot'], closedByParent: true }),
|
3651 | 'tfoot': new HtmlTagDefinition({ closedByChildren: ['tbody'], closedByParent: true }),
|
3652 | 'tr': new HtmlTagDefinition({ closedByChildren: ['tr'], closedByParent: true }),
|
3653 | 'td': new HtmlTagDefinition({ closedByChildren: ['td', 'th'], closedByParent: true }),
|
3654 | 'th': new HtmlTagDefinition({ closedByChildren: ['td', 'th'], closedByParent: true }),
|
3655 | 'col': new HtmlTagDefinition({ isVoid: true }),
|
3656 | 'svg': new HtmlTagDefinition({ implicitNamespacePrefix: 'svg' }),
|
3657 | 'foreignObject': new HtmlTagDefinition({
|
3658 |
|
3659 |
|
3660 |
|
3661 |
|
3662 |
|
3663 | implicitNamespacePrefix: 'svg',
|
3664 |
|
3665 |
|
3666 | preventNamespaceInheritance: true,
|
3667 | }),
|
3668 | 'math': new HtmlTagDefinition({ implicitNamespacePrefix: 'math' }),
|
3669 | 'li': new HtmlTagDefinition({ closedByChildren: ['li'], closedByParent: true }),
|
3670 | 'dt': new HtmlTagDefinition({ closedByChildren: ['dt', 'dd'] }),
|
3671 | 'dd': new HtmlTagDefinition({ closedByChildren: ['dt', 'dd'], closedByParent: true }),
|
3672 | 'rb': new HtmlTagDefinition({ closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true }),
|
3673 | 'rt': new HtmlTagDefinition({ closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true }),
|
3674 | 'rtc': new HtmlTagDefinition({ closedByChildren: ['rb', 'rtc', 'rp'], closedByParent: true }),
|
3675 | 'rp': new HtmlTagDefinition({ closedByChildren: ['rb', 'rt', 'rtc', 'rp'], closedByParent: true }),
|
3676 | 'optgroup': new HtmlTagDefinition({ closedByChildren: ['optgroup'], closedByParent: true }),
|
3677 | 'option': new HtmlTagDefinition({ closedByChildren: ['option', 'optgroup'], closedByParent: true }),
|
3678 | 'pre': new HtmlTagDefinition({ ignoreFirstLf: true }),
|
3679 | 'listing': new HtmlTagDefinition({ ignoreFirstLf: true }),
|
3680 | 'style': new HtmlTagDefinition({ contentType: TagContentType.RAW_TEXT }),
|
3681 | 'script': new HtmlTagDefinition({ contentType: TagContentType.RAW_TEXT }),
|
3682 | 'title': new HtmlTagDefinition({
|
3683 |
|
3684 |
|
3685 | contentType: { default: TagContentType.ESCAPABLE_RAW_TEXT, svg: TagContentType.PARSABLE_DATA }
|
3686 | }),
|
3687 | 'textarea': new HtmlTagDefinition({ contentType: TagContentType.ESCAPABLE_RAW_TEXT, ignoreFirstLf: true }),
|
3688 | };
|
3689 | }
|
3690 |
|
3691 |
|
3692 | return (_b = (_a = TAG_DEFINITIONS[tagName]) !== null && _a !== void 0 ? _a : TAG_DEFINITIONS[tagName.toLowerCase()]) !== null && _b !== void 0 ? _b : _DEFAULT_TAG_DEFINITION;
|
3693 | }
|
3694 |
|
3695 | |
3696 |
|
3697 |
|
3698 |
|
3699 |
|
3700 |
|
3701 |
|
3702 | const _SELECTOR_REGEXP = new RegExp('(\\:not\\()|' +
|
3703 | '(([\\.\\#]?)[-\\w]+)|' +
|
3704 |
|
3705 |
|
3706 | '(?:\\[([-.\\w*]+)(?:=([\"\']?)([^\\]\"\']*)\\5)?\\])|' +
|
3707 |
|
3708 |
|
3709 | '(\\))|' +
|
3710 | '(\\s*,\\s*)',
|
3711 | 'g');
|
3712 | |
3713 |
|
3714 |
|
3715 |
|
3716 |
|
3717 | class CssSelector {
|
3718 | constructor() {
|
3719 | this.element = null;
|
3720 | this.classNames = [];
|
3721 | |
3722 |
|
3723 |
|
3724 |
|
3725 |
|
3726 |
|
3727 |
|
3728 |
|
3729 |
|
3730 |
|
3731 |
|
3732 | this.attrs = [];
|
3733 | this.notSelectors = [];
|
3734 | }
|
3735 | static parse(selector) {
|
3736 | const results = [];
|
3737 | const _addResult = (res, cssSel) => {
|
3738 | if (cssSel.notSelectors.length > 0 && !cssSel.element && cssSel.classNames.length == 0 &&
|
3739 | cssSel.attrs.length == 0) {
|
3740 | cssSel.element = '*';
|
3741 | }
|
3742 | res.push(cssSel);
|
3743 | };
|
3744 | let cssSelector = new CssSelector();
|
3745 | let match;
|
3746 | let current = cssSelector;
|
3747 | let inNot = false;
|
3748 | _SELECTOR_REGEXP.lastIndex = 0;
|
3749 | while (match = _SELECTOR_REGEXP.exec(selector)) {
|
3750 | if (match[1 ]) {
|
3751 | if (inNot) {
|
3752 | throw new Error('Nesting :not in a selector is not allowed');
|
3753 | }
|
3754 | inNot = true;
|
3755 | current = new CssSelector();
|
3756 | cssSelector.notSelectors.push(current);
|
3757 | }
|
3758 | const tag = match[2 ];
|
3759 | if (tag) {
|
3760 | const prefix = match[3 ];
|
3761 | if (prefix === '#') {
|
3762 |
|
3763 | current.addAttribute('id', tag.substr(1));
|
3764 | }
|
3765 | else if (prefix === '.') {
|
3766 |
|
3767 | current.addClassName(tag.substr(1));
|
3768 | }
|
3769 | else {
|
3770 |
|
3771 | current.setElement(tag);
|
3772 | }
|
3773 | }
|
3774 | const attribute = match[4 ];
|
3775 | if (attribute) {
|
3776 | current.addAttribute(attribute, match[6 ]);
|
3777 | }
|
3778 | if (match[7 ]) {
|
3779 | inNot = false;
|
3780 | current = cssSelector;
|
3781 | }
|
3782 | if (match[8 ]) {
|
3783 | if (inNot) {
|
3784 | throw new Error('Multiple selectors in :not are not supported');
|
3785 | }
|
3786 | _addResult(results, cssSelector);
|
3787 | cssSelector = current = new CssSelector();
|
3788 | }
|
3789 | }
|
3790 | _addResult(results, cssSelector);
|
3791 | return results;
|
3792 | }
|
3793 | isElementSelector() {
|
3794 | return this.hasElementSelector() && this.classNames.length == 0 && this.attrs.length == 0 &&
|
3795 | this.notSelectors.length === 0;
|
3796 | }
|
3797 | hasElementSelector() {
|
3798 | return !!this.element;
|
3799 | }
|
3800 | setElement(element = null) {
|
3801 | this.element = element;
|
3802 | }
|
3803 |
|
3804 | getMatchingElementTemplate() {
|
3805 | const tagName = this.element || 'div';
|
3806 | const classAttr = this.classNames.length > 0 ? ` class="${this.classNames.join(' ')}"` : '';
|
3807 | let attrs = '';
|
3808 | for (let i = 0; i < this.attrs.length; i += 2) {
|
3809 | const attrName = this.attrs[i];
|
3810 | const attrValue = this.attrs[i + 1] !== '' ? `="${this.attrs[i + 1]}"` : '';
|
3811 | attrs += ` ${attrName}${attrValue}`;
|
3812 | }
|
3813 | return getHtmlTagDefinition(tagName).isVoid ? `<${tagName}${classAttr}${attrs}/>` :
|
3814 | `<${tagName}${classAttr}${attrs}></${tagName}>`;
|
3815 | }
|
3816 | getAttrs() {
|
3817 | const result = [];
|
3818 | if (this.classNames.length > 0) {
|
3819 | result.push('class', this.classNames.join(' '));
|
3820 | }
|
3821 | return result.concat(this.attrs);
|
3822 | }
|
3823 | addAttribute(name, value = '') {
|
3824 | this.attrs.push(name, value && value.toLowerCase() || '');
|
3825 | }
|
3826 | addClassName(name) {
|
3827 | this.classNames.push(name.toLowerCase());
|
3828 | }
|
3829 | toString() {
|
3830 | let res = this.element || '';
|
3831 | if (this.classNames) {
|
3832 | this.classNames.forEach(klass => res += `.${klass}`);
|
3833 | }
|
3834 | if (this.attrs) {
|
3835 | for (let i = 0; i < this.attrs.length; i += 2) {
|
3836 | const name = this.attrs[i];
|
3837 | const value = this.attrs[i + 1];
|
3838 | res += `[${name}${value ? '=' + value : ''}]`;
|
3839 | }
|
3840 | }
|
3841 | this.notSelectors.forEach(notSelector => res += `:not(${notSelector})`);
|
3842 | return res;
|
3843 | }
|
3844 | }
|
3845 | |
3846 |
|
3847 |
|
3848 |
|
3849 | class SelectorMatcher {
|
3850 | constructor() {
|
3851 | this._elementMap = new Map();
|
3852 | this._elementPartialMap = new Map();
|
3853 | this._classMap = new Map();
|
3854 | this._classPartialMap = new Map();
|
3855 | this._attrValueMap = new Map();
|
3856 | this._attrValuePartialMap = new Map();
|
3857 | this._listContexts = [];
|
3858 | }
|
3859 | static createNotMatcher(notSelectors) {
|
3860 | const notMatcher = new SelectorMatcher();
|
3861 | notMatcher.addSelectables(notSelectors, null);
|
3862 | return notMatcher;
|
3863 | }
|
3864 | addSelectables(cssSelectors, callbackCtxt) {
|
3865 | let listContext = null;
|
3866 | if (cssSelectors.length > 1) {
|
3867 | listContext = new SelectorListContext(cssSelectors);
|
3868 | this._listContexts.push(listContext);
|
3869 | }
|
3870 | for (let i = 0; i < cssSelectors.length; i++) {
|
3871 | this._addSelectable(cssSelectors[i], callbackCtxt, listContext);
|
3872 | }
|
3873 | }
|
3874 | |
3875 |
|
3876 |
|
3877 |
|
3878 |
|
3879 | _addSelectable(cssSelector, callbackCtxt, listContext) {
|
3880 | let matcher = this;
|
3881 | const element = cssSelector.element;
|
3882 | const classNames = cssSelector.classNames;
|
3883 | const attrs = cssSelector.attrs;
|
3884 | const selectable = new SelectorContext(cssSelector, callbackCtxt, listContext);
|
3885 | if (element) {
|
3886 | const isTerminal = attrs.length === 0 && classNames.length === 0;
|
3887 | if (isTerminal) {
|
3888 | this._addTerminal(matcher._elementMap, element, selectable);
|
3889 | }
|
3890 | else {
|
3891 | matcher = this._addPartial(matcher._elementPartialMap, element);
|
3892 | }
|
3893 | }
|
3894 | if (classNames) {
|
3895 | for (let i = 0; i < classNames.length; i++) {
|
3896 | const isTerminal = attrs.length === 0 && i === classNames.length - 1;
|
3897 | const className = classNames[i];
|
3898 | if (isTerminal) {
|
3899 | this._addTerminal(matcher._classMap, className, selectable);
|
3900 | }
|
3901 | else {
|
3902 | matcher = this._addPartial(matcher._classPartialMap, className);
|
3903 | }
|
3904 | }
|
3905 | }
|
3906 | if (attrs) {
|
3907 | for (let i = 0; i < attrs.length; i += 2) {
|
3908 | const isTerminal = i === attrs.length - 2;
|
3909 | const name = attrs[i];
|
3910 | const value = attrs[i + 1];
|
3911 | if (isTerminal) {
|
3912 | const terminalMap = matcher._attrValueMap;
|
3913 | let terminalValuesMap = terminalMap.get(name);
|
3914 | if (!terminalValuesMap) {
|
3915 | terminalValuesMap = new Map();
|
3916 | terminalMap.set(name, terminalValuesMap);
|
3917 | }
|
3918 | this._addTerminal(terminalValuesMap, value, selectable);
|
3919 | }
|
3920 | else {
|
3921 | const partialMap = matcher._attrValuePartialMap;
|
3922 | let partialValuesMap = partialMap.get(name);
|
3923 | if (!partialValuesMap) {
|
3924 | partialValuesMap = new Map();
|
3925 | partialMap.set(name, partialValuesMap);
|
3926 | }
|
3927 | matcher = this._addPartial(partialValuesMap, value);
|
3928 | }
|
3929 | }
|
3930 | }
|
3931 | }
|
3932 | _addTerminal(map, name, selectable) {
|
3933 | let terminalList = map.get(name);
|
3934 | if (!terminalList) {
|
3935 | terminalList = [];
|
3936 | map.set(name, terminalList);
|
3937 | }
|
3938 | terminalList.push(selectable);
|
3939 | }
|
3940 | _addPartial(map, name) {
|
3941 | let matcher = map.get(name);
|
3942 | if (!matcher) {
|
3943 | matcher = new SelectorMatcher();
|
3944 | map.set(name, matcher);
|
3945 | }
|
3946 | return matcher;
|
3947 | }
|
3948 | |
3949 |
|
3950 |
|
3951 |
|
3952 |
|
3953 |
|
3954 |
|
3955 | match(cssSelector, matchedCallback) {
|
3956 | let result = false;
|
3957 | const element = cssSelector.element;
|
3958 | const classNames = cssSelector.classNames;
|
3959 | const attrs = cssSelector.attrs;
|
3960 | for (let i = 0; i < this._listContexts.length; i++) {
|
3961 | this._listContexts[i].alreadyMatched = false;
|
3962 | }
|
3963 | result = this._matchTerminal(this._elementMap, element, cssSelector, matchedCallback) || result;
|
3964 | result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) ||
|
3965 | result;
|
3966 | if (classNames) {
|
3967 | for (let i = 0; i < classNames.length; i++) {
|
3968 | const className = classNames[i];
|
3969 | result =
|
3970 | this._matchTerminal(this._classMap, className, cssSelector, matchedCallback) || result;
|
3971 | result =
|
3972 | this._matchPartial(this._classPartialMap, className, cssSelector, matchedCallback) ||
|
3973 | result;
|
3974 | }
|
3975 | }
|
3976 | if (attrs) {
|
3977 | for (let i = 0; i < attrs.length; i += 2) {
|
3978 | const name = attrs[i];
|
3979 | const value = attrs[i + 1];
|
3980 | const terminalValuesMap = this._attrValueMap.get(name);
|
3981 | if (value) {
|
3982 | result =
|
3983 | this._matchTerminal(terminalValuesMap, '', cssSelector, matchedCallback) || result;
|
3984 | }
|
3985 | result =
|
3986 | this._matchTerminal(terminalValuesMap, value, cssSelector, matchedCallback) || result;
|
3987 | const partialValuesMap = this._attrValuePartialMap.get(name);
|
3988 | if (value) {
|
3989 | result = this._matchPartial(partialValuesMap, '', cssSelector, matchedCallback) || result;
|
3990 | }
|
3991 | result =
|
3992 | this._matchPartial(partialValuesMap, value, cssSelector, matchedCallback) || result;
|
3993 | }
|
3994 | }
|
3995 | return result;
|
3996 | }
|
3997 |
|
3998 | _matchTerminal(map, name, cssSelector, matchedCallback) {
|
3999 | if (!map || typeof name !== 'string') {
|
4000 | return false;
|
4001 | }
|
4002 | let selectables = map.get(name) || [];
|
4003 | const starSelectables = map.get('*');
|
4004 | if (starSelectables) {
|
4005 | selectables = selectables.concat(starSelectables);
|
4006 | }
|
4007 | if (selectables.length === 0) {
|
4008 | return false;
|
4009 | }
|
4010 | let selectable;
|
4011 | let result = false;
|
4012 | for (let i = 0; i < selectables.length; i++) {
|
4013 | selectable = selectables[i];
|
4014 | result = selectable.finalize(cssSelector, matchedCallback) || result;
|
4015 | }
|
4016 | return result;
|
4017 | }
|
4018 |
|
4019 | _matchPartial(map, name, cssSelector, matchedCallback) {
|
4020 | if (!map || typeof name !== 'string') {
|
4021 | return false;
|
4022 | }
|
4023 | const nestedSelector = map.get(name);
|
4024 | if (!nestedSelector) {
|
4025 | return false;
|
4026 | }
|
4027 |
|
4028 |
|
4029 |
|
4030 | return nestedSelector.match(cssSelector, matchedCallback);
|
4031 | }
|
4032 | }
|
4033 | class SelectorListContext {
|
4034 | constructor(selectors) {
|
4035 | this.selectors = selectors;
|
4036 | this.alreadyMatched = false;
|
4037 | }
|
4038 | }
|
4039 |
|
4040 | class SelectorContext {
|
4041 | constructor(selector, cbContext, listContext) {
|
4042 | this.selector = selector;
|
4043 | this.cbContext = cbContext;
|
4044 | this.listContext = listContext;
|
4045 | this.notSelectors = selector.notSelectors;
|
4046 | }
|
4047 | finalize(cssSelector, callback) {
|
4048 | let result = true;
|
4049 | if (this.notSelectors.length > 0 && (!this.listContext || !this.listContext.alreadyMatched)) {
|
4050 | const notMatcher = SelectorMatcher.createNotMatcher(this.notSelectors);
|
4051 | result = !notMatcher.match(cssSelector, null);
|
4052 | }
|
4053 | if (result && callback && (!this.listContext || !this.listContext.alreadyMatched)) {
|
4054 | if (this.listContext) {
|
4055 | this.listContext.alreadyMatched = true;
|
4056 | }
|
4057 | callback(this.selector, this.cbContext);
|
4058 | }
|
4059 | return result;
|
4060 | }
|
4061 | }
|
4062 |
|
4063 | |
4064 |
|
4065 |
|
4066 |
|
4067 |
|
4068 |
|
4069 |
|
4070 |
|
4071 |
|
4072 |
|
4073 | const emitDistinctChangesOnlyDefaultValue = false;
|
4074 | var ViewEncapsulation;
|
4075 | (function (ViewEncapsulation) {
|
4076 | ViewEncapsulation[ViewEncapsulation["Emulated"] = 0] = "Emulated";
|
4077 |
|
4078 | ViewEncapsulation[ViewEncapsulation["None"] = 2] = "None";
|
4079 | ViewEncapsulation[ViewEncapsulation["ShadowDom"] = 3] = "ShadowDom";
|
4080 | })(ViewEncapsulation || (ViewEncapsulation = {}));
|
4081 | var ChangeDetectionStrategy;
|
4082 | (function (ChangeDetectionStrategy) {
|
4083 | ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush";
|
4084 | ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default";
|
4085 | })(ChangeDetectionStrategy || (ChangeDetectionStrategy = {}));
|
4086 | const CUSTOM_ELEMENTS_SCHEMA = {
|
4087 | name: 'custom-elements'
|
4088 | };
|
4089 | const NO_ERRORS_SCHEMA = {
|
4090 | name: 'no-errors-schema'
|
4091 | };
|
4092 | var SecurityContext;
|
4093 | (function (SecurityContext) {
|
4094 | SecurityContext[SecurityContext["NONE"] = 0] = "NONE";
|
4095 | SecurityContext[SecurityContext["HTML"] = 1] = "HTML";
|
4096 | SecurityContext[SecurityContext["STYLE"] = 2] = "STYLE";
|
4097 | SecurityContext[SecurityContext["SCRIPT"] = 3] = "SCRIPT";
|
4098 | SecurityContext[SecurityContext["URL"] = 4] = "URL";
|
4099 | SecurityContext[SecurityContext["RESOURCE_URL"] = 5] = "RESOURCE_URL";
|
4100 | })(SecurityContext || (SecurityContext = {}));
|
4101 | var MissingTranslationStrategy;
|
4102 | (function (MissingTranslationStrategy) {
|
4103 | MissingTranslationStrategy[MissingTranslationStrategy["Error"] = 0] = "Error";
|
4104 | MissingTranslationStrategy[MissingTranslationStrategy["Warning"] = 1] = "Warning";
|
4105 | MissingTranslationStrategy[MissingTranslationStrategy["Ignore"] = 2] = "Ignore";
|
4106 | })(MissingTranslationStrategy || (MissingTranslationStrategy = {}));
|
4107 | function parserSelectorToSimpleSelector(selector) {
|
4108 | const classes = selector.classNames && selector.classNames.length ?
|
4109 | [8 , ...selector.classNames] :
|
4110 | [];
|
4111 | const elementName = selector.element && selector.element !== '*' ? selector.element : '';
|
4112 | return [elementName, ...selector.attrs, ...classes];
|
4113 | }
|
4114 | function parserSelectorToNegativeSelector(selector) {
|
4115 | const classes = selector.classNames && selector.classNames.length ?
|
4116 | [8 , ...selector.classNames] :
|
4117 | [];
|
4118 | if (selector.element) {
|
4119 | return [
|
4120 | 1 | 4 , selector.element, ...selector.attrs, ...classes
|
4121 | ];
|
4122 | }
|
4123 | else if (selector.attrs.length) {
|
4124 | return [1 | 2 , ...selector.attrs, ...classes];
|
4125 | }
|
4126 | else {
|
4127 | return selector.classNames && selector.classNames.length ?
|
4128 | [1 | 8 , ...selector.classNames] :
|
4129 | [];
|
4130 | }
|
4131 | }
|
4132 | function parserSelectorToR3Selector(selector) {
|
4133 | const positive = parserSelectorToSimpleSelector(selector);
|
4134 | const negative = selector.notSelectors && selector.notSelectors.length ?
|
4135 | selector.notSelectors.map(notSelector => parserSelectorToNegativeSelector(notSelector)) :
|
4136 | [];
|
4137 | return positive.concat(...negative);
|
4138 | }
|
4139 | function parseSelectorToR3Selector(selector) {
|
4140 | return selector ? CssSelector.parse(selector).map(parserSelectorToR3Selector) : [];
|
4141 | }
|
4142 |
|
4143 | |
4144 |
|
4145 |
|
4146 |
|
4147 |
|
4148 |
|
4149 |
|
4150 |
|
4151 | var TypeModifier;
|
4152 | (function (TypeModifier) {
|
4153 | TypeModifier[TypeModifier["Const"] = 0] = "Const";
|
4154 | })(TypeModifier || (TypeModifier = {}));
|
4155 | class Type {
|
4156 | constructor(modifiers = []) {
|
4157 | this.modifiers = modifiers;
|
4158 | }
|
4159 | hasModifier(modifier) {
|
4160 | return this.modifiers.indexOf(modifier) !== -1;
|
4161 | }
|
4162 | }
|
4163 | var BuiltinTypeName;
|
4164 | (function (BuiltinTypeName) {
|
4165 | BuiltinTypeName[BuiltinTypeName["Dynamic"] = 0] = "Dynamic";
|
4166 | BuiltinTypeName[BuiltinTypeName["Bool"] = 1] = "Bool";
|
4167 | BuiltinTypeName[BuiltinTypeName["String"] = 2] = "String";
|
4168 | BuiltinTypeName[BuiltinTypeName["Int"] = 3] = "Int";
|
4169 | BuiltinTypeName[BuiltinTypeName["Number"] = 4] = "Number";
|
4170 | BuiltinTypeName[BuiltinTypeName["Function"] = 5] = "Function";
|
4171 | BuiltinTypeName[BuiltinTypeName["Inferred"] = 6] = "Inferred";
|
4172 | BuiltinTypeName[BuiltinTypeName["None"] = 7] = "None";
|
4173 | })(BuiltinTypeName || (BuiltinTypeName = {}));
|
4174 | class BuiltinType extends Type {
|
4175 | constructor(name, modifiers) {
|
4176 | super(modifiers);
|
4177 | this.name = name;
|
4178 | }
|
4179 | visitType(visitor, context) {
|
4180 | return visitor.visitBuiltinType(this, context);
|
4181 | }
|
4182 | }
|
4183 | class ExpressionType extends Type {
|
4184 | constructor(value, modifiers, typeParams = null) {
|
4185 | super(modifiers);
|
4186 | this.value = value;
|
4187 | this.typeParams = typeParams;
|
4188 | }
|
4189 | visitType(visitor, context) {
|
4190 | return visitor.visitExpressionType(this, context);
|
4191 | }
|
4192 | }
|
4193 | const DYNAMIC_TYPE = new BuiltinType(BuiltinTypeName.Dynamic);
|
4194 | const INFERRED_TYPE = new BuiltinType(BuiltinTypeName.Inferred);
|
4195 | const BOOL_TYPE = new BuiltinType(BuiltinTypeName.Bool);
|
4196 | const INT_TYPE = new BuiltinType(BuiltinTypeName.Int);
|
4197 | const NUMBER_TYPE = new BuiltinType(BuiltinTypeName.Number);
|
4198 | const STRING_TYPE = new BuiltinType(BuiltinTypeName.String);
|
4199 | const FUNCTION_TYPE = new BuiltinType(BuiltinTypeName.Function);
|
4200 | const NONE_TYPE = new BuiltinType(BuiltinTypeName.None);
|
4201 |
|
4202 | var UnaryOperator;
|
4203 | (function (UnaryOperator) {
|
4204 | UnaryOperator[UnaryOperator["Minus"] = 0] = "Minus";
|
4205 | UnaryOperator[UnaryOperator["Plus"] = 1] = "Plus";
|
4206 | })(UnaryOperator || (UnaryOperator = {}));
|
4207 | var BinaryOperator;
|
4208 | (function (BinaryOperator) {
|
4209 | BinaryOperator[BinaryOperator["Equals"] = 0] = "Equals";
|
4210 | BinaryOperator[BinaryOperator["NotEquals"] = 1] = "NotEquals";
|
4211 | BinaryOperator[BinaryOperator["Identical"] = 2] = "Identical";
|
4212 | BinaryOperator[BinaryOperator["NotIdentical"] = 3] = "NotIdentical";
|
4213 | BinaryOperator[BinaryOperator["Minus"] = 4] = "Minus";
|
4214 | BinaryOperator[BinaryOperator["Plus"] = 5] = "Plus";
|
4215 | BinaryOperator[BinaryOperator["Divide"] = 6] = "Divide";
|
4216 | BinaryOperator[BinaryOperator["Multiply"] = 7] = "Multiply";
|
4217 | BinaryOperator[BinaryOperator["Modulo"] = 8] = "Modulo";
|
4218 | BinaryOperator[BinaryOperator["And"] = 9] = "And";
|
4219 | BinaryOperator[BinaryOperator["Or"] = 10] = "Or";
|
4220 | BinaryOperator[BinaryOperator["BitwiseAnd"] = 11] = "BitwiseAnd";
|
4221 | BinaryOperator[BinaryOperator["Lower"] = 12] = "Lower";
|
4222 | BinaryOperator[BinaryOperator["LowerEquals"] = 13] = "LowerEquals";
|
4223 | BinaryOperator[BinaryOperator["Bigger"] = 14] = "Bigger";
|
4224 | BinaryOperator[BinaryOperator["BiggerEquals"] = 15] = "BiggerEquals";
|
4225 | })(BinaryOperator || (BinaryOperator = {}));
|
4226 | function nullSafeIsEquivalent(base, other) {
|
4227 | if (base == null || other == null) {
|
4228 | return base == other;
|
4229 | }
|
4230 | return base.isEquivalent(other);
|
4231 | }
|
4232 | function areAllEquivalentPredicate(base, other, equivalentPredicate) {
|
4233 | const len = base.length;
|
4234 | if (len !== other.length) {
|
4235 | return false;
|
4236 | }
|
4237 | for (let i = 0; i < len; i++) {
|
4238 | if (!equivalentPredicate(base[i], other[i])) {
|
4239 | return false;
|
4240 | }
|
4241 | }
|
4242 | return true;
|
4243 | }
|
4244 | function areAllEquivalent(base, other) {
|
4245 | return areAllEquivalentPredicate(base, other, (baseElement, otherElement) => baseElement.isEquivalent(otherElement));
|
4246 | }
|
4247 | class Expression {
|
4248 | constructor(type, sourceSpan) {
|
4249 | this.type = type || null;
|
4250 | this.sourceSpan = sourceSpan || null;
|
4251 | }
|
4252 | prop(name, sourceSpan) {
|
4253 | return new ReadPropExpr(this, name, null, sourceSpan);
|
4254 | }
|
4255 | key(index, type, sourceSpan) {
|
4256 | return new ReadKeyExpr(this, index, type, sourceSpan);
|
4257 | }
|
4258 | callMethod(name, params, sourceSpan) {
|
4259 | return new InvokeMethodExpr(this, name, params, null, sourceSpan);
|
4260 | }
|
4261 | callFn(params, sourceSpan, pure) {
|
4262 | return new InvokeFunctionExpr(this, params, null, sourceSpan, pure);
|
4263 | }
|
4264 | instantiate(params, type, sourceSpan) {
|
4265 | return new InstantiateExpr(this, params, type, sourceSpan);
|
4266 | }
|
4267 | conditional(trueCase, falseCase = null, sourceSpan) {
|
4268 | return new ConditionalExpr(this, trueCase, falseCase, null, sourceSpan);
|
4269 | }
|
4270 | equals(rhs, sourceSpan) {
|
4271 | return new BinaryOperatorExpr(BinaryOperator.Equals, this, rhs, null, sourceSpan);
|
4272 | }
|
4273 | notEquals(rhs, sourceSpan) {
|
4274 | return new BinaryOperatorExpr(BinaryOperator.NotEquals, this, rhs, null, sourceSpan);
|
4275 | }
|
4276 | identical(rhs, sourceSpan) {
|
4277 | return new BinaryOperatorExpr(BinaryOperator.Identical, this, rhs, null, sourceSpan);
|
4278 | }
|
4279 | notIdentical(rhs, sourceSpan) {
|
4280 | return new BinaryOperatorExpr(BinaryOperator.NotIdentical, this, rhs, null, sourceSpan);
|
4281 | }
|
4282 | minus(rhs, sourceSpan) {
|
4283 | return new BinaryOperatorExpr(BinaryOperator.Minus, this, rhs, null, sourceSpan);
|
4284 | }
|
4285 | plus(rhs, sourceSpan) {
|
4286 | return new BinaryOperatorExpr(BinaryOperator.Plus, this, rhs, null, sourceSpan);
|
4287 | }
|
4288 | divide(rhs, sourceSpan) {
|
4289 | return new BinaryOperatorExpr(BinaryOperator.Divide, this, rhs, null, sourceSpan);
|
4290 | }
|
4291 | multiply(rhs, sourceSpan) {
|
4292 | return new BinaryOperatorExpr(BinaryOperator.Multiply, this, rhs, null, sourceSpan);
|
4293 | }
|
4294 | modulo(rhs, sourceSpan) {
|
4295 | return new BinaryOperatorExpr(BinaryOperator.Modulo, this, rhs, null, sourceSpan);
|
4296 | }
|
4297 | and(rhs, sourceSpan) {
|
4298 | return new BinaryOperatorExpr(BinaryOperator.And, this, rhs, null, sourceSpan);
|
4299 | }
|
4300 | bitwiseAnd(rhs, sourceSpan, parens = true) {
|
4301 | return new BinaryOperatorExpr(BinaryOperator.BitwiseAnd, this, rhs, null, sourceSpan, parens);
|
4302 | }
|
4303 | or(rhs, sourceSpan) {
|
4304 | return new BinaryOperatorExpr(BinaryOperator.Or, this, rhs, null, sourceSpan);
|
4305 | }
|
4306 | lower(rhs, sourceSpan) {
|
4307 | return new BinaryOperatorExpr(BinaryOperator.Lower, this, rhs, null, sourceSpan);
|
4308 | }
|
4309 | lowerEquals(rhs, sourceSpan) {
|
4310 | return new BinaryOperatorExpr(BinaryOperator.LowerEquals, this, rhs, null, sourceSpan);
|
4311 | }
|
4312 | bigger(rhs, sourceSpan) {
|
4313 | return new BinaryOperatorExpr(BinaryOperator.Bigger, this, rhs, null, sourceSpan);
|
4314 | }
|
4315 | biggerEquals(rhs, sourceSpan) {
|
4316 | return new BinaryOperatorExpr(BinaryOperator.BiggerEquals, this, rhs, null, sourceSpan);
|
4317 | }
|
4318 | isBlank(sourceSpan) {
|
4319 |
|
4320 |
|
4321 | return this.equals(TYPED_NULL_EXPR, sourceSpan);
|
4322 | }
|
4323 | cast(type, sourceSpan) {
|
4324 | return new CastExpr(this, type, sourceSpan);
|
4325 | }
|
4326 | toStmt() {
|
4327 | return new ExpressionStatement(this, null);
|
4328 | }
|
4329 | }
|
4330 | var BuiltinVar;
|
4331 | (function (BuiltinVar) {
|
4332 | BuiltinVar[BuiltinVar["This"] = 0] = "This";
|
4333 | BuiltinVar[BuiltinVar["Super"] = 1] = "Super";
|
4334 | BuiltinVar[BuiltinVar["CatchError"] = 2] = "CatchError";
|
4335 | BuiltinVar[BuiltinVar["CatchStack"] = 3] = "CatchStack";
|
4336 | })(BuiltinVar || (BuiltinVar = {}));
|
4337 | class ReadVarExpr extends Expression {
|
4338 | constructor(name, type, sourceSpan) {
|
4339 | super(type, sourceSpan);
|
4340 | if (typeof name === 'string') {
|
4341 | this.name = name;
|
4342 | this.builtin = null;
|
4343 | }
|
4344 | else {
|
4345 | this.name = null;
|
4346 | this.builtin = name;
|
4347 | }
|
4348 | }
|
4349 | isEquivalent(e) {
|
4350 | return e instanceof ReadVarExpr && this.name === e.name && this.builtin === e.builtin;
|
4351 | }
|
4352 | isConstant() {
|
4353 | return false;
|
4354 | }
|
4355 | visitExpression(visitor, context) {
|
4356 | return visitor.visitReadVarExpr(this, context);
|
4357 | }
|
4358 | set(value) {
|
4359 | if (!this.name) {
|
4360 | throw new Error(`Built in variable ${this.builtin} can not be assigned to.`);
|
4361 | }
|
4362 | return new WriteVarExpr(this.name, value, null, this.sourceSpan);
|
4363 | }
|
4364 | }
|
4365 | class TypeofExpr extends Expression {
|
4366 | constructor(expr, type, sourceSpan) {
|
4367 | super(type, sourceSpan);
|
4368 | this.expr = expr;
|
4369 | }
|
4370 | visitExpression(visitor, context) {
|
4371 | return visitor.visitTypeofExpr(this, context);
|
4372 | }
|
4373 | isEquivalent(e) {
|
4374 | return e instanceof TypeofExpr && e.expr.isEquivalent(this.expr);
|
4375 | }
|
4376 | isConstant() {
|
4377 | return this.expr.isConstant();
|
4378 | }
|
4379 | }
|
4380 | class WrappedNodeExpr extends Expression {
|
4381 | constructor(node, type, sourceSpan) {
|
4382 | super(type, sourceSpan);
|
4383 | this.node = node;
|
4384 | }
|
4385 | isEquivalent(e) {
|
4386 | return e instanceof WrappedNodeExpr && this.node === e.node;
|
4387 | }
|
4388 | isConstant() {
|
4389 | return false;
|
4390 | }
|
4391 | visitExpression(visitor, context) {
|
4392 | return visitor.visitWrappedNodeExpr(this, context);
|
4393 | }
|
4394 | }
|
4395 | class WriteVarExpr extends Expression {
|
4396 | constructor(name, value, type, sourceSpan) {
|
4397 | super(type || value.type, sourceSpan);
|
4398 | this.name = name;
|
4399 | this.value = value;
|
4400 | }
|
4401 | isEquivalent(e) {
|
4402 | return e instanceof WriteVarExpr && this.name === e.name && this.value.isEquivalent(e.value);
|
4403 | }
|
4404 | isConstant() {
|
4405 | return false;
|
4406 | }
|
4407 | visitExpression(visitor, context) {
|
4408 | return visitor.visitWriteVarExpr(this, context);
|
4409 | }
|
4410 | toDeclStmt(type, modifiers) {
|
4411 | return new DeclareVarStmt(this.name, this.value, type, modifiers, this.sourceSpan);
|
4412 | }
|
4413 | toConstDecl() {
|
4414 | return this.toDeclStmt(INFERRED_TYPE, [StmtModifier.Final]);
|
4415 | }
|
4416 | }
|
4417 | class WriteKeyExpr extends Expression {
|
4418 | constructor(receiver, index, value, type, sourceSpan) {
|
4419 | super(type || value.type, sourceSpan);
|
4420 | this.receiver = receiver;
|
4421 | this.index = index;
|
4422 | this.value = value;
|
4423 | }
|
4424 | isEquivalent(e) {
|
4425 | return e instanceof WriteKeyExpr && this.receiver.isEquivalent(e.receiver) &&
|
4426 | this.index.isEquivalent(e.index) && this.value.isEquivalent(e.value);
|
4427 | }
|
4428 | isConstant() {
|
4429 | return false;
|
4430 | }
|
4431 | visitExpression(visitor, context) {
|
4432 | return visitor.visitWriteKeyExpr(this, context);
|
4433 | }
|
4434 | }
|
4435 | class WritePropExpr extends Expression {
|
4436 | constructor(receiver, name, value, type, sourceSpan) {
|
4437 | super(type || value.type, sourceSpan);
|
4438 | this.receiver = receiver;
|
4439 | this.name = name;
|
4440 | this.value = value;
|
4441 | }
|
4442 | isEquivalent(e) {
|
4443 | return e instanceof WritePropExpr && this.receiver.isEquivalent(e.receiver) &&
|
4444 | this.name === e.name && this.value.isEquivalent(e.value);
|
4445 | }
|
4446 | isConstant() {
|
4447 | return false;
|
4448 | }
|
4449 | visitExpression(visitor, context) {
|
4450 | return visitor.visitWritePropExpr(this, context);
|
4451 | }
|
4452 | }
|
4453 | var BuiltinMethod;
|
4454 | (function (BuiltinMethod) {
|
4455 | BuiltinMethod[BuiltinMethod["ConcatArray"] = 0] = "ConcatArray";
|
4456 | BuiltinMethod[BuiltinMethod["SubscribeObservable"] = 1] = "SubscribeObservable";
|
4457 | BuiltinMethod[BuiltinMethod["Bind"] = 2] = "Bind";
|
4458 | })(BuiltinMethod || (BuiltinMethod = {}));
|
4459 | class InvokeMethodExpr extends Expression {
|
4460 | constructor(receiver, method, args, type, sourceSpan) {
|
4461 | super(type, sourceSpan);
|
4462 | this.receiver = receiver;
|
4463 | this.args = args;
|
4464 | if (typeof method === 'string') {
|
4465 | this.name = method;
|
4466 | this.builtin = null;
|
4467 | }
|
4468 | else {
|
4469 | this.name = null;
|
4470 | this.builtin = method;
|
4471 | }
|
4472 | }
|
4473 | isEquivalent(e) {
|
4474 | return e instanceof InvokeMethodExpr && this.receiver.isEquivalent(e.receiver) &&
|
4475 | this.name === e.name && this.builtin === e.builtin && areAllEquivalent(this.args, e.args);
|
4476 | }
|
4477 | isConstant() {
|
4478 | return false;
|
4479 | }
|
4480 | visitExpression(visitor, context) {
|
4481 | return visitor.visitInvokeMethodExpr(this, context);
|
4482 | }
|
4483 | }
|
4484 | class InvokeFunctionExpr extends Expression {
|
4485 | constructor(fn, args, type, sourceSpan, pure = false) {
|
4486 | super(type, sourceSpan);
|
4487 | this.fn = fn;
|
4488 | this.args = args;
|
4489 | this.pure = pure;
|
4490 | }
|
4491 | isEquivalent(e) {
|
4492 | return e instanceof InvokeFunctionExpr && this.fn.isEquivalent(e.fn) &&
|
4493 | areAllEquivalent(this.args, e.args) && this.pure === e.pure;
|
4494 | }
|
4495 | isConstant() {
|
4496 | return false;
|
4497 | }
|
4498 | visitExpression(visitor, context) {
|
4499 | return visitor.visitInvokeFunctionExpr(this, context);
|
4500 | }
|
4501 | }
|
4502 | class TaggedTemplateExpr extends Expression {
|
4503 | constructor(tag, template, type, sourceSpan) {
|
4504 | super(type, sourceSpan);
|
4505 | this.tag = tag;
|
4506 | this.template = template;
|
4507 | }
|
4508 | isEquivalent(e) {
|
4509 | return e instanceof TaggedTemplateExpr && this.tag.isEquivalent(e.tag) &&
|
4510 | areAllEquivalentPredicate(this.template.elements, e.template.elements, (a, b) => a.text === b.text) &&
|
4511 | areAllEquivalent(this.template.expressions, e.template.expressions);
|
4512 | }
|
4513 | isConstant() {
|
4514 | return false;
|
4515 | }
|
4516 | visitExpression(visitor, context) {
|
4517 | return visitor.visitTaggedTemplateExpr(this, context);
|
4518 | }
|
4519 | }
|
4520 | class InstantiateExpr extends Expression {
|
4521 | constructor(classExpr, args, type, sourceSpan) {
|
4522 | super(type, sourceSpan);
|
4523 | this.classExpr = classExpr;
|
4524 | this.args = args;
|
4525 | }
|
4526 | isEquivalent(e) {
|
4527 | return e instanceof InstantiateExpr && this.classExpr.isEquivalent(e.classExpr) &&
|
4528 | areAllEquivalent(this.args, e.args);
|
4529 | }
|
4530 | isConstant() {
|
4531 | return false;
|
4532 | }
|
4533 | visitExpression(visitor, context) {
|
4534 | return visitor.visitInstantiateExpr(this, context);
|
4535 | }
|
4536 | }
|
4537 | class LiteralExpr extends Expression {
|
4538 | constructor(value, type, sourceSpan) {
|
4539 | super(type, sourceSpan);
|
4540 | this.value = value;
|
4541 | }
|
4542 | isEquivalent(e) {
|
4543 | return e instanceof LiteralExpr && this.value === e.value;
|
4544 | }
|
4545 | isConstant() {
|
4546 | return true;
|
4547 | }
|
4548 | visitExpression(visitor, context) {
|
4549 | return visitor.visitLiteralExpr(this, context);
|
4550 | }
|
4551 | }
|
4552 | class TemplateLiteral {
|
4553 | constructor(elements, expressions) {
|
4554 | this.elements = elements;
|
4555 | this.expressions = expressions;
|
4556 | }
|
4557 | }
|
4558 | class TemplateLiteralElement {
|
4559 | constructor(text, sourceSpan, rawText) {
|
4560 | var _a;
|
4561 | this.text = text;
|
4562 | this.sourceSpan = sourceSpan;
|
4563 |
|
4564 |
|
4565 |
|
4566 |
|
4567 |
|
4568 |
|
4569 | this.rawText = (_a = rawText !== null && rawText !== void 0 ? rawText : sourceSpan === null || sourceSpan === void 0 ? void 0 : sourceSpan.toString()) !== null && _a !== void 0 ? _a : escapeForTemplateLiteral(escapeSlashes(text));
|
4570 | }
|
4571 | }
|
4572 | class MessagePiece {
|
4573 | constructor(text, sourceSpan) {
|
4574 | this.text = text;
|
4575 | this.sourceSpan = sourceSpan;
|
4576 | }
|
4577 | }
|
4578 | class LiteralPiece extends MessagePiece {
|
4579 | }
|
4580 | class PlaceholderPiece extends MessagePiece {
|
4581 | }
|
4582 | class LocalizedString extends Expression {
|
4583 | constructor(metaBlock, messageParts, placeHolderNames, expressions, sourceSpan) {
|
4584 | super(STRING_TYPE, sourceSpan);
|
4585 | this.metaBlock = metaBlock;
|
4586 | this.messageParts = messageParts;
|
4587 | this.placeHolderNames = placeHolderNames;
|
4588 | this.expressions = expressions;
|
4589 | }
|
4590 | isEquivalent(e) {
|
4591 |
|
4592 | return false;
|
4593 | }
|
4594 | isConstant() {
|
4595 | return false;
|
4596 | }
|
4597 | visitExpression(visitor, context) {
|
4598 | return visitor.visitLocalizedString(this, context);
|
4599 | }
|
4600 | |
4601 |
|
4602 |
|
4603 |
|
4604 |
|
4605 |
|
4606 |
|
4607 |
|
4608 | serializeI18nHead() {
|
4609 | const MEANING_SEPARATOR = '|';
|
4610 | const ID_SEPARATOR = '@@';
|
4611 | const LEGACY_ID_INDICATOR = '␟';
|
4612 | let metaBlock = this.metaBlock.description || '';
|
4613 | if (this.metaBlock.meaning) {
|
4614 | metaBlock = `${this.metaBlock.meaning}${MEANING_SEPARATOR}${metaBlock}`;
|
4615 | }
|
4616 | if (this.metaBlock.customId) {
|
4617 | metaBlock = `${metaBlock}${ID_SEPARATOR}${this.metaBlock.customId}`;
|
4618 | }
|
4619 | if (this.metaBlock.legacyIds) {
|
4620 | this.metaBlock.legacyIds.forEach(legacyId => {
|
4621 | metaBlock = `${metaBlock}${LEGACY_ID_INDICATOR}${legacyId}`;
|
4622 | });
|
4623 | }
|
4624 | return createCookedRawString(metaBlock, this.messageParts[0].text, this.getMessagePartSourceSpan(0));
|
4625 | }
|
4626 | getMessagePartSourceSpan(i) {
|
4627 | var _a, _b;
|
4628 | return (_b = (_a = this.messageParts[i]) === null || _a === void 0 ? void 0 : _a.sourceSpan) !== null && _b !== void 0 ? _b : this.sourceSpan;
|
4629 | }
|
4630 | getPlaceholderSourceSpan(i) {
|
4631 | var _a, _b, _c, _d;
|
4632 | return (_d = (_b = (_a = this.placeHolderNames[i]) === null || _a === void 0 ? void 0 : _a.sourceSpan) !== null && _b !== void 0 ? _b : (_c = this.expressions[i]) === null || _c === void 0 ? void 0 : _c.sourceSpan) !== null && _d !== void 0 ? _d : this.sourceSpan;
|
4633 | }
|
4634 | |
4635 |
|
4636 |
|
4637 |
|
4638 |
|
4639 |
|
4640 |
|
4641 | serializeI18nTemplatePart(partIndex) {
|
4642 | const placeholderName = this.placeHolderNames[partIndex - 1].text;
|
4643 | const messagePart = this.messageParts[partIndex];
|
4644 | return createCookedRawString(placeholderName, messagePart.text, this.getMessagePartSourceSpan(partIndex));
|
4645 | }
|
4646 | }
|
4647 | const escapeSlashes = (str) => str.replace(/\\/g, '\\\\');
|
4648 | const escapeStartingColon = (str) => str.replace(/^:/, '\\:');
|
4649 | const escapeColons = (str) => str.replace(/:/g, '\\:');
|
4650 | const escapeForTemplateLiteral = (str) => str.replace(/`/g, '\\`').replace(/\${/g, '$\\{');
|
4651 | |
4652 |
|
4653 |
|
4654 |
|
4655 |
|
4656 |
|
4657 |
|
4658 |
|
4659 |
|
4660 |
|
4661 |
|
4662 |
|
4663 |
|
4664 |
|
4665 | function createCookedRawString(metaBlock, messagePart, range) {
|
4666 | if (metaBlock === '') {
|
4667 | return {
|
4668 | cooked: messagePart,
|
4669 | raw: escapeForTemplateLiteral(escapeStartingColon(escapeSlashes(messagePart))),
|
4670 | range,
|
4671 | };
|
4672 | }
|
4673 | else {
|
4674 | return {
|
4675 | cooked: `:${metaBlock}:${messagePart}`,
|
4676 | raw: escapeForTemplateLiteral(`:${escapeColons(escapeSlashes(metaBlock))}:${escapeSlashes(messagePart)}`),
|
4677 | range,
|
4678 | };
|
4679 | }
|
4680 | }
|
4681 | class ExternalExpr extends Expression {
|
4682 | constructor(value, type, typeParams = null, sourceSpan) {
|
4683 | super(type, sourceSpan);
|
4684 | this.value = value;
|
4685 | this.typeParams = typeParams;
|
4686 | }
|
4687 | isEquivalent(e) {
|
4688 | return e instanceof ExternalExpr && this.value.name === e.value.name &&
|
4689 | this.value.moduleName === e.value.moduleName && this.value.runtime === e.value.runtime;
|
4690 | }
|
4691 | isConstant() {
|
4692 | return false;
|
4693 | }
|
4694 | visitExpression(visitor, context) {
|
4695 | return visitor.visitExternalExpr(this, context);
|
4696 | }
|
4697 | }
|
4698 | class ExternalReference {
|
4699 | constructor(moduleName, name, runtime) {
|
4700 | this.moduleName = moduleName;
|
4701 | this.name = name;
|
4702 | this.runtime = runtime;
|
4703 | }
|
4704 | }
|
4705 | class ConditionalExpr extends Expression {
|
4706 | constructor(condition, trueCase, falseCase = null, type, sourceSpan) {
|
4707 | super(type || trueCase.type, sourceSpan);
|
4708 | this.condition = condition;
|
4709 | this.falseCase = falseCase;
|
4710 | this.trueCase = trueCase;
|
4711 | }
|
4712 | isEquivalent(e) {
|
4713 | return e instanceof ConditionalExpr && this.condition.isEquivalent(e.condition) &&
|
4714 | this.trueCase.isEquivalent(e.trueCase) && nullSafeIsEquivalent(this.falseCase, e.falseCase);
|
4715 | }
|
4716 | isConstant() {
|
4717 | return false;
|
4718 | }
|
4719 | visitExpression(visitor, context) {
|
4720 | return visitor.visitConditionalExpr(this, context);
|
4721 | }
|
4722 | }
|
4723 | class NotExpr extends Expression {
|
4724 | constructor(condition, sourceSpan) {
|
4725 | super(BOOL_TYPE, sourceSpan);
|
4726 | this.condition = condition;
|
4727 | }
|
4728 | isEquivalent(e) {
|
4729 | return e instanceof NotExpr && this.condition.isEquivalent(e.condition);
|
4730 | }
|
4731 | isConstant() {
|
4732 | return false;
|
4733 | }
|
4734 | visitExpression(visitor, context) {
|
4735 | return visitor.visitNotExpr(this, context);
|
4736 | }
|
4737 | }
|
4738 | class AssertNotNull extends Expression {
|
4739 | constructor(condition, sourceSpan) {
|
4740 | super(condition.type, sourceSpan);
|
4741 | this.condition = condition;
|
4742 | }
|
4743 | isEquivalent(e) {
|
4744 | return e instanceof AssertNotNull && this.condition.isEquivalent(e.condition);
|
4745 | }
|
4746 | isConstant() {
|
4747 | return false;
|
4748 | }
|
4749 | visitExpression(visitor, context) {
|
4750 | return visitor.visitAssertNotNullExpr(this, context);
|
4751 | }
|
4752 | }
|
4753 | class CastExpr extends Expression {
|
4754 | constructor(value, type, sourceSpan) {
|
4755 | super(type, sourceSpan);
|
4756 | this.value = value;
|
4757 | }
|
4758 | isEquivalent(e) {
|
4759 | return e instanceof CastExpr && this.value.isEquivalent(e.value);
|
4760 | }
|
4761 | isConstant() {
|
4762 | return false;
|
4763 | }
|
4764 | visitExpression(visitor, context) {
|
4765 | return visitor.visitCastExpr(this, context);
|
4766 | }
|
4767 | }
|
4768 | class FnParam {
|
4769 | constructor(name, type = null) {
|
4770 | this.name = name;
|
4771 | this.type = type;
|
4772 | }
|
4773 | isEquivalent(param) {
|
4774 | return this.name === param.name;
|
4775 | }
|
4776 | }
|
4777 | class FunctionExpr extends Expression {
|
4778 | constructor(params, statements, type, sourceSpan, name) {
|
4779 | super(type, sourceSpan);
|
4780 | this.params = params;
|
4781 | this.statements = statements;
|
4782 | this.name = name;
|
4783 | }
|
4784 | isEquivalent(e) {
|
4785 | return e instanceof FunctionExpr && areAllEquivalent(this.params, e.params) &&
|
4786 | areAllEquivalent(this.statements, e.statements);
|
4787 | }
|
4788 | isConstant() {
|
4789 | return false;
|
4790 | }
|
4791 | visitExpression(visitor, context) {
|
4792 | return visitor.visitFunctionExpr(this, context);
|
4793 | }
|
4794 | toDeclStmt(name, modifiers) {
|
4795 | return new DeclareFunctionStmt(name, this.params, this.statements, this.type, modifiers, this.sourceSpan);
|
4796 | }
|
4797 | }
|
4798 | class UnaryOperatorExpr extends Expression {
|
4799 | constructor(operator, expr, type, sourceSpan, parens = true) {
|
4800 | super(type || NUMBER_TYPE, sourceSpan);
|
4801 | this.operator = operator;
|
4802 | this.expr = expr;
|
4803 | this.parens = parens;
|
4804 | }
|
4805 | isEquivalent(e) {
|
4806 | return e instanceof UnaryOperatorExpr && this.operator === e.operator &&
|
4807 | this.expr.isEquivalent(e.expr);
|
4808 | }
|
4809 | isConstant() {
|
4810 | return false;
|
4811 | }
|
4812 | visitExpression(visitor, context) {
|
4813 | return visitor.visitUnaryOperatorExpr(this, context);
|
4814 | }
|
4815 | }
|
4816 | class BinaryOperatorExpr extends Expression {
|
4817 | constructor(operator, lhs, rhs, type, sourceSpan, parens = true) {
|
4818 | super(type || lhs.type, sourceSpan);
|
4819 | this.operator = operator;
|
4820 | this.rhs = rhs;
|
4821 | this.parens = parens;
|
4822 | this.lhs = lhs;
|
4823 | }
|
4824 | isEquivalent(e) {
|
4825 | return e instanceof BinaryOperatorExpr && this.operator === e.operator &&
|
4826 | this.lhs.isEquivalent(e.lhs) && this.rhs.isEquivalent(e.rhs);
|
4827 | }
|
4828 | isConstant() {
|
4829 | return false;
|
4830 | }
|
4831 | visitExpression(visitor, context) {
|
4832 | return visitor.visitBinaryOperatorExpr(this, context);
|
4833 | }
|
4834 | }
|
4835 | class ReadPropExpr extends Expression {
|
4836 | constructor(receiver, name, type, sourceSpan) {
|
4837 | super(type, sourceSpan);
|
4838 | this.receiver = receiver;
|
4839 | this.name = name;
|
4840 | }
|
4841 | isEquivalent(e) {
|
4842 | return e instanceof ReadPropExpr && this.receiver.isEquivalent(e.receiver) &&
|
4843 | this.name === e.name;
|
4844 | }
|
4845 | isConstant() {
|
4846 | return false;
|
4847 | }
|
4848 | visitExpression(visitor, context) {
|
4849 | return visitor.visitReadPropExpr(this, context);
|
4850 | }
|
4851 | set(value) {
|
4852 | return new WritePropExpr(this.receiver, this.name, value, null, this.sourceSpan);
|
4853 | }
|
4854 | }
|
4855 | class ReadKeyExpr extends Expression {
|
4856 | constructor(receiver, index, type, sourceSpan) {
|
4857 | super(type, sourceSpan);
|
4858 | this.receiver = receiver;
|
4859 | this.index = index;
|
4860 | }
|
4861 | isEquivalent(e) {
|
4862 | return e instanceof ReadKeyExpr && this.receiver.isEquivalent(e.receiver) &&
|
4863 | this.index.isEquivalent(e.index);
|
4864 | }
|
4865 | isConstant() {
|
4866 | return false;
|
4867 | }
|
4868 | visitExpression(visitor, context) {
|
4869 | return visitor.visitReadKeyExpr(this, context);
|
4870 | }
|
4871 | set(value) {
|
4872 | return new WriteKeyExpr(this.receiver, this.index, value, null, this.sourceSpan);
|
4873 | }
|
4874 | }
|
4875 | class LiteralArrayExpr extends Expression {
|
4876 | constructor(entries, type, sourceSpan) {
|
4877 | super(type, sourceSpan);
|
4878 | this.entries = entries;
|
4879 | }
|
4880 | isConstant() {
|
4881 | return this.entries.every(e => e.isConstant());
|
4882 | }
|
4883 | isEquivalent(e) {
|
4884 | return e instanceof LiteralArrayExpr && areAllEquivalent(this.entries, e.entries);
|
4885 | }
|
4886 | visitExpression(visitor, context) {
|
4887 | return visitor.visitLiteralArrayExpr(this, context);
|
4888 | }
|
4889 | }
|
4890 | class LiteralMapEntry {
|
4891 | constructor(key, value, quoted) {
|
4892 | this.key = key;
|
4893 | this.value = value;
|
4894 | this.quoted = quoted;
|
4895 | }
|
4896 | isEquivalent(e) {
|
4897 | return this.key === e.key && this.value.isEquivalent(e.value);
|
4898 | }
|
4899 | }
|
4900 | class LiteralMapExpr extends Expression {
|
4901 | constructor(entries, type, sourceSpan) {
|
4902 | super(type, sourceSpan);
|
4903 | this.entries = entries;
|
4904 | this.valueType = null;
|
4905 | if (type) {
|
4906 | this.valueType = type.valueType;
|
4907 | }
|
4908 | }
|
4909 | isEquivalent(e) {
|
4910 | return e instanceof LiteralMapExpr && areAllEquivalent(this.entries, e.entries);
|
4911 | }
|
4912 | isConstant() {
|
4913 | return this.entries.every(e => e.value.isConstant());
|
4914 | }
|
4915 | visitExpression(visitor, context) {
|
4916 | return visitor.visitLiteralMapExpr(this, context);
|
4917 | }
|
4918 | }
|
4919 | const THIS_EXPR = new ReadVarExpr(BuiltinVar.This, null, null);
|
4920 | const SUPER_EXPR = new ReadVarExpr(BuiltinVar.Super, null, null);
|
4921 | const CATCH_ERROR_VAR = new ReadVarExpr(BuiltinVar.CatchError, null, null);
|
4922 | const CATCH_STACK_VAR = new ReadVarExpr(BuiltinVar.CatchStack, null, null);
|
4923 | const NULL_EXPR = new LiteralExpr(null, null, null);
|
4924 | const TYPED_NULL_EXPR = new LiteralExpr(null, INFERRED_TYPE, null);
|
4925 |
|
4926 | var StmtModifier;
|
4927 | (function (StmtModifier) {
|
4928 | StmtModifier[StmtModifier["Final"] = 0] = "Final";
|
4929 | StmtModifier[StmtModifier["Private"] = 1] = "Private";
|
4930 | StmtModifier[StmtModifier["Exported"] = 2] = "Exported";
|
4931 | StmtModifier[StmtModifier["Static"] = 3] = "Static";
|
4932 | })(StmtModifier || (StmtModifier = {}));
|
4933 | class LeadingComment {
|
4934 | constructor(text, multiline, trailingNewline) {
|
4935 | this.text = text;
|
4936 | this.multiline = multiline;
|
4937 | this.trailingNewline = trailingNewline;
|
4938 | }
|
4939 | toString() {
|
4940 | return this.multiline ? ` ${this.text} ` : this.text;
|
4941 | }
|
4942 | }
|
4943 | class JSDocComment extends LeadingComment {
|
4944 | constructor(tags) {
|
4945 | super('', true, true);
|
4946 | this.tags = tags;
|
4947 | }
|
4948 | toString() {
|
4949 | return serializeTags(this.tags);
|
4950 | }
|
4951 | }
|
4952 | class Statement {
|
4953 | constructor(modifiers = [], sourceSpan = null, leadingComments) {
|
4954 | this.modifiers = modifiers;
|
4955 | this.sourceSpan = sourceSpan;
|
4956 | this.leadingComments = leadingComments;
|
4957 | }
|
4958 | hasModifier(modifier) {
|
4959 | return this.modifiers.indexOf(modifier) !== -1;
|
4960 | }
|
4961 | addLeadingComment(leadingComment) {
|
4962 | var _a;
|
4963 | this.leadingComments = (_a = this.leadingComments) !== null && _a !== void 0 ? _a : [];
|
4964 | this.leadingComments.push(leadingComment);
|
4965 | }
|
4966 | }
|
4967 | class DeclareVarStmt extends Statement {
|
4968 | constructor(name, value, type, modifiers, sourceSpan, leadingComments) {
|
4969 | super(modifiers, sourceSpan, leadingComments);
|
4970 | this.name = name;
|
4971 | this.value = value;
|
4972 | this.type = type || (value && value.type) || null;
|
4973 | }
|
4974 | isEquivalent(stmt) {
|
4975 | return stmt instanceof DeclareVarStmt && this.name === stmt.name &&
|
4976 | (this.value ? !!stmt.value && this.value.isEquivalent(stmt.value) : !stmt.value);
|
4977 | }
|
4978 | visitStatement(visitor, context) {
|
4979 | return visitor.visitDeclareVarStmt(this, context);
|
4980 | }
|
4981 | }
|
4982 | class DeclareFunctionStmt extends Statement {
|
4983 | constructor(name, params, statements, type, modifiers, sourceSpan, leadingComments) {
|
4984 | super(modifiers, sourceSpan, leadingComments);
|
4985 | this.name = name;
|
4986 | this.params = params;
|
4987 | this.statements = statements;
|
4988 | this.type = type || null;
|
4989 | }
|
4990 | isEquivalent(stmt) {
|
4991 | return stmt instanceof DeclareFunctionStmt && areAllEquivalent(this.params, stmt.params) &&
|
4992 | areAllEquivalent(this.statements, stmt.statements);
|
4993 | }
|
4994 | visitStatement(visitor, context) {
|
4995 | return visitor.visitDeclareFunctionStmt(this, context);
|
4996 | }
|
4997 | }
|
4998 | class ExpressionStatement extends Statement {
|
4999 | constructor(expr, sourceSpan, leadingComments) {
|
5000 | super([], sourceSpan, leadingComments);
|
5001 | this.expr = expr;
|
5002 | }
|
5003 | isEquivalent(stmt) {
|
5004 | return stmt instanceof ExpressionStatement && this.expr.isEquivalent(stmt.expr);
|
5005 | }
|
5006 | visitStatement(visitor, context) {
|
5007 | return visitor.visitExpressionStmt(this, context);
|
5008 | }
|
5009 | }
|
5010 | class ReturnStatement extends Statement {
|
5011 | constructor(value, sourceSpan = null, leadingComments) {
|
5012 | super([], sourceSpan, leadingComments);
|
5013 | this.value = value;
|
5014 | }
|
5015 | isEquivalent(stmt) {
|
5016 | return stmt instanceof ReturnStatement && this.value.isEquivalent(stmt.value);
|
5017 | }
|
5018 | visitStatement(visitor, context) {
|
5019 | return visitor.visitReturnStmt(this, context);
|
5020 | }
|
5021 | }
|
5022 | class IfStmt extends Statement {
|
5023 | constructor(condition, trueCase, falseCase = [], sourceSpan, leadingComments) {
|
5024 | super([], sourceSpan, leadingComments);
|
5025 | this.condition = condition;
|
5026 | this.trueCase = trueCase;
|
5027 | this.falseCase = falseCase;
|
5028 | }
|
5029 | isEquivalent(stmt) {
|
5030 | return stmt instanceof IfStmt && this.condition.isEquivalent(stmt.condition) &&
|
5031 | areAllEquivalent(this.trueCase, stmt.trueCase) &&
|
5032 | areAllEquivalent(this.falseCase, stmt.falseCase);
|
5033 | }
|
5034 | visitStatement(visitor, context) {
|
5035 | return visitor.visitIfStmt(this, context);
|
5036 | }
|
5037 | }
|
5038 | function jsDocComment(tags = []) {
|
5039 | return new JSDocComment(tags);
|
5040 | }
|
5041 | function variable(name, type, sourceSpan) {
|
5042 | return new ReadVarExpr(name, type, sourceSpan);
|
5043 | }
|
5044 | function importExpr(id, typeParams = null, sourceSpan) {
|
5045 | return new ExternalExpr(id, null, typeParams, sourceSpan);
|
5046 | }
|
5047 | function expressionType(expr, typeModifiers, typeParams) {
|
5048 | return new ExpressionType(expr, typeModifiers, typeParams);
|
5049 | }
|
5050 | function typeofExpr(expr) {
|
5051 | return new TypeofExpr(expr);
|
5052 | }
|
5053 | function literalArr(values, type, sourceSpan) {
|
5054 | return new LiteralArrayExpr(values, type, sourceSpan);
|
5055 | }
|
5056 | function literalMap(values, type = null) {
|
5057 | return new LiteralMapExpr(values.map(e => new LiteralMapEntry(e.key, e.value, e.quoted)), type, null);
|
5058 | }
|
5059 | function not(expr, sourceSpan) {
|
5060 | return new NotExpr(expr, sourceSpan);
|
5061 | }
|
5062 | function assertNotNull(expr, sourceSpan) {
|
5063 | return new AssertNotNull(expr, sourceSpan);
|
5064 | }
|
5065 | function fn(params, body, type, sourceSpan, name) {
|
5066 | return new FunctionExpr(params, body, type, sourceSpan, name);
|
5067 | }
|
5068 | function ifStmt(condition, thenClause, elseClause, sourceSpan, leadingComments) {
|
5069 | return new IfStmt(condition, thenClause, elseClause, sourceSpan, leadingComments);
|
5070 | }
|
5071 | function taggedTemplate(tag, template, type, sourceSpan) {
|
5072 | return new TaggedTemplateExpr(tag, template, type, sourceSpan);
|
5073 | }
|
5074 | function literal(value, type, sourceSpan) {
|
5075 | return new LiteralExpr(value, type, sourceSpan);
|
5076 | }
|
5077 | function localizedString(metaBlock, messageParts, placeholderNames, expressions, sourceSpan) {
|
5078 | return new LocalizedString(metaBlock, messageParts, placeholderNames, expressions, sourceSpan);
|
5079 | }
|
5080 | function isNull(exp) {
|
5081 | return exp instanceof LiteralExpr && exp.value === null;
|
5082 | }
|
5083 | |
5084 |
|
5085 |
|
5086 |
|
5087 | function tagToString(tag) {
|
5088 | let out = '';
|
5089 | if (tag.tagName) {
|
5090 | out += ` @${tag.tagName}`;
|
5091 | }
|
5092 | if (tag.text) {
|
5093 | if (tag.text.match(/\/\*|\*\//)) {
|
5094 | throw new Error('JSDoc text cannot contain "/*" and "*/"');
|
5095 | }
|
5096 | out += ' ' + tag.text.replace(/@/g, '\\@');
|
5097 | }
|
5098 | return out;
|
5099 | }
|
5100 | function serializeTags(tags) {
|
5101 | if (tags.length === 0)
|
5102 | return '';
|
5103 | if (tags.length === 1 && tags[0].tagName && !tags[0].text) {
|
5104 |
|
5105 | return `*${tagToString(tags[0])} `;
|
5106 | }
|
5107 | let out = '*\n';
|
5108 | for (const tag of tags) {
|
5109 | out += ' *';
|
5110 |
|
5111 | out += tagToString(tag).replace(/\n/g, '\n * ');
|
5112 | out += '\n';
|
5113 | }
|
5114 | out += ' ';
|
5115 | return out;
|
5116 | }
|
5117 |
|
5118 | |
5119 |
|
5120 |
|
5121 |
|
5122 |
|
5123 |
|
5124 |
|
5125 | const CONSTANT_PREFIX = '_c';
|
5126 | |
5127 |
|
5128 |
|
5129 |
|
5130 |
|
5131 |
|
5132 |
|
5133 |
|
5134 | const UNKNOWN_VALUE_KEY = variable('<unknown>');
|
5135 | |
5136 |
|
5137 |
|
5138 |
|
5139 |
|
5140 |
|
5141 | const KEY_CONTEXT = {};
|
5142 | |
5143 |
|
5144 |
|
5145 |
|
5146 |
|
5147 | const POOL_INCLUSION_LENGTH_THRESHOLD_FOR_STRINGS = 50;
|
5148 | |
5149 |
|
5150 |
|
5151 |
|
5152 |
|
5153 |
|
5154 |
|
5155 |
|
5156 | class FixupExpression extends Expression {
|
5157 | constructor(resolved) {
|
5158 | super(resolved.type);
|
5159 | this.resolved = resolved;
|
5160 | this.original = resolved;
|
5161 | }
|
5162 | visitExpression(visitor, context) {
|
5163 | if (context === KEY_CONTEXT) {
|
5164 |
|
5165 |
|
5166 | return this.original.visitExpression(visitor, context);
|
5167 | }
|
5168 | else {
|
5169 | return this.resolved.visitExpression(visitor, context);
|
5170 | }
|
5171 | }
|
5172 | isEquivalent(e) {
|
5173 | return e instanceof FixupExpression && this.resolved.isEquivalent(e.resolved);
|
5174 | }
|
5175 | isConstant() {
|
5176 | return true;
|
5177 | }
|
5178 | fixup(expression) {
|
5179 | this.resolved = expression;
|
5180 | this.shared = true;
|
5181 | }
|
5182 | }
|
5183 | |
5184 |
|
5185 |
|
5186 |
|
5187 |
|
5188 | class ConstantPool {
|
5189 | constructor(isClosureCompilerEnabled = false) {
|
5190 | this.isClosureCompilerEnabled = isClosureCompilerEnabled;
|
5191 | this.statements = [];
|
5192 | this.literals = new Map();
|
5193 | this.literalFactories = new Map();
|
5194 | this.injectorDefinitions = new Map();
|
5195 | this.directiveDefinitions = new Map();
|
5196 | this.componentDefinitions = new Map();
|
5197 | this.pipeDefinitions = new Map();
|
5198 | this.nextNameIndex = 0;
|
5199 | }
|
5200 | getConstLiteral(literal, forceShared) {
|
5201 | if ((literal instanceof LiteralExpr && !isLongStringLiteral(literal)) ||
|
5202 | literal instanceof FixupExpression) {
|
5203 |
|
5204 |
|
5205 | return literal;
|
5206 | }
|
5207 | const key = this.keyOf(literal);
|
5208 | let fixup = this.literals.get(key);
|
5209 | let newValue = false;
|
5210 | if (!fixup) {
|
5211 | fixup = new FixupExpression(literal);
|
5212 | this.literals.set(key, fixup);
|
5213 | newValue = true;
|
5214 | }
|
5215 | if ((!newValue && !fixup.shared) || (newValue && forceShared)) {
|
5216 |
|
5217 | const name = this.freshName();
|
5218 | let definition;
|
5219 | let usage;
|
5220 | if (this.isClosureCompilerEnabled && isLongStringLiteral(literal)) {
|
5221 |
|
5222 |
|
5223 |
|
5224 |
|
5225 |
|
5226 |
|
5227 |
|
5228 |
|
5229 |
|
5230 |
|
5231 |
|
5232 |
|
5233 |
|
5234 | definition = variable(name).set(new FunctionExpr([],
|
5235 | [
|
5236 |
|
5237 | new ReturnStatement(literal),
|
5238 | ]));
|
5239 | usage = variable(name).callFn([]);
|
5240 | }
|
5241 | else {
|
5242 |
|
5243 |
|
5244 | definition = variable(name).set(literal);
|
5245 | usage = variable(name);
|
5246 | }
|
5247 | this.statements.push(definition.toDeclStmt(INFERRED_TYPE, [StmtModifier.Final]));
|
5248 | fixup.fixup(usage);
|
5249 | }
|
5250 | return fixup;
|
5251 | }
|
5252 | getDefinition(type, kind, ctx, forceShared = false) {
|
5253 | const definitions = this.definitionsOf(kind);
|
5254 | let fixup = definitions.get(type);
|
5255 | let newValue = false;
|
5256 | if (!fixup) {
|
5257 | const property = this.propertyNameOf(kind);
|
5258 | fixup = new FixupExpression(ctx.importExpr(type).prop(property));
|
5259 | definitions.set(type, fixup);
|
5260 | newValue = true;
|
5261 | }
|
5262 | if ((!newValue && !fixup.shared) || (newValue && forceShared)) {
|
5263 | const name = this.freshName();
|
5264 | this.statements.push(variable(name).set(fixup.resolved).toDeclStmt(INFERRED_TYPE, [StmtModifier.Final]));
|
5265 | fixup.fixup(variable(name));
|
5266 | }
|
5267 | return fixup;
|
5268 | }
|
5269 | getLiteralFactory(literal) {
|
5270 |
|
5271 | if (literal instanceof LiteralArrayExpr) {
|
5272 | const argumentsForKey = literal.entries.map(e => e.isConstant() ? e : UNKNOWN_VALUE_KEY);
|
5273 | const key = this.keyOf(literalArr(argumentsForKey));
|
5274 | return this._getLiteralFactory(key, literal.entries, entries => literalArr(entries));
|
5275 | }
|
5276 | else {
|
5277 | const expressionForKey = literalMap(literal.entries.map(e => ({
|
5278 | key: e.key,
|
5279 | value: e.value.isConstant() ? e.value : UNKNOWN_VALUE_KEY,
|
5280 | quoted: e.quoted
|
5281 | })));
|
5282 | const key = this.keyOf(expressionForKey);
|
5283 | return this._getLiteralFactory(key, literal.entries.map(e => e.value), entries => literalMap(entries.map((value, index) => ({
|
5284 | key: literal.entries[index].key,
|
5285 | value,
|
5286 | quoted: literal.entries[index].quoted
|
5287 | }))));
|
5288 | }
|
5289 | }
|
5290 | _getLiteralFactory(key, values, resultMap) {
|
5291 | let literalFactory = this.literalFactories.get(key);
|
5292 | const literalFactoryArguments = values.filter((e => !e.isConstant()));
|
5293 | if (!literalFactory) {
|
5294 | const resultExpressions = values.map((e, index) => e.isConstant() ? this.getConstLiteral(e, true) : variable(`a${index}`));
|
5295 | const parameters = resultExpressions.filter(isVariable).map(e => new FnParam(e.name, DYNAMIC_TYPE));
|
5296 | const pureFunctionDeclaration = fn(parameters, [new ReturnStatement(resultMap(resultExpressions))], INFERRED_TYPE);
|
5297 | const name = this.freshName();
|
5298 | this.statements.push(variable(name).set(pureFunctionDeclaration).toDeclStmt(INFERRED_TYPE, [
|
5299 | StmtModifier.Final
|
5300 | ]));
|
5301 | literalFactory = variable(name);
|
5302 | this.literalFactories.set(key, literalFactory);
|
5303 | }
|
5304 | return { literalFactory, literalFactoryArguments };
|
5305 | }
|
5306 | |
5307 |
|
5308 |
|
5309 |
|
5310 |
|
5311 |
|
5312 |
|
5313 | uniqueName(prefix) {
|
5314 | return `${prefix}${this.nextNameIndex++}`;
|
5315 | }
|
5316 | definitionsOf(kind) {
|
5317 | switch (kind) {
|
5318 | case 2 :
|
5319 | return this.componentDefinitions;
|
5320 | case 1 :
|
5321 | return this.directiveDefinitions;
|
5322 | case 0 :
|
5323 | return this.injectorDefinitions;
|
5324 | case 3 :
|
5325 | return this.pipeDefinitions;
|
5326 | }
|
5327 | }
|
5328 | propertyNameOf(kind) {
|
5329 | switch (kind) {
|
5330 | case 2 :
|
5331 | return 'ɵcmp';
|
5332 | case 1 :
|
5333 | return 'ɵdir';
|
5334 | case 0 :
|
5335 | return 'ɵinj';
|
5336 | case 3 :
|
5337 | return 'ɵpipe';
|
5338 | }
|
5339 | }
|
5340 | freshName() {
|
5341 | return this.uniqueName(CONSTANT_PREFIX);
|
5342 | }
|
5343 | keyOf(expression) {
|
5344 | return expression.visitExpression(new KeyVisitor(), KEY_CONTEXT);
|
5345 | }
|
5346 | }
|
5347 | |
5348 |
|
5349 |
|
5350 |
|
5351 |
|
5352 |
|
5353 | class KeyVisitor {
|
5354 | constructor() {
|
5355 | this.visitWrappedNodeExpr = invalid;
|
5356 | this.visitWriteVarExpr = invalid;
|
5357 | this.visitWriteKeyExpr = invalid;
|
5358 | this.visitWritePropExpr = invalid;
|
5359 | this.visitInvokeMethodExpr = invalid;
|
5360 | this.visitInvokeFunctionExpr = invalid;
|
5361 | this.visitTaggedTemplateExpr = invalid;
|
5362 | this.visitInstantiateExpr = invalid;
|
5363 | this.visitConditionalExpr = invalid;
|
5364 | this.visitNotExpr = invalid;
|
5365 | this.visitAssertNotNullExpr = invalid;
|
5366 | this.visitCastExpr = invalid;
|
5367 | this.visitFunctionExpr = invalid;
|
5368 | this.visitUnaryOperatorExpr = invalid;
|
5369 | this.visitBinaryOperatorExpr = invalid;
|
5370 | this.visitReadPropExpr = invalid;
|
5371 | this.visitReadKeyExpr = invalid;
|
5372 | this.visitCommaExpr = invalid;
|
5373 | this.visitLocalizedString = invalid;
|
5374 | }
|
5375 | visitLiteralExpr(ast) {
|
5376 | return `${typeof ast.value === 'string' ? '"' + ast.value + '"' : ast.value}`;
|
5377 | }
|
5378 | visitLiteralArrayExpr(ast, context) {
|
5379 | return `[${ast.entries.map(entry => entry.visitExpression(this, context)).join(',')}]`;
|
5380 | }
|
5381 | visitLiteralMapExpr(ast, context) {
|
5382 | const mapKey = (entry) => {
|
5383 | const quote = entry.quoted ? '"' : '';
|
5384 | return `${quote}${entry.key}${quote}`;
|
5385 | };
|
5386 | const mapEntry = (entry) => `${mapKey(entry)}:${entry.value.visitExpression(this, context)}`;
|
5387 | return `{${ast.entries.map(mapEntry).join(',')}`;
|
5388 | }
|
5389 | visitExternalExpr(ast) {
|
5390 | return ast.value.moduleName ? `EX:${ast.value.moduleName}:${ast.value.name}` :
|
5391 | `EX:${ast.value.runtime.name}`;
|
5392 | }
|
5393 | visitReadVarExpr(node) {
|
5394 | return `VAR:${node.name}`;
|
5395 | }
|
5396 | visitTypeofExpr(node, context) {
|
5397 | return `TYPEOF:${node.expr.visitExpression(this, context)}`;
|
5398 | }
|
5399 | }
|
5400 | function invalid(arg) {
|
5401 | throw new Error(`Invalid state: Visitor ${this.constructor.name} doesn't handle ${arg.constructor.name}`);
|
5402 | }
|
5403 | function isVariable(e) {
|
5404 | return e instanceof ReadVarExpr;
|
5405 | }
|
5406 | function isLongStringLiteral(expr) {
|
5407 | return expr instanceof LiteralExpr && typeof expr.value === 'string' &&
|
5408 | expr.value.length >= POOL_INCLUSION_LENGTH_THRESHOLD_FOR_STRINGS;
|
5409 | }
|
5410 |
|
5411 | |
5412 |
|
5413 |
|
5414 |
|
5415 |
|
5416 |
|
5417 |
|
5418 | const CORE = '@angular/core';
|
5419 | class Identifiers {
|
5420 | }
|
5421 | Identifiers.ANALYZE_FOR_ENTRY_COMPONENTS = {
|
5422 | name: 'ANALYZE_FOR_ENTRY_COMPONENTS',
|
5423 | moduleName: CORE,
|
5424 | };
|
5425 | Identifiers.ElementRef = { name: 'ElementRef', moduleName: CORE };
|
5426 | Identifiers.NgModuleRef = { name: 'NgModuleRef', moduleName: CORE };
|
5427 | Identifiers.ViewContainerRef = { name: 'ViewContainerRef', moduleName: CORE };
|
5428 | Identifiers.ChangeDetectorRef = {
|
5429 | name: 'ChangeDetectorRef',
|
5430 | moduleName: CORE,
|
5431 | };
|
5432 | Identifiers.QueryList = { name: 'QueryList', moduleName: CORE };
|
5433 | Identifiers.TemplateRef = { name: 'TemplateRef', moduleName: CORE };
|
5434 | Identifiers.Renderer2 = { name: 'Renderer2', moduleName: CORE };
|
5435 | Identifiers.CodegenComponentFactoryResolver = {
|
5436 | name: 'ɵCodegenComponentFactoryResolver',
|
5437 | moduleName: CORE,
|
5438 | };
|
5439 | Identifiers.ComponentFactoryResolver = {
|
5440 | name: 'ComponentFactoryResolver',
|
5441 | moduleName: CORE,
|
5442 | };
|
5443 | Identifiers.ComponentFactory = { name: 'ComponentFactory', moduleName: CORE };
|
5444 | Identifiers.ComponentRef = { name: 'ComponentRef', moduleName: CORE };
|
5445 | Identifiers.NgModuleFactory = { name: 'NgModuleFactory', moduleName: CORE };
|
5446 | Identifiers.createModuleFactory = {
|
5447 | name: 'ɵcmf',
|
5448 | moduleName: CORE,
|
5449 | };
|
5450 | Identifiers.moduleDef = {
|
5451 | name: 'ɵmod',
|
5452 | moduleName: CORE,
|
5453 | };
|
5454 | Identifiers.moduleProviderDef = {
|
5455 | name: 'ɵmpd',
|
5456 | moduleName: CORE,
|
5457 | };
|
5458 | Identifiers.RegisterModuleFactoryFn = {
|
5459 | name: 'ɵregisterModuleFactory',
|
5460 | moduleName: CORE,
|
5461 | };
|
5462 | Identifiers.inject = { name: 'ɵɵinject', moduleName: CORE };
|
5463 | Identifiers.directiveInject = { name: 'ɵɵdirectiveInject', moduleName: CORE };
|
5464 | Identifiers.INJECTOR = { name: 'INJECTOR', moduleName: CORE };
|
5465 | Identifiers.Injector = { name: 'Injector', moduleName: CORE };
|
5466 | Identifiers.ɵɵdefineInjectable = { name: 'ɵɵdefineInjectable', moduleName: CORE };
|
5467 | Identifiers.InjectableDef = { name: 'ɵɵInjectableDef', moduleName: CORE };
|
5468 | Identifiers.ViewEncapsulation = {
|
5469 | name: 'ViewEncapsulation',
|
5470 | moduleName: CORE,
|
5471 | };
|
5472 | Identifiers.ChangeDetectionStrategy = {
|
5473 | name: 'ChangeDetectionStrategy',
|
5474 | moduleName: CORE,
|
5475 | };
|
5476 | Identifiers.SecurityContext = {
|
5477 | name: 'SecurityContext',
|
5478 | moduleName: CORE,
|
5479 | };
|
5480 | Identifiers.LOCALE_ID = { name: 'LOCALE_ID', moduleName: CORE };
|
5481 | Identifiers.TRANSLATIONS_FORMAT = {
|
5482 | name: 'TRANSLATIONS_FORMAT',
|
5483 | moduleName: CORE,
|
5484 | };
|
5485 | Identifiers.inlineInterpolate = {
|
5486 | name: 'ɵinlineInterpolate',
|
5487 | moduleName: CORE,
|
5488 | };
|
5489 | Identifiers.interpolate = { name: 'ɵinterpolate', moduleName: CORE };
|
5490 | Identifiers.EMPTY_ARRAY = { name: 'ɵEMPTY_ARRAY', moduleName: CORE };
|
5491 | Identifiers.EMPTY_MAP = { name: 'ɵEMPTY_MAP', moduleName: CORE };
|
5492 | Identifiers.Renderer = { name: 'Renderer', moduleName: CORE };
|
5493 | Identifiers.viewDef = { name: 'ɵvid', moduleName: CORE };
|
5494 | Identifiers.elementDef = { name: 'ɵeld', moduleName: CORE };
|
5495 | Identifiers.anchorDef = { name: 'ɵand', moduleName: CORE };
|
5496 | Identifiers.textDef = { name: 'ɵted', moduleName: CORE };
|
5497 | Identifiers.directiveDef = { name: 'ɵdid', moduleName: CORE };
|
5498 | Identifiers.providerDef = { name: 'ɵprd', moduleName: CORE };
|
5499 | Identifiers.queryDef = { name: 'ɵqud', moduleName: CORE };
|
5500 | Identifiers.pureArrayDef = { name: 'ɵpad', moduleName: CORE };
|
5501 | Identifiers.pureObjectDef = { name: 'ɵpod', moduleName: CORE };
|
5502 | Identifiers.purePipeDef = { name: 'ɵppd', moduleName: CORE };
|
5503 | Identifiers.pipeDef = { name: 'ɵpid', moduleName: CORE };
|
5504 | Identifiers.nodeValue = { name: 'ɵnov', moduleName: CORE };
|
5505 | Identifiers.ngContentDef = { name: 'ɵncd', moduleName: CORE };
|
5506 | Identifiers.unwrapValue = { name: 'ɵunv', moduleName: CORE };
|
5507 | Identifiers.createRendererType2 = { name: 'ɵcrt', moduleName: CORE };
|
5508 |
|
5509 | Identifiers.RendererType2 = {
|
5510 | name: 'RendererType2',
|
5511 | moduleName: CORE,
|
5512 | };
|
5513 |
|
5514 | Identifiers.ViewDefinition = {
|
5515 | name: 'ɵViewDefinition',
|
5516 | moduleName: CORE,
|
5517 | };
|
5518 | Identifiers.createComponentFactory = { name: 'ɵccf', moduleName: CORE };
|
5519 | Identifiers.setClassMetadata = { name: 'ɵsetClassMetadata', moduleName: CORE };
|
5520 |
|
5521 | |
5522 |
|
5523 |
|
5524 |
|
5525 |
|
5526 |
|
5527 |
|
5528 | |
5529 |
|
5530 |
|
5531 |
|
5532 |
|
5533 | class StaticSymbol {
|
5534 | constructor(filePath, name, members) {
|
5535 | this.filePath = filePath;
|
5536 | this.name = name;
|
5537 | this.members = members;
|
5538 | }
|
5539 | assertNoMembers() {
|
5540 | if (this.members.length) {
|
5541 | throw new Error(`Illegal state: symbol without members expected, but got ${JSON.stringify(this)}.`);
|
5542 | }
|
5543 | }
|
5544 | }
|
5545 |
|
5546 | |
5547 |
|
5548 |
|
5549 |
|
5550 |
|
5551 |
|
5552 |
|
5553 | const DASH_CASE_REGEXP = /-+([a-z0-9])/g;
|
5554 | function dashCaseToCamelCase(input) {
|
5555 | return input.replace(DASH_CASE_REGEXP, (...m) => m[1].toUpperCase());
|
5556 | }
|
5557 | function splitAtColon(input, defaultValues) {
|
5558 | return _splitAt(input, ':', defaultValues);
|
5559 | }
|
5560 | function splitAtPeriod(input, defaultValues) {
|
5561 | return _splitAt(input, '.', defaultValues);
|
5562 | }
|
5563 | function _splitAt(input, character, defaultValues) {
|
5564 | const characterIndex = input.indexOf(character);
|
5565 | if (characterIndex == -1)
|
5566 | return defaultValues;
|
5567 | return [input.slice(0, characterIndex).trim(), input.slice(characterIndex + 1).trim()];
|
5568 | }
|
5569 | function error(msg) {
|
5570 | throw new Error(`Internal Error: ${msg}`);
|
5571 | }
|
5572 | function utf8Encode(str) {
|
5573 | let encoded = [];
|
5574 | for (let index = 0; index < str.length; index++) {
|
5575 | let codePoint = str.charCodeAt(index);
|
5576 |
|
5577 |
|
5578 | if (codePoint >= 0xd800 && codePoint <= 0xdbff && str.length > (index + 1)) {
|
5579 | const low = str.charCodeAt(index + 1);
|
5580 | if (low >= 0xdc00 && low <= 0xdfff) {
|
5581 | index++;
|
5582 | codePoint = ((codePoint - 0xd800) << 10) + low - 0xdc00 + 0x10000;
|
5583 | }
|
5584 | }
|
5585 | if (codePoint <= 0x7f) {
|
5586 | encoded.push(codePoint);
|
5587 | }
|
5588 | else if (codePoint <= 0x7ff) {
|
5589 | encoded.push(((codePoint >> 6) & 0x1F) | 0xc0, (codePoint & 0x3f) | 0x80);
|
5590 | }
|
5591 | else if (codePoint <= 0xffff) {
|
5592 | encoded.push((codePoint >> 12) | 0xe0, ((codePoint >> 6) & 0x3f) | 0x80, (codePoint & 0x3f) | 0x80);
|
5593 | }
|
5594 | else if (codePoint <= 0x1fffff) {
|
5595 | encoded.push(((codePoint >> 18) & 0x07) | 0xf0, ((codePoint >> 12) & 0x3f) | 0x80, ((codePoint >> 6) & 0x3f) | 0x80, (codePoint & 0x3f) | 0x80);
|
5596 | }
|
5597 | }
|
5598 | return encoded;
|
5599 | }
|
5600 | function stringify$1(token) {
|
5601 | if (typeof token === 'string') {
|
5602 | return token;
|
5603 | }
|
5604 | if (Array.isArray(token)) {
|
5605 | return '[' + token.map(stringify$1).join(', ') + ']';
|
5606 | }
|
5607 | if (token == null) {
|
5608 | return '' + token;
|
5609 | }
|
5610 | if (token.overriddenName) {
|
5611 | return `${token.overriddenName}`;
|
5612 | }
|
5613 | if (token.name) {
|
5614 | return `${token.name}`;
|
5615 | }
|
5616 | if (!token.toString) {
|
5617 | return 'object';
|
5618 | }
|
5619 |
|
5620 |
|
5621 | const res = token.toString();
|
5622 | if (res == null) {
|
5623 | return '' + res;
|
5624 | }
|
5625 | const newLineIndex = res.indexOf('\n');
|
5626 | return newLineIndex === -1 ? res : res.substring(0, newLineIndex);
|
5627 | }
|
5628 | class Version {
|
5629 | constructor(full) {
|
5630 | this.full = full;
|
5631 | const splits = full.split('.');
|
5632 | this.major = splits[0];
|
5633 | this.minor = splits[1];
|
5634 | this.patch = splits.slice(2).join('.');
|
5635 | }
|
5636 | }
|
5637 | const __window = typeof window !== 'undefined' && window;
|
5638 | const __self = typeof self !== 'undefined' && typeof WorkerGlobalScope !== 'undefined' &&
|
5639 | self instanceof WorkerGlobalScope && self;
|
5640 | const __global = typeof global !== 'undefined' && global;
|
5641 |
|
5642 |
|
5643 | const _global = __global || __window || __self;
|
5644 | function newArray(size, value) {
|
5645 | const list = [];
|
5646 | for (let i = 0; i < size; i++) {
|
5647 | list.push(value);
|
5648 | }
|
5649 | return list;
|
5650 | }
|
5651 | |
5652 |
|
5653 |
|
5654 |
|
5655 |
|
5656 |
|
5657 |
|
5658 |
|
5659 | function partitionArray(arr, conditionFn) {
|
5660 | const truthy = [];
|
5661 | const falsy = [];
|
5662 | for (const item of arr) {
|
5663 | (conditionFn(item) ? truthy : falsy).push(item);
|
5664 | }
|
5665 | return [truthy, falsy];
|
5666 | }
|
5667 |
|
5668 | |
5669 |
|
5670 |
|
5671 |
|
5672 |
|
5673 |
|
5674 |
|
5675 | function sanitizeIdentifier(name) {
|
5676 | return name.replace(/\W/g, '_');
|
5677 | }
|
5678 | let _anonymousTypeIndex = 0;
|
5679 | function identifierName(compileIdentifier) {
|
5680 | if (!compileIdentifier || !compileIdentifier.reference) {
|
5681 | return null;
|
5682 | }
|
5683 | const ref = compileIdentifier.reference;
|
5684 | if (ref instanceof StaticSymbol) {
|
5685 | return ref.name;
|
5686 | }
|
5687 | if (ref['__anonymousType']) {
|
5688 | return ref['__anonymousType'];
|
5689 | }
|
5690 | let identifier = stringify$1(ref);
|
5691 | if (identifier.indexOf('(') >= 0) {
|
5692 |
|
5693 | identifier = `anonymous_${_anonymousTypeIndex++}`;
|
5694 | ref['__anonymousType'] = identifier;
|
5695 | }
|
5696 | else {
|
5697 | identifier = sanitizeIdentifier(identifier);
|
5698 | }
|
5699 | return identifier;
|
5700 | }
|
5701 | var CompileSummaryKind;
|
5702 | (function (CompileSummaryKind) {
|
5703 | CompileSummaryKind[CompileSummaryKind["Pipe"] = 0] = "Pipe";
|
5704 | CompileSummaryKind[CompileSummaryKind["Directive"] = 1] = "Directive";
|
5705 | CompileSummaryKind[CompileSummaryKind["NgModule"] = 2] = "NgModule";
|
5706 | CompileSummaryKind[CompileSummaryKind["Injectable"] = 3] = "Injectable";
|
5707 | })(CompileSummaryKind || (CompileSummaryKind = {}));
|
5708 | function flatten(list) {
|
5709 | return list.reduce((flat, item) => {
|
5710 | const flatItem = Array.isArray(item) ? flatten(item) : item;
|
5711 | return flat.concat(flatItem);
|
5712 | }, []);
|
5713 | }
|
5714 |
|
5715 | |
5716 |
|
5717 |
|
5718 |
|
5719 |
|
5720 |
|
5721 |
|
5722 | const CORE$1 = '@angular/core';
|
5723 | class Identifiers$1 {
|
5724 | }
|
5725 |
|
5726 | Identifiers$1.NEW_METHOD = 'factory';
|
5727 | Identifiers$1.TRANSFORM_METHOD = 'transform';
|
5728 | Identifiers$1.PATCH_DEPS = 'patchedDeps';
|
5729 | Identifiers$1.core = { name: null, moduleName: CORE$1 };
|
5730 |
|
5731 | Identifiers$1.namespaceHTML = { name: 'ɵɵnamespaceHTML', moduleName: CORE$1 };
|
5732 | Identifiers$1.namespaceMathML = { name: 'ɵɵnamespaceMathML', moduleName: CORE$1 };
|
5733 | Identifiers$1.namespaceSVG = { name: 'ɵɵnamespaceSVG', moduleName: CORE$1 };
|
5734 | Identifiers$1.element = { name: 'ɵɵelement', moduleName: CORE$1 };
|
5735 | Identifiers$1.elementStart = { name: 'ɵɵelementStart', moduleName: CORE$1 };
|
5736 | Identifiers$1.elementEnd = { name: 'ɵɵelementEnd', moduleName: CORE$1 };
|
5737 | Identifiers$1.advance = { name: 'ɵɵadvance', moduleName: CORE$1 };
|
5738 | Identifiers$1.syntheticHostProperty = { name: 'ɵɵsyntheticHostProperty', moduleName: CORE$1 };
|
5739 | Identifiers$1.syntheticHostListener = { name: 'ɵɵsyntheticHostListener', moduleName: CORE$1 };
|
5740 | Identifiers$1.attribute = { name: 'ɵɵattribute', moduleName: CORE$1 };
|
5741 | Identifiers$1.attributeInterpolate1 = { name: 'ɵɵattributeInterpolate1', moduleName: CORE$1 };
|
5742 | Identifiers$1.attributeInterpolate2 = { name: 'ɵɵattributeInterpolate2', moduleName: CORE$1 };
|
5743 | Identifiers$1.attributeInterpolate3 = { name: 'ɵɵattributeInterpolate3', moduleName: CORE$1 };
|
5744 | Identifiers$1.attributeInterpolate4 = { name: 'ɵɵattributeInterpolate4', moduleName: CORE$1 };
|
5745 | Identifiers$1.attributeInterpolate5 = { name: 'ɵɵattributeInterpolate5', moduleName: CORE$1 };
|
5746 | Identifiers$1.attributeInterpolate6 = { name: 'ɵɵattributeInterpolate6', moduleName: CORE$1 };
|
5747 | Identifiers$1.attributeInterpolate7 = { name: 'ɵɵattributeInterpolate7', moduleName: CORE$1 };
|
5748 | Identifiers$1.attributeInterpolate8 = { name: 'ɵɵattributeInterpolate8', moduleName: CORE$1 };
|
5749 | Identifiers$1.attributeInterpolateV = { name: 'ɵɵattributeInterpolateV', moduleName: CORE$1 };
|
5750 | Identifiers$1.classProp = { name: 'ɵɵclassProp', moduleName: CORE$1 };
|
5751 | Identifiers$1.elementContainerStart = { name: 'ɵɵelementContainerStart', moduleName: CORE$1 };
|
5752 | Identifiers$1.elementContainerEnd = { name: 'ɵɵelementContainerEnd', moduleName: CORE$1 };
|
5753 | Identifiers$1.elementContainer = { name: 'ɵɵelementContainer', moduleName: CORE$1 };
|
5754 | Identifiers$1.styleMap = { name: 'ɵɵstyleMap', moduleName: CORE$1 };
|
5755 | Identifiers$1.styleMapInterpolate1 = { name: 'ɵɵstyleMapInterpolate1', moduleName: CORE$1 };
|
5756 | Identifiers$1.styleMapInterpolate2 = { name: 'ɵɵstyleMapInterpolate2', moduleName: CORE$1 };
|
5757 | Identifiers$1.styleMapInterpolate3 = { name: 'ɵɵstyleMapInterpolate3', moduleName: CORE$1 };
|
5758 | Identifiers$1.styleMapInterpolate4 = { name: 'ɵɵstyleMapInterpolate4', moduleName: CORE$1 };
|
5759 | Identifiers$1.styleMapInterpolate5 = { name: 'ɵɵstyleMapInterpolate5', moduleName: CORE$1 };
|
5760 | Identifiers$1.styleMapInterpolate6 = { name: 'ɵɵstyleMapInterpolate6', moduleName: CORE$1 };
|
5761 | Identifiers$1.styleMapInterpolate7 = { name: 'ɵɵstyleMapInterpolate7', moduleName: CORE$1 };
|
5762 | Identifiers$1.styleMapInterpolate8 = { name: 'ɵɵstyleMapInterpolate8', moduleName: CORE$1 };
|
5763 | Identifiers$1.styleMapInterpolateV = { name: 'ɵɵstyleMapInterpolateV', moduleName: CORE$1 };
|
5764 | Identifiers$1.classMap = { name: 'ɵɵclassMap', moduleName: CORE$1 };
|
5765 | Identifiers$1.classMapInterpolate1 = { name: 'ɵɵclassMapInterpolate1', moduleName: CORE$1 };
|
5766 | Identifiers$1.classMapInterpolate2 = { name: 'ɵɵclassMapInterpolate2', moduleName: CORE$1 };
|
5767 | Identifiers$1.classMapInterpolate3 = { name: 'ɵɵclassMapInterpolate3', moduleName: CORE$1 };
|
5768 | Identifiers$1.classMapInterpolate4 = { name: 'ɵɵclassMapInterpolate4', moduleName: CORE$1 };
|
5769 | Identifiers$1.classMapInterpolate5 = { name: 'ɵɵclassMapInterpolate5', moduleName: CORE$1 };
|
5770 | Identifiers$1.classMapInterpolate6 = { name: 'ɵɵclassMapInterpolate6', moduleName: CORE$1 };
|
5771 | Identifiers$1.classMapInterpolate7 = { name: 'ɵɵclassMapInterpolate7', moduleName: CORE$1 };
|
5772 | Identifiers$1.classMapInterpolate8 = { name: 'ɵɵclassMapInterpolate8', moduleName: CORE$1 };
|
5773 | Identifiers$1.classMapInterpolateV = { name: 'ɵɵclassMapInterpolateV', moduleName: CORE$1 };
|
5774 | Identifiers$1.styleProp = { name: 'ɵɵstyleProp', moduleName: CORE$1 };
|
5775 | Identifiers$1.stylePropInterpolate1 = { name: 'ɵɵstylePropInterpolate1', moduleName: CORE$1 };
|
5776 | Identifiers$1.stylePropInterpolate2 = { name: 'ɵɵstylePropInterpolate2', moduleName: CORE$1 };
|
5777 | Identifiers$1.stylePropInterpolate3 = { name: 'ɵɵstylePropInterpolate3', moduleName: CORE$1 };
|
5778 | Identifiers$1.stylePropInterpolate4 = { name: 'ɵɵstylePropInterpolate4', moduleName: CORE$1 };
|
5779 | Identifiers$1.stylePropInterpolate5 = { name: 'ɵɵstylePropInterpolate5', moduleName: CORE$1 };
|
5780 | Identifiers$1.stylePropInterpolate6 = { name: 'ɵɵstylePropInterpolate6', moduleName: CORE$1 };
|
5781 | Identifiers$1.stylePropInterpolate7 = { name: 'ɵɵstylePropInterpolate7', moduleName: CORE$1 };
|
5782 | Identifiers$1.stylePropInterpolate8 = { name: 'ɵɵstylePropInterpolate8', moduleName: CORE$1 };
|
5783 | Identifiers$1.stylePropInterpolateV = { name: 'ɵɵstylePropInterpolateV', moduleName: CORE$1 };
|
5784 | Identifiers$1.nextContext = { name: 'ɵɵnextContext', moduleName: CORE$1 };
|
5785 | Identifiers$1.templateCreate = { name: 'ɵɵtemplate', moduleName: CORE$1 };
|
5786 | Identifiers$1.text = { name: 'ɵɵtext', moduleName: CORE$1 };
|
5787 | Identifiers$1.enableBindings = { name: 'ɵɵenableBindings', moduleName: CORE$1 };
|
5788 | Identifiers$1.disableBindings = { name: 'ɵɵdisableBindings', moduleName: CORE$1 };
|
5789 | Identifiers$1.getCurrentView = { name: 'ɵɵgetCurrentView', moduleName: CORE$1 };
|
5790 | Identifiers$1.textInterpolate = { name: 'ɵɵtextInterpolate', moduleName: CORE$1 };
|
5791 | Identifiers$1.textInterpolate1 = { name: 'ɵɵtextInterpolate1', moduleName: CORE$1 };
|
5792 | Identifiers$1.textInterpolate2 = { name: 'ɵɵtextInterpolate2', moduleName: CORE$1 };
|
5793 | Identifiers$1.textInterpolate3 = { name: 'ɵɵtextInterpolate3', moduleName: CORE$1 };
|
5794 | Identifiers$1.textInterpolate4 = { name: 'ɵɵtextInterpolate4', moduleName: CORE$1 };
|
5795 | Identifiers$1.textInterpolate5 = { name: 'ɵɵtextInterpolate5', moduleName: CORE$1 };
|
5796 | Identifiers$1.textInterpolate6 = { name: 'ɵɵtextInterpolate6', moduleName: CORE$1 };
|
5797 | Identifiers$1.textInterpolate7 = { name: 'ɵɵtextInterpolate7', moduleName: CORE$1 };
|
5798 | Identifiers$1.textInterpolate8 = { name: 'ɵɵtextInterpolate8', moduleName: CORE$1 };
|
5799 | Identifiers$1.textInterpolateV = { name: 'ɵɵtextInterpolateV', moduleName: CORE$1 };
|
5800 | Identifiers$1.restoreView = { name: 'ɵɵrestoreView', moduleName: CORE$1 };
|
5801 | Identifiers$1.pureFunction0 = { name: 'ɵɵpureFunction0', moduleName: CORE$1 };
|
5802 | Identifiers$1.pureFunction1 = { name: 'ɵɵpureFunction1', moduleName: CORE$1 };
|
5803 | Identifiers$1.pureFunction2 = { name: 'ɵɵpureFunction2', moduleName: CORE$1 };
|
5804 | Identifiers$1.pureFunction3 = { name: 'ɵɵpureFunction3', moduleName: CORE$1 };
|
5805 | Identifiers$1.pureFunction4 = { name: 'ɵɵpureFunction4', moduleName: CORE$1 };
|
5806 | Identifiers$1.pureFunction5 = { name: 'ɵɵpureFunction5', moduleName: CORE$1 };
|
5807 | Identifiers$1.pureFunction6 = { name: 'ɵɵpureFunction6', moduleName: CORE$1 };
|
5808 | Identifiers$1.pureFunction7 = { name: 'ɵɵpureFunction7', moduleName: CORE$1 };
|
5809 | Identifiers$1.pureFunction8 = { name: 'ɵɵpureFunction8', moduleName: CORE$1 };
|
5810 | Identifiers$1.pureFunctionV = { name: 'ɵɵpureFunctionV', moduleName: CORE$1 };
|
5811 | Identifiers$1.pipeBind1 = { name: 'ɵɵpipeBind1', moduleName: CORE$1 };
|
5812 | Identifiers$1.pipeBind2 = { name: 'ɵɵpipeBind2', moduleName: CORE$1 };
|
5813 | Identifiers$1.pipeBind3 = { name: 'ɵɵpipeBind3', moduleName: CORE$1 };
|
5814 | Identifiers$1.pipeBind4 = { name: 'ɵɵpipeBind4', moduleName: CORE$1 };
|
5815 | Identifiers$1.pipeBindV = { name: 'ɵɵpipeBindV', moduleName: CORE$1 };
|
5816 | Identifiers$1.hostProperty = { name: 'ɵɵhostProperty', moduleName: CORE$1 };
|
5817 | Identifiers$1.property = { name: 'ɵɵproperty', moduleName: CORE$1 };
|
5818 | Identifiers$1.propertyInterpolate = { name: 'ɵɵpropertyInterpolate', moduleName: CORE$1 };
|
5819 | Identifiers$1.propertyInterpolate1 = { name: 'ɵɵpropertyInterpolate1', moduleName: CORE$1 };
|
5820 | Identifiers$1.propertyInterpolate2 = { name: 'ɵɵpropertyInterpolate2', moduleName: CORE$1 };
|
5821 | Identifiers$1.propertyInterpolate3 = { name: 'ɵɵpropertyInterpolate3', moduleName: CORE$1 };
|
5822 | Identifiers$1.propertyInterpolate4 = { name: 'ɵɵpropertyInterpolate4', moduleName: CORE$1 };
|
5823 | Identifiers$1.propertyInterpolate5 = { name: 'ɵɵpropertyInterpolate5', moduleName: CORE$1 };
|
5824 | Identifiers$1.propertyInterpolate6 = { name: 'ɵɵpropertyInterpolate6', moduleName: CORE$1 };
|
5825 | Identifiers$1.propertyInterpolate7 = { name: 'ɵɵpropertyInterpolate7', moduleName: CORE$1 };
|
5826 | Identifiers$1.propertyInterpolate8 = { name: 'ɵɵpropertyInterpolate8', moduleName: CORE$1 };
|
5827 | Identifiers$1.propertyInterpolateV = { name: 'ɵɵpropertyInterpolateV', moduleName: CORE$1 };
|
5828 | Identifiers$1.i18n = { name: 'ɵɵi18n', moduleName: CORE$1 };
|
5829 | Identifiers$1.i18nAttributes = { name: 'ɵɵi18nAttributes', moduleName: CORE$1 };
|
5830 | Identifiers$1.i18nExp = { name: 'ɵɵi18nExp', moduleName: CORE$1 };
|
5831 | Identifiers$1.i18nStart = { name: 'ɵɵi18nStart', moduleName: CORE$1 };
|
5832 | Identifiers$1.i18nEnd = { name: 'ɵɵi18nEnd', moduleName: CORE$1 };
|
5833 | Identifiers$1.i18nApply = { name: 'ɵɵi18nApply', moduleName: CORE$1 };
|
5834 | Identifiers$1.i18nPostprocess = { name: 'ɵɵi18nPostprocess', moduleName: CORE$1 };
|
5835 | Identifiers$1.pipe = { name: 'ɵɵpipe', moduleName: CORE$1 };
|
5836 | Identifiers$1.projection = { name: 'ɵɵprojection', moduleName: CORE$1 };
|
5837 | Identifiers$1.projectionDef = { name: 'ɵɵprojectionDef', moduleName: CORE$1 };
|
5838 | Identifiers$1.reference = { name: 'ɵɵreference', moduleName: CORE$1 };
|
5839 | Identifiers$1.inject = { name: 'ɵɵinject', moduleName: CORE$1 };
|
5840 | Identifiers$1.injectAttribute = { name: 'ɵɵinjectAttribute', moduleName: CORE$1 };
|
5841 | Identifiers$1.injectPipeChangeDetectorRef = { name: 'ɵɵinjectPipeChangeDetectorRef', moduleName: CORE$1 };
|
5842 | Identifiers$1.directiveInject = { name: 'ɵɵdirectiveInject', moduleName: CORE$1 };
|
5843 | Identifiers$1.invalidFactory = { name: 'ɵɵinvalidFactory', moduleName: CORE$1 };
|
5844 | Identifiers$1.invalidFactoryDep = { name: 'ɵɵinvalidFactoryDep', moduleName: CORE$1 };
|
5845 | Identifiers$1.templateRefExtractor = { name: 'ɵɵtemplateRefExtractor', moduleName: CORE$1 };
|
5846 | Identifiers$1.forwardRef = { name: 'forwardRef', moduleName: CORE$1 };
|
5847 | Identifiers$1.resolveForwardRef = { name: 'resolveForwardRef', moduleName: CORE$1 };
|
5848 | Identifiers$1.resolveWindow = { name: 'ɵɵresolveWindow', moduleName: CORE$1 };
|
5849 | Identifiers$1.resolveDocument = { name: 'ɵɵresolveDocument', moduleName: CORE$1 };
|
5850 | Identifiers$1.resolveBody = { name: 'ɵɵresolveBody', moduleName: CORE$1 };
|
5851 | Identifiers$1.defineComponent = { name: 'ɵɵdefineComponent', moduleName: CORE$1 };
|
5852 | Identifiers$1.declareComponent = { name: 'ɵɵngDeclareComponent', moduleName: CORE$1 };
|
5853 | Identifiers$1.setComponentScope = { name: 'ɵɵsetComponentScope', moduleName: CORE$1 };
|
5854 | Identifiers$1.ChangeDetectionStrategy = {
|
5855 | name: 'ChangeDetectionStrategy',
|
5856 | moduleName: CORE$1,
|
5857 | };
|
5858 | Identifiers$1.ViewEncapsulation = {
|
5859 | name: 'ViewEncapsulation',
|
5860 | moduleName: CORE$1,
|
5861 | };
|
5862 | Identifiers$1.ComponentDefWithMeta = {
|
5863 | name: 'ɵɵComponentDefWithMeta',
|
5864 | moduleName: CORE$1,
|
5865 | };
|
5866 | Identifiers$1.FactoryDef = {
|
5867 | name: 'ɵɵFactoryDef',
|
5868 | moduleName: CORE$1,
|
5869 | };
|
5870 | Identifiers$1.defineDirective = { name: 'ɵɵdefineDirective', moduleName: CORE$1 };
|
5871 | Identifiers$1.declareDirective = { name: 'ɵɵngDeclareDirective', moduleName: CORE$1 };
|
5872 | Identifiers$1.DirectiveDefWithMeta = {
|
5873 | name: 'ɵɵDirectiveDefWithMeta',
|
5874 | moduleName: CORE$1,
|
5875 | };
|
5876 | Identifiers$1.InjectorDef = {
|
5877 | name: 'ɵɵInjectorDef',
|
5878 | moduleName: CORE$1,
|
5879 | };
|
5880 | Identifiers$1.defineInjector = {
|
5881 | name: 'ɵɵdefineInjector',
|
5882 | moduleName: CORE$1,
|
5883 | };
|
5884 | Identifiers$1.NgModuleDefWithMeta = {
|
5885 | name: 'ɵɵNgModuleDefWithMeta',
|
5886 | moduleName: CORE$1,
|
5887 | };
|
5888 | Identifiers$1.ModuleWithProviders = {
|
5889 | name: 'ModuleWithProviders',
|
5890 | moduleName: CORE$1,
|
5891 | };
|
5892 | Identifiers$1.defineNgModule = { name: 'ɵɵdefineNgModule', moduleName: CORE$1 };
|
5893 | Identifiers$1.setNgModuleScope = { name: 'ɵɵsetNgModuleScope', moduleName: CORE$1 };
|
5894 | Identifiers$1.PipeDefWithMeta = { name: 'ɵɵPipeDefWithMeta', moduleName: CORE$1 };
|
5895 | Identifiers$1.definePipe = { name: 'ɵɵdefinePipe', moduleName: CORE$1 };
|
5896 | Identifiers$1.declarePipe = { name: 'ɵɵngDeclarePipe', moduleName: CORE$1 };
|
5897 | Identifiers$1.queryRefresh = { name: 'ɵɵqueryRefresh', moduleName: CORE$1 };
|
5898 | Identifiers$1.viewQuery = { name: 'ɵɵviewQuery', moduleName: CORE$1 };
|
5899 | Identifiers$1.loadQuery = { name: 'ɵɵloadQuery', moduleName: CORE$1 };
|
5900 | Identifiers$1.contentQuery = { name: 'ɵɵcontentQuery', moduleName: CORE$1 };
|
5901 | Identifiers$1.NgOnChangesFeature = { name: 'ɵɵNgOnChangesFeature', moduleName: CORE$1 };
|
5902 | Identifiers$1.InheritDefinitionFeature = { name: 'ɵɵInheritDefinitionFeature', moduleName: CORE$1 };
|
5903 | Identifiers$1.CopyDefinitionFeature = { name: 'ɵɵCopyDefinitionFeature', moduleName: CORE$1 };
|
5904 | Identifiers$1.ProvidersFeature = { name: 'ɵɵProvidersFeature', moduleName: CORE$1 };
|
5905 | Identifiers$1.listener = { name: 'ɵɵlistener', moduleName: CORE$1 };
|
5906 | Identifiers$1.getFactoryOf = {
|
5907 | name: 'ɵɵgetFactoryOf',
|
5908 | moduleName: CORE$1,
|
5909 | };
|
5910 | Identifiers$1.getInheritedFactory = {
|
5911 | name: 'ɵɵgetInheritedFactory',
|
5912 | moduleName: CORE$1,
|
5913 | };
|
5914 |
|
5915 | Identifiers$1.sanitizeHtml = { name: 'ɵɵsanitizeHtml', moduleName: CORE$1 };
|
5916 | Identifiers$1.sanitizeStyle = { name: 'ɵɵsanitizeStyle', moduleName: CORE$1 };
|
5917 | Identifiers$1.sanitizeResourceUrl = { name: 'ɵɵsanitizeResourceUrl', moduleName: CORE$1 };
|
5918 | Identifiers$1.sanitizeScript = { name: 'ɵɵsanitizeScript', moduleName: CORE$1 };
|
5919 | Identifiers$1.sanitizeUrl = { name: 'ɵɵsanitizeUrl', moduleName: CORE$1 };
|
5920 | Identifiers$1.sanitizeUrlOrResourceUrl = { name: 'ɵɵsanitizeUrlOrResourceUrl', moduleName: CORE$1 };
|
5921 | Identifiers$1.trustConstantHtml = { name: 'ɵɵtrustConstantHtml', moduleName: CORE$1 };
|
5922 | Identifiers$1.trustConstantResourceUrl = { name: 'ɵɵtrustConstantResourceUrl', moduleName: CORE$1 };
|
5923 |
|
5924 | |
5925 |
|
5926 |
|
5927 |
|
5928 |
|
5929 |
|
5930 |
|
5931 |
|
5932 | const VERSION = 3;
|
5933 | const JS_B64_PREFIX = '# sourceMappingURL=data:application/json;base64,';
|
5934 | class SourceMapGenerator {
|
5935 | constructor(file = null) {
|
5936 | this.file = file;
|
5937 | this.sourcesContent = new Map();
|
5938 | this.lines = [];
|
5939 | this.lastCol0 = 0;
|
5940 | this.hasMappings = false;
|
5941 | }
|
5942 |
|
5943 | addSource(url, content = null) {
|
5944 | if (!this.sourcesContent.has(url)) {
|
5945 | this.sourcesContent.set(url, content);
|
5946 | }
|
5947 | return this;
|
5948 | }
|
5949 | addLine() {
|
5950 | this.lines.push([]);
|
5951 | this.lastCol0 = 0;
|
5952 | return this;
|
5953 | }
|
5954 | addMapping(col0, sourceUrl, sourceLine0, sourceCol0) {
|
5955 | if (!this.currentLine) {
|
5956 | throw new Error(`A line must be added before mappings can be added`);
|
5957 | }
|
5958 | if (sourceUrl != null && !this.sourcesContent.has(sourceUrl)) {
|
5959 | throw new Error(`Unknown source file "${sourceUrl}"`);
|
5960 | }
|
5961 | if (col0 == null) {
|
5962 | throw new Error(`The column in the generated code must be provided`);
|
5963 | }
|
5964 | if (col0 < this.lastCol0) {
|
5965 | throw new Error(`Mapping should be added in output order`);
|
5966 | }
|
5967 | if (sourceUrl && (sourceLine0 == null || sourceCol0 == null)) {
|
5968 | throw new Error(`The source location must be provided when a source url is provided`);
|
5969 | }
|
5970 | this.hasMappings = true;
|
5971 | this.lastCol0 = col0;
|
5972 | this.currentLine.push({ col0, sourceUrl, sourceLine0, sourceCol0 });
|
5973 | return this;
|
5974 | }
|
5975 | |
5976 |
|
5977 |
|
5978 |
|
5979 | get currentLine() {
|
5980 | return this.lines.slice(-1)[0];
|
5981 | }
|
5982 | toJSON() {
|
5983 | if (!this.hasMappings) {
|
5984 | return null;
|
5985 | }
|
5986 | const sourcesIndex = new Map();
|
5987 | const sources = [];
|
5988 | const sourcesContent = [];
|
5989 | Array.from(this.sourcesContent.keys()).forEach((url, i) => {
|
5990 | sourcesIndex.set(url, i);
|
5991 | sources.push(url);
|
5992 | sourcesContent.push(this.sourcesContent.get(url) || null);
|
5993 | });
|
5994 | let mappings = '';
|
5995 | let lastCol0 = 0;
|
5996 | let lastSourceIndex = 0;
|
5997 | let lastSourceLine0 = 0;
|
5998 | let lastSourceCol0 = 0;
|
5999 | this.lines.forEach(segments => {
|
6000 | lastCol0 = 0;
|
6001 | mappings += segments
|
6002 | .map(segment => {
|
6003 |
|
6004 | let segAsStr = toBase64VLQ(segment.col0 - lastCol0);
|
6005 | lastCol0 = segment.col0;
|
6006 | if (segment.sourceUrl != null) {
|
6007 |
|
6008 | segAsStr +=
|
6009 | toBase64VLQ(sourcesIndex.get(segment.sourceUrl) - lastSourceIndex);
|
6010 | lastSourceIndex = sourcesIndex.get(segment.sourceUrl);
|
6011 |
|
6012 | segAsStr += toBase64VLQ(segment.sourceLine0 - lastSourceLine0);
|
6013 | lastSourceLine0 = segment.sourceLine0;
|
6014 |
|
6015 | segAsStr += toBase64VLQ(segment.sourceCol0 - lastSourceCol0);
|
6016 | lastSourceCol0 = segment.sourceCol0;
|
6017 | }
|
6018 | return segAsStr;
|
6019 | })
|
6020 | .join(',');
|
6021 | mappings += ';';
|
6022 | });
|
6023 | mappings = mappings.slice(0, -1);
|
6024 | return {
|
6025 | 'file': this.file || '',
|
6026 | 'version': VERSION,
|
6027 | 'sourceRoot': '',
|
6028 | 'sources': sources,
|
6029 | 'sourcesContent': sourcesContent,
|
6030 | 'mappings': mappings,
|
6031 | };
|
6032 | }
|
6033 | toJsComment() {
|
6034 | return this.hasMappings ? '//' + JS_B64_PREFIX + toBase64String(JSON.stringify(this, null, 0)) :
|
6035 | '';
|
6036 | }
|
6037 | }
|
6038 | function toBase64String(value) {
|
6039 | let b64 = '';
|
6040 | const encoded = utf8Encode(value);
|
6041 | for (let i = 0; i < encoded.length;) {
|
6042 | const i1 = encoded[i++];
|
6043 | const i2 = i < encoded.length ? encoded[i++] : null;
|
6044 | const i3 = i < encoded.length ? encoded[i++] : null;
|
6045 | b64 += toBase64Digit(i1 >> 2);
|
6046 | b64 += toBase64Digit(((i1 & 3) << 4) | (i2 === null ? 0 : i2 >> 4));
|
6047 | b64 += i2 === null ? '=' : toBase64Digit(((i2 & 15) << 2) | (i3 === null ? 0 : i3 >> 6));
|
6048 | b64 += i2 === null || i3 === null ? '=' : toBase64Digit(i3 & 63);
|
6049 | }
|
6050 | return b64;
|
6051 | }
|
6052 | function toBase64VLQ(value) {
|
6053 | value = value < 0 ? ((-value) << 1) + 1 : value << 1;
|
6054 | let out = '';
|
6055 | do {
|
6056 | let digit = value & 31;
|
6057 | value = value >> 5;
|
6058 | if (value > 0) {
|
6059 | digit = digit | 32;
|
6060 | }
|
6061 | out += toBase64Digit(digit);
|
6062 | } while (value > 0);
|
6063 | return out;
|
6064 | }
|
6065 | const B64_DIGITS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
6066 | function toBase64Digit(value) {
|
6067 | if (value < 0 || value >= 64) {
|
6068 | throw new Error(`Can only encode value in the range [0, 63]`);
|
6069 | }
|
6070 | return B64_DIGITS[value];
|
6071 | }
|
6072 |
|
6073 | |
6074 |
|
6075 |
|
6076 |
|
6077 |
|
6078 |
|
6079 |
|
6080 | const _SINGLE_QUOTE_ESCAPE_STRING_RE = /'|\\|\n|\r|\$/g;
|
6081 | const _LEGAL_IDENTIFIER_RE = /^[$A-Z_][0-9A-Z_$]*$/i;
|
6082 | const _INDENT_WITH = ' ';
|
6083 | const CATCH_ERROR_VAR$1 = variable('error', null, null);
|
6084 | const CATCH_STACK_VAR$1 = variable('stack', null, null);
|
6085 | class _EmittedLine {
|
6086 | constructor(indent) {
|
6087 | this.indent = indent;
|
6088 | this.partsLength = 0;
|
6089 | this.parts = [];
|
6090 | this.srcSpans = [];
|
6091 | }
|
6092 | }
|
6093 | class EmitterVisitorContext {
|
6094 | constructor(_indent) {
|
6095 | this._indent = _indent;
|
6096 | this._classes = [];
|
6097 | this._preambleLineCount = 0;
|
6098 | this._lines = [new _EmittedLine(_indent)];
|
6099 | }
|
6100 | static createRoot() {
|
6101 | return new EmitterVisitorContext(0);
|
6102 | }
|
6103 | |
6104 |
|
6105 |
|
6106 |
|
6107 | get _currentLine() {
|
6108 | return this._lines[this._lines.length - 1];
|
6109 | }
|
6110 | println(from, lastPart = '') {
|
6111 | this.print(from || null, lastPart, true);
|
6112 | }
|
6113 | lineIsEmpty() {
|
6114 | return this._currentLine.parts.length === 0;
|
6115 | }
|
6116 | lineLength() {
|
6117 | return this._currentLine.indent * _INDENT_WITH.length + this._currentLine.partsLength;
|
6118 | }
|
6119 | print(from, part, newLine = false) {
|
6120 | if (part.length > 0) {
|
6121 | this._currentLine.parts.push(part);
|
6122 | this._currentLine.partsLength += part.length;
|
6123 | this._currentLine.srcSpans.push(from && from.sourceSpan || null);
|
6124 | }
|
6125 | if (newLine) {
|
6126 | this._lines.push(new _EmittedLine(this._indent));
|
6127 | }
|
6128 | }
|
6129 | removeEmptyLastLine() {
|
6130 | if (this.lineIsEmpty()) {
|
6131 | this._lines.pop();
|
6132 | }
|
6133 | }
|
6134 | incIndent() {
|
6135 | this._indent++;
|
6136 | if (this.lineIsEmpty()) {
|
6137 | this._currentLine.indent = this._indent;
|
6138 | }
|
6139 | }
|
6140 | decIndent() {
|
6141 | this._indent--;
|
6142 | if (this.lineIsEmpty()) {
|
6143 | this._currentLine.indent = this._indent;
|
6144 | }
|
6145 | }
|
6146 | pushClass(clazz) {
|
6147 | this._classes.push(clazz);
|
6148 | }
|
6149 | popClass() {
|
6150 | return this._classes.pop();
|
6151 | }
|
6152 | get currentClass() {
|
6153 | return this._classes.length > 0 ? this._classes[this._classes.length - 1] : null;
|
6154 | }
|
6155 | toSource() {
|
6156 | return this.sourceLines
|
6157 | .map(l => l.parts.length > 0 ? _createIndent(l.indent) + l.parts.join('') : '')
|
6158 | .join('\n');
|
6159 | }
|
6160 | toSourceMapGenerator(genFilePath, startsAtLine = 0) {
|
6161 | const map = new SourceMapGenerator(genFilePath);
|
6162 | let firstOffsetMapped = false;
|
6163 | const mapFirstOffsetIfNeeded = () => {
|
6164 | if (!firstOffsetMapped) {
|
6165 |
|
6166 |
|
6167 |
|
6168 | map.addSource(genFilePath, ' ').addMapping(0, genFilePath, 0, 0);
|
6169 | firstOffsetMapped = true;
|
6170 | }
|
6171 | };
|
6172 | for (let i = 0; i < startsAtLine; i++) {
|
6173 | map.addLine();
|
6174 | mapFirstOffsetIfNeeded();
|
6175 | }
|
6176 | this.sourceLines.forEach((line, lineIdx) => {
|
6177 | map.addLine();
|
6178 | const spans = line.srcSpans;
|
6179 | const parts = line.parts;
|
6180 | let col0 = line.indent * _INDENT_WITH.length;
|
6181 | let spanIdx = 0;
|
6182 |
|
6183 | while (spanIdx < spans.length && !spans[spanIdx]) {
|
6184 | col0 += parts[spanIdx].length;
|
6185 | spanIdx++;
|
6186 | }
|
6187 | if (spanIdx < spans.length && lineIdx === 0 && col0 === 0) {
|
6188 | firstOffsetMapped = true;
|
6189 | }
|
6190 | else {
|
6191 | mapFirstOffsetIfNeeded();
|
6192 | }
|
6193 | while (spanIdx < spans.length) {
|
6194 | const span = spans[spanIdx];
|
6195 | const source = span.start.file;
|
6196 | const sourceLine = span.start.line;
|
6197 | const sourceCol = span.start.col;
|
6198 | map.addSource(source.url, source.content)
|
6199 | .addMapping(col0, source.url, sourceLine, sourceCol);
|
6200 | col0 += parts[spanIdx].length;
|
6201 | spanIdx++;
|
6202 |
|
6203 | while (spanIdx < spans.length && (span === spans[spanIdx] || !spans[spanIdx])) {
|
6204 | col0 += parts[spanIdx].length;
|
6205 | spanIdx++;
|
6206 | }
|
6207 | }
|
6208 | });
|
6209 | return map;
|
6210 | }
|
6211 | setPreambleLineCount(count) {
|
6212 | return this._preambleLineCount = count;
|
6213 | }
|
6214 | spanOf(line, column) {
|
6215 | const emittedLine = this._lines[line - this._preambleLineCount];
|
6216 | if (emittedLine) {
|
6217 | let columnsLeft = column - _createIndent(emittedLine.indent).length;
|
6218 | for (let partIndex = 0; partIndex < emittedLine.parts.length; partIndex++) {
|
6219 | const part = emittedLine.parts[partIndex];
|
6220 | if (part.length > columnsLeft) {
|
6221 | return emittedLine.srcSpans[partIndex];
|
6222 | }
|
6223 | columnsLeft -= part.length;
|
6224 | }
|
6225 | }
|
6226 | return null;
|
6227 | }
|
6228 | |
6229 |
|
6230 |
|
6231 |
|
6232 | get sourceLines() {
|
6233 | if (this._lines.length && this._lines[this._lines.length - 1].parts.length === 0) {
|
6234 | return this._lines.slice(0, -1);
|
6235 | }
|
6236 | return this._lines;
|
6237 | }
|
6238 | }
|
6239 | class AbstractEmitterVisitor {
|
6240 | constructor(_escapeDollarInStrings) {
|
6241 | this._escapeDollarInStrings = _escapeDollarInStrings;
|
6242 | }
|
6243 | printLeadingComments(stmt, ctx) {
|
6244 | if (stmt.leadingComments === undefined) {
|
6245 | return;
|
6246 | }
|
6247 | for (const comment of stmt.leadingComments) {
|
6248 | if (comment instanceof JSDocComment) {
|
6249 | ctx.print(stmt, `/*${comment.toString()}*/`, comment.trailingNewline);
|
6250 | }
|
6251 | else {
|
6252 | if (comment.multiline) {
|
6253 | ctx.print(stmt, `/* ${comment.text} */`, comment.trailingNewline);
|
6254 | }
|
6255 | else {
|
6256 | comment.text.split('\n').forEach((line) => {
|
6257 | ctx.println(stmt, `// ${line}`);
|
6258 | });
|
6259 | }
|
6260 | }
|
6261 | }
|
6262 | }
|
6263 | visitExpressionStmt(stmt, ctx) {
|
6264 | this.printLeadingComments(stmt, ctx);
|
6265 | stmt.expr.visitExpression(this, ctx);
|
6266 | ctx.println(stmt, ';');
|
6267 | return null;
|
6268 | }
|
6269 | visitReturnStmt(stmt, ctx) {
|
6270 | this.printLeadingComments(stmt, ctx);
|
6271 | ctx.print(stmt, `return `);
|
6272 | stmt.value.visitExpression(this, ctx);
|
6273 | ctx.println(stmt, ';');
|
6274 | return null;
|
6275 | }
|
6276 | visitIfStmt(stmt, ctx) {
|
6277 | this.printLeadingComments(stmt, ctx);
|
6278 | ctx.print(stmt, `if (`);
|
6279 | stmt.condition.visitExpression(this, ctx);
|
6280 | ctx.print(stmt, `) {`);
|
6281 | const hasElseCase = stmt.falseCase != null && stmt.falseCase.length > 0;
|
6282 | if (stmt.trueCase.length <= 1 && !hasElseCase) {
|
6283 | ctx.print(stmt, ` `);
|
6284 | this.visitAllStatements(stmt.trueCase, ctx);
|
6285 | ctx.removeEmptyLastLine();
|
6286 | ctx.print(stmt, ` `);
|
6287 | }
|
6288 | else {
|
6289 | ctx.println();
|
6290 | ctx.incIndent();
|
6291 | this.visitAllStatements(stmt.trueCase, ctx);
|
6292 | ctx.decIndent();
|
6293 | if (hasElseCase) {
|
6294 | ctx.println(stmt, `} else {`);
|
6295 | ctx.incIndent();
|
6296 | this.visitAllStatements(stmt.falseCase, ctx);
|
6297 | ctx.decIndent();
|
6298 | }
|
6299 | }
|
6300 | ctx.println(stmt, `}`);
|
6301 | return null;
|
6302 | }
|
6303 | visitThrowStmt(stmt, ctx) {
|
6304 | this.printLeadingComments(stmt, ctx);
|
6305 | ctx.print(stmt, `throw `);
|
6306 | stmt.error.visitExpression(this, ctx);
|
6307 | ctx.println(stmt, `;`);
|
6308 | return null;
|
6309 | }
|
6310 | visitWriteVarExpr(expr, ctx) {
|
6311 | const lineWasEmpty = ctx.lineIsEmpty();
|
6312 | if (!lineWasEmpty) {
|
6313 | ctx.print(expr, '(');
|
6314 | }
|
6315 | ctx.print(expr, `${expr.name} = `);
|
6316 | expr.value.visitExpression(this, ctx);
|
6317 | if (!lineWasEmpty) {
|
6318 | ctx.print(expr, ')');
|
6319 | }
|
6320 | return null;
|
6321 | }
|
6322 | visitWriteKeyExpr(expr, ctx) {
|
6323 | const lineWasEmpty = ctx.lineIsEmpty();
|
6324 | if (!lineWasEmpty) {
|
6325 | ctx.print(expr, '(');
|
6326 | }
|
6327 | expr.receiver.visitExpression(this, ctx);
|
6328 | ctx.print(expr, `[`);
|
6329 | expr.index.visitExpression(this, ctx);
|
6330 | ctx.print(expr, `] = `);
|
6331 | expr.value.visitExpression(this, ctx);
|
6332 | if (!lineWasEmpty) {
|
6333 | ctx.print(expr, ')');
|
6334 | }
|
6335 | return null;
|
6336 | }
|
6337 | visitWritePropExpr(expr, ctx) {
|
6338 | const lineWasEmpty = ctx.lineIsEmpty();
|
6339 | if (!lineWasEmpty) {
|
6340 | ctx.print(expr, '(');
|
6341 | }
|
6342 | expr.receiver.visitExpression(this, ctx);
|
6343 | ctx.print(expr, `.${expr.name} = `);
|
6344 | expr.value.visitExpression(this, ctx);
|
6345 | if (!lineWasEmpty) {
|
6346 | ctx.print(expr, ')');
|
6347 | }
|
6348 | return null;
|
6349 | }
|
6350 | visitInvokeMethodExpr(expr, ctx) {
|
6351 | expr.receiver.visitExpression(this, ctx);
|
6352 | let name = expr.name;
|
6353 | if (expr.builtin != null) {
|
6354 | name = this.getBuiltinMethodName(expr.builtin);
|
6355 | if (name == null) {
|
6356 |
|
6357 | return null;
|
6358 | }
|
6359 | }
|
6360 | ctx.print(expr, `.${name}(`);
|
6361 | this.visitAllExpressions(expr.args, ctx, `,`);
|
6362 | ctx.print(expr, `)`);
|
6363 | return null;
|
6364 | }
|
6365 | visitInvokeFunctionExpr(expr, ctx) {
|
6366 | expr.fn.visitExpression(this, ctx);
|
6367 | ctx.print(expr, `(`);
|
6368 | this.visitAllExpressions(expr.args, ctx, ',');
|
6369 | ctx.print(expr, `)`);
|
6370 | return null;
|
6371 | }
|
6372 | visitTaggedTemplateExpr(expr, ctx) {
|
6373 | expr.tag.visitExpression(this, ctx);
|
6374 | ctx.print(expr, '`' + expr.template.elements[0].rawText);
|
6375 | for (let i = 1; i < expr.template.elements.length; i++) {
|
6376 | ctx.print(expr, '${');
|
6377 | expr.template.expressions[i - 1].visitExpression(this, ctx);
|
6378 | ctx.print(expr, `}${expr.template.elements[i].rawText}`);
|
6379 | }
|
6380 | ctx.print(expr, '`');
|
6381 | return null;
|
6382 | }
|
6383 | visitWrappedNodeExpr(ast, ctx) {
|
6384 | throw new Error('Abstract emitter cannot visit WrappedNodeExpr.');
|
6385 | }
|
6386 | visitTypeofExpr(expr, ctx) {
|
6387 | ctx.print(expr, 'typeof ');
|
6388 | expr.expr.visitExpression(this, ctx);
|
6389 | }
|
6390 | visitReadVarExpr(ast, ctx) {
|
6391 | let varName = ast.name;
|
6392 | if (ast.builtin != null) {
|
6393 | switch (ast.builtin) {
|
6394 | case BuiltinVar.Super:
|
6395 | varName = 'super';
|
6396 | break;
|
6397 | case BuiltinVar.This:
|
6398 | varName = 'this';
|
6399 | break;
|
6400 | case BuiltinVar.CatchError:
|
6401 | varName = CATCH_ERROR_VAR$1.name;
|
6402 | break;
|
6403 | case BuiltinVar.CatchStack:
|
6404 | varName = CATCH_STACK_VAR$1.name;
|
6405 | break;
|
6406 | default:
|
6407 | throw new Error(`Unknown builtin variable ${ast.builtin}`);
|
6408 | }
|
6409 | }
|
6410 | ctx.print(ast, varName);
|
6411 | return null;
|
6412 | }
|
6413 | visitInstantiateExpr(ast, ctx) {
|
6414 | ctx.print(ast, `new `);
|
6415 | ast.classExpr.visitExpression(this, ctx);
|
6416 | ctx.print(ast, `(`);
|
6417 | this.visitAllExpressions(ast.args, ctx, ',');
|
6418 | ctx.print(ast, `)`);
|
6419 | return null;
|
6420 | }
|
6421 | visitLiteralExpr(ast, ctx) {
|
6422 | const value = ast.value;
|
6423 | if (typeof value === 'string') {
|
6424 | ctx.print(ast, escapeIdentifier(value, this._escapeDollarInStrings));
|
6425 | }
|
6426 | else {
|
6427 | ctx.print(ast, `${value}`);
|
6428 | }
|
6429 | return null;
|
6430 | }
|
6431 | visitLocalizedString(ast, ctx) {
|
6432 | const head = ast.serializeI18nHead();
|
6433 | ctx.print(ast, '$localize `' + head.raw);
|
6434 | for (let i = 1; i < ast.messageParts.length; i++) {
|
6435 | ctx.print(ast, '${');
|
6436 | ast.expressions[i - 1].visitExpression(this, ctx);
|
6437 | ctx.print(ast, `}${ast.serializeI18nTemplatePart(i).raw}`);
|
6438 | }
|
6439 | ctx.print(ast, '`');
|
6440 | return null;
|
6441 | }
|
6442 | visitConditionalExpr(ast, ctx) {
|
6443 | ctx.print(ast, `(`);
|
6444 | ast.condition.visitExpression(this, ctx);
|
6445 | ctx.print(ast, '? ');
|
6446 | ast.trueCase.visitExpression(this, ctx);
|
6447 | ctx.print(ast, ': ');
|
6448 | ast.falseCase.visitExpression(this, ctx);
|
6449 | ctx.print(ast, `)`);
|
6450 | return null;
|
6451 | }
|
6452 | visitNotExpr(ast, ctx) {
|
6453 | ctx.print(ast, '!');
|
6454 | ast.condition.visitExpression(this, ctx);
|
6455 | return null;
|
6456 | }
|
6457 | visitAssertNotNullExpr(ast, ctx) {
|
6458 | ast.condition.visitExpression(this, ctx);
|
6459 | return null;
|
6460 | }
|
6461 | visitUnaryOperatorExpr(ast, ctx) {
|
6462 | let opStr;
|
6463 | switch (ast.operator) {
|
6464 | case UnaryOperator.Plus:
|
6465 | opStr = '+';
|
6466 | break;
|
6467 | case UnaryOperator.Minus:
|
6468 | opStr = '-';
|
6469 | break;
|
6470 | default:
|
6471 | throw new Error(`Unknown operator ${ast.operator}`);
|
6472 | }
|
6473 | if (ast.parens)
|
6474 | ctx.print(ast, `(`);
|
6475 | ctx.print(ast, opStr);
|
6476 | ast.expr.visitExpression(this, ctx);
|
6477 | if (ast.parens)
|
6478 | ctx.print(ast, `)`);
|
6479 | return null;
|
6480 | }
|
6481 | visitBinaryOperatorExpr(ast, ctx) {
|
6482 | let opStr;
|
6483 | switch (ast.operator) {
|
6484 | case BinaryOperator.Equals:
|
6485 | opStr = '==';
|
6486 | break;
|
6487 | case BinaryOperator.Identical:
|
6488 | opStr = '===';
|
6489 | break;
|
6490 | case BinaryOperator.NotEquals:
|
6491 | opStr = '!=';
|
6492 | break;
|
6493 | case BinaryOperator.NotIdentical:
|
6494 | opStr = '!==';
|
6495 | break;
|
6496 | case BinaryOperator.And:
|
6497 | opStr = '&&';
|
6498 | break;
|
6499 | case BinaryOperator.BitwiseAnd:
|
6500 | opStr = '&';
|
6501 | break;
|
6502 | case BinaryOperator.Or:
|
6503 | opStr = '||';
|
6504 | break;
|
6505 | case BinaryOperator.Plus:
|
6506 | opStr = '+';
|
6507 | break;
|
6508 | case BinaryOperator.Minus:
|
6509 | opStr = '-';
|
6510 | break;
|
6511 | case BinaryOperator.Divide:
|
6512 | opStr = '/';
|
6513 | break;
|
6514 | case BinaryOperator.Multiply:
|
6515 | opStr = '*';
|
6516 | break;
|
6517 | case BinaryOperator.Modulo:
|
6518 | opStr = '%';
|
6519 | break;
|
6520 | case BinaryOperator.Lower:
|
6521 | opStr = '<';
|
6522 | break;
|
6523 | case BinaryOperator.LowerEquals:
|
6524 | opStr = '<=';
|
6525 | break;
|
6526 | case BinaryOperator.Bigger:
|
6527 | opStr = '>';
|
6528 | break;
|
6529 | case BinaryOperator.BiggerEquals:
|
6530 | opStr = '>=';
|
6531 | break;
|
6532 | default:
|
6533 | throw new Error(`Unknown operator ${ast.operator}`);
|
6534 | }
|
6535 | if (ast.parens)
|
6536 | ctx.print(ast, `(`);
|
6537 | ast.lhs.visitExpression(this, ctx);
|
6538 | ctx.print(ast, ` ${opStr} `);
|
6539 | ast.rhs.visitExpression(this, ctx);
|
6540 | if (ast.parens)
|
6541 | ctx.print(ast, `)`);
|
6542 | return null;
|
6543 | }
|
6544 | visitReadPropExpr(ast, ctx) {
|
6545 | ast.receiver.visitExpression(this, ctx);
|
6546 | ctx.print(ast, `.`);
|
6547 | ctx.print(ast, ast.name);
|
6548 | return null;
|
6549 | }
|
6550 | visitReadKeyExpr(ast, ctx) {
|
6551 | ast.receiver.visitExpression(this, ctx);
|
6552 | ctx.print(ast, `[`);
|
6553 | ast.index.visitExpression(this, ctx);
|
6554 | ctx.print(ast, `]`);
|
6555 | return null;
|
6556 | }
|
6557 | visitLiteralArrayExpr(ast, ctx) {
|
6558 | ctx.print(ast, `[`);
|
6559 | this.visitAllExpressions(ast.entries, ctx, ',');
|
6560 | ctx.print(ast, `]`);
|
6561 | return null;
|
6562 | }
|
6563 | visitLiteralMapExpr(ast, ctx) {
|
6564 | ctx.print(ast, `{`);
|
6565 | this.visitAllObjects(entry => {
|
6566 | ctx.print(ast, `${escapeIdentifier(entry.key, this._escapeDollarInStrings, entry.quoted)}:`);
|
6567 | entry.value.visitExpression(this, ctx);
|
6568 | }, ast.entries, ctx, ',');
|
6569 | ctx.print(ast, `}`);
|
6570 | return null;
|
6571 | }
|
6572 | visitCommaExpr(ast, ctx) {
|
6573 | ctx.print(ast, '(');
|
6574 | this.visitAllExpressions(ast.parts, ctx, ',');
|
6575 | ctx.print(ast, ')');
|
6576 | return null;
|
6577 | }
|
6578 | visitAllExpressions(expressions, ctx, separator) {
|
6579 | this.visitAllObjects(expr => expr.visitExpression(this, ctx), expressions, ctx, separator);
|
6580 | }
|
6581 | visitAllObjects(handler, expressions, ctx, separator) {
|
6582 | let incrementedIndent = false;
|
6583 | for (let i = 0; i < expressions.length; i++) {
|
6584 | if (i > 0) {
|
6585 | if (ctx.lineLength() > 80) {
|
6586 | ctx.print(null, separator, true);
|
6587 | if (!incrementedIndent) {
|
6588 |
|
6589 | ctx.incIndent();
|
6590 | ctx.incIndent();
|
6591 | incrementedIndent = true;
|
6592 | }
|
6593 | }
|
6594 | else {
|
6595 | ctx.print(null, separator, false);
|
6596 | }
|
6597 | }
|
6598 | handler(expressions[i]);
|
6599 | }
|
6600 | if (incrementedIndent) {
|
6601 |
|
6602 | ctx.decIndent();
|
6603 | ctx.decIndent();
|
6604 | }
|
6605 | }
|
6606 | visitAllStatements(statements, ctx) {
|
6607 | statements.forEach((stmt) => stmt.visitStatement(this, ctx));
|
6608 | }
|
6609 | }
|
6610 | function escapeIdentifier(input, escapeDollar, alwaysQuote = true) {
|
6611 | if (input == null) {
|
6612 | return null;
|
6613 | }
|
6614 | const body = input.replace(_SINGLE_QUOTE_ESCAPE_STRING_RE, (...match) => {
|
6615 | if (match[0] == '$') {
|
6616 | return escapeDollar ? '\\$' : '$';
|
6617 | }
|
6618 | else if (match[0] == '\n') {
|
6619 | return '\\n';
|
6620 | }
|
6621 | else if (match[0] == '\r') {
|
6622 | return '\\r';
|
6623 | }
|
6624 | else {
|
6625 | return `\\${match[0]}`;
|
6626 | }
|
6627 | });
|
6628 | const requiresQuotes = alwaysQuote || !_LEGAL_IDENTIFIER_RE.test(body);
|
6629 | return requiresQuotes ? `'${body}'` : body;
|
6630 | }
|
6631 | function _createIndent(count) {
|
6632 | let res = '';
|
6633 | for (let i = 0; i < count; i++) {
|
6634 | res += _INDENT_WITH;
|
6635 | }
|
6636 | return res;
|
6637 | }
|
6638 |
|
6639 | |
6640 |
|
6641 |
|
6642 |
|
6643 |
|
6644 |
|
6645 |
|
6646 | |
6647 |
|
6648 |
|
6649 | function mapToMapExpression(map) {
|
6650 | const result = Object.keys(map).map(key => ({
|
6651 | key,
|
6652 |
|
6653 |
|
6654 | value: map[key],
|
6655 | quoted: false,
|
6656 | }));
|
6657 | return literalMap(result);
|
6658 | }
|
6659 | function typeWithParameters(type, numParams) {
|
6660 | if (numParams === 0) {
|
6661 | return expressionType(type);
|
6662 | }
|
6663 | const params = [];
|
6664 | for (let i = 0; i < numParams; i++) {
|
6665 | params.push(DYNAMIC_TYPE);
|
6666 | }
|
6667 | return expressionType(type, undefined, params);
|
6668 | }
|
6669 | const ANIMATE_SYMBOL_PREFIX = '@';
|
6670 | function prepareSyntheticPropertyName(name) {
|
6671 | return `${ANIMATE_SYMBOL_PREFIX}${name}`;
|
6672 | }
|
6673 | function prepareSyntheticListenerName(name, phase) {
|
6674 | return `${ANIMATE_SYMBOL_PREFIX}${name}.${phase}`;
|
6675 | }
|
6676 | function getSafePropertyAccessString(accessor, name) {
|
6677 | const escapedName = escapeIdentifier(name, false, false);
|
6678 | return escapedName !== name ? `${accessor}[${escapedName}]` : `${accessor}.${name}`;
|
6679 | }
|
6680 | function prepareSyntheticListenerFunctionName(name, phase) {
|
6681 | return `animation_${name}_${phase}`;
|
6682 | }
|
6683 | function jitOnlyGuardedExpression(expr) {
|
6684 | return guardedExpression('ngJitMode', expr);
|
6685 | }
|
6686 | function devOnlyGuardedExpression(expr) {
|
6687 | return guardedExpression('ngDevMode', expr);
|
6688 | }
|
6689 | function guardedExpression(guard, expr) {
|
6690 | const guardExpr = new ExternalExpr({ name: guard, moduleName: null });
|
6691 | const guardNotDefined = new BinaryOperatorExpr(BinaryOperator.Identical, new TypeofExpr(guardExpr), literal('undefined'));
|
6692 | const guardUndefinedOrTrue = new BinaryOperatorExpr(BinaryOperator.Or, guardNotDefined, guardExpr, undefined,
|
6693 | undefined, true);
|
6694 | return new BinaryOperatorExpr(BinaryOperator.And, guardUndefinedOrTrue, expr);
|
6695 | }
|
6696 |
|
6697 | |
6698 |
|
6699 |
|
6700 |
|
6701 |
|
6702 |
|
6703 |
|
6704 | class Text {
|
6705 | constructor(value, sourceSpan) {
|
6706 | this.value = value;
|
6707 | this.sourceSpan = sourceSpan;
|
6708 | }
|
6709 | visit(visitor) {
|
6710 | return visitor.visitText(this);
|
6711 | }
|
6712 | }
|
6713 | class BoundText {
|
6714 | constructor(value, sourceSpan, i18n) {
|
6715 | this.value = value;
|
6716 | this.sourceSpan = sourceSpan;
|
6717 | this.i18n = i18n;
|
6718 | }
|
6719 | visit(visitor) {
|
6720 | return visitor.visitBoundText(this);
|
6721 | }
|
6722 | }
|
6723 | |
6724 |
|
6725 |
|
6726 |
|
6727 |
|
6728 |
|
6729 | class TextAttribute {
|
6730 | constructor(name, value, sourceSpan, keySpan, valueSpan, i18n) {
|
6731 | this.name = name;
|
6732 | this.value = value;
|
6733 | this.sourceSpan = sourceSpan;
|
6734 | this.keySpan = keySpan;
|
6735 | this.valueSpan = valueSpan;
|
6736 | this.i18n = i18n;
|
6737 | }
|
6738 | visit(visitor) {
|
6739 | return visitor.visitTextAttribute(this);
|
6740 | }
|
6741 | }
|
6742 | class BoundAttribute {
|
6743 | constructor(name, type, securityContext, value, unit, sourceSpan, keySpan, valueSpan, i18n) {
|
6744 | this.name = name;
|
6745 | this.type = type;
|
6746 | this.securityContext = securityContext;
|
6747 | this.value = value;
|
6748 | this.unit = unit;
|
6749 | this.sourceSpan = sourceSpan;
|
6750 | this.keySpan = keySpan;
|
6751 | this.valueSpan = valueSpan;
|
6752 | this.i18n = i18n;
|
6753 | }
|
6754 | static fromBoundElementProperty(prop, i18n) {
|
6755 | if (prop.keySpan === undefined) {
|
6756 | throw new Error(`Unexpected state: keySpan must be defined for bound attributes but was not for ${prop.name}: ${prop.sourceSpan}`);
|
6757 | }
|
6758 | return new BoundAttribute(prop.name, prop.type, prop.securityContext, prop.value, prop.unit, prop.sourceSpan, prop.keySpan, prop.valueSpan, i18n);
|
6759 | }
|
6760 | visit(visitor) {
|
6761 | return visitor.visitBoundAttribute(this);
|
6762 | }
|
6763 | }
|
6764 | class BoundEvent {
|
6765 | constructor(name, type, handler, target, phase, sourceSpan, handlerSpan, keySpan) {
|
6766 | this.name = name;
|
6767 | this.type = type;
|
6768 | this.handler = handler;
|
6769 | this.target = target;
|
6770 | this.phase = phase;
|
6771 | this.sourceSpan = sourceSpan;
|
6772 | this.handlerSpan = handlerSpan;
|
6773 | this.keySpan = keySpan;
|
6774 | }
|
6775 | static fromParsedEvent(event) {
|
6776 | const target = event.type === 0 ? event.targetOrPhase : null;
|
6777 | const phase = event.type === 1 ? event.targetOrPhase : null;
|
6778 | if (event.keySpan === undefined) {
|
6779 | throw new Error(`Unexpected state: keySpan must be defined for bound event but was not for ${event.name}: ${event.sourceSpan}`);
|
6780 | }
|
6781 | return new BoundEvent(event.name, event.type, event.handler, target, phase, event.sourceSpan, event.handlerSpan, event.keySpan);
|
6782 | }
|
6783 | visit(visitor) {
|
6784 | return visitor.visitBoundEvent(this);
|
6785 | }
|
6786 | }
|
6787 | class Element {
|
6788 | constructor(name, attributes, inputs, outputs, children, references, sourceSpan, startSourceSpan, endSourceSpan, i18n) {
|
6789 | this.name = name;
|
6790 | this.attributes = attributes;
|
6791 | this.inputs = inputs;
|
6792 | this.outputs = outputs;
|
6793 | this.children = children;
|
6794 | this.references = references;
|
6795 | this.sourceSpan = sourceSpan;
|
6796 | this.startSourceSpan = startSourceSpan;
|
6797 | this.endSourceSpan = endSourceSpan;
|
6798 | this.i18n = i18n;
|
6799 | }
|
6800 | visit(visitor) {
|
6801 | return visitor.visitElement(this);
|
6802 | }
|
6803 | }
|
6804 | class Template {
|
6805 | constructor(tagName, attributes, inputs, outputs, templateAttrs, children, references, variables, sourceSpan, startSourceSpan, endSourceSpan, i18n) {
|
6806 | this.tagName = tagName;
|
6807 | this.attributes = attributes;
|
6808 | this.inputs = inputs;
|
6809 | this.outputs = outputs;
|
6810 | this.templateAttrs = templateAttrs;
|
6811 | this.children = children;
|
6812 | this.references = references;
|
6813 | this.variables = variables;
|
6814 | this.sourceSpan = sourceSpan;
|
6815 | this.startSourceSpan = startSourceSpan;
|
6816 | this.endSourceSpan = endSourceSpan;
|
6817 | this.i18n = i18n;
|
6818 | }
|
6819 | visit(visitor) {
|
6820 | return visitor.visitTemplate(this);
|
6821 | }
|
6822 | }
|
6823 | class Content {
|
6824 | constructor(selector, attributes, sourceSpan, i18n) {
|
6825 | this.selector = selector;
|
6826 | this.attributes = attributes;
|
6827 | this.sourceSpan = sourceSpan;
|
6828 | this.i18n = i18n;
|
6829 | this.name = 'ng-content';
|
6830 | }
|
6831 | visit(visitor) {
|
6832 | return visitor.visitContent(this);
|
6833 | }
|
6834 | }
|
6835 | class Variable {
|
6836 | constructor(name, value, sourceSpan, keySpan, valueSpan) {
|
6837 | this.name = name;
|
6838 | this.value = value;
|
6839 | this.sourceSpan = sourceSpan;
|
6840 | this.keySpan = keySpan;
|
6841 | this.valueSpan = valueSpan;
|
6842 | }
|
6843 | visit(visitor) {
|
6844 | return visitor.visitVariable(this);
|
6845 | }
|
6846 | }
|
6847 | class Reference {
|
6848 | constructor(name, value, sourceSpan, keySpan, valueSpan) {
|
6849 | this.name = name;
|
6850 | this.value = value;
|
6851 | this.sourceSpan = sourceSpan;
|
6852 | this.keySpan = keySpan;
|
6853 | this.valueSpan = valueSpan;
|
6854 | }
|
6855 | visit(visitor) {
|
6856 | return visitor.visitReference(this);
|
6857 | }
|
6858 | }
|
6859 | class Icu {
|
6860 | constructor(vars, placeholders, sourceSpan, i18n) {
|
6861 | this.vars = vars;
|
6862 | this.placeholders = placeholders;
|
6863 | this.sourceSpan = sourceSpan;
|
6864 | this.i18n = i18n;
|
6865 | }
|
6866 | visit(visitor) {
|
6867 | return visitor.visitIcu(this);
|
6868 | }
|
6869 | }
|
6870 | class RecursiveVisitor {
|
6871 | visitElement(element) {
|
6872 | visitAll(this, element.attributes);
|
6873 | visitAll(this, element.children);
|
6874 | visitAll(this, element.references);
|
6875 | }
|
6876 | visitTemplate(template) {
|
6877 | visitAll(this, template.attributes);
|
6878 | visitAll(this, template.children);
|
6879 | visitAll(this, template.references);
|
6880 | visitAll(this, template.variables);
|
6881 | }
|
6882 | visitContent(content) { }
|
6883 | visitVariable(variable) { }
|
6884 | visitReference(reference) { }
|
6885 | visitTextAttribute(attribute) { }
|
6886 | visitBoundAttribute(attribute) { }
|
6887 | visitBoundEvent(attribute) { }
|
6888 | visitText(text) { }
|
6889 | visitBoundText(text) { }
|
6890 | visitIcu(icu) { }
|
6891 | }
|
6892 | function visitAll(visitor, nodes) {
|
6893 | const result = [];
|
6894 | if (visitor.visit) {
|
6895 | for (const node of nodes) {
|
6896 | const newNode = visitor.visit(node) || node.visit(visitor);
|
6897 | }
|
6898 | }
|
6899 | else {
|
6900 | for (const node of nodes) {
|
6901 | const newNode = node.visit(visitor);
|
6902 | if (newNode) {
|
6903 | result.push(newNode);
|
6904 | }
|
6905 | }
|
6906 | }
|
6907 | return result;
|
6908 | }
|
6909 |
|
6910 | |
6911 |
|
6912 |
|
6913 |
|
6914 |
|
6915 |
|
6916 |
|
6917 | class Message {
|
6918 | |
6919 |
|
6920 |
|
6921 |
|
6922 |
|
6923 |
|
6924 |
|
6925 |
|
6926 | constructor(nodes, placeholders, placeholderToMessage, meaning, description, customId) {
|
6927 | this.nodes = nodes;
|
6928 | this.placeholders = placeholders;
|
6929 | this.placeholderToMessage = placeholderToMessage;
|
6930 | this.meaning = meaning;
|
6931 | this.description = description;
|
6932 | this.customId = customId;
|
6933 | this.id = this.customId;
|
6934 |
|
6935 | this.legacyIds = [];
|
6936 | if (nodes.length) {
|
6937 | this.sources = [{
|
6938 | filePath: nodes[0].sourceSpan.start.file.url,
|
6939 | startLine: nodes[0].sourceSpan.start.line + 1,
|
6940 | startCol: nodes[0].sourceSpan.start.col + 1,
|
6941 | endLine: nodes[nodes.length - 1].sourceSpan.end.line + 1,
|
6942 | endCol: nodes[0].sourceSpan.start.col + 1
|
6943 | }];
|
6944 | }
|
6945 | else {
|
6946 | this.sources = [];
|
6947 | }
|
6948 | }
|
6949 | }
|
6950 | class Text$1 {
|
6951 | constructor(value, sourceSpan) {
|
6952 | this.value = value;
|
6953 | this.sourceSpan = sourceSpan;
|
6954 | }
|
6955 | visit(visitor, context) {
|
6956 | return visitor.visitText(this, context);
|
6957 | }
|
6958 | }
|
6959 |
|
6960 | class Container {
|
6961 | constructor(children, sourceSpan) {
|
6962 | this.children = children;
|
6963 | this.sourceSpan = sourceSpan;
|
6964 | }
|
6965 | visit(visitor, context) {
|
6966 | return visitor.visitContainer(this, context);
|
6967 | }
|
6968 | }
|
6969 | class Icu$1 {
|
6970 | constructor(expression, type, cases, sourceSpan) {
|
6971 | this.expression = expression;
|
6972 | this.type = type;
|
6973 | this.cases = cases;
|
6974 | this.sourceSpan = sourceSpan;
|
6975 | }
|
6976 | visit(visitor, context) {
|
6977 | return visitor.visitIcu(this, context);
|
6978 | }
|
6979 | }
|
6980 | class TagPlaceholder {
|
6981 | constructor(tag, attrs, startName, closeName, children, isVoid,
|
6982 | // TODO sourceSpan should cover all (we need a startSourceSpan and endSourceSpan)
|
6983 | sourceSpan, startSourceSpan, endSourceSpan) {
|
6984 | this.tag = tag;
|
6985 | this.attrs = attrs;
|
6986 | this.startName = startName;
|
6987 | this.closeName = closeName;
|
6988 | this.children = children;
|
6989 | this.isVoid = isVoid;
|
6990 | this.sourceSpan = sourceSpan;
|
6991 | this.startSourceSpan = startSourceSpan;
|
6992 | this.endSourceSpan = endSourceSpan;
|
6993 | }
|
6994 | visit(visitor, context) {
|
6995 | return visitor.visitTagPlaceholder(this, context);
|
6996 | }
|
6997 | }
|
6998 | class Placeholder {
|
6999 | constructor(value, name, sourceSpan) {
|
7000 | this.value = value;
|
7001 | this.name = name;
|
7002 | this.sourceSpan = sourceSpan;
|
7003 | }
|
7004 | visit(visitor, context) {
|
7005 | return visitor.visitPlaceholder(this, context);
|
7006 | }
|
7007 | }
|
7008 | class IcuPlaceholder {
|
7009 | constructor(value, name, sourceSpan) {
|
7010 | this.value = value;
|
7011 | this.name = name;
|
7012 | this.sourceSpan = sourceSpan;
|
7013 | }
|
7014 | visit(visitor, context) {
|
7015 | return visitor.visitIcuPlaceholder(this, context);
|
7016 | }
|
7017 | }
|
7018 |
|
7019 | |
7020 |
|
7021 |
|
7022 |
|
7023 |
|
7024 |
|
7025 |
|
7026 | |
7027 |
|
7028 |
|
7029 |
|
7030 |
|
7031 |
|
7032 |
|
7033 | class BigInteger {
|
7034 | |
7035 |
|
7036 |
|
7037 | constructor(digits) {
|
7038 | this.digits = digits;
|
7039 | }
|
7040 | static zero() {
|
7041 | return new BigInteger([0]);
|
7042 | }
|
7043 | static one() {
|
7044 | return new BigInteger([1]);
|
7045 | }
|
7046 | |
7047 |
|
7048 |
|
7049 | clone() {
|
7050 | return new BigInteger(this.digits.slice());
|
7051 | }
|
7052 | |
7053 |
|
7054 |
|
7055 |
|
7056 | add(other) {
|
7057 | const result = this.clone();
|
7058 | result.addToSelf(other);
|
7059 | return result;
|
7060 | }
|
7061 | |
7062 |
|
7063 |
|
7064 | addToSelf(other) {
|
7065 | const maxNrOfDigits = Math.max(this.digits.length, other.digits.length);
|
7066 | let carry = 0;
|
7067 | for (let i = 0; i < maxNrOfDigits; i++) {
|
7068 | let digitSum = carry;
|
7069 | if (i < this.digits.length) {
|
7070 | digitSum += this.digits[i];
|
7071 | }
|
7072 | if (i < other.digits.length) {
|
7073 | digitSum += other.digits[i];
|
7074 | }
|
7075 | if (digitSum >= 10) {
|
7076 | this.digits[i] = digitSum - 10;
|
7077 | carry = 1;
|
7078 | }
|
7079 | else {
|
7080 | this.digits[i] = digitSum;
|
7081 | carry = 0;
|
7082 | }
|
7083 | }
|
7084 |
|
7085 | if (carry > 0) {
|
7086 | this.digits[maxNrOfDigits] = 1;
|
7087 | }
|
7088 | }
|
7089 | |
7090 |
|
7091 |
|
7092 |
|
7093 | toString() {
|
7094 | let res = '';
|
7095 | for (let i = this.digits.length - 1; i >= 0; i--) {
|
7096 | res += this.digits[i];
|
7097 | }
|
7098 | return res;
|
7099 | }
|
7100 | }
|
7101 | |
7102 |
|
7103 |
|
7104 |
|
7105 | class BigIntForMultiplication {
|
7106 | constructor(value) {
|
7107 | this.powerOfTwos = [value];
|
7108 | }
|
7109 | |
7110 |
|
7111 |
|
7112 | getValue() {
|
7113 | return this.powerOfTwos[0];
|
7114 | }
|
7115 | |
7116 |
|
7117 |
|
7118 |
|
7119 |
|
7120 |
|
7121 |
|
7122 |
|
7123 |
|
7124 |
|
7125 |
|
7126 |
|
7127 |
|
7128 |
|
7129 |
|
7130 |
|
7131 |
|
7132 |
|
7133 |
|
7134 |
|
7135 |
|
7136 |
|
7137 |
|
7138 |
|
7139 |
|
7140 |
|
7141 | multiplyBy(num) {
|
7142 | const product = BigInteger.zero();
|
7143 | this.multiplyByAndAddTo(num, product);
|
7144 | return product;
|
7145 | }
|
7146 | |
7147 |
|
7148 |
|
7149 |
|
7150 | multiplyByAndAddTo(num, result) {
|
7151 | for (let exponent = 0; num !== 0; num = num >>> 1, exponent++) {
|
7152 | if (num & 1) {
|
7153 | const value = this.getMultipliedByPowerOfTwo(exponent);
|
7154 | result.addToSelf(value);
|
7155 | }
|
7156 | }
|
7157 | }
|
7158 | |
7159 |
|
7160 |
|
7161 | getMultipliedByPowerOfTwo(exponent) {
|
7162 |
|
7163 |
|
7164 |
|
7165 | for (let i = this.powerOfTwos.length; i <= exponent; i++) {
|
7166 | const previousPower = this.powerOfTwos[i - 1];
|
7167 | this.powerOfTwos[i] = previousPower.add(previousPower);
|
7168 | }
|
7169 | return this.powerOfTwos[exponent];
|
7170 | }
|
7171 | }
|
7172 | |
7173 |
|
7174 |
|
7175 |
|
7176 |
|
7177 |
|
7178 | class BigIntExponentiation {
|
7179 | constructor(base) {
|
7180 | this.base = base;
|
7181 | this.exponents = [new BigIntForMultiplication(BigInteger.one())];
|
7182 | }
|
7183 | |
7184 |
|
7185 |
|
7186 |
|
7187 | toThePowerOf(exponent) {
|
7188 |
|
7189 |
|
7190 |
|
7191 | for (let i = this.exponents.length; i <= exponent; i++) {
|
7192 | const value = this.exponents[i - 1].multiplyBy(this.base);
|
7193 | this.exponents[i] = new BigIntForMultiplication(value);
|
7194 | }
|
7195 | return this.exponents[exponent];
|
7196 | }
|
7197 | }
|
7198 |
|
7199 | |
7200 |
|
7201 |
|
7202 |
|
7203 |
|
7204 |
|
7205 |
|
7206 | |
7207 |
|
7208 |
|
7209 | function computeDigest(message) {
|
7210 | return sha1(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
|
7211 | }
|
7212 | |
7213 |
|
7214 |
|
7215 | function decimalDigest(message) {
|
7216 | return message.id || computeDecimalDigest(message);
|
7217 | }
|
7218 | |
7219 |
|
7220 |
|
7221 | function computeDecimalDigest(message) {
|
7222 | const visitor = new _SerializerIgnoreIcuExpVisitor();
|
7223 | const parts = message.nodes.map(a => a.visit(visitor, null));
|
7224 | return computeMsgId(parts.join(''), message.meaning);
|
7225 | }
|
7226 | |
7227 |
|
7228 |
|
7229 |
|
7230 |
|
7231 |
|
7232 |
|
7233 | class _SerializerVisitor {
|
7234 | visitText(text, context) {
|
7235 | return text.value;
|
7236 | }
|
7237 | visitContainer(container, context) {
|
7238 | return `[${container.children.map(child => child.visit(this)).join(', ')}]`;
|
7239 | }
|
7240 | visitIcu(icu, context) {
|
7241 | const strCases = Object.keys(icu.cases).map((k) => `${k} {${icu.cases[k].visit(this)}}`);
|
7242 | return `{${icu.expression}, ${icu.type}, ${strCases.join(', ')}}`;
|
7243 | }
|
7244 | visitTagPlaceholder(ph, context) {
|
7245 | return ph.isVoid ?
|
7246 | `<ph tag name="${ph.startName}"/>` :
|
7247 | `<ph tag name="${ph.startName}">${ph.children.map(child => child.visit(this)).join(', ')}</ph name="${ph.closeName}">`;
|
7248 | }
|
7249 | visitPlaceholder(ph, context) {
|
7250 | return ph.value ? `<ph name="${ph.name}">${ph.value}</ph>` : `<ph name="${ph.name}"/>`;
|
7251 | }
|
7252 | visitIcuPlaceholder(ph, context) {
|
7253 | return `<ph icu name="${ph.name}">${ph.value.visit(this)}</ph>`;
|
7254 | }
|
7255 | }
|
7256 | const serializerVisitor = new _SerializerVisitor();
|
7257 | function serializeNodes(nodes) {
|
7258 | return nodes.map(a => a.visit(serializerVisitor, null));
|
7259 | }
|
7260 | |
7261 |
|
7262 |
|
7263 |
|
7264 |
|
7265 |
|
7266 |
|
7267 | class _SerializerIgnoreIcuExpVisitor extends _SerializerVisitor {
|
7268 | visitIcu(icu, context) {
|
7269 | let strCases = Object.keys(icu.cases).map((k) => `${k} {${icu.cases[k].visit(this)}}`);
|
7270 |
|
7271 | return `{${icu.type}, ${strCases.join(', ')}}`;
|
7272 | }
|
7273 | }
|
7274 | |
7275 |
|
7276 |
|
7277 |
|
7278 |
|
7279 |
|
7280 |
|
7281 |
|
7282 | function sha1(str) {
|
7283 | const utf8 = utf8Encode(str);
|
7284 | const words32 = bytesToWords32(utf8, Endian.Big);
|
7285 | const len = utf8.length * 8;
|
7286 | const w = newArray(80);
|
7287 | let a = 0x67452301, b = 0xefcdab89, c = 0x98badcfe, d = 0x10325476, e = 0xc3d2e1f0;
|
7288 | words32[len >> 5] |= 0x80 << (24 - len % 32);
|
7289 | words32[((len + 64 >> 9) << 4) + 15] = len;
|
7290 | for (let i = 0; i < words32.length; i += 16) {
|
7291 | const h0 = a, h1 = b, h2 = c, h3 = d, h4 = e;
|
7292 | for (let j = 0; j < 80; j++) {
|
7293 | if (j < 16) {
|
7294 | w[j] = words32[i + j];
|
7295 | }
|
7296 | else {
|
7297 | w[j] = rol32(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1);
|
7298 | }
|
7299 | const fkVal = fk(j, b, c, d);
|
7300 | const f = fkVal[0];
|
7301 | const k = fkVal[1];
|
7302 | const temp = [rol32(a, 5), f, e, k, w[j]].reduce(add32);
|
7303 | e = d;
|
7304 | d = c;
|
7305 | c = rol32(b, 30);
|
7306 | b = a;
|
7307 | a = temp;
|
7308 | }
|
7309 | a = add32(a, h0);
|
7310 | b = add32(b, h1);
|
7311 | c = add32(c, h2);
|
7312 | d = add32(d, h3);
|
7313 | e = add32(e, h4);
|
7314 | }
|
7315 | return bytesToHexString(words32ToByteString([a, b, c, d, e]));
|
7316 | }
|
7317 | function fk(index, b, c, d) {
|
7318 | if (index < 20) {
|
7319 | return [(b & c) | (~b & d), 0x5a827999];
|
7320 | }
|
7321 | if (index < 40) {
|
7322 | return [b ^ c ^ d, 0x6ed9eba1];
|
7323 | }
|
7324 | if (index < 60) {
|
7325 | return [(b & c) | (b & d) | (c & d), 0x8f1bbcdc];
|
7326 | }
|
7327 | return [b ^ c ^ d, 0xca62c1d6];
|
7328 | }
|
7329 | |
7330 |
|
7331 |
|
7332 |
|
7333 |
|
7334 |
|
7335 |
|
7336 |
|
7337 | function fingerprint(str) {
|
7338 | const utf8 = utf8Encode(str);
|
7339 | let hi = hash32(utf8, 0);
|
7340 | let lo = hash32(utf8, 102072);
|
7341 | if (hi == 0 && (lo == 0 || lo == 1)) {
|
7342 | hi = hi ^ 0x130f9bef;
|
7343 | lo = lo ^ -0x6b5f56d8;
|
7344 | }
|
7345 | return [hi, lo];
|
7346 | }
|
7347 | function computeMsgId(msg, meaning = '') {
|
7348 | let msgFingerprint = fingerprint(msg);
|
7349 | if (meaning) {
|
7350 | const meaningFingerprint = fingerprint(meaning);
|
7351 | msgFingerprint = add64(rol64(msgFingerprint, 1), meaningFingerprint);
|
7352 | }
|
7353 | const hi = msgFingerprint[0];
|
7354 | const lo = msgFingerprint[1];
|
7355 | return wordsToDecimalString(hi & 0x7fffffff, lo);
|
7356 | }
|
7357 | function hash32(bytes, c) {
|
7358 | let a = 0x9e3779b9, b = 0x9e3779b9;
|
7359 | let i;
|
7360 | const len = bytes.length;
|
7361 | for (i = 0; i + 12 <= len; i += 12) {
|
7362 | a = add32(a, wordAt(bytes, i, Endian.Little));
|
7363 | b = add32(b, wordAt(bytes, i + 4, Endian.Little));
|
7364 | c = add32(c, wordAt(bytes, i + 8, Endian.Little));
|
7365 | const res = mix(a, b, c);
|
7366 | a = res[0], b = res[1], c = res[2];
|
7367 | }
|
7368 | a = add32(a, wordAt(bytes, i, Endian.Little));
|
7369 | b = add32(b, wordAt(bytes, i + 4, Endian.Little));
|
7370 |
|
7371 | c = add32(c, len);
|
7372 | c = add32(c, wordAt(bytes, i + 8, Endian.Little) << 8);
|
7373 | return mix(a, b, c)[2];
|
7374 | }
|
7375 |
|
7376 | function mix(a, b, c) {
|
7377 | a = sub32(a, b);
|
7378 | a = sub32(a, c);
|
7379 | a ^= c >>> 13;
|
7380 | b = sub32(b, c);
|
7381 | b = sub32(b, a);
|
7382 | b ^= a << 8;
|
7383 | c = sub32(c, a);
|
7384 | c = sub32(c, b);
|
7385 | c ^= b >>> 13;
|
7386 | a = sub32(a, b);
|
7387 | a = sub32(a, c);
|
7388 | a ^= c >>> 12;
|
7389 | b = sub32(b, c);
|
7390 | b = sub32(b, a);
|
7391 | b ^= a << 16;
|
7392 | c = sub32(c, a);
|
7393 | c = sub32(c, b);
|
7394 | c ^= b >>> 5;
|
7395 | a = sub32(a, b);
|
7396 | a = sub32(a, c);
|
7397 | a ^= c >>> 3;
|
7398 | b = sub32(b, c);
|
7399 | b = sub32(b, a);
|
7400 | b ^= a << 10;
|
7401 | c = sub32(c, a);
|
7402 | c = sub32(c, b);
|
7403 | c ^= b >>> 15;
|
7404 | return [a, b, c];
|
7405 | }
|
7406 |
|
7407 |
|
7408 | var Endian;
|
7409 | (function (Endian) {
|
7410 | Endian[Endian["Little"] = 0] = "Little";
|
7411 | Endian[Endian["Big"] = 1] = "Big";
|
7412 | })(Endian || (Endian = {}));
|
7413 | function add32(a, b) {
|
7414 | return add32to64(a, b)[1];
|
7415 | }
|
7416 | function add32to64(a, b) {
|
7417 | const low = (a & 0xffff) + (b & 0xffff);
|
7418 | const high = (a >>> 16) + (b >>> 16) + (low >>> 16);
|
7419 | return [high >>> 16, (high << 16) | (low & 0xffff)];
|
7420 | }
|
7421 | function add64(a, b) {
|
7422 | const ah = a[0], al = a[1];
|
7423 | const bh = b[0], bl = b[1];
|
7424 | const result = add32to64(al, bl);
|
7425 | const carry = result[0];
|
7426 | const l = result[1];
|
7427 | const h = add32(add32(ah, bh), carry);
|
7428 | return [h, l];
|
7429 | }
|
7430 | function sub32(a, b) {
|
7431 | const low = (a & 0xffff) - (b & 0xffff);
|
7432 | const high = (a >> 16) - (b >> 16) + (low >> 16);
|
7433 | return (high << 16) | (low & 0xffff);
|
7434 | }
|
7435 |
|
7436 | function rol32(a, count) {
|
7437 | return (a << count) | (a >>> (32 - count));
|
7438 | }
|
7439 |
|
7440 | function rol64(num, count) {
|
7441 | const hi = num[0], lo = num[1];
|
7442 | const h = (hi << count) | (lo >>> (32 - count));
|
7443 | const l = (lo << count) | (hi >>> (32 - count));
|
7444 | return [h, l];
|
7445 | }
|
7446 | function bytesToWords32(bytes, endian) {
|
7447 | const size = (bytes.length + 3) >>> 2;
|
7448 | const words32 = [];
|
7449 | for (let i = 0; i < size; i++) {
|
7450 | words32[i] = wordAt(bytes, i * 4, endian);
|
7451 | }
|
7452 | return words32;
|
7453 | }
|
7454 | function byteAt(bytes, index) {
|
7455 | return index >= bytes.length ? 0 : bytes[index];
|
7456 | }
|
7457 | function wordAt(bytes, index, endian) {
|
7458 | let word = 0;
|
7459 | if (endian === Endian.Big) {
|
7460 | for (let i = 0; i < 4; i++) {
|
7461 | word += byteAt(bytes, index + i) << (24 - 8 * i);
|
7462 | }
|
7463 | }
|
7464 | else {
|
7465 | for (let i = 0; i < 4; i++) {
|
7466 | word += byteAt(bytes, index + i) << 8 * i;
|
7467 | }
|
7468 | }
|
7469 | return word;
|
7470 | }
|
7471 | function words32ToByteString(words32) {
|
7472 | return words32.reduce((bytes, word) => bytes.concat(word32ToByteString(word)), []);
|
7473 | }
|
7474 | function word32ToByteString(word) {
|
7475 | let bytes = [];
|
7476 | for (let i = 0; i < 4; i++) {
|
7477 | bytes.push((word >>> 8 * (3 - i)) & 0xff);
|
7478 | }
|
7479 | return bytes;
|
7480 | }
|
7481 | function bytesToHexString(bytes) {
|
7482 | let hex = '';
|
7483 | for (let i = 0; i < bytes.length; i++) {
|
7484 | const b = byteAt(bytes, i);
|
7485 | hex += (b >>> 4).toString(16) + (b & 0x0f).toString(16);
|
7486 | }
|
7487 | return hex.toLowerCase();
|
7488 | }
|
7489 | |
7490 |
|
7491 |
|
7492 |
|
7493 |
|
7494 |
|
7495 |
|
7496 |
|
7497 | const base256 = new BigIntExponentiation(256);
|
7498 | |
7499 |
|
7500 |
|
7501 |
|
7502 |
|
7503 |
|
7504 | function wordsToDecimalString(hi, lo) {
|
7505 |
|
7506 |
|
7507 |
|
7508 | const decimal = base256.toThePowerOf(0).multiplyBy(lo);
|
7509 |
|
7510 |
|
7511 | base256.toThePowerOf(4).multiplyByAndAddTo(hi, decimal);
|
7512 | return decimal.toString();
|
7513 | }
|
7514 |
|
7515 | |
7516 |
|
7517 |
|
7518 |
|
7519 |
|
7520 |
|
7521 |
|
7522 |
|
7523 | function toPublicName(internalName) {
|
7524 | return internalName.toUpperCase().replace(/[^A-Z0-9_]/g, '_');
|
7525 | }
|
7526 |
|
7527 | |
7528 |
|
7529 |
|
7530 |
|
7531 |
|
7532 |
|
7533 |
|
7534 |
|
7535 | const CLOSURE_TRANSLATION_VAR_PREFIX = 'MSG_';
|
7536 | |
7537 |
|
7538 |
|
7539 |
|
7540 |
|
7541 | const TRANSLATION_VAR_PREFIX = 'i18n_';
|
7542 |
|
7543 | const I18N_ATTR = 'i18n';
|
7544 | const I18N_ATTR_PREFIX = 'i18n-';
|
7545 |
|
7546 | const I18N_ICU_VAR_PREFIX = 'VAR_';
|
7547 |
|
7548 | const I18N_ICU_MAPPING_PREFIX = 'I18N_EXP_';
|
7549 |
|
7550 | const I18N_PLACEHOLDER_SYMBOL = '�';
|
7551 | function isI18nAttribute(name) {
|
7552 | return name === I18N_ATTR || name.startsWith(I18N_ATTR_PREFIX);
|
7553 | }
|
7554 | function isI18nRootNode(meta) {
|
7555 | return meta instanceof Message;
|
7556 | }
|
7557 | function isSingleI18nIcu(meta) {
|
7558 | return isI18nRootNode(meta) && meta.nodes.length === 1 && meta.nodes[0] instanceof Icu$1;
|
7559 | }
|
7560 | function hasI18nMeta(node) {
|
7561 | return !!node.i18n;
|
7562 | }
|
7563 | function hasI18nAttrs(element) {
|
7564 | return element.attrs.some((attr) => isI18nAttribute(attr.name));
|
7565 | }
|
7566 | function icuFromI18nMessage(message) {
|
7567 | return message.nodes[0];
|
7568 | }
|
7569 | function wrapI18nPlaceholder(content, contextId = 0) {
|
7570 | const blockId = contextId > 0 ? `:${contextId}` : '';
|
7571 | return `${I18N_PLACEHOLDER_SYMBOL}${content}${blockId}${I18N_PLACEHOLDER_SYMBOL}`;
|
7572 | }
|
7573 | function assembleI18nBoundString(strings, bindingStartIndex = 0, contextId = 0) {
|
7574 | if (!strings.length)
|
7575 | return '';
|
7576 | let acc = '';
|
7577 | const lastIdx = strings.length - 1;
|
7578 | for (let i = 0; i < lastIdx; i++) {
|
7579 | acc += `${strings[i]}${wrapI18nPlaceholder(bindingStartIndex + i, contextId)}`;
|
7580 | }
|
7581 | acc += strings[lastIdx];
|
7582 | return acc;
|
7583 | }
|
7584 | function getSeqNumberGenerator(startsAt = 0) {
|
7585 | let current = startsAt;
|
7586 | return () => current++;
|
7587 | }
|
7588 | function placeholdersToParams(placeholders) {
|
7589 | const params = {};
|
7590 | placeholders.forEach((values, key) => {
|
7591 | params[key] = literal(values.length > 1 ? `[${values.join('|')}]` : values[0]);
|
7592 | });
|
7593 | return params;
|
7594 | }
|
7595 | function updatePlaceholderMap(map, name, ...values) {
|
7596 | const current = map.get(name) || [];
|
7597 | current.push(...values);
|
7598 | map.set(name, current);
|
7599 | }
|
7600 | function assembleBoundTextPlaceholders(meta, bindingStartIndex = 0, contextId = 0) {
|
7601 | const startIdx = bindingStartIndex;
|
7602 | const placeholders = new Map();
|
7603 | const node = meta instanceof Message ? meta.nodes.find(node => node instanceof Container) : meta;
|
7604 | if (node) {
|
7605 | node
|
7606 | .children
|
7607 | .filter((child) => child instanceof Placeholder)
|
7608 | .forEach((child, idx) => {
|
7609 | const content = wrapI18nPlaceholder(startIdx + idx, contextId);
|
7610 | updatePlaceholderMap(placeholders, child.name, content);
|
7611 | });
|
7612 | }
|
7613 | return placeholders;
|
7614 | }
|
7615 | |
7616 |
|
7617 |
|
7618 |
|
7619 |
|
7620 |
|
7621 |
|
7622 |
|
7623 |
|
7624 |
|
7625 | function i18nFormatPlaceholderNames(params = {}, useCamelCase) {
|
7626 | const _params = {};
|
7627 | if (params && Object.keys(params).length) {
|
7628 | Object.keys(params).forEach(key => _params[formatI18nPlaceholderName(key, useCamelCase)] = params[key]);
|
7629 | }
|
7630 | return _params;
|
7631 | }
|
7632 | |
7633 |
|
7634 |
|
7635 |
|
7636 |
|
7637 |
|
7638 |
|
7639 |
|
7640 | function formatI18nPlaceholderName(name, useCamelCase = true) {
|
7641 | const publicName = toPublicName(name);
|
7642 | if (!useCamelCase) {
|
7643 | return publicName;
|
7644 | }
|
7645 | const chunks = publicName.split('_');
|
7646 | if (chunks.length === 1) {
|
7647 |
|
7648 | return name.toLowerCase();
|
7649 | }
|
7650 | let postfix;
|
7651 |
|
7652 | if (/^\d+$/.test(chunks[chunks.length - 1])) {
|
7653 | postfix = chunks.pop();
|
7654 | }
|
7655 | let raw = chunks.shift().toLowerCase();
|
7656 | if (chunks.length) {
|
7657 | raw += chunks.map(c => c.charAt(0).toUpperCase() + c.slice(1).toLowerCase()).join('');
|
7658 | }
|
7659 | return postfix ? `${raw}_${postfix}` : raw;
|
7660 | }
|
7661 | |
7662 |
|
7663 |
|
7664 |
|
7665 |
|
7666 |
|
7667 | function getTranslationConstPrefix(extra) {
|
7668 | return `${CLOSURE_TRANSLATION_VAR_PREFIX}${extra}`.toUpperCase();
|
7669 | }
|
7670 | |
7671 |
|
7672 |
|
7673 |
|
7674 | function declareI18nVariable(variable) {
|
7675 | return new DeclareVarStmt(variable.name, undefined, INFERRED_TYPE, undefined, variable.sourceSpan);
|
7676 | }
|
7677 |
|
7678 | |
7679 |
|
7680 |
|
7681 |
|
7682 |
|
7683 |
|
7684 |
|
7685 | |
7686 |
|
7687 |
|
7688 |
|
7689 |
|
7690 |
|
7691 |
|
7692 |
|
7693 | const UNSAFE_OBJECT_KEY_NAME_REGEXP = /[-.]/;
|
7694 |
|
7695 | const TEMPORARY_NAME = '_t';
|
7696 |
|
7697 | const CONTEXT_NAME = 'ctx';
|
7698 |
|
7699 | const RENDER_FLAGS = 'rf';
|
7700 |
|
7701 | const REFERENCE_PREFIX = '_r';
|
7702 |
|
7703 | const IMPLICIT_REFERENCE = '$implicit';
|
7704 |
|
7705 | const NON_BINDABLE_ATTR = 'ngNonBindable';
|
7706 | |
7707 |
|
7708 |
|
7709 |
|
7710 |
|
7711 | function temporaryAllocator(statements, name) {
|
7712 | let temp = null;
|
7713 | return () => {
|
7714 | if (!temp) {
|
7715 | statements.push(new DeclareVarStmt(TEMPORARY_NAME, undefined, DYNAMIC_TYPE));
|
7716 | temp = variable(name);
|
7717 | }
|
7718 | return temp;
|
7719 | };
|
7720 | }
|
7721 | function unsupported(feature) {
|
7722 | if (this) {
|
7723 | throw new Error(`Builder ${this.constructor.name} doesn't support ${feature} yet`);
|
7724 | }
|
7725 | throw new Error(`Feature ${feature} is not supported yet`);
|
7726 | }
|
7727 | function invalid$1(arg) {
|
7728 | throw new Error(`Invalid state: Visitor ${this.constructor.name} doesn't handle ${arg.constructor.name}`);
|
7729 | }
|
7730 | function asLiteral(value) {
|
7731 | if (Array.isArray(value)) {
|
7732 | return literalArr(value.map(asLiteral));
|
7733 | }
|
7734 | return literal(value, INFERRED_TYPE);
|
7735 | }
|
7736 | function conditionallyCreateMapObjectLiteral(keys, keepDeclared) {
|
7737 | if (Object.getOwnPropertyNames(keys).length > 0) {
|
7738 | return mapToExpression(keys, keepDeclared);
|
7739 | }
|
7740 | return null;
|
7741 | }
|
7742 | function mapToExpression(map, keepDeclared) {
|
7743 | return literalMap(Object.getOwnPropertyNames(map).map(key => {
|
7744 |
|
7745 |
|
7746 | const value = map[key];
|
7747 | let declaredName;
|
7748 | let publicName;
|
7749 | let minifiedName;
|
7750 | let needsDeclaredName;
|
7751 | if (Array.isArray(value)) {
|
7752 | [publicName, declaredName] = value;
|
7753 | minifiedName = key;
|
7754 | needsDeclaredName = publicName !== declaredName;
|
7755 | }
|
7756 | else {
|
7757 | [declaredName, publicName] = splitAtColon(key, [key, value]);
|
7758 | minifiedName = declaredName;
|
7759 |
|
7760 |
|
7761 |
|
7762 | needsDeclaredName = publicName !== declaredName && key.includes(':');
|
7763 | }
|
7764 | return {
|
7765 | key: minifiedName,
|
7766 |
|
7767 | quoted: UNSAFE_OBJECT_KEY_NAME_REGEXP.test(minifiedName),
|
7768 | value: (keepDeclared && needsDeclaredName) ?
|
7769 | literalArr([asLiteral(publicName), asLiteral(declaredName)]) :
|
7770 | asLiteral(publicName)
|
7771 | };
|
7772 | }));
|
7773 | }
|
7774 | |
7775 |
|
7776 |
|
7777 | function trimTrailingNulls(parameters) {
|
7778 | while (isNull(parameters[parameters.length - 1])) {
|
7779 | parameters.pop();
|
7780 | }
|
7781 | return parameters;
|
7782 | }
|
7783 | function getQueryPredicate(query, constantPool) {
|
7784 | if (Array.isArray(query.predicate)) {
|
7785 | let predicate = [];
|
7786 | query.predicate.forEach((selector) => {
|
7787 |
|
7788 |
|
7789 |
|
7790 | const selectors = selector.split(',').map(token => literal(token.trim()));
|
7791 | predicate.push(...selectors);
|
7792 | });
|
7793 | return constantPool.getConstLiteral(literalArr(predicate), true);
|
7794 | }
|
7795 | else {
|
7796 | return query.predicate;
|
7797 | }
|
7798 | }
|
7799 | |
7800 |
|
7801 |
|
7802 |
|
7803 |
|
7804 | class DefinitionMap {
|
7805 | constructor() {
|
7806 | this.values = [];
|
7807 | }
|
7808 | set(key, value) {
|
7809 | if (value) {
|
7810 | this.values.push({ key: key, value, quoted: false });
|
7811 | }
|
7812 | }
|
7813 | toLiteralMap() {
|
7814 | return literalMap(this.values);
|
7815 | }
|
7816 | }
|
7817 | |
7818 |
|
7819 |
|
7820 |
|
7821 |
|
7822 |
|
7823 |
|
7824 |
|
7825 |
|
7826 | function getAttrsForDirectiveMatching(elOrTpl) {
|
7827 | const attributesMap = {};
|
7828 | if (elOrTpl instanceof Template && elOrTpl.tagName !== 'ng-template') {
|
7829 | elOrTpl.templateAttrs.forEach(a => attributesMap[a.name] = '');
|
7830 | }
|
7831 | else {
|
7832 | elOrTpl.attributes.forEach(a => {
|
7833 | if (!isI18nAttribute(a.name)) {
|
7834 | attributesMap[a.name] = a.value;
|
7835 | }
|
7836 | });
|
7837 | elOrTpl.inputs.forEach(i => {
|
7838 | attributesMap[i.name] = '';
|
7839 | });
|
7840 | elOrTpl.outputs.forEach(o => {
|
7841 | attributesMap[o.name] = '';
|
7842 | });
|
7843 | }
|
7844 | return attributesMap;
|
7845 | }
|
7846 |
|
7847 | function chainedInstruction(reference, calls, span) {
|
7848 | let expression = importExpr(reference, null, span);
|
7849 | if (calls.length > 0) {
|
7850 | for (let i = 0; i < calls.length; i++) {
|
7851 | expression = expression.callFn(calls[i], span);
|
7852 | }
|
7853 | }
|
7854 | else {
|
7855 |
|
7856 | expression = expression.callFn([], span);
|
7857 | }
|
7858 | return expression;
|
7859 | }
|
7860 | |
7861 |
|
7862 |
|
7863 |
|
7864 |
|
7865 | function getInterpolationArgsLength(interpolation) {
|
7866 | const { expressions, strings } = interpolation;
|
7867 | if (expressions.length === 1 && strings.length === 2 && strings[0] === '' && strings[1] === '') {
|
7868 |
|
7869 |
|
7870 |
|
7871 | return 1;
|
7872 | }
|
7873 | else {
|
7874 | return expressions.length + strings.length;
|
7875 | }
|
7876 | }
|
7877 |
|
7878 | |
7879 |
|
7880 |
|
7881 |
|
7882 |
|
7883 |
|
7884 |
|
7885 | var R3FactoryDelegateType;
|
7886 | (function (R3FactoryDelegateType) {
|
7887 | R3FactoryDelegateType[R3FactoryDelegateType["Class"] = 0] = "Class";
|
7888 | R3FactoryDelegateType[R3FactoryDelegateType["Function"] = 1] = "Function";
|
7889 | R3FactoryDelegateType[R3FactoryDelegateType["Factory"] = 2] = "Factory";
|
7890 | })(R3FactoryDelegateType || (R3FactoryDelegateType = {}));
|
7891 | var R3FactoryTarget;
|
7892 | (function (R3FactoryTarget) {
|
7893 | R3FactoryTarget[R3FactoryTarget["Directive"] = 0] = "Directive";
|
7894 | R3FactoryTarget[R3FactoryTarget["Component"] = 1] = "Component";
|
7895 | R3FactoryTarget[R3FactoryTarget["Injectable"] = 2] = "Injectable";
|
7896 | R3FactoryTarget[R3FactoryTarget["Pipe"] = 3] = "Pipe";
|
7897 | R3FactoryTarget[R3FactoryTarget["NgModule"] = 4] = "NgModule";
|
7898 | })(R3FactoryTarget || (R3FactoryTarget = {}));
|
7899 | |
7900 |
|
7901 |
|
7902 |
|
7903 |
|
7904 |
|
7905 |
|
7906 |
|
7907 | var R3ResolvedDependencyType;
|
7908 | (function (R3ResolvedDependencyType) {
|
7909 | |
7910 |
|
7911 |
|
7912 | R3ResolvedDependencyType[R3ResolvedDependencyType["Token"] = 0] = "Token";
|
7913 | |
7914 |
|
7915 |
|
7916 |
|
7917 |
|
7918 | R3ResolvedDependencyType[R3ResolvedDependencyType["Attribute"] = 1] = "Attribute";
|
7919 | |
7920 |
|
7921 |
|
7922 | R3ResolvedDependencyType[R3ResolvedDependencyType["ChangeDetectorRef"] = 2] = "ChangeDetectorRef";
|
7923 | |
7924 |
|
7925 |
|
7926 | R3ResolvedDependencyType[R3ResolvedDependencyType["Invalid"] = 3] = "Invalid";
|
7927 | })(R3ResolvedDependencyType || (R3ResolvedDependencyType = {}));
|
7928 | |
7929 |
|
7930 |
|
7931 | function compileFactoryFunction(meta) {
|
7932 | const t = variable('t');
|
7933 | const statements = [];
|
7934 | let ctorDepsType = NONE_TYPE;
|
7935 |
|
7936 |
|
7937 |
|
7938 |
|
7939 |
|
7940 | const typeForCtor = !isDelegatedMetadata(meta) ?
|
7941 | new BinaryOperatorExpr(BinaryOperator.Or, t, meta.internalType) :
|
7942 | t;
|
7943 | let ctorExpr = null;
|
7944 | if (meta.deps !== null) {
|
7945 |
|
7946 | if (meta.deps !== 'invalid') {
|
7947 | ctorExpr = new InstantiateExpr(typeForCtor, injectDependencies(meta.deps, meta.injectFn, meta.target === R3FactoryTarget.Pipe));
|
7948 | ctorDepsType = createCtorDepsType(meta.deps);
|
7949 | }
|
7950 | }
|
7951 | else {
|
7952 | const baseFactory = variable(`ɵ${meta.name}_BaseFactory`);
|
7953 | const getInheritedFactory = importExpr(Identifiers$1.getInheritedFactory);
|
7954 | const baseFactoryStmt = baseFactory
|
7955 | .set(getInheritedFactory.callFn([meta.internalType], undefined, true))
|
7956 | .toDeclStmt(INFERRED_TYPE, [StmtModifier.Exported, StmtModifier.Final]);
|
7957 | statements.push(baseFactoryStmt);
|
7958 |
|
7959 | ctorExpr = baseFactory.callFn([typeForCtor]);
|
7960 | }
|
7961 | const ctorExprFinal = ctorExpr;
|
7962 | const body = [];
|
7963 | let retExpr = null;
|
7964 | function makeConditionalFactory(nonCtorExpr) {
|
7965 | const r = variable('r');
|
7966 | body.push(r.set(NULL_EXPR).toDeclStmt());
|
7967 | let ctorStmt = null;
|
7968 | if (ctorExprFinal !== null) {
|
7969 | ctorStmt = r.set(ctorExprFinal).toStmt();
|
7970 | }
|
7971 | else {
|
7972 | ctorStmt = importExpr(Identifiers$1.invalidFactory).callFn([]).toStmt();
|
7973 | }
|
7974 | body.push(ifStmt(t, [ctorStmt], [r.set(nonCtorExpr).toStmt()]));
|
7975 | return r;
|
7976 | }
|
7977 | if (isDelegatedMetadata(meta) && meta.delegateType === R3FactoryDelegateType.Factory) {
|
7978 | const delegateFactory = variable(`ɵ${meta.name}_BaseFactory`);
|
7979 | const getFactoryOf = importExpr(Identifiers$1.getFactoryOf);
|
7980 | if (meta.delegate.isEquivalent(meta.internalType)) {
|
7981 | throw new Error(`Illegal state: compiling factory that delegates to itself`);
|
7982 | }
|
7983 | const delegateFactoryStmt = delegateFactory.set(getFactoryOf.callFn([meta.delegate])).toDeclStmt(INFERRED_TYPE, [
|
7984 | StmtModifier.Exported, StmtModifier.Final
|
7985 | ]);
|
7986 | statements.push(delegateFactoryStmt);
|
7987 | retExpr = makeConditionalFactory(delegateFactory.callFn([]));
|
7988 | }
|
7989 | else if (isDelegatedMetadata(meta)) {
|
7990 |
|
7991 |
|
7992 | const delegateArgs = injectDependencies(meta.delegateDeps, meta.injectFn, meta.target === R3FactoryTarget.Pipe);
|
7993 |
|
7994 | const factoryExpr = new (meta.delegateType === R3FactoryDelegateType.Class ?
|
7995 | InstantiateExpr :
|
7996 | InvokeFunctionExpr)(meta.delegate, delegateArgs);
|
7997 | retExpr = makeConditionalFactory(factoryExpr);
|
7998 | }
|
7999 | else if (isExpressionFactoryMetadata(meta)) {
|
8000 |
|
8001 | retExpr = makeConditionalFactory(meta.expression);
|
8002 | }
|
8003 | else {
|
8004 | retExpr = ctorExpr;
|
8005 | }
|
8006 | if (retExpr !== null) {
|
8007 | body.push(new ReturnStatement(retExpr));
|
8008 | }
|
8009 | else {
|
8010 | body.push(importExpr(Identifiers$1.invalidFactory).callFn([]).toStmt());
|
8011 | }
|
8012 | return {
|
8013 | factory: fn([new FnParam('t', DYNAMIC_TYPE)], body, INFERRED_TYPE, undefined, `${meta.name}_Factory`),
|
8014 | statements,
|
8015 | type: expressionType(importExpr(Identifiers$1.FactoryDef, [typeWithParameters(meta.type.type, meta.typeArgumentCount), ctorDepsType]))
|
8016 | };
|
8017 | }
|
8018 | function injectDependencies(deps, injectFn, isPipe) {
|
8019 | return deps.map((dep, index) => compileInjectDependency(dep, injectFn, isPipe, index));
|
8020 | }
|
8021 | function compileInjectDependency(dep, injectFn, isPipe, index) {
|
8022 |
|
8023 | switch (dep.resolved) {
|
8024 | case R3ResolvedDependencyType.Token:
|
8025 | case R3ResolvedDependencyType.ChangeDetectorRef:
|
8026 |
|
8027 | const flags = 0 | (dep.self ? 2 : 0) |
|
8028 | (dep.skipSelf ? 4 : 0) | (dep.host ? 1 : 0) |
|
8029 | (dep.optional ? 8 : 0);
|
8030 |
|
8031 |
|
8032 |
|
8033 | let flagsParam = (flags !== 0 || dep.optional) ? literal(flags) : null;
|
8034 |
|
8035 | if (isPipe && dep.resolved === R3ResolvedDependencyType.ChangeDetectorRef) {
|
8036 | return importExpr(Identifiers$1.injectPipeChangeDetectorRef).callFn(flagsParam ? [flagsParam] : []);
|
8037 | }
|
8038 |
|
8039 | const injectArgs = [dep.token];
|
8040 | if (flagsParam) {
|
8041 | injectArgs.push(flagsParam);
|
8042 | }
|
8043 | return importExpr(injectFn).callFn(injectArgs);
|
8044 | case R3ResolvedDependencyType.Attribute:
|
8045 |
|
8046 | return importExpr(Identifiers$1.injectAttribute).callFn([dep.token]);
|
8047 | case R3ResolvedDependencyType.Invalid:
|
8048 | return importExpr(Identifiers$1.invalidFactoryDep).callFn([literal(index)]);
|
8049 | default:
|
8050 | return unsupported(`Unknown R3ResolvedDependencyType: ${R3ResolvedDependencyType[dep.resolved]}`);
|
8051 | }
|
8052 | }
|
8053 | function createCtorDepsType(deps) {
|
8054 | let hasTypes = false;
|
8055 | const attributeTypes = deps.map(dep => {
|
8056 | const type = createCtorDepType(dep);
|
8057 | if (type !== null) {
|
8058 | hasTypes = true;
|
8059 | return type;
|
8060 | }
|
8061 | else {
|
8062 | return literal(null);
|
8063 | }
|
8064 | });
|
8065 | if (hasTypes) {
|
8066 | return expressionType(literalArr(attributeTypes));
|
8067 | }
|
8068 | else {
|
8069 | return NONE_TYPE;
|
8070 | }
|
8071 | }
|
8072 | function createCtorDepType(dep) {
|
8073 | const entries = [];
|
8074 | if (dep.resolved === R3ResolvedDependencyType.Attribute) {
|
8075 | if (dep.attribute !== null) {
|
8076 | entries.push({ key: 'attribute', value: dep.attribute, quoted: false });
|
8077 | }
|
8078 | }
|
8079 | if (dep.optional) {
|
8080 | entries.push({ key: 'optional', value: literal(true), quoted: false });
|
8081 | }
|
8082 | if (dep.host) {
|
8083 | entries.push({ key: 'host', value: literal(true), quoted: false });
|
8084 | }
|
8085 | if (dep.self) {
|
8086 | entries.push({ key: 'self', value: literal(true), quoted: false });
|
8087 | }
|
8088 | if (dep.skipSelf) {
|
8089 | entries.push({ key: 'skipSelf', value: literal(true), quoted: false });
|
8090 | }
|
8091 | return entries.length > 0 ? literalMap(entries) : null;
|
8092 | }
|
8093 | function isDelegatedMetadata(meta) {
|
8094 | return meta.delegateType !== undefined;
|
8095 | }
|
8096 | function isExpressionFactoryMetadata(meta) {
|
8097 | return meta.expression !== undefined;
|
8098 | }
|
8099 |
|
8100 | |
8101 |
|
8102 |
|
8103 |
|
8104 |
|
8105 |
|
8106 |
|
8107 | function compileInjectable(meta) {
|
8108 | let result = null;
|
8109 | const factoryMeta = {
|
8110 | name: meta.name,
|
8111 | type: meta.type,
|
8112 | internalType: meta.internalType,
|
8113 | typeArgumentCount: meta.typeArgumentCount,
|
8114 | deps: [],
|
8115 | injectFn: Identifiers.inject,
|
8116 | target: R3FactoryTarget.Injectable,
|
8117 | };
|
8118 | if (meta.useClass !== undefined) {
|
8119 |
|
8120 |
|
8121 |
|
8122 |
|
8123 |
|
8124 |
|
8125 | const useClassOnSelf = meta.useClass.isEquivalent(meta.internalType);
|
8126 | let deps = undefined;
|
8127 | if (meta.userDeps !== undefined) {
|
8128 | deps = meta.userDeps;
|
8129 | }
|
8130 | if (deps !== undefined) {
|
8131 |
|
8132 | result = compileFactoryFunction(Object.assign(Object.assign({}, factoryMeta), { delegate: meta.useClass, delegateDeps: deps, delegateType: R3FactoryDelegateType.Class }));
|
8133 | }
|
8134 | else if (useClassOnSelf) {
|
8135 | result = compileFactoryFunction(factoryMeta);
|
8136 | }
|
8137 | else {
|
8138 | result = delegateToFactory(meta.type.value, meta.useClass);
|
8139 | }
|
8140 | }
|
8141 | else if (meta.useFactory !== undefined) {
|
8142 | if (meta.userDeps !== undefined) {
|
8143 | result = compileFactoryFunction(Object.assign(Object.assign({}, factoryMeta), { delegate: meta.useFactory, delegateDeps: meta.userDeps || [], delegateType: R3FactoryDelegateType.Function }));
|
8144 | }
|
8145 | else {
|
8146 | result = {
|
8147 | statements: [],
|
8148 | factory: fn([], [new ReturnStatement(meta.useFactory.callFn([]))])
|
8149 | };
|
8150 | }
|
8151 | }
|
8152 | else if (meta.useValue !== undefined) {
|
8153 |
|
8154 |
|
8155 |
|
8156 | result = compileFactoryFunction(Object.assign(Object.assign({}, factoryMeta), { expression: meta.useValue }));
|
8157 | }
|
8158 | else if (meta.useExisting !== undefined) {
|
8159 |
|
8160 | result = compileFactoryFunction(Object.assign(Object.assign({}, factoryMeta), { expression: importExpr(Identifiers.inject).callFn([meta.useExisting]) }));
|
8161 | }
|
8162 | else {
|
8163 | result = delegateToFactory(meta.type.value, meta.internalType);
|
8164 | }
|
8165 | const token = meta.internalType;
|
8166 | const injectableProps = { token, factory: result.factory };
|
8167 |
|
8168 | if (meta.providedIn.value !== null) {
|
8169 | injectableProps.providedIn = meta.providedIn;
|
8170 | }
|
8171 | const expression = importExpr(Identifiers.ɵɵdefineInjectable).callFn([mapToMapExpression(injectableProps)]);
|
8172 | const type = new ExpressionType(importExpr(Identifiers.InjectableDef, [typeWithParameters(meta.type.type, meta.typeArgumentCount)]));
|
8173 | return {
|
8174 | expression,
|
8175 | type,
|
8176 | statements: result.statements,
|
8177 | };
|
8178 | }
|
8179 | function delegateToFactory(type, internalType) {
|
8180 | return {
|
8181 | statements: [],
|
8182 |
|
8183 |
|
8184 |
|
8185 | factory: type.node === internalType.node ?
|
8186 | internalType.prop('ɵfac') :
|
8187 | fn([new FnParam('t', DYNAMIC_TYPE)], [new ReturnStatement(internalType.callMethod('ɵfac', [variable('t')]))])
|
8188 | };
|
8189 | }
|
8190 |
|
8191 | |
8192 |
|
8193 |
|
8194 |
|
8195 |
|
8196 |
|
8197 |
|
8198 | const UNUSABLE_INTERPOLATION_REGEXPS = [
|
8199 | /^\s*$/,
|
8200 | /[<>]/,
|
8201 | /^[{}]$/,
|
8202 | /&(#|[a-z])/i,
|
8203 | /^\/\//,
|
8204 | ];
|
8205 | function assertInterpolationSymbols(identifier, value) {
|
8206 | if (value != null && !(Array.isArray(value) && value.length == 2)) {
|
8207 | throw new Error(`Expected '${identifier}' to be an array, [start, end].`);
|
8208 | }
|
8209 | else if (value != null) {
|
8210 | const start = value[0];
|
8211 | const end = value[1];
|
8212 | // Check for unusable interpolation symbols
|
8213 | UNUSABLE_INTERPOLATION_REGEXPS.forEach(regexp => {
|
8214 | if (regexp.test(start) || regexp.test(end)) {
|
8215 | throw new Error(`['${start}', '${end}'] contains unusable interpolation symbol.`);
|
8216 | }
|
8217 | });
|
8218 | }
|
8219 | }
|
8220 |
|
8221 | /**
|
8222 | * @license
|
8223 | * Copyright Google LLC All Rights Reserved.
|
8224 | *
|
8225 | * Use of this source code is governed by an MIT-style license that can be
|
8226 | * found in the LICENSE file at https://angular.io/license
|
8227 | */
|
8228 | class InterpolationConfig {
|
8229 | constructor(start, end) {
|
8230 | this.start = start;
|
8231 | this.end = end;
|
8232 | }
|
8233 | static fromArray(markers) {
|
8234 | if (!markers) {
|
8235 | return DEFAULT_INTERPOLATION_CONFIG;
|
8236 | }
|
8237 | assertInterpolationSymbols('interpolation', markers);
|
8238 | return new InterpolationConfig(markers[0], markers[1]);
|
8239 | }
|
8240 | }
|
8241 | const DEFAULT_INTERPOLATION_CONFIG = new InterpolationConfig('{{', '}}');
|
8242 |
|
8243 | /**
|
8244 | * @license
|
8245 | * Copyright Google LLC All Rights Reserved.
|
8246 | *
|
8247 | * Use of this source code is governed by an MIT-style license that can be
|
8248 | * found in the LICENSE file at https://angular.io/license
|
8249 | */
|
8250 | /**
|
8251 | * In TypeScript, tagged template functions expect a "template object", which is an array of
|
8252 | * "cooked" strings plus a `raw` property that contains an array of "raw" strings. This is
|
8253 | * typically constructed with a function called `__makeTemplateObject(cooked, raw)`, but it may not
|
8254 | * be available in all environments.
|
8255 | *
|
8256 | * This is a JavaScript polyfill that uses __makeTemplateObject when it's available, but otherwise
|
8257 | * creates an inline helper with the same functionality.
|
8258 | *
|
8259 | * In the inline function, if `Object.defineProperty` is available we use that to attach the `raw`
|
8260 | * array.
|
8261 | */
|
8262 | const makeTemplateObjectPolyfill = '(this&&this.__makeTemplateObject||function(e,t){return Object.defineProperty?Object.defineProperty(e,"raw",{value:t}):e.raw=t,e})';
|
8263 | class AbstractJsEmitterVisitor extends AbstractEmitterVisitor {
|
8264 | constructor() {
|
8265 | super(false);
|
8266 | }
|
8267 | visitDeclareClassStmt(stmt, ctx) {
|
8268 | ctx.pushClass(stmt);
|
8269 | this._visitClassConstructor(stmt, ctx);
|
8270 | if (stmt.parent != null) {
|
8271 | ctx.print(stmt, `${stmt.name}.prototype = Object.create(`);
|
8272 | stmt.parent.visitExpression(this, ctx);
|
8273 | ctx.println(stmt, `.prototype);`);
|
8274 | }
|
8275 | stmt.getters.forEach((getter) => this._visitClassGetter(stmt, getter, ctx));
|
8276 | stmt.methods.forEach((method) => this._visitClassMethod(stmt, method, ctx));
|
8277 | ctx.popClass();
|
8278 | return null;
|
8279 | }
|
8280 | _visitClassConstructor(stmt, ctx) {
|
8281 | ctx.print(stmt, `function ${stmt.name}(`);
|
8282 | if (stmt.constructorMethod != null) {
|
8283 | this._visitParams(stmt.constructorMethod.params, ctx);
|
8284 | }
|
8285 | ctx.println(stmt, `) {`);
|
8286 | ctx.incIndent();
|
8287 | if (stmt.constructorMethod != null) {
|
8288 | if (stmt.constructorMethod.body.length > 0) {
|
8289 | ctx.println(stmt, `var self = this;`);
|
8290 | this.visitAllStatements(stmt.constructorMethod.body, ctx);
|
8291 | }
|
8292 | }
|
8293 | ctx.decIndent();
|
8294 | ctx.println(stmt, `}`);
|
8295 | }
|
8296 | _visitClassGetter(stmt, getter, ctx) {
|
8297 | ctx.println(stmt, `Object.defineProperty(${stmt.name}.prototype, '${getter.name}', { get: function() {`);
|
8298 | ctx.incIndent();
|
8299 | if (getter.body.length > 0) {
|
8300 | ctx.println(stmt, `var self = this;`);
|
8301 | this.visitAllStatements(getter.body, ctx);
|
8302 | }
|
8303 | ctx.decIndent();
|
8304 | ctx.println(stmt, `}});`);
|
8305 | }
|
8306 | _visitClassMethod(stmt, method, ctx) {
|
8307 | ctx.print(stmt, `${stmt.name}.prototype.${method.name} = function(`);
|
8308 | this._visitParams(method.params, ctx);
|
8309 | ctx.println(stmt, `) {`);
|
8310 | ctx.incIndent();
|
8311 | if (method.body.length > 0) {
|
8312 | ctx.println(stmt, `var self = this;`);
|
8313 | this.visitAllStatements(method.body, ctx);
|
8314 | }
|
8315 | ctx.decIndent();
|
8316 | ctx.println(stmt, `};`);
|
8317 | }
|
8318 | visitWrappedNodeExpr(ast, ctx) {
|
8319 | throw new Error('Cannot emit a WrappedNodeExpr in Javascript.');
|
8320 | }
|
8321 | visitReadVarExpr(ast, ctx) {
|
8322 | if (ast.builtin === BuiltinVar.This) {
|
8323 | ctx.print(ast, 'self');
|
8324 | }
|
8325 | else if (ast.builtin === BuiltinVar.Super) {
|
8326 | throw new Error(`'super' needs to be handled at a parent ast node, not at the variable level!`);
|
8327 | }
|
8328 | else {
|
8329 | super.visitReadVarExpr(ast, ctx);
|
8330 | }
|
8331 | return null;
|
8332 | }
|
8333 | visitDeclareVarStmt(stmt, ctx) {
|
8334 | ctx.print(stmt, `var ${stmt.name}`);
|
8335 | if (stmt.value) {
|
8336 | ctx.print(stmt, ' = ');
|
8337 | stmt.value.visitExpression(this, ctx);
|
8338 | }
|
8339 | ctx.println(stmt, `;`);
|
8340 | return null;
|
8341 | }
|
8342 | visitCastExpr(ast, ctx) {
|
8343 | ast.value.visitExpression(this, ctx);
|
8344 | return null;
|
8345 | }
|
8346 | visitInvokeFunctionExpr(expr, ctx) {
|
8347 | const fnExpr = expr.fn;
|
8348 | if (fnExpr instanceof ReadVarExpr && fnExpr.builtin === BuiltinVar.Super) {
|
8349 | ctx.currentClass.parent.visitExpression(this, ctx);
|
8350 | ctx.print(expr, `.call(this`);
|
8351 | if (expr.args.length > 0) {
|
8352 | ctx.print(expr, `, `);
|
8353 | this.visitAllExpressions(expr.args, ctx, ',');
|
8354 | }
|
8355 | ctx.print(expr, `)`);
|
8356 | }
|
8357 | else {
|
8358 | super.visitInvokeFunctionExpr(expr, ctx);
|
8359 | }
|
8360 | return null;
|
8361 | }
|
8362 | visitTaggedTemplateExpr(ast, ctx) {
|
8363 | // The following convoluted piece of code is effectively the downlevelled equivalent of
|
8364 | // ```
|
8365 | // tag`...`
|
8366 | // ```
|
8367 | // which is effectively like:
|
8368 | // ```
|
8369 | // tag(__makeTemplateObject(cooked, raw), expression1, expression2, ...);
|
8370 | // ```
|
8371 | const elements = ast.template.elements;
|
8372 | ast.tag.visitExpression(this, ctx);
|
8373 | ctx.print(ast, `(${makeTemplateObjectPolyfill}(`);
|
8374 | ctx.print(ast, `[${elements.map(part => escapeIdentifier(part.text, false)).join(', ')}], `);
|
8375 | ctx.print(ast, `[${elements.map(part => escapeIdentifier(part.rawText, false)).join(', ')}])`);
|
8376 | ast.template.expressions.forEach(expression => {
|
8377 | ctx.print(ast, ', ');
|
8378 | expression.visitExpression(this, ctx);
|
8379 | });
|
8380 | ctx.print(ast, ')');
|
8381 | return null;
|
8382 | }
|
8383 | visitFunctionExpr(ast, ctx) {
|
8384 | ctx.print(ast, `function${ast.name ? ' ' + ast.name : ''}(`);
|
8385 | this._visitParams(ast.params, ctx);
|
8386 | ctx.println(ast, `) {`);
|
8387 | ctx.incIndent();
|
8388 | this.visitAllStatements(ast.statements, ctx);
|
8389 | ctx.decIndent();
|
8390 | ctx.print(ast, `}`);
|
8391 | return null;
|
8392 | }
|
8393 | visitDeclareFunctionStmt(stmt, ctx) {
|
8394 | ctx.print(stmt, `function ${stmt.name}(`);
|
8395 | this._visitParams(stmt.params, ctx);
|
8396 | ctx.println(stmt, `) {`);
|
8397 | ctx.incIndent();
|
8398 | this.visitAllStatements(stmt.statements, ctx);
|
8399 | ctx.decIndent();
|
8400 | ctx.println(stmt, `}`);
|
8401 | return null;
|
8402 | }
|
8403 | visitTryCatchStmt(stmt, ctx) {
|
8404 | ctx.println(stmt, `try {`);
|
8405 | ctx.incIndent();
|
8406 | this.visitAllStatements(stmt.bodyStmts, ctx);
|
8407 | ctx.decIndent();
|
8408 | ctx.println(stmt, `} catch (${CATCH_ERROR_VAR$1.name}) {`);
|
8409 | ctx.incIndent();
|
8410 | const catchStmts = [CATCH_STACK_VAR$1.set(CATCH_ERROR_VAR$1.prop('stack')).toDeclStmt(null, [
|
8411 | StmtModifier.Final
|
8412 | ])].concat(stmt.catchStmts);
|
8413 | this.visitAllStatements(catchStmts, ctx);
|
8414 | ctx.decIndent();
|
8415 | ctx.println(stmt, `}`);
|
8416 | return null;
|
8417 | }
|
8418 | visitLocalizedString(ast, ctx) {
|
8419 | // The following convoluted piece of code is effectively the downlevelled equivalent of
|
8420 | // ```
|
8421 | // $localize `...`
|
8422 | // ```
|
8423 | // which is effectively like:
|
8424 | // ```
|
8425 | // $localize(__makeTemplateObject(cooked, raw), expression1, expression2, ...);
|
8426 | // ```
|
8427 | ctx.print(ast, `$localize(${makeTemplateObjectPolyfill}(`);
|
8428 | const parts = [ast.serializeI18nHead()];
|
8429 | for (let i = 1; i < ast.messageParts.length; i++) {
|
8430 | parts.push(ast.serializeI18nTemplatePart(i));
|
8431 | }
|
8432 | ctx.print(ast, `[${parts.map(part => escapeIdentifier(part.cooked, false)).join(', ')}], `);
|
8433 | ctx.print(ast, `[${parts.map(part => escapeIdentifier(part.raw, false)).join(', ')}])`);
|
8434 | ast.expressions.forEach(expression => {
|
8435 | ctx.print(ast, ', ');
|
8436 | expression.visitExpression(this, ctx);
|
8437 | });
|
8438 | ctx.print(ast, ')');
|
8439 | return null;
|
8440 | }
|
8441 | _visitParams(params, ctx) {
|
8442 | this.visitAllObjects(param => ctx.print(null, param.name), params, ctx, ',');
|
8443 | }
|
8444 | getBuiltinMethodName(method) {
|
8445 | let name;
|
8446 | switch (method) {
|
8447 | case BuiltinMethod.ConcatArray:
|
8448 | name = 'concat';
|
8449 | break;
|
8450 | case BuiltinMethod.SubscribeObservable:
|
8451 | name = 'subscribe';
|
8452 | break;
|
8453 | case BuiltinMethod.Bind:
|
8454 | name = 'bind';
|
8455 | break;
|
8456 | default:
|
8457 | throw new Error(`Unknown builtin method: ${method}`);
|
8458 | }
|
8459 | return name;
|
8460 | }
|
8461 | }
|
8462 |
|
8463 | /**
|
8464 | * @license
|
8465 | * Copyright Google LLC All Rights Reserved.
|
8466 | *
|
8467 | * Use of this source code is governed by an MIT-style license that can be
|
8468 | * found in the LICENSE file at https://angular.io/license
|
8469 | */
|
8470 | /**
|
8471 | * The Trusted Types policy, or null if Trusted Types are not
|
8472 | * enabled/supported, or undefined if the policy has not been created yet.
|
8473 | */
|
8474 | let policy;
|
8475 | /**
|
8476 | * Returns the Trusted Types policy, or null if Trusted Types are not
|
8477 | * enabled/supported. The first call to this function will create the policy.
|
8478 | */
|
8479 | function getPolicy() {
|
8480 | if (policy === undefined) {
|
8481 | policy = null;
|
8482 | if (_global.trustedTypes) {
|
8483 | try {
|
8484 | policy =
|
8485 | _global.trustedTypes.createPolicy('angular#unsafe-jit', {
|
8486 | createScript: (s) => s,
|
8487 | });
|
8488 | }
|
8489 | catch (_a) {
|
8490 | // trustedTypes.createPolicy throws if called with a name that is
|
8491 | // already registered, even in report-only mode. Until the API changes,
|
8492 | // catch the error not to break the applications functionally. In such
|
8493 | // cases, the code will fall back to using strings.
|
8494 | }
|
8495 | }
|
8496 | }
|
8497 | return policy;
|
8498 | }
|
8499 | /**
|
8500 | * Unsafely promote a string to a TrustedScript, falling back to strings when
|
8501 | * Trusted Types are not available.
|
8502 | * @security In particular, it must be assured that the provided string will
|
8503 | * never cause an XSS vulnerability if used in a context that will be
|
8504 | * interpreted and executed as a script by a browser, e.g. when calling eval.
|
8505 | */
|
8506 | function trustedScriptFromString(script) {
|
8507 | var _a;
|
8508 | return ((_a = getPolicy()) === null || _a === void 0 ? void 0 : _a.createScript(script)) || script;
|
8509 | }
|
8510 | /**
|
8511 | * Unsafely call the Function constructor with the given string arguments.
|
8512 | * @security This is a security-sensitive function; any use of this function
|
8513 | * must go through security review. In particular, it must be assured that it
|
8514 | * is only called from the JIT compiler, as use in other code can lead to XSS
|
8515 | * vulnerabilities.
|
8516 | */
|
8517 | function newTrustedFunctionForJIT(...args) {
|
8518 | if (!_global.trustedTypes) {
|
8519 | // In environments that don't support Trusted Types, fall back to the most
|
8520 | // straightforward implementation:
|
8521 | return new Function(...args);
|
8522 | }
|
8523 | // Chrome currently does not support passing TrustedScript to the Function
|
8524 | // constructor. The following implements the workaround proposed on the page
|
8525 | // below, where the Chromium bug is also referenced:
|
8526 | // https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor
|
8527 | const fnArgs = args.slice(0, -1).join(',');
|
8528 | const fnBody = args[args.length - 1];
|
8529 | const body = `(function anonymous(${fnArgs}
|
8530 | ) { ${fnBody}
|
8531 | })`;
|
8532 | // Using eval directly confuses the compiler and prevents this module from
|
8533 | // being stripped out of JS binaries even if not used. The global['eval']
|
8534 | // indirection fixes that.
|
8535 | const fn = _global['eval'](trustedScriptFromString(body));
|
8536 | if (fn.bind === undefined) {
|
8537 | // Workaround for a browser bug that only exists in Chrome 83, where passing
|
8538 | // a TrustedScript to eval just returns the TrustedScript back without
|
8539 | // evaluating it. In that case, fall back to the most straightforward
|
8540 | // implementation:
|
8541 | return new Function(...args);
|
8542 | }
|
8543 | // To completely mimic the behavior of calling "new Function", two more
|
8544 | // things need to happen:
|
8545 | // 1. Stringifying the resulting function should return its source code
|
8546 | fn.toString = () => body;
|
8547 | // 2. When calling the resulting function, `this` should refer to `global`
|
8548 | return fn.bind(_global);
|
8549 | // When Trusted Types support in Function constructors is widely available,
|
8550 | // the implementation of this function can be simplified to:
|
8551 | // return new Function(...args.map(a => trustedScriptFromString(a)));
|
8552 | }
|
8553 |
|
8554 | /**
|
8555 | * @license
|
8556 | * Copyright Google LLC All Rights Reserved.
|
8557 | *
|
8558 | * Use of this source code is governed by an MIT-style license that can be
|
8559 | * found in the LICENSE file at https://angular.io/license
|
8560 | */
|
8561 | /**
|
8562 | * A helper class to manage the evaluation of JIT generated code.
|
8563 | */
|
8564 | class JitEvaluator {
|
8565 | /**
|
8566 | *
|
8567 | * @param sourceUrl The URL of the generated code.
|
8568 | * @param statements An array of Angular statement AST nodes to be evaluated.
|
8569 | * @param reflector A helper used when converting the statements to executable code.
|
8570 | * @param createSourceMaps If true then create a source-map for the generated code and include it
|
8571 | * inline as a source-map comment.
|
8572 | * @returns A map of all the variables in the generated code.
|
8573 | */
|
8574 | evaluateStatements(sourceUrl, statements, reflector, createSourceMaps) {
|
8575 | const converter = new JitEmitterVisitor(reflector);
|
8576 | const ctx = EmitterVisitorContext.createRoot();
|
8577 | // Ensure generated code is in strict mode
|
8578 | if (statements.length > 0 && !isUseStrictStatement(statements[0])) {
|
8579 | statements = [
|
8580 | literal('use strict').toStmt(),
|
8581 | ...statements,
|
8582 | ];
|
8583 | }
|
8584 | converter.visitAllStatements(statements, ctx);
|
8585 | converter.createReturnStmt(ctx);
|
8586 | return this.evaluateCode(sourceUrl, ctx, converter.getArgs(), createSourceMaps);
|
8587 | }
|
8588 | /**
|
8589 | * Evaluate a piece of JIT generated code.
|
8590 | * @param sourceUrl The URL of this generated code.
|
8591 | * @param ctx A context object that contains an AST of the code to be evaluated.
|
8592 | * @param vars A map containing the names and values of variables that the evaluated code might
|
8593 | * reference.
|
8594 | * @param createSourceMap If true then create a source-map for the generated code and include it
|
8595 | * inline as a source-map comment.
|
8596 | * @returns The result of evaluating the code.
|
8597 | */
|
8598 | evaluateCode(sourceUrl, ctx, vars, createSourceMap) {
|
8599 | let fnBody = `"use strict";${ctx.toSource()}\n//# sourceURL=${sourceUrl}`;
|
8600 | const fnArgNames = [];
|
8601 | const fnArgValues = [];
|
8602 | for (const argName in vars) {
|
8603 | fnArgValues.push(vars[argName]);
|
8604 | fnArgNames.push(argName);
|
8605 | }
|
8606 | if (createSourceMap) {
|
8607 | // using `new Function(...)` generates a header, 1 line of no arguments, 2 lines otherwise
|
8608 | // E.g. ```
|
8609 | // function anonymous(a,b,c
|
8610 | // /**/) { ... }```
|
8611 | // We don't want to hard code this fact, so we auto detect it via an empty function first.
|
8612 | const emptyFn = newTrustedFunctionForJIT(...fnArgNames.concat('return null;')).toString();
|
8613 | const headerLines = emptyFn.slice(0, emptyFn.indexOf('return null;')).split('\n').length - 1;
|
8614 | fnBody += `\n${ctx.toSourceMapGenerator(sourceUrl, headerLines).toJsComment()}`;
|
8615 | }
|
8616 | const fn = newTrustedFunctionForJIT(...fnArgNames.concat(fnBody));
|
8617 | return this.executeFunction(fn, fnArgValues);
|
8618 | }
|
8619 | /**
|
8620 | * Execute a JIT generated function by calling it.
|
8621 | *
|
8622 | * This method can be overridden in tests to capture the functions that are generated
|
8623 | * by this `JitEvaluator` class.
|
8624 | *
|
8625 | * @param fn A function to execute.
|
8626 | * @param args The arguments to pass to the function being executed.
|
8627 | * @returns The return value of the executed function.
|
8628 | */
|
8629 | executeFunction(fn, args) {
|
8630 | return fn(...args);
|
8631 | }
|
8632 | }
|
8633 | /**
|
8634 | * An Angular AST visitor that converts AST nodes into executable JavaScript code.
|
8635 | */
|
8636 | class JitEmitterVisitor extends AbstractJsEmitterVisitor {
|
8637 | constructor(reflector) {
|
8638 | super();
|
8639 | this.reflector = reflector;
|
8640 | this._evalArgNames = [];
|
8641 | this._evalArgValues = [];
|
8642 | this._evalExportedVars = [];
|
8643 | }
|
8644 | createReturnStmt(ctx) {
|
8645 | const stmt = new ReturnStatement(new LiteralMapExpr(this._evalExportedVars.map(resultVar => new LiteralMapEntry(resultVar, variable(resultVar), false))));
|
8646 | stmt.visitStatement(this, ctx);
|
8647 | }
|
8648 | getArgs() {
|
8649 | const result = {};
|
8650 | for (let i = 0; i < this._evalArgNames.length; i++) {
|
8651 | result[this._evalArgNames[i]] = this._evalArgValues[i];
|
8652 | }
|
8653 | return result;
|
8654 | }
|
8655 | visitExternalExpr(ast, ctx) {
|
8656 | this._emitReferenceToExternal(ast, this.reflector.resolveExternalReference(ast.value), ctx);
|
8657 | return null;
|
8658 | }
|
8659 | visitWrappedNodeExpr(ast, ctx) {
|
8660 | this._emitReferenceToExternal(ast, ast.node, ctx);
|
8661 | return null;
|
8662 | }
|
8663 | visitDeclareVarStmt(stmt, ctx) {
|
8664 | if (stmt.hasModifier(StmtModifier.Exported)) {
|
8665 | this._evalExportedVars.push(stmt.name);
|
8666 | }
|
8667 | return super.visitDeclareVarStmt(stmt, ctx);
|
8668 | }
|
8669 | visitDeclareFunctionStmt(stmt, ctx) {
|
8670 | if (stmt.hasModifier(StmtModifier.Exported)) {
|
8671 | this._evalExportedVars.push(stmt.name);
|
8672 | }
|
8673 | return super.visitDeclareFunctionStmt(stmt, ctx);
|
8674 | }
|
8675 | visitDeclareClassStmt(stmt, ctx) {
|
8676 | if (stmt.hasModifier(StmtModifier.Exported)) {
|
8677 | this._evalExportedVars.push(stmt.name);
|
8678 | }
|
8679 | return super.visitDeclareClassStmt(stmt, ctx);
|
8680 | }
|
8681 | _emitReferenceToExternal(ast, value, ctx) {
|
8682 | let id = this._evalArgValues.indexOf(value);
|
8683 | if (id === -1) {
|
8684 | id = this._evalArgValues.length;
|
8685 | this._evalArgValues.push(value);
|
8686 | const name = identifierName({ reference: value }) || 'val';
|
8687 | this._evalArgNames.push(`jit_${name}_${id}`);
|
8688 | }
|
8689 | ctx.print(ast, this._evalArgNames[id]);
|
8690 | }
|
8691 | }
|
8692 | function isUseStrictStatement(statement) {
|
8693 | return statement.isEquivalent(literal('use strict').toStmt());
|
8694 | }
|
8695 |
|
8696 | /**
|
8697 | * @license
|
8698 | * Copyright Google LLC All Rights Reserved.
|
8699 | *
|
8700 | * Use of this source code is governed by an MIT-style license that can be
|
8701 | * found in the LICENSE file at https://angular.io/license
|
8702 | */
|
8703 | const $EOF = 0;
|
8704 | const $BSPACE = 8;
|
8705 | const $TAB = 9;
|
8706 | const $LF = 10;
|
8707 | const $VTAB = 11;
|
8708 | const $FF = 12;
|
8709 | const $CR = 13;
|
8710 | const $SPACE = 32;
|
8711 | const $BANG = 33;
|
8712 | const $DQ = 34;
|
8713 | const $HASH = 35;
|
8714 | const $$ = 36;
|
8715 | const $PERCENT = 37;
|
8716 | const $AMPERSAND = 38;
|
8717 | const $SQ = 39;
|
8718 | const $LPAREN = 40;
|
8719 | const $RPAREN = 41;
|
8720 | const $STAR = 42;
|
8721 | const $PLUS = 43;
|
8722 | const $COMMA = 44;
|
8723 | const $MINUS = 45;
|
8724 | const $PERIOD = 46;
|
8725 | const $SLASH = 47;
|
8726 | const $COLON = 58;
|
8727 | const $SEMICOLON = 59;
|
8728 | const $LT = 60;
|
8729 | const $EQ = 61;
|
8730 | const $GT = 62;
|
8731 | const $QUESTION = 63;
|
8732 | const $0 = 48;
|
8733 | const $7 = 55;
|
8734 | const $9 = 57;
|
8735 | const $A = 65;
|
8736 | const $E = 69;
|
8737 | const $F = 70;
|
8738 | const $X = 88;
|
8739 | const $Z = 90;
|
8740 | const $LBRACKET = 91;
|
8741 | const $BACKSLASH = 92;
|
8742 | const $RBRACKET = 93;
|
8743 | const $CARET = 94;
|
8744 | const $_ = 95;
|
8745 | const $a = 97;
|
8746 | const $b = 98;
|
8747 | const $e = 101;
|
8748 | const $f = 102;
|
8749 | const $n = 110;
|
8750 | const $r = 114;
|
8751 | const $t = 116;
|
8752 | const $u = 117;
|
8753 | const $v = 118;
|
8754 | const $x = 120;
|
8755 | const $z = 122;
|
8756 | const $LBRACE = 123;
|
8757 | const $BAR = 124;
|
8758 | const $RBRACE = 125;
|
8759 | const $NBSP = 160;
|
8760 | const $BT = 96;
|
8761 | function isWhitespace(code) {
|
8762 | return (code >= $TAB && code <= $SPACE) || (code == $NBSP);
|
8763 | }
|
8764 | function isDigit(code) {
|
8765 | return $0 <= code && code <= $9;
|
8766 | }
|
8767 | function isAsciiLetter(code) {
|
8768 | return code >= $a && code <= $z || code >= $A && code <= $Z;
|
8769 | }
|
8770 | function isAsciiHexDigit(code) {
|
8771 | return code >= $a && code <= $f || code >= $A && code <= $F || isDigit(code);
|
8772 | }
|
8773 | function isNewLine(code) {
|
8774 | return code === $LF || code === $CR;
|
8775 | }
|
8776 | function isOctalDigit(code) {
|
8777 | return $0 <= code && code <= $7;
|
8778 | }
|
8779 |
|
8780 | /**
|
8781 | * @license
|
8782 | * Copyright Google LLC All Rights Reserved.
|
8783 | *
|
8784 | * Use of this source code is governed by an MIT-style license that can be
|
8785 | * found in the LICENSE file at https://angular.io/license
|
8786 | */
|
8787 | class ParseLocation {
|
8788 | constructor(file, offset, line, col) {
|
8789 | this.file = file;
|
8790 | this.offset = offset;
|
8791 | this.line = line;
|
8792 | this.col = col;
|
8793 | }
|
8794 | toString() {
|
8795 | return this.offset != null ? `${this.file.url}@${this.line}:${this.col}` : this.file.url;
|
8796 | }
|
8797 | moveBy(delta) {
|
8798 | const source = this.file.content;
|
8799 | const len = source.length;
|
8800 | let offset = this.offset;
|
8801 | let line = this.line;
|
8802 | let col = this.col;
|
8803 | while (offset > 0 && delta < 0) {
|
8804 | offset--;
|
8805 | delta++;
|
8806 | const ch = source.charCodeAt(offset);
|
8807 | if (ch == $LF) {
|
8808 | line--;
|
8809 | const priorLine = source.substr(0, offset - 1).lastIndexOf(String.fromCharCode($LF));
|
8810 | col = priorLine > 0 ? offset - priorLine : offset;
|
8811 | }
|
8812 | else {
|
8813 | col--;
|
8814 | }
|
8815 | }
|
8816 | while (offset < len && delta > 0) {
|
8817 | const ch = source.charCodeAt(offset);
|
8818 | offset++;
|
8819 | delta--;
|
8820 | if (ch == $LF) {
|
8821 | line++;
|
8822 | col = 0;
|
8823 | }
|
8824 | else {
|
8825 | col++;
|
8826 | }
|
8827 | }
|
8828 | return new ParseLocation(this.file, offset, line, col);
|
8829 | }
|
8830 | // Return the source around the location
|
8831 | // Up to `maxChars` or `maxLines` on each side of the location
|
8832 | getContext(maxChars, maxLines) {
|
8833 | const content = this.file.content;
|
8834 | let startOffset = this.offset;
|
8835 | if (startOffset != null) {
|
8836 | if (startOffset > content.length - 1) {
|
8837 | startOffset = content.length - 1;
|
8838 | }
|
8839 | let endOffset = startOffset;
|
8840 | let ctxChars = 0;
|
8841 | let ctxLines = 0;
|
8842 | while (ctxChars < maxChars && startOffset > 0) {
|
8843 | startOffset--;
|
8844 | ctxChars++;
|
8845 | if (content[startOffset] == '\n') {
|
8846 | if (++ctxLines == maxLines) {
|
8847 | break;
|
8848 | }
|
8849 | }
|
8850 | }
|
8851 | ctxChars = 0;
|
8852 | ctxLines = 0;
|
8853 | while (ctxChars < maxChars && endOffset < content.length - 1) {
|
8854 | endOffset++;
|
8855 | ctxChars++;
|
8856 | if (content[endOffset] == '\n') {
|
8857 | if (++ctxLines == maxLines) {
|
8858 | break;
|
8859 | }
|
8860 | }
|
8861 | }
|
8862 | return {
|
8863 | before: content.substring(startOffset, this.offset),
|
8864 | after: content.substring(this.offset, endOffset + 1),
|
8865 | };
|
8866 | }
|
8867 | return null;
|
8868 | }
|
8869 | }
|
8870 | class ParseSourceFile {
|
8871 | constructor(content, url) {
|
8872 | this.content = content;
|
8873 | this.url = url;
|
8874 | }
|
8875 | }
|
8876 | class ParseSourceSpan {
|
8877 | /**
|
8878 | * Create an object that holds information about spans of tokens/nodes captured during
|
8879 | * lexing/parsing of text.
|
8880 | *
|
8881 | * @param start
|
8882 | * The location of the start of the span (having skipped leading trivia).
|
8883 | * Skipping leading trivia makes source-spans more "user friendly", since things like HTML
|
8884 | * elements will appear to begin at the start of the opening tag, rather than at the start of any
|
8885 | * leading trivia, which could include newlines.
|
8886 | *
|
8887 | * @param end
|
8888 | * The location of the end of the span.
|
8889 | *
|
8890 | * @param fullStart
|
8891 | * The start of the token without skipping the leading trivia.
|
8892 | * This is used by tooling that splits tokens further, such as extracting Angular interpolations
|
8893 | * from text tokens. Such tooling creates new source-spans relative to the original token's
|
8894 | * source-span. If leading trivia characters have been skipped then the new source-spans may be
|
8895 | * incorrectly offset.
|
8896 | *
|
8897 | * @param details
|
8898 | * Additional information (such as identifier names) that should be associated with the span.
|
8899 | */
|
8900 | constructor(start, end, fullStart = start, details = null) {
|
8901 | this.start = start;
|
8902 | this.end = end;
|
8903 | this.fullStart = fullStart;
|
8904 | this.details = details;
|
8905 | }
|
8906 | toString() {
|
8907 | return this.start.file.content.substring(this.start.offset, this.end.offset);
|
8908 | }
|
8909 | }
|
8910 | var ParseErrorLevel;
|
8911 | (function (ParseErrorLevel) {
|
8912 | ParseErrorLevel[ParseErrorLevel["WARNING"] = 0] = "WARNING";
|
8913 | ParseErrorLevel[ParseErrorLevel["ERROR"] = 1] = "ERROR";
|
8914 | })(ParseErrorLevel || (ParseErrorLevel = {}));
|
8915 | class ParseError {
|
8916 | constructor(span, msg, level = ParseErrorLevel.ERROR) {
|
8917 | this.span = span;
|
8918 | this.msg = msg;
|
8919 | this.level = level;
|
8920 | }
|
8921 | contextualMessage() {
|
8922 | const ctx = this.span.start.getContext(100, 3);
|
8923 | return ctx ? `${this.msg} ("${ctx.before}[${ParseErrorLevel[this.level]} ->]${ctx.after}")` :
|
8924 | this.msg;
|
8925 | }
|
8926 | toString() {
|
8927 | const details = this.span.details ? `, ${this.span.details}` : '';
|
8928 | return `${this.contextualMessage()}: ${this.span.start}${details}`;
|
8929 | }
|
8930 | }
|
8931 | /**
|
8932 | * Generates Source Span object for a given R3 Type for JIT mode.
|
8933 | *
|
8934 | * @param kind Component or Directive.
|
8935 | * @param typeName name of the Component or Directive.
|
8936 | * @param sourceUrl reference to Component or Directive source.
|
8937 | * @returns instance of ParseSourceSpan that represent a given Component or Directive.
|
8938 | */
|
8939 | function r3JitTypeSourceSpan(kind, typeName, sourceUrl) {
|
8940 | const sourceFileName = `in ${kind} ${typeName} in ${sourceUrl}`;
|
8941 | const sourceFile = new ParseSourceFile('', sourceFileName);
|
8942 | return new ParseSourceSpan(new ParseLocation(sourceFile, -1, -1, -1), new ParseLocation(sourceFile, -1, -1, -1));
|
8943 | }
|
8944 |
|
8945 | /**
|
8946 | * @license
|
8947 | * Copyright Google LLC All Rights Reserved.
|
8948 | *
|
8949 | * Use of this source code is governed by an MIT-style license that can be
|
8950 | * found in the LICENSE file at https://angular.io/license
|
8951 | */
|
8952 | /**
|
8953 | * Implementation of `CompileReflector` which resolves references to @angular/core
|
8954 | * symbols at runtime, according to a consumer-provided mapping.
|
8955 | *
|
8956 | * Only supports `resolveExternalReference`, all other methods throw.
|
8957 | */
|
8958 | class R3JitReflector {
|
8959 | constructor(context) {
|
8960 | this.context = context;
|
8961 | }
|
8962 | resolveExternalReference(ref) {
|
8963 | // This reflector only handles @angular/core imports.
|
8964 | if (ref.moduleName !== '@angular/core') {
|
8965 | throw new Error(`Cannot resolve external reference to ${ref.moduleName}, only references to @angular/core are supported.`);
|
8966 | }
|
8967 | if (!this.context.hasOwnProperty(ref.name)) {
|
8968 | throw new Error(`No value provided for @angular/core symbol '${ref.name}'.`);
|
8969 | }
|
8970 | return this.context[ref.name];
|
8971 | }
|
8972 | parameters(typeOrFunc) {
|
8973 | throw new Error('Not implemented.');
|
8974 | }
|
8975 | annotations(typeOrFunc) {
|
8976 | throw new Error('Not implemented.');
|
8977 | }
|
8978 | shallowAnnotations(typeOrFunc) {
|
8979 | throw new Error('Not implemented.');
|
8980 | }
|
8981 | tryAnnotations(typeOrFunc) {
|
8982 | throw new Error('Not implemented.');
|
8983 | }
|
8984 | propMetadata(typeOrFunc) {
|
8985 | throw new Error('Not implemented.');
|
8986 | }
|
8987 | hasLifecycleHook(type, lcProperty) {
|
8988 | throw new Error('Not implemented.');
|
8989 | }
|
8990 | guards(typeOrFunc) {
|
8991 | throw new Error('Not implemented.');
|
8992 | }
|
8993 | componentModuleUrl(type, cmpMetadata) {
|
8994 | throw new Error('Not implemented.');
|
8995 | }
|
8996 | }
|
8997 |
|
8998 | /**
|
8999 | * @license
|
9000 | * Copyright Google LLC All Rights Reserved.
|
9001 | *
|
9002 | * Use of this source code is governed by an MIT-style license that can be
|
9003 | * found in the LICENSE file at https://angular.io/license
|
9004 | */
|
9005 | function mapLiteral(obj, quoted = false) {
|
9006 | return literalMap(Object.keys(obj).map(key => ({
|
9007 | key,
|
9008 | quoted,
|
9009 | value: obj[key],
|
9010 | })));
|
9011 | }
|
9012 |
|
9013 | /**
|
9014 | * @license
|
9015 | * Copyright Google LLC All Rights Reserved.
|
9016 | *
|
9017 | * Use of this source code is governed by an MIT-style license that can be
|
9018 | * found in the LICENSE file at https://angular.io/license
|
9019 | */
|
9020 | /**
|
9021 | * Construct an `R3NgModuleDef` for the given `R3NgModuleMetadata`.
|
9022 | */
|
9023 | function compileNgModule(meta) {
|
9024 | const { internalType, type: moduleType, bootstrap, declarations, imports, exports, schemas, containsForwardDecls, emitInline, id } = meta;
|
9025 | const additionalStatements = [];
|
9026 | const definitionMap = { type: internalType };
|
9027 | // Only generate the keys in the metadata if the arrays have values.
|
9028 | if (bootstrap.length) {
|
9029 | definitionMap.bootstrap = refsToArray(bootstrap, containsForwardDecls);
|
9030 | }
|
9031 | // If requested to emit scope information inline, pass the declarations, imports and exports to
|
9032 | // the `ɵɵdefineNgModule` call. The JIT compilation uses this.
|
9033 | if (emitInline) {
|
9034 | if (declarations.length) {
|
9035 | definitionMap.declarations = refsToArray(declarations, containsForwardDecls);
|
9036 | }
|
9037 | if (imports.length) {
|
9038 | definitionMap.imports = refsToArray(imports, containsForwardDecls);
|
9039 | }
|
9040 | if (exports.length) {
|
9041 | definitionMap.exports = refsToArray(exports, containsForwardDecls);
|
9042 | }
|
9043 | }
|
9044 | // If not emitting inline, the scope information is not passed into `ɵɵdefineNgModule` as it would
|
9045 | // prevent tree-shaking of the declarations, imports and exports references.
|
9046 | else {
|
9047 | const setNgModuleScopeCall = generateSetNgModuleScopeCall(meta);
|
9048 | if (setNgModuleScopeCall !== null) {
|
9049 | additionalStatements.push(setNgModuleScopeCall);
|
9050 | }
|
9051 | }
|
9052 | if (schemas && schemas.length) {
|
9053 | definitionMap.schemas = literalArr(schemas.map(ref => ref.value));
|
9054 | }
|
9055 | if (id) {
|
9056 | definitionMap.id = id;
|
9057 | }
|
9058 | const expression = importExpr(Identifiers$1.defineNgModule).callFn([mapToMapExpression(definitionMap)]);
|
9059 | const type = new ExpressionType(importExpr(Identifiers$1.NgModuleDefWithMeta, [
|
9060 | new ExpressionType(moduleType.type), tupleTypeOf(declarations), tupleTypeOf(imports),
|
9061 | tupleTypeOf(exports)
|
9062 | ]));
|
9063 | return { expression, type, additionalStatements };
|
9064 | }
|
9065 | /**
|
9066 | * Generates a function call to `ɵɵsetNgModuleScope` with all necessary information so that the
|
9067 | * transitive module scope can be computed during runtime in JIT mode. This call is marked pure
|
9068 | * such that the references to declarations, imports and exports may be elided causing these
|
9069 | * symbols to become tree-shakeable.
|
9070 | */
|
9071 | function generateSetNgModuleScopeCall(meta) {
|
9072 | const { adjacentType: moduleType, declarations, imports, exports, containsForwardDecls } = meta;
|
9073 | const scopeMap = {};
|
9074 | if (declarations.length) {
|
9075 | scopeMap.declarations = refsToArray(declarations, containsForwardDecls);
|
9076 | }
|
9077 | if (imports.length) {
|
9078 | scopeMap.imports = refsToArray(imports, containsForwardDecls);
|
9079 | }
|
9080 | if (exports.length) {
|
9081 | scopeMap.exports = refsToArray(exports, containsForwardDecls);
|
9082 | }
|
9083 | if (Object.keys(scopeMap).length === 0) {
|
9084 | return null;
|
9085 | }
|
9086 | // setNgModuleScope(...)
|
9087 | const fnCall = new InvokeFunctionExpr(
|
9088 | /* fn */ importExpr(Identifiers$1.setNgModuleScope),
|
9089 | /* args */ [moduleType, mapToMapExpression(scopeMap)]);
|
9090 | // (ngJitMode guard) && setNgModuleScope(...)
|
9091 | const guardedCall = jitOnlyGuardedExpression(fnCall);
|
9092 | // function() { (ngJitMode guard) && setNgModuleScope(...); }
|
9093 | const iife = new FunctionExpr(
|
9094 | /* params */ [],
|
9095 | /* statements */ [guardedCall.toStmt()]);
|
9096 | // (function() { (ngJitMode guard) && setNgModuleScope(...); })()
|
9097 | const iifeCall = new InvokeFunctionExpr(
|
9098 | /* fn */ iife,
|
9099 | /* args */ []);
|
9100 | return iifeCall.toStmt();
|
9101 | }
|
9102 | function compileInjector(meta) {
|
9103 | const result = compileFactoryFunction({
|
9104 | name: meta.name,
|
9105 | type: meta.type,
|
9106 | internalType: meta.internalType,
|
9107 | typeArgumentCount: 0,
|
9108 | deps: meta.deps,
|
9109 | injectFn: Identifiers$1.inject,
|
9110 | target: R3FactoryTarget.NgModule,
|
9111 | });
|
9112 | const definitionMap = {
|
9113 | factory: result.factory,
|
9114 | };
|
9115 | if (meta.providers !== null) {
|
9116 | definitionMap.providers = meta.providers;
|
9117 | }
|
9118 | if (meta.imports.length > 0) {
|
9119 | definitionMap.imports = literalArr(meta.imports);
|
9120 | }
|
9121 | const expression = importExpr(Identifiers$1.defineInjector).callFn([mapToMapExpression(definitionMap)]);
|
9122 | const type = new ExpressionType(importExpr(Identifiers$1.InjectorDef, [new ExpressionType(meta.type.type)]));
|
9123 | return { expression, type, statements: result.statements };
|
9124 | }
|
9125 | function tupleTypeOf(exp) {
|
9126 | const types = exp.map(ref => typeofExpr(ref.type));
|
9127 | return exp.length > 0 ? expressionType(literalArr(types)) : NONE_TYPE;
|
9128 | }
|
9129 | function refsToArray(refs, shouldForwardDeclare) {
|
9130 | const values = literalArr(refs.map(ref => ref.value));
|
9131 | return shouldForwardDeclare ? fn([], [new ReturnStatement(values)]) : values;
|
9132 | }
|
9133 |
|
9134 | /**
|
9135 | * @license
|
9136 | * Copyright Google LLC All Rights Reserved.
|
9137 | *
|
9138 | * Use of this source code is governed by an MIT-style license that can be
|
9139 | * found in the LICENSE file at https://angular.io/license
|
9140 | */
|
9141 | function compilePipeFromMetadata(metadata) {
|
9142 | const definitionMapValues = [];
|
9143 | // e.g. `name: 'myPipe'`
|
9144 | definitionMapValues.push({ key: 'name', value: literal(metadata.pipeName), quoted: false });
|
9145 | // e.g. `type: MyPipe`
|
9146 | definitionMapValues.push({ key: 'type', value: metadata.type.value, quoted: false });
|
9147 | // e.g. `pure: true`
|
9148 | definitionMapValues.push({ key: 'pure', value: literal(metadata.pure), quoted: false });
|
9149 | const expression = importExpr(Identifiers$1.definePipe).callFn([literalMap(definitionMapValues)]);
|
9150 | const type = createPipeType(metadata);
|
9151 | return { expression, type };
|
9152 | }
|
9153 | function createPipeType(metadata) {
|
9154 | return new ExpressionType(importExpr(Identifiers$1.PipeDefWithMeta, [
|
9155 | typeWithParameters(metadata.type.type, metadata.typeArgumentCount),
|
9156 | new ExpressionType(new LiteralExpr(metadata.pipeName)),
|
9157 | ]));
|
9158 | }
|
9159 |
|
9160 | /**
|
9161 | * @license
|
9162 | * Copyright Google LLC All Rights Reserved.
|
9163 | *
|
9164 | * Use of this source code is governed by an MIT-style license that can be
|
9165 | * found in the LICENSE file at https://angular.io/license
|
9166 | */
|
9167 | class ParserError {
|
9168 | constructor(message, input, errLocation, ctxLocation) {
|
9169 | this.input = input;
|
9170 | this.errLocation = errLocation;
|
9171 | this.ctxLocation = ctxLocation;
|
9172 | this.message = `Parser Error: ${message} ${errLocation} [${input}] in ${ctxLocation}`;
|
9173 | }
|
9174 | }
|
9175 | class ParseSpan {
|
9176 | constructor(start, end) {
|
9177 | this.start = start;
|
9178 | this.end = end;
|
9179 | }
|
9180 | toAbsolute(absoluteOffset) {
|
9181 | return new AbsoluteSourceSpan(absoluteOffset + this.start, absoluteOffset + this.end);
|
9182 | }
|
9183 | }
|
9184 | class AST {
|
9185 | constructor(span,
|
9186 | /**
|
9187 | * Absolute location of the expression AST in a source code file.
|
9188 | */
|
9189 | sourceSpan) {
|
9190 | this.span = span;
|
9191 | this.sourceSpan = sourceSpan;
|
9192 | }
|
9193 | visit(visitor, context = null) {
|
9194 | return null;
|
9195 | }
|
9196 | toString() {
|
9197 | return 'AST';
|
9198 | }
|
9199 | }
|
9200 | class ASTWithName extends AST {
|
9201 | constructor(span, sourceSpan, nameSpan) {
|
9202 | super(span, sourceSpan);
|
9203 | this.nameSpan = nameSpan;
|
9204 | }
|
9205 | }
|
9206 | /**
|
9207 | * Represents a quoted expression of the form:
|
9208 | *
|
9209 | * quote = prefix `:` uninterpretedExpression
|
9210 | * prefix = identifier
|
9211 | * uninterpretedExpression = arbitrary string
|
9212 | *
|
9213 | * A quoted expression is meant to be pre-processed by an AST transformer that
|
9214 | * converts it into another AST that no longer contains quoted expressions.
|
9215 | * It is meant to allow third-party developers to extend Angular template
|
9216 | * expression language. The `uninterpretedExpression` part of the quote is
|
9217 | * therefore not interpreted by the Angular's own expression parser.
|
9218 | */
|
9219 | class Quote extends AST {
|
9220 | constructor(span, sourceSpan, prefix, uninterpretedExpression, location) {
|
9221 | super(span, sourceSpan);
|
9222 | this.prefix = prefix;
|
9223 | this.uninterpretedExpression = uninterpretedExpression;
|
9224 | this.location = location;
|
9225 | }
|
9226 | visit(visitor, context = null) {
|
9227 | return visitor.visitQuote(this, context);
|
9228 | }
|
9229 | toString() {
|
9230 | return 'Quote';
|
9231 | }
|
9232 | }
|
9233 | class EmptyExpr extends AST {
|
9234 | visit(visitor, context = null) {
|
9235 | // do nothing
|
9236 | }
|
9237 | }
|
9238 | class ImplicitReceiver extends AST {
|
9239 | visit(visitor, context = null) {
|
9240 | return visitor.visitImplicitReceiver(this, context);
|
9241 | }
|
9242 | }
|
9243 | /**
|
9244 | * Receiver when something is accessed through `this` (e.g. `this.foo`). Note that this class
|
9245 | * inherits from `ImplicitReceiver`, because accessing something through `this` is treated the
|
9246 | * same as accessing it implicitly inside of an Angular template (e.g. `[attr.title]="this.title"`
|
9247 | * is the same as `[attr.title]="title"`.). Inheriting allows for the `this` accesses to be treated
|
9248 | * the same as implicit ones, except for a couple of exceptions like `$event` and `$any`.
|
9249 | * TODO: we should find a way for this class not to extend from `ImplicitReceiver` in the future.
|
9250 | */
|
9251 | class ThisReceiver extends ImplicitReceiver {
|
9252 | visit(visitor, context = null) {
|
9253 | var _a;
|
9254 | return (_a = visitor.visitThisReceiver) === null || _a === void 0 ? void 0 : _a.call(visitor, this, context);
|
9255 | }
|
9256 | }
|
9257 | /**
|
9258 | * Multiple expressions separated by a semicolon.
|
9259 | */
|
9260 | class Chain extends AST {
|
9261 | constructor(span, sourceSpan, expressions) {
|
9262 | super(span, sourceSpan);
|
9263 | this.expressions = expressions;
|
9264 | }
|
9265 | visit(visitor, context = null) {
|
9266 | return visitor.visitChain(this, context);
|
9267 | }
|
9268 | }
|
9269 | class Conditional extends AST {
|
9270 | constructor(span, sourceSpan, condition, trueExp, falseExp) {
|
9271 | super(span, sourceSpan);
|
9272 | this.condition = condition;
|
9273 | this.trueExp = trueExp;
|
9274 | this.falseExp = falseExp;
|
9275 | }
|
9276 | visit(visitor, context = null) {
|
9277 | return visitor.visitConditional(this, context);
|
9278 | }
|
9279 | }
|
9280 | class PropertyRead extends ASTWithName {
|
9281 | constructor(span, sourceSpan, nameSpan, receiver, name) {
|
9282 | super(span, sourceSpan, nameSpan);
|
9283 | this.receiver = receiver;
|
9284 | this.name = name;
|
9285 | }
|
9286 | visit(visitor, context = null) {
|
9287 | return visitor.visitPropertyRead(this, context);
|
9288 | }
|
9289 | }
|
9290 | class PropertyWrite extends ASTWithName {
|
9291 | constructor(span, sourceSpan, nameSpan, receiver, name, value) {
|
9292 | super(span, sourceSpan, nameSpan);
|
9293 | this.receiver = receiver;
|
9294 | this.name = name;
|
9295 | this.value = value;
|
9296 | }
|
9297 | visit(visitor, context = null) {
|
9298 | return visitor.visitPropertyWrite(this, context);
|
9299 | }
|
9300 | }
|
9301 | class SafePropertyRead extends ASTWithName {
|
9302 | constructor(span, sourceSpan, nameSpan, receiver, name) {
|
9303 | super(span, sourceSpan, nameSpan);
|
9304 | this.receiver = receiver;
|
9305 | this.name = name;
|
9306 | }
|
9307 | visit(visitor, context = null) {
|
9308 | return visitor.visitSafePropertyRead(this, context);
|
9309 | }
|
9310 | }
|
9311 | class KeyedRead extends AST {
|
9312 | constructor(span, sourceSpan, obj, key) {
|
9313 | super(span, sourceSpan);
|
9314 | this.obj = obj;
|
9315 | this.key = key;
|
9316 | }
|
9317 | visit(visitor, context = null) {
|
9318 | return visitor.visitKeyedRead(this, context);
|
9319 | }
|
9320 | }
|
9321 | class KeyedWrite extends AST {
|
9322 | constructor(span, sourceSpan, obj, key, value) {
|
9323 | super(span, sourceSpan);
|
9324 | this.obj = obj;
|
9325 | this.key = key;
|
9326 | this.value = value;
|
9327 | }
|
9328 | visit(visitor, context = null) {
|
9329 | return visitor.visitKeyedWrite(this, context);
|
9330 | }
|
9331 | }
|
9332 | class BindingPipe extends ASTWithName {
|
9333 | constructor(span, sourceSpan, exp, name, args, nameSpan) {
|
9334 | super(span, sourceSpan, nameSpan);
|
9335 | this.exp = exp;
|
9336 | this.name = name;
|
9337 | this.args = args;
|
9338 | }
|
9339 | visit(visitor, context = null) {
|
9340 | return visitor.visitPipe(this, context);
|
9341 | }
|
9342 | }
|
9343 | class LiteralPrimitive extends AST {
|
9344 | constructor(span, sourceSpan, value) {
|
9345 | super(span, sourceSpan);
|
9346 | this.value = value;
|
9347 | }
|
9348 | visit(visitor, context = null) {
|
9349 | return visitor.visitLiteralPrimitive(this, context);
|
9350 | }
|
9351 | }
|
9352 | class LiteralArray extends AST {
|
9353 | constructor(span, sourceSpan, expressions) {
|
9354 | super(span, sourceSpan);
|
9355 | this.expressions = expressions;
|
9356 | }
|
9357 | visit(visitor, context = null) {
|
9358 | return visitor.visitLiteralArray(this, context);
|
9359 | }
|
9360 | }
|
9361 | class LiteralMap extends AST {
|
9362 | constructor(span, sourceSpan, keys, values) {
|
9363 | super(span, sourceSpan);
|
9364 | this.keys = keys;
|
9365 | this.values = values;
|
9366 | }
|
9367 | visit(visitor, context = null) {
|
9368 | return visitor.visitLiteralMap(this, context);
|
9369 | }
|
9370 | }
|
9371 | class Interpolation extends AST {
|
9372 | constructor(span, sourceSpan, strings, expressions) {
|
9373 | super(span, sourceSpan);
|
9374 | this.strings = strings;
|
9375 | this.expressions = expressions;
|
9376 | }
|
9377 | visit(visitor, context = null) {
|
9378 | return visitor.visitInterpolation(this, context);
|
9379 | }
|
9380 | }
|
9381 | class Binary extends AST {
|
9382 | constructor(span, sourceSpan, operation, left, right) {
|
9383 | super(span, sourceSpan);
|
9384 | this.operation = operation;
|
9385 | this.left = left;
|
9386 | this.right = right;
|
9387 | }
|
9388 | visit(visitor, context = null) {
|
9389 | return visitor.visitBinary(this, context);
|
9390 | }
|
9391 | }
|
9392 | /**
|
9393 | * For backwards compatibility reasons, `Unary` inherits from `Binary` and mimics the binary AST
|
9394 | * node that was originally used. This inheritance relation can be deleted in some future major,
|
9395 | * after consumers have been given a chance to fully support Unary.
|
9396 | */
|
9397 | class Unary extends Binary {
|
9398 | /**
|
9399 | * During the deprecation period this constructor is private, to avoid consumers from creating
|
9400 | * a `Unary` with the fallback properties for `Binary`.
|
9401 | */
|
9402 | constructor(span, sourceSpan, operator, expr, binaryOp, binaryLeft, binaryRight) {
|
9403 | super(span, sourceSpan, binaryOp, binaryLeft, binaryRight);
|
9404 | this.operator = operator;
|
9405 | this.expr = expr;
|
9406 | }
|
9407 | /**
|
9408 | * Creates a unary minus expression "-x", represented as `Binary` using "0 - x".
|
9409 | */
|
9410 | static createMinus(span, sourceSpan, expr) {
|
9411 | return new Unary(span, sourceSpan, '-', expr, '-', new LiteralPrimitive(span, sourceSpan, 0), expr);
|
9412 | }
|
9413 | /**
|
9414 | * Creates a unary plus expression "+x", represented as `Binary` using "x - 0".
|
9415 | */
|
9416 | static createPlus(span, sourceSpan, expr) {
|
9417 | return new Unary(span, sourceSpan, '+', expr, '-', expr, new LiteralPrimitive(span, sourceSpan, 0));
|
9418 | }
|
9419 | visit(visitor, context = null) {
|
9420 | if (visitor.visitUnary !== undefined) {
|
9421 | return visitor.visitUnary(this, context);
|
9422 | }
|
9423 | return visitor.visitBinary(this, context);
|
9424 | }
|
9425 | }
|
9426 | class PrefixNot extends AST {
|
9427 | constructor(span, sourceSpan, expression) {
|
9428 | super(span, sourceSpan);
|
9429 | this.expression = expression;
|
9430 | }
|
9431 | visit(visitor, context = null) {
|
9432 | return visitor.visitPrefixNot(this, context);
|
9433 | }
|
9434 | }
|
9435 | class NonNullAssert extends AST {
|
9436 | constructor(span, sourceSpan, expression) {
|
9437 | super(span, sourceSpan);
|
9438 | this.expression = expression;
|
9439 | }
|
9440 | visit(visitor, context = null) {
|
9441 | return visitor.visitNonNullAssert(this, context);
|
9442 | }
|
9443 | }
|
9444 | class MethodCall extends ASTWithName {
|
9445 | constructor(span, sourceSpan, nameSpan, receiver, name, args) {
|
9446 | super(span, sourceSpan, nameSpan);
|
9447 | this.receiver = receiver;
|
9448 | this.name = name;
|
9449 | this.args = args;
|
9450 | }
|
9451 | visit(visitor, context = null) {
|
9452 | return visitor.visitMethodCall(this, context);
|
9453 | }
|
9454 | }
|
9455 | class SafeMethodCall extends ASTWithName {
|
9456 | constructor(span, sourceSpan, nameSpan, receiver, name, args) {
|
9457 | super(span, sourceSpan, nameSpan);
|
9458 | this.receiver = receiver;
|
9459 | this.name = name;
|
9460 | this.args = args;
|
9461 | }
|
9462 | visit(visitor, context = null) {
|
9463 | return visitor.visitSafeMethodCall(this, context);
|
9464 | }
|
9465 | }
|
9466 | class FunctionCall extends AST {
|
9467 | constructor(span, sourceSpan, target, args) {
|
9468 | super(span, sourceSpan);
|
9469 | this.target = target;
|
9470 | this.args = args;
|
9471 | }
|
9472 | visit(visitor, context = null) {
|
9473 | return visitor.visitFunctionCall(this, context);
|
9474 | }
|
9475 | }
|
9476 | /**
|
9477 | * Records the absolute position of a text span in a source file, where `start` and `end` are the
|
9478 | * starting and ending byte offsets, respectively, of the text span in a source file.
|
9479 | */
|
9480 | class AbsoluteSourceSpan {
|
9481 | constructor(start, end) {
|
9482 | this.start = start;
|
9483 | this.end = end;
|
9484 | }
|
9485 | }
|
9486 | class ASTWithSource extends AST {
|
9487 | constructor(ast, source, location, absoluteOffset, errors) {
|
9488 | super(new ParseSpan(0, source === null ? 0 : source.length), new AbsoluteSourceSpan(absoluteOffset, source === null ? absoluteOffset : absoluteOffset + source.length));
|
9489 | this.ast = ast;
|
9490 | this.source = source;
|
9491 | this.location = location;
|
9492 | this.errors = errors;
|
9493 | }
|
9494 | visit(visitor, context = null) {
|
9495 | if (visitor.visitASTWithSource) {
|
9496 | return visitor.visitASTWithSource(this, context);
|
9497 | }
|
9498 | return this.ast.visit(visitor, context);
|
9499 | }
|
9500 | toString() {
|
9501 | return `${this.source} in ${this.location}`;
|
9502 | }
|
9503 | }
|
9504 | class VariableBinding {
|
9505 | /**
|
9506 | * @param sourceSpan entire span of the binding.
|
9507 | * @param key name of the LHS along with its span.
|
9508 | * @param value optional value for the RHS along with its span.
|
9509 | */
|
9510 | constructor(sourceSpan, key, value) {
|
9511 | this.sourceSpan = sourceSpan;
|
9512 | this.key = key;
|
9513 | this.value = value;
|
9514 | }
|
9515 | }
|
9516 | class ExpressionBinding {
|
9517 | /**
|
9518 | * @param sourceSpan entire span of the binding.
|
9519 | * @param key binding name, like ngForOf, ngForTrackBy, ngIf, along with its
|
9520 | * span. Note that the length of the span may not be the same as
|
9521 | * `key.source.length`. For example,
|
9522 | * 1. key.source = ngFor, key.span is for "ngFor"
|
9523 | * 2. key.source = ngForOf, key.span is for "of"
|
9524 | * 3. key.source = ngForTrackBy, key.span is for "trackBy"
|
9525 | * @param value optional expression for the RHS.
|
9526 | */
|
9527 | constructor(sourceSpan, key, value) {
|
9528 | this.sourceSpan = sourceSpan;
|
9529 | this.key = key;
|
9530 | this.value = value;
|
9531 | }
|
9532 | }
|
9533 | class RecursiveAstVisitor {
|
9534 | visit(ast, context) {
|
9535 | // The default implementation just visits every node.
|
9536 | // Classes that extend RecursiveAstVisitor should override this function
|
9537 | // to selectively visit the specified node.
|
9538 | ast.visit(this, context);
|
9539 | }
|
9540 | visitUnary(ast, context) {
|
9541 | this.visit(ast.expr, context);
|
9542 | }
|
9543 | visitBinary(ast, context) {
|
9544 | this.visit(ast.left, context);
|
9545 | this.visit(ast.right, context);
|
9546 | }
|
9547 | visitChain(ast, context) {
|
9548 | this.visitAll(ast.expressions, context);
|
9549 | }
|
9550 | visitConditional(ast, context) {
|
9551 | this.visit(ast.condition, context);
|
9552 | this.visit(ast.trueExp, context);
|
9553 | this.visit(ast.falseExp, context);
|
9554 | }
|
9555 | visitPipe(ast, context) {
|
9556 | this.visit(ast.exp, context);
|
9557 | this.visitAll(ast.args, context);
|
9558 | }
|
9559 | visitFunctionCall(ast, context) {
|
9560 | if (ast.target) {
|
9561 | this.visit(ast.target, context);
|
9562 | }
|
9563 | this.visitAll(ast.args, context);
|
9564 | }
|
9565 | visitImplicitReceiver(ast, context) { }
|
9566 | visitThisReceiver(ast, context) { }
|
9567 | visitInterpolation(ast, context) {
|
9568 | this.visitAll(ast.expressions, context);
|
9569 | }
|
9570 | visitKeyedRead(ast, context) {
|
9571 | this.visit(ast.obj, context);
|
9572 | this.visit(ast.key, context);
|
9573 | }
|
9574 | visitKeyedWrite(ast, context) {
|
9575 | this.visit(ast.obj, context);
|
9576 | this.visit(ast.key, context);
|
9577 | this.visit(ast.value, context);
|
9578 | }
|
9579 | visitLiteralArray(ast, context) {
|
9580 | this.visitAll(ast.expressions, context);
|
9581 | }
|
9582 | visitLiteralMap(ast, context) {
|
9583 | this.visitAll(ast.values, context);
|
9584 | }
|
9585 | visitLiteralPrimitive(ast, context) { }
|
9586 | visitMethodCall(ast, context) {
|
9587 | this.visit(ast.receiver, context);
|
9588 | this.visitAll(ast.args, context);
|
9589 | }
|
9590 | visitPrefixNot(ast, context) {
|
9591 | this.visit(ast.expression, context);
|
9592 | }
|
9593 | visitNonNullAssert(ast, context) {
|
9594 | this.visit(ast.expression, context);
|
9595 | }
|
9596 | visitPropertyRead(ast, context) {
|
9597 | this.visit(ast.receiver, context);
|
9598 | }
|
9599 | visitPropertyWrite(ast, context) {
|
9600 | this.visit(ast.receiver, context);
|
9601 | this.visit(ast.value, context);
|
9602 | }
|
9603 | visitSafePropertyRead(ast, context) {
|
9604 | this.visit(ast.receiver, context);
|
9605 | }
|
9606 | visitSafeMethodCall(ast, context) {
|
9607 | this.visit(ast.receiver, context);
|
9608 | this.visitAll(ast.args, context);
|
9609 | }
|
9610 | visitQuote(ast, context) { }
|
9611 | // This is not part of the AstVisitor interface, just a helper method
|
9612 | visitAll(asts, context) {
|
9613 | for (const ast of asts) {
|
9614 | this.visit(ast, context);
|
9615 | }
|
9616 | }
|
9617 | }
|
9618 | class AstTransformer {
|
9619 | visitImplicitReceiver(ast, context) {
|
9620 | return ast;
|
9621 | }
|
9622 | visitThisReceiver(ast, context) {
|
9623 | return ast;
|
9624 | }
|
9625 | visitInterpolation(ast, context) {
|
9626 | return new Interpolation(ast.span, ast.sourceSpan, ast.strings, this.visitAll(ast.expressions));
|
9627 | }
|
9628 | visitLiteralPrimitive(ast, context) {
|
9629 | return new LiteralPrimitive(ast.span, ast.sourceSpan, ast.value);
|
9630 | }
|
9631 | visitPropertyRead(ast, context) {
|
9632 | return new PropertyRead(ast.span, ast.sourceSpan, ast.nameSpan, ast.receiver.visit(this), ast.name);
|
9633 | }
|
9634 | visitPropertyWrite(ast, context) {
|
9635 | return new PropertyWrite(ast.span, ast.sourceSpan, ast.nameSpan, ast.receiver.visit(this), ast.name, ast.value.visit(this));
|
9636 | }
|
9637 | visitSafePropertyRead(ast, context) {
|
9638 | return new SafePropertyRead(ast.span, ast.sourceSpan, ast.nameSpan, ast.receiver.visit(this), ast.name);
|
9639 | }
|
9640 | visitMethodCall(ast, context) {
|
9641 | return new MethodCall(ast.span, ast.sourceSpan, ast.nameSpan, ast.receiver.visit(this), ast.name, this.visitAll(ast.args));
|
9642 | }
|
9643 | visitSafeMethodCall(ast, context) {
|
9644 | return new SafeMethodCall(ast.span, ast.sourceSpan, ast.nameSpan, ast.receiver.visit(this), ast.name, this.visitAll(ast.args));
|
9645 | }
|
9646 | visitFunctionCall(ast, context) {
|
9647 | return new FunctionCall(ast.span, ast.sourceSpan, ast.target.visit(this), this.visitAll(ast.args));
|
9648 | }
|
9649 | visitLiteralArray(ast, context) {
|
9650 | return new LiteralArray(ast.span, ast.sourceSpan, this.visitAll(ast.expressions));
|
9651 | }
|
9652 | visitLiteralMap(ast, context) {
|
9653 | return new LiteralMap(ast.span, ast.sourceSpan, ast.keys, this.visitAll(ast.values));
|
9654 | }
|
9655 | visitUnary(ast, context) {
|
9656 | switch (ast.operator) {
|
9657 | case '+':
|
9658 | return Unary.createPlus(ast.span, ast.sourceSpan, ast.expr.visit(this));
|
9659 | case '-':
|
9660 | return Unary.createMinus(ast.span, ast.sourceSpan, ast.expr.visit(this));
|
9661 | default:
|
9662 | throw new Error(`Unknown unary operator ${ast.operator}`);
|
9663 | }
|
9664 | }
|
9665 | visitBinary(ast, context) {
|
9666 | return new Binary(ast.span, ast.sourceSpan, ast.operation, ast.left.visit(this), ast.right.visit(this));
|
9667 | }
|
9668 | visitPrefixNot(ast, context) {
|
9669 | return new PrefixNot(ast.span, ast.sourceSpan, ast.expression.visit(this));
|
9670 | }
|
9671 | visitNonNullAssert(ast, context) {
|
9672 | return new NonNullAssert(ast.span, ast.sourceSpan, ast.expression.visit(this));
|
9673 | }
|
9674 | visitConditional(ast, context) {
|
9675 | return new Conditional(ast.span, ast.sourceSpan, ast.condition.visit(this), ast.trueExp.visit(this), ast.falseExp.visit(this));
|
9676 | }
|
9677 | visitPipe(ast, context) {
|
9678 | return new BindingPipe(ast.span, ast.sourceSpan, ast.exp.visit(this), ast.name, this.visitAll(ast.args), ast.nameSpan);
|
9679 | }
|
9680 | visitKeyedRead(ast, context) {
|
9681 | return new KeyedRead(ast.span, ast.sourceSpan, ast.obj.visit(this), ast.key.visit(this));
|
9682 | }
|
9683 | visitKeyedWrite(ast, context) {
|
9684 | return new KeyedWrite(ast.span, ast.sourceSpan, ast.obj.visit(this), ast.key.visit(this), ast.value.visit(this));
|
9685 | }
|
9686 | visitAll(asts) {
|
9687 | const res = [];
|
9688 | for (let i = 0; i < asts.length; ++i) {
|
9689 | res[i] = asts[i].visit(this);
|
9690 | }
|
9691 | return res;
|
9692 | }
|
9693 | visitChain(ast, context) {
|
9694 | return new Chain(ast.span, ast.sourceSpan, this.visitAll(ast.expressions));
|
9695 | }
|
9696 | visitQuote(ast, context) {
|
9697 | return new Quote(ast.span, ast.sourceSpan, ast.prefix, ast.uninterpretedExpression, ast.location);
|
9698 | }
|
9699 | }
|
9700 | // A transformer that only creates new nodes if the transformer makes a change or
|
9701 | // a change is made a child node.
|
9702 | class AstMemoryEfficientTransformer {
|
9703 | visitImplicitReceiver(ast, context) {
|
9704 | return ast;
|
9705 | }
|
9706 | visitThisReceiver(ast, context) {
|
9707 | return ast;
|
9708 | }
|
9709 | visitInterpolation(ast, context) {
|
9710 | const expressions = this.visitAll(ast.expressions);
|
9711 | if (expressions !== ast.expressions)
|
9712 | return new Interpolation(ast.span, ast.sourceSpan, ast.strings, expressions);
|
9713 | return ast;
|
9714 | }
|
9715 | visitLiteralPrimitive(ast, context) {
|
9716 | return ast;
|
9717 | }
|
9718 | visitPropertyRead(ast, context) {
|
9719 | const receiver = ast.receiver.visit(this);
|
9720 | if (receiver !== ast.receiver) {
|
9721 | return new PropertyRead(ast.span, ast.sourceSpan, ast.nameSpan, receiver, ast.name);
|
9722 | }
|
9723 | return ast;
|
9724 | }
|
9725 | visitPropertyWrite(ast, context) {
|
9726 | const receiver = ast.receiver.visit(this);
|
9727 | const value = ast.value.visit(this);
|
9728 | if (receiver !== ast.receiver || value !== ast.value) {
|
9729 | return new PropertyWrite(ast.span, ast.sourceSpan, ast.nameSpan, receiver, ast.name, value);
|
9730 | }
|
9731 | return ast;
|
9732 | }
|
9733 | visitSafePropertyRead(ast, context) {
|
9734 | const receiver = ast.receiver.visit(this);
|
9735 | if (receiver !== ast.receiver) {
|
9736 | return new SafePropertyRead(ast.span, ast.sourceSpan, ast.nameSpan, receiver, ast.name);
|
9737 | }
|
9738 | return ast;
|
9739 | }
|
9740 | visitMethodCall(ast, context) {
|
9741 | const receiver = ast.receiver.visit(this);
|
9742 | const args = this.visitAll(ast.args);
|
9743 | if (receiver !== ast.receiver || args !== ast.args) {
|
9744 | return new MethodCall(ast.span, ast.sourceSpan, ast.nameSpan, receiver, ast.name, args);
|
9745 | }
|
9746 | return ast;
|
9747 | }
|
9748 | visitSafeMethodCall(ast, context) {
|
9749 | const receiver = ast.receiver.visit(this);
|
9750 | const args = this.visitAll(ast.args);
|
9751 | if (receiver !== ast.receiver || args !== ast.args) {
|
9752 | return new SafeMethodCall(ast.span, ast.sourceSpan, ast.nameSpan, receiver, ast.name, args);
|
9753 | }
|
9754 | return ast;
|
9755 | }
|
9756 | visitFunctionCall(ast, context) {
|
9757 | const target = ast.target && ast.target.visit(this);
|
9758 | const args = this.visitAll(ast.args);
|
9759 | if (target !== ast.target || args !== ast.args) {
|
9760 | return new FunctionCall(ast.span, ast.sourceSpan, target, args);
|
9761 | }
|
9762 | return ast;
|
9763 | }
|
9764 | visitLiteralArray(ast, context) {
|
9765 | const expressions = this.visitAll(ast.expressions);
|
9766 | if (expressions !== ast.expressions) {
|
9767 | return new LiteralArray(ast.span, ast.sourceSpan, expressions);
|
9768 | }
|
9769 | return ast;
|
9770 | }
|
9771 | visitLiteralMap(ast, context) {
|
9772 | const values = this.visitAll(ast.values);
|
9773 | if (values !== ast.values) {
|
9774 | return new LiteralMap(ast.span, ast.sourceSpan, ast.keys, values);
|
9775 | }
|
9776 | return ast;
|
9777 | }
|
9778 | visitUnary(ast, context) {
|
9779 | const expr = ast.expr.visit(this);
|
9780 | if (expr !== ast.expr) {
|
9781 | switch (ast.operator) {
|
9782 | case '+':
|
9783 | return Unary.createPlus(ast.span, ast.sourceSpan, expr);
|
9784 | case '-':
|
9785 | return Unary.createMinus(ast.span, ast.sourceSpan, expr);
|
9786 | default:
|
9787 | throw new Error(`Unknown unary operator ${ast.operator}`);
|
9788 | }
|
9789 | }
|
9790 | return ast;
|
9791 | }
|
9792 | visitBinary(ast, context) {
|
9793 | const left = ast.left.visit(this);
|
9794 | const right = ast.right.visit(this);
|
9795 | if (left !== ast.left || right !== ast.right) {
|
9796 | return new Binary(ast.span, ast.sourceSpan, ast.operation, left, right);
|
9797 | }
|
9798 | return ast;
|
9799 | }
|
9800 | visitPrefixNot(ast, context) {
|
9801 | const expression = ast.expression.visit(this);
|
9802 | if (expression !== ast.expression) {
|
9803 | return new PrefixNot(ast.span, ast.sourceSpan, expression);
|
9804 | }
|
9805 | return ast;
|
9806 | }
|
9807 | visitNonNullAssert(ast, context) {
|
9808 | const expression = ast.expression.visit(this);
|
9809 | if (expression !== ast.expression) {
|
9810 | return new NonNullAssert(ast.span, ast.sourceSpan, expression);
|
9811 | }
|
9812 | return ast;
|
9813 | }
|
9814 | visitConditional(ast, context) {
|
9815 | const condition = ast.condition.visit(this);
|
9816 | const trueExp = ast.trueExp.visit(this);
|
9817 | const falseExp = ast.falseExp.visit(this);
|
9818 | if (condition !== ast.condition || trueExp !== ast.trueExp || falseExp !== ast.falseExp) {
|
9819 | return new Conditional(ast.span, ast.sourceSpan, condition, trueExp, falseExp);
|
9820 | }
|
9821 | return ast;
|
9822 | }
|
9823 | visitPipe(ast, context) {
|
9824 | const exp = ast.exp.visit(this);
|
9825 | const args = this.visitAll(ast.args);
|
9826 | if (exp !== ast.exp || args !== ast.args) {
|
9827 | return new BindingPipe(ast.span, ast.sourceSpan, exp, ast.name, args, ast.nameSpan);
|
9828 | }
|
9829 | return ast;
|
9830 | }
|
9831 | visitKeyedRead(ast, context) {
|
9832 | const obj = ast.obj.visit(this);
|
9833 | const key = ast.key.visit(this);
|
9834 | if (obj !== ast.obj || key !== ast.key) {
|
9835 | return new KeyedRead(ast.span, ast.sourceSpan, obj, key);
|
9836 | }
|
9837 | return ast;
|
9838 | }
|
9839 | visitKeyedWrite(ast, context) {
|
9840 | const obj = ast.obj.visit(this);
|
9841 | const key = ast.key.visit(this);
|
9842 | const value = ast.value.visit(this);
|
9843 | if (obj !== ast.obj || key !== ast.key || value !== ast.value) {
|
9844 | return new KeyedWrite(ast.span, ast.sourceSpan, obj, key, value);
|
9845 | }
|
9846 | return ast;
|
9847 | }
|
9848 | visitAll(asts) {
|
9849 | const res = [];
|
9850 | let modified = false;
|
9851 | for (let i = 0; i < asts.length; ++i) {
|
9852 | const original = asts[i];
|
9853 | const value = original.visit(this);
|
9854 | res[i] = value;
|
9855 | modified = modified || value !== original;
|
9856 | }
|
9857 | return modified ? res : asts;
|
9858 | }
|
9859 | visitChain(ast, context) {
|
9860 | const expressions = this.visitAll(ast.expressions);
|
9861 | if (expressions !== ast.expressions) {
|
9862 | return new Chain(ast.span, ast.sourceSpan, expressions);
|
9863 | }
|
9864 | return ast;
|
9865 | }
|
9866 | visitQuote(ast, context) {
|
9867 | return ast;
|
9868 | }
|
9869 | }
|
9870 | // Bindings
|
9871 | class ParsedProperty {
|
9872 | constructor(name, expression, type,
|
9873 | // TODO(FW-2095): `keySpan` should really be required but allows `undefined` so VE does
|
9874 | // not need to be updated. Make `keySpan` required when VE is removed.
|
9875 | sourceSpan, keySpan, valueSpan) {
|
9876 | this.name = name;
|
9877 | this.expression = expression;
|
9878 | this.type = type;
|
9879 | this.sourceSpan = sourceSpan;
|
9880 | this.keySpan = keySpan;
|
9881 | this.valueSpan = valueSpan;
|
9882 | this.isLiteral = this.type === ParsedPropertyType.LITERAL_ATTR;
|
9883 | this.isAnimation = this.type === ParsedPropertyType.ANIMATION;
|
9884 | }
|
9885 | }
|
9886 | var ParsedPropertyType;
|
9887 | (function (ParsedPropertyType) {
|
9888 | ParsedPropertyType[ParsedPropertyType["DEFAULT"] = 0] = "DEFAULT";
|
9889 | ParsedPropertyType[ParsedPropertyType["LITERAL_ATTR"] = 1] = "LITERAL_ATTR";
|
9890 | ParsedPropertyType[ParsedPropertyType["ANIMATION"] = 2] = "ANIMATION";
|
9891 | })(ParsedPropertyType || (ParsedPropertyType = {}));
|
9892 | class ParsedEvent {
|
9893 | // Regular events have a target
|
9894 | // Animation events have a phase
|
9895 | constructor(name, targetOrPhase, type, handler, sourceSpan,
|
9896 | // TODO(FW-2095): keySpan should be required but was made optional to avoid changing VE
|
9897 | handlerSpan, keySpan) {
|
9898 | this.name = name;
|
9899 | this.targetOrPhase = targetOrPhase;
|
9900 | this.type = type;
|
9901 | this.handler = handler;
|
9902 | this.sourceSpan = sourceSpan;
|
9903 | this.handlerSpan = handlerSpan;
|
9904 | this.keySpan = keySpan;
|
9905 | }
|
9906 | }
|
9907 | /**
|
9908 | * ParsedVariable represents a variable declaration in a microsyntax expression.
|
9909 | */
|
9910 | class ParsedVariable {
|
9911 | constructor(name, value, sourceSpan, keySpan, valueSpan) {
|
9912 | this.name = name;
|
9913 | this.value = value;
|
9914 | this.sourceSpan = sourceSpan;
|
9915 | this.keySpan = keySpan;
|
9916 | this.valueSpan = valueSpan;
|
9917 | }
|
9918 | }
|
9919 | class BoundElementProperty {
|
9920 | constructor(name, type, securityContext, value, unit, sourceSpan, keySpan, valueSpan) {
|
9921 | this.name = name;
|
9922 | this.type = type;
|
9923 | this.securityContext = securityContext;
|
9924 | this.value = value;
|
9925 | this.unit = unit;
|
9926 | this.sourceSpan = sourceSpan;
|
9927 | this.keySpan = keySpan;
|
9928 | this.valueSpan = valueSpan;
|
9929 | }
|
9930 | }
|
9931 |
|
9932 | /**
|
9933 | * @license
|
9934 | * Copyright Google LLC All Rights Reserved.
|
9935 | *
|
9936 | * Use of this source code is governed by an MIT-style license that can be
|
9937 | * found in the LICENSE file at https://angular.io/license
|
9938 | */
|
9939 | class EventHandlerVars {
|
9940 | }
|
9941 | EventHandlerVars.event = variable('$event');
|
9942 | class ConvertActionBindingResult {
|
9943 | constructor(
|
9944 | /**
|
9945 | * Render2 compatible statements,
|
9946 | */
|
9947 | stmts,
|
9948 | /**
|
9949 | * Variable name used with render2 compatible statements.
|
9950 | */
|
9951 | allowDefault) {
|
9952 | this.stmts = stmts;
|
9953 | this.allowDefault = allowDefault;
|
9954 | /**
|
9955 | * This is bit of a hack. It converts statements which render2 expects to statements which are
|
9956 | * expected by render3.
|
9957 | *
|
9958 | * Example: `<div click="doSomething($event)">` will generate:
|
9959 | *
|
9960 | * Render3:
|
9961 | * ```
|
9962 | * const pd_b:any = ((<any>ctx.doSomething($event)) !== false);
|
9963 | * return pd_b;
|
9964 | * ```
|
9965 | *
|
9966 | * but render2 expects:
|
9967 | * ```
|
9968 | * return ctx.doSomething($event);
|
9969 | * ```
|
9970 | */
|
9971 | // TODO(misko): remove this hack once we no longer support ViewEngine.
|
9972 | this.render3Stmts = stmts.map((statement) => {
|
9973 | if (statement instanceof DeclareVarStmt && statement.name == allowDefault.name &&
|
9974 | statement.value instanceof BinaryOperatorExpr) {
|
9975 | const lhs = statement.value.lhs;
|
9976 | return new ReturnStatement(lhs.value);
|
9977 | }
|
9978 | return statement;
|
9979 | });
|
9980 | }
|
9981 | }
|
9982 | /**
|
9983 | * Converts the given expression AST into an executable output AST, assuming the expression is
|
9984 | * used in an action binding (e.g. an event handler).
|
9985 | */
|
9986 | function convertActionBinding(localResolver, implicitReceiver, action, bindingId, interpolationFunction, baseSourceSpan, implicitReceiverAccesses, globals) {
|
9987 | if (!localResolver) {
|
9988 | localResolver = new DefaultLocalResolver(globals);
|
9989 | }
|
9990 | const actionWithoutBuiltins = convertPropertyBindingBuiltins({
|
9991 | createLiteralArrayConverter: (argCount) => {
|
9992 | // Note: no caching for literal arrays in actions.
|
9993 | return (args) => literalArr(args);
|
9994 | },
|
9995 | createLiteralMapConverter: (keys) => {
|
9996 | // Note: no caching for literal maps in actions.
|
9997 | return (values) => {
|
9998 | const entries = keys.map((k, i) => ({
|
9999 | key: k.key,
|
10000 | value: values[i],
|
10001 | quoted: k.quoted,
|
10002 | }));
|
10003 | return literalMap(entries);
|
10004 | };
|
10005 | },
|
10006 | createPipeConverter: (name) => {
|
10007 | throw new Error(`Illegal State: Actions are not allowed to contain pipes. Pipe: ${name}`);
|
10008 | }
|
10009 | }, action);
|
10010 | const visitor = new _AstToIrVisitor(localResolver, implicitReceiver, bindingId, interpolationFunction, baseSourceSpan, implicitReceiverAccesses);
|
10011 | const actionStmts = [];
|
10012 | flattenStatements(actionWithoutBuiltins.visit(visitor, _Mode.Statement), actionStmts);
|
10013 | prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts);
|
10014 | if (visitor.usesImplicitReceiver) {
|
10015 | localResolver.notifyImplicitReceiverUse();
|
10016 | }
|
10017 | const lastIndex = actionStmts.length - 1;
|
10018 | let preventDefaultVar = null;
|
10019 | if (lastIndex >= 0) {
|
10020 | const lastStatement = actionStmts[lastIndex];
|
10021 | const returnExpr = convertStmtIntoExpression(lastStatement);
|
10022 | if (returnExpr) {
|
10023 | // Note: We need to cast the result of the method call to dynamic,
|
10024 | // as it might be a void method!
|
10025 | preventDefaultVar = createPreventDefaultVar(bindingId);
|
10026 | actionStmts[lastIndex] =
|
10027 | preventDefaultVar.set(returnExpr.cast(DYNAMIC_TYPE).notIdentical(literal(false)))
|
10028 | .toDeclStmt(null, [StmtModifier.Final]);
|
10029 | }
|
10030 | }
|
10031 | return new ConvertActionBindingResult(actionStmts, preventDefaultVar);
|
10032 | }
|
10033 | function convertPropertyBindingBuiltins(converterFactory, ast) {
|
10034 | return convertBuiltins(converterFactory, ast);
|
10035 | }
|
10036 | class ConvertPropertyBindingResult {
|
10037 | constructor(stmts, currValExpr) {
|
10038 | this.stmts = stmts;
|
10039 | this.currValExpr = currValExpr;
|
10040 | }
|
10041 | }
|
10042 | var BindingForm;
|
10043 | (function (BindingForm) {
|
10044 | // The general form of binding expression, supports all expressions.
|
10045 | BindingForm[BindingForm["General"] = 0] = "General";
|
10046 | // Try to generate a simple binding (no temporaries or statements)
|
10047 | // otherwise generate a general binding
|
10048 | BindingForm[BindingForm["TrySimple"] = 1] = "TrySimple";
|
10049 | // Inlines assignment of temporaries into the generated expression. The result may still
|
10050 | // have statements attached for declarations of temporary variables.
|
10051 | // This is the only relevant form for Ivy, the other forms are only used in ViewEngine.
|
10052 | BindingForm[BindingForm["Expression"] = 2] = "Expression";
|
10053 | })(BindingForm || (BindingForm = {}));
|
10054 | /**
|
10055 | * Converts the given expression AST into an executable output AST, assuming the expression
|
10056 | * is used in property binding. The expression has to be preprocessed via
|
10057 | * `convertPropertyBindingBuiltins`.
|
10058 | */
|
10059 | function convertPropertyBinding(localResolver, implicitReceiver, expressionWithoutBuiltins, bindingId, form, interpolationFunction) {
|
10060 | if (!localResolver) {
|
10061 | localResolver = new DefaultLocalResolver();
|
10062 | }
|
10063 | const visitor = new _AstToIrVisitor(localResolver, implicitReceiver, bindingId, interpolationFunction);
|
10064 | const outputExpr = expressionWithoutBuiltins.visit(visitor, _Mode.Expression);
|
10065 | const stmts = getStatementsFromVisitor(visitor, bindingId);
|
10066 | if (visitor.usesImplicitReceiver) {
|
10067 | localResolver.notifyImplicitReceiverUse();
|
10068 | }
|
10069 | if (visitor.temporaryCount === 0 && form == BindingForm.TrySimple) {
|
10070 | return new ConvertPropertyBindingResult([], outputExpr);
|
10071 | }
|
10072 | else if (form === BindingForm.Expression) {
|
10073 | return new ConvertPropertyBindingResult(stmts, outputExpr);
|
10074 | }
|
10075 | const currValExpr = createCurrValueExpr(bindingId);
|
10076 | stmts.push(currValExpr.set(outputExpr).toDeclStmt(DYNAMIC_TYPE, [StmtModifier.Final]));
|
10077 | return new ConvertPropertyBindingResult(stmts, currValExpr);
|
10078 | }
|
10079 | /**
|
10080 | * Given some expression, such as a binding or interpolation expression, and a context expression to
|
10081 | * look values up on, visit each facet of the given expression resolving values from the context
|
10082 | * expression such that a list of arguments can be derived from the found values that can be used as
|
10083 | * arguments to an external update instruction.
|
10084 | *
|
10085 | * @param localResolver The resolver to use to look up expressions by name appropriately
|
10086 | * @param contextVariableExpression The expression representing the context variable used to create
|
10087 | * the final argument expressions
|
10088 | * @param expressionWithArgumentsToExtract The expression to visit to figure out what values need to
|
10089 | * be resolved and what arguments list to build.
|
10090 | * @param bindingId A name prefix used to create temporary variable names if they're needed for the
|
10091 | * arguments generated
|
10092 | * @returns An array of expressions that can be passed as arguments to instruction expressions like
|
10093 | * `o.importExpr(R3.propertyInterpolate).callFn(result)`
|
10094 | */
|
10095 | function convertUpdateArguments(localResolver, contextVariableExpression, expressionWithArgumentsToExtract, bindingId) {
|
10096 | const visitor = new _AstToIrVisitor(localResolver, contextVariableExpression, bindingId, undefined);
|
10097 | const outputExpr = expressionWithArgumentsToExtract.visit(visitor, _Mode.Expression);
|
10098 | if (visitor.usesImplicitReceiver) {
|
10099 | localResolver.notifyImplicitReceiverUse();
|
10100 | }
|
10101 | const stmts = getStatementsFromVisitor(visitor, bindingId);
|
10102 | // Removing the first argument, because it was a length for ViewEngine, not Ivy.
|
10103 | let args = outputExpr.args.slice(1);
|
10104 | if (expressionWithArgumentsToExtract instanceof Interpolation) {
|
10105 | // If we're dealing with an interpolation of 1 value with an empty prefix and suffix, reduce the
|
10106 | // args returned to just the value, because we're going to pass it to a special instruction.
|
10107 | const strings = expressionWithArgumentsToExtract.strings;
|
10108 | if (args.length === 3 && strings[0] === '' && strings[1] === '') {
|
10109 | // Single argument interpolate instructions.
|
10110 | args = [args[1]];
|
10111 | }
|
10112 | else if (args.length >= 19) {
|
10113 | // 19 or more arguments must be passed to the `interpolateV`-style instructions, which accept
|
10114 | // an array of arguments
|
10115 | args = [literalArr(args)];
|
10116 | }
|
10117 | }
|
10118 | return { stmts, args };
|
10119 | }
|
10120 | function getStatementsFromVisitor(visitor, bindingId) {
|
10121 | const stmts = [];
|
10122 | for (let i = 0; i < visitor.temporaryCount; i++) {
|
10123 | stmts.push(temporaryDeclaration(bindingId, i));
|
10124 | }
|
10125 | return stmts;
|
10126 | }
|
10127 | function convertBuiltins(converterFactory, ast) {
|
10128 | const visitor = new _BuiltinAstConverter(converterFactory);
|
10129 | return ast.visit(visitor);
|
10130 | }
|
10131 | function temporaryName(bindingId, temporaryNumber) {
|
10132 | return `tmp_${bindingId}_${temporaryNumber}`;
|
10133 | }
|
10134 | function temporaryDeclaration(bindingId, temporaryNumber) {
|
10135 | return new DeclareVarStmt(temporaryName(bindingId, temporaryNumber), NULL_EXPR);
|
10136 | }
|
10137 | function prependTemporaryDecls(temporaryCount, bindingId, statements) {
|
10138 | for (let i = temporaryCount - 1; i >= 0; i--) {
|
10139 | statements.unshift(temporaryDeclaration(bindingId, i));
|
10140 | }
|
10141 | }
|
10142 | var _Mode;
|
10143 | (function (_Mode) {
|
10144 | _Mode[_Mode["Statement"] = 0] = "Statement";
|
10145 | _Mode[_Mode["Expression"] = 1] = "Expression";
|
10146 | })(_Mode || (_Mode = {}));
|
10147 | function ensureStatementMode(mode, ast) {
|
10148 | if (mode !== _Mode.Statement) {
|
10149 | throw new Error(`Expected a statement, but saw ${ast}`);
|
10150 | }
|
10151 | }
|
10152 | function ensureExpressionMode(mode, ast) {
|
10153 | if (mode !== _Mode.Expression) {
|
10154 | throw new Error(`Expected an expression, but saw ${ast}`);
|
10155 | }
|
10156 | }
|
10157 | function convertToStatementIfNeeded(mode, expr) {
|
10158 | if (mode === _Mode.Statement) {
|
10159 | return expr.toStmt();
|
10160 | }
|
10161 | else {
|
10162 | return expr;
|
10163 | }
|
10164 | }
|
10165 | class _BuiltinAstConverter extends AstTransformer {
|
10166 | constructor(_converterFactory) {
|
10167 | super();
|
10168 | this._converterFactory = _converterFactory;
|
10169 | }
|
10170 | visitPipe(ast, context) {
|
10171 | const args = [ast.exp, ...ast.args].map(ast => ast.visit(this, context));
|
10172 | return new BuiltinFunctionCall(ast.span, ast.sourceSpan, args, this._converterFactory.createPipeConverter(ast.name, args.length));
|
10173 | }
|
10174 | visitLiteralArray(ast, context) {
|
10175 | const args = ast.expressions.map(ast => ast.visit(this, context));
|
10176 | return new BuiltinFunctionCall(ast.span, ast.sourceSpan, args, this._converterFactory.createLiteralArrayConverter(ast.expressions.length));
|
10177 | }
|
10178 | visitLiteralMap(ast, context) {
|
10179 | const args = ast.values.map(ast => ast.visit(this, context));
|
10180 | return new BuiltinFunctionCall(ast.span, ast.sourceSpan, args, this._converterFactory.createLiteralMapConverter(ast.keys));
|
10181 | }
|
10182 | }
|
10183 | class _AstToIrVisitor {
|
10184 | constructor(_localResolver, _implicitReceiver, bindingId, interpolationFunction, baseSourceSpan, implicitReceiverAccesses) {
|
10185 | this._localResolver = _localResolver;
|
10186 | this._implicitReceiver = _implicitReceiver;
|
10187 | this.bindingId = bindingId;
|
10188 | this.interpolationFunction = interpolationFunction;
|
10189 | this.baseSourceSpan = baseSourceSpan;
|
10190 | this.implicitReceiverAccesses = implicitReceiverAccesses;
|
10191 | this._nodeMap = new Map();
|
10192 | this._resultMap = new Map();
|
10193 | this._currentTemporary = 0;
|
10194 | this.temporaryCount = 0;
|
10195 | this.usesImplicitReceiver = false;
|
10196 | }
|
10197 | visitUnary(ast, mode) {
|
10198 | let op;
|
10199 | switch (ast.operator) {
|
10200 | case '+':
|
10201 | op = UnaryOperator.Plus;
|
10202 | break;
|
10203 | case '-':
|
10204 | op = UnaryOperator.Minus;
|
10205 | break;
|
10206 | default:
|
10207 | throw new Error(`Unsupported operator ${ast.operator}`);
|
10208 | }
|
10209 | return convertToStatementIfNeeded(mode, new UnaryOperatorExpr(op, this._visit(ast.expr, _Mode.Expression), undefined, this.convertSourceSpan(ast.span)));
|
10210 | }
|
10211 | visitBinary(ast, mode) {
|
10212 | let op;
|
10213 | switch (ast.operation) {
|
10214 | case '+':
|
10215 | op = BinaryOperator.Plus;
|
10216 | break;
|
10217 | case '-':
|
10218 | op = BinaryOperator.Minus;
|
10219 | break;
|
10220 | case '*':
|
10221 | op = BinaryOperator.Multiply;
|
10222 | break;
|
10223 | case '/':
|
10224 | op = BinaryOperator.Divide;
|
10225 | break;
|
10226 | case '%':
|
10227 | op = BinaryOperator.Modulo;
|
10228 | break;
|
10229 | case '&&':
|
10230 | op = BinaryOperator.And;
|
10231 | break;
|
10232 | case '||':
|
10233 | op = BinaryOperator.Or;
|
10234 | break;
|
10235 | case '==':
|
10236 | op = BinaryOperator.Equals;
|
10237 | break;
|
10238 | case '!=':
|
10239 | op = BinaryOperator.NotEquals;
|
10240 | break;
|
10241 | case '===':
|
10242 | op = BinaryOperator.Identical;
|
10243 | break;
|
10244 | case '!==':
|
10245 | op = BinaryOperator.NotIdentical;
|
10246 | break;
|
10247 | case '<':
|
10248 | op = BinaryOperator.Lower;
|
10249 | break;
|
10250 | case '>':
|
10251 | op = BinaryOperator.Bigger;
|
10252 | break;
|
10253 | case '<=':
|
10254 | op = BinaryOperator.LowerEquals;
|
10255 | break;
|
10256 | case '>=':
|
10257 | op = BinaryOperator.BiggerEquals;
|
10258 | break;
|
10259 | default:
|
10260 | throw new Error(`Unsupported operation ${ast.operation}`);
|
10261 | }
|
10262 | return convertToStatementIfNeeded(mode, new BinaryOperatorExpr(op, this._visit(ast.left, _Mode.Expression), this._visit(ast.right, _Mode.Expression), undefined, this.convertSourceSpan(ast.span)));
|
10263 | }
|
10264 | visitChain(ast, mode) {
|
10265 | ensureStatementMode(mode, ast);
|
10266 | return this.visitAll(ast.expressions, mode);
|
10267 | }
|
10268 | visitConditional(ast, mode) {
|
10269 | const value = this._visit(ast.condition, _Mode.Expression);
|
10270 | return convertToStatementIfNeeded(mode, value.conditional(this._visit(ast.trueExp, _Mode.Expression), this._visit(ast.falseExp, _Mode.Expression), this.convertSourceSpan(ast.span)));
|
10271 | }
|
10272 | visitPipe(ast, mode) {
|
10273 | throw new Error(`Illegal state: Pipes should have been converted into functions. Pipe: ${ast.name}`);
|
10274 | }
|
10275 | visitFunctionCall(ast, mode) {
|
10276 | const convertedArgs = this.visitAll(ast.args, _Mode.Expression);
|
10277 | let fnResult;
|
10278 | if (ast instanceof BuiltinFunctionCall) {
|
10279 | fnResult = ast.converter(convertedArgs);
|
10280 | }
|
10281 | else {
|
10282 | fnResult = this._visit(ast.target, _Mode.Expression)
|
10283 | .callFn(convertedArgs, this.convertSourceSpan(ast.span));
|
10284 | }
|
10285 | return convertToStatementIfNeeded(mode, fnResult);
|
10286 | }
|
10287 | visitImplicitReceiver(ast, mode) {
|
10288 | ensureExpressionMode(mode, ast);
|
10289 | this.usesImplicitReceiver = true;
|
10290 | return this._implicitReceiver;
|
10291 | }
|
10292 | visitThisReceiver(ast, mode) {
|
10293 | return this.visitImplicitReceiver(ast, mode);
|
10294 | }
|
10295 | visitInterpolation(ast, mode) {
|
10296 | ensureExpressionMode(mode, ast);
|
10297 | const args = [literal(ast.expressions.length)];
|
10298 | for (let i = 0; i < ast.strings.length - 1; i++) {
|
10299 | args.push(literal(ast.strings[i]));
|
10300 | args.push(this._visit(ast.expressions[i], _Mode.Expression));
|
10301 | }
|
10302 | args.push(literal(ast.strings[ast.strings.length - 1]));
|
10303 | if (this.interpolationFunction) {
|
10304 | return this.interpolationFunction(args);
|
10305 | }
|
10306 | return ast.expressions.length <= 9 ?
|
10307 | importExpr(Identifiers.inlineInterpolate).callFn(args) :
|
10308 | importExpr(Identifiers.interpolate).callFn([
|
10309 | args[0], literalArr(args.slice(1), undefined, this.convertSourceSpan(ast.span))
|
10310 | ]);
|
10311 | }
|
10312 | visitKeyedRead(ast, mode) {
|
10313 | const leftMostSafe = this.leftMostSafeNode(ast);
|
10314 | if (leftMostSafe) {
|
10315 | return this.convertSafeAccess(ast, leftMostSafe, mode);
|
10316 | }
|
10317 | else {
|
10318 | return convertToStatementIfNeeded(mode, this._visit(ast.obj, _Mode.Expression).key(this._visit(ast.key, _Mode.Expression)));
|
10319 | }
|
10320 | }
|
10321 | visitKeyedWrite(ast, mode) {
|
10322 | const obj = this._visit(ast.obj, _Mode.Expression);
|
10323 | const key = this._visit(ast.key, _Mode.Expression);
|
10324 | const value = this._visit(ast.value, _Mode.Expression);
|
10325 | return convertToStatementIfNeeded(mode, obj.key(key).set(value));
|
10326 | }
|
10327 | visitLiteralArray(ast, mode) {
|
10328 | throw new Error(`Illegal State: literal arrays should have been converted into functions`);
|
10329 | }
|
10330 | visitLiteralMap(ast, mode) {
|
10331 | throw new Error(`Illegal State: literal maps should have been converted into functions`);
|
10332 | }
|
10333 | visitLiteralPrimitive(ast, mode) {
|
10334 | // For literal values of null, undefined, true, or false allow type interference
|
10335 | // to infer the type.
|
10336 | const type = ast.value === null || ast.value === undefined || ast.value === true || ast.value === true ?
|
10337 | INFERRED_TYPE :
|
10338 | undefined;
|
10339 | return convertToStatementIfNeeded(mode, literal(ast.value, type, this.convertSourceSpan(ast.span)));
|
10340 | }
|
10341 | _getLocal(name, receiver) {
|
10342 | var _a;
|
10343 | if (((_a = this._localResolver.globals) === null || _a === void 0 ? void 0 : _a.has(name)) && receiver instanceof ThisReceiver) {
|
10344 | return null;
|
10345 | }
|
10346 | return this._localResolver.getLocal(name);
|
10347 | }
|
10348 | visitMethodCall(ast, mode) {
|
10349 | if (ast.receiver instanceof ImplicitReceiver &&
|
10350 | !(ast.receiver instanceof ThisReceiver) && ast.name === '$any') {
|
10351 | const args = this.visitAll(ast.args, _Mode.Expression);
|
10352 | if (args.length != 1) {
|
10353 | throw new Error(`Invalid call to $any, expected 1 argument but received ${args.length || 'none'}`);
|
10354 | }
|
10355 | return args[0].cast(DYNAMIC_TYPE, this.convertSourceSpan(ast.span));
|
10356 | }
|
10357 | const leftMostSafe = this.leftMostSafeNode(ast);
|
10358 | if (leftMostSafe) {
|
10359 | return this.convertSafeAccess(ast, leftMostSafe, mode);
|
10360 | }
|
10361 | else {
|
10362 | const args = this.visitAll(ast.args, _Mode.Expression);
|
10363 | const prevUsesImplicitReceiver = this.usesImplicitReceiver;
|
10364 | let result = null;
|
10365 | const receiver = this._visit(ast.receiver, _Mode.Expression);
|
10366 | if (receiver === this._implicitReceiver) {
|
10367 | const varExpr = this._getLocal(ast.name, ast.receiver);
|
10368 | if (varExpr) {
|
10369 | // Restore the previous "usesImplicitReceiver" state since the implicit
|
10370 | // receiver has been replaced with a resolved local expression.
|
10371 | this.usesImplicitReceiver = prevUsesImplicitReceiver;
|
10372 | result = varExpr.callFn(args);
|
10373 | this.addImplicitReceiverAccess(ast.name);
|
10374 | }
|
10375 | }
|
10376 | if (result == null) {
|
10377 | result = receiver.callMethod(ast.name, args, this.convertSourceSpan(ast.span));
|
10378 | }
|
10379 | return convertToStatementIfNeeded(mode, result);
|
10380 | }
|
10381 | }
|
10382 | visitPrefixNot(ast, mode) {
|
10383 | return convertToStatementIfNeeded(mode, not(this._visit(ast.expression, _Mode.Expression)));
|
10384 | }
|
10385 | visitNonNullAssert(ast, mode) {
|
10386 | return convertToStatementIfNeeded(mode, assertNotNull(this._visit(ast.expression, _Mode.Expression)));
|
10387 | }
|
10388 | visitPropertyRead(ast, mode) {
|
10389 | const leftMostSafe = this.leftMostSafeNode(ast);
|
10390 | if (leftMostSafe) {
|
10391 | return this.convertSafeAccess(ast, leftMostSafe, mode);
|
10392 | }
|
10393 | else {
|
10394 | let result = null;
|
10395 | const prevUsesImplicitReceiver = this.usesImplicitReceiver;
|
10396 | const receiver = this._visit(ast.receiver, _Mode.Expression);
|
10397 | if (receiver === this._implicitReceiver) {
|
10398 | result = this._getLocal(ast.name, ast.receiver);
|
10399 | if (result) {
|
10400 | // Restore the previous "usesImplicitReceiver" state since the implicit
|
10401 | // receiver has been replaced with a resolved local expression.
|
10402 | this.usesImplicitReceiver = prevUsesImplicitReceiver;
|
10403 | this.addImplicitReceiverAccess(ast.name);
|
10404 | }
|
10405 | }
|
10406 | if (result == null) {
|
10407 | result = receiver.prop(ast.name);
|
10408 | }
|
10409 | return convertToStatementIfNeeded(mode, result);
|
10410 | }
|
10411 | }
|
10412 | visitPropertyWrite(ast, mode) {
|
10413 | const receiver = this._visit(ast.receiver, _Mode.Expression);
|
10414 | const prevUsesImplicitReceiver = this.usesImplicitReceiver;
|
10415 | let varExpr = null;
|
10416 | if (receiver === this._implicitReceiver) {
|
10417 | const localExpr = this._getLocal(ast.name, ast.receiver);
|
10418 | if (localExpr) {
|
10419 | if (localExpr instanceof ReadPropExpr) {
|
10420 | // If the local variable is a property read expression, it's a reference
|
10421 | // to a 'context.property' value and will be used as the target of the
|
10422 | // write expression.
|
10423 | varExpr = localExpr;
|
10424 | // Restore the previous "usesImplicitReceiver" state since the implicit
|
10425 | // receiver has been replaced with a resolved local expression.
|
10426 | this.usesImplicitReceiver = prevUsesImplicitReceiver;
|
10427 | this.addImplicitReceiverAccess(ast.name);
|
10428 | }
|
10429 | else {
|
10430 | // Otherwise it's an error.
|
10431 | const receiver = ast.name;
|
10432 | const value = (ast.value instanceof PropertyRead) ? ast.value.name : undefined;
|
10433 | throw new Error(`Cannot assign value "${value}" to template variable "${receiver}". Template variables are read-only.`);
|
10434 | }
|
10435 | }
|
10436 | }
|
10437 | // If no local expression could be produced, use the original receiver's
|
10438 | // property as the target.
|
10439 | if (varExpr === null) {
|
10440 | varExpr = receiver.prop(ast.name);
|
10441 | }
|
10442 | return convertToStatementIfNeeded(mode, varExpr.set(this._visit(ast.value, _Mode.Expression)));
|
10443 | }
|
10444 | visitSafePropertyRead(ast, mode) {
|
10445 | return this.convertSafeAccess(ast, this.leftMostSafeNode(ast), mode);
|
10446 | }
|
10447 | visitSafeMethodCall(ast, mode) {
|
10448 | return this.convertSafeAccess(ast, this.leftMostSafeNode(ast), mode);
|
10449 | }
|
10450 | visitAll(asts, mode) {
|
10451 | return asts.map(ast => this._visit(ast, mode));
|
10452 | }
|
10453 | visitQuote(ast, mode) {
|
10454 | throw new Error(`Quotes are not supported for evaluation!
|
10455 | Statement: ${ast.uninterpretedExpression} located at ${ast.location}`);
|
10456 | }
|
10457 | _visit(ast, mode) {
|
10458 | const result = this._resultMap.get(ast);
|
10459 | if (result)
|
10460 | return result;
|
10461 | return (this._nodeMap.get(ast) || ast).visit(this, mode);
|
10462 | }
|
10463 | convertSafeAccess(ast, leftMostSafe, mode) {
|
10464 | // If the expression contains a safe access node on the left it needs to be converted to
|
10465 | // an expression that guards the access to the member by checking the receiver for blank. As
|
10466 | // execution proceeds from left to right, the left most part of the expression must be guarded
|
10467 | // first but, because member access is left associative, the right side of the expression is at
|
10468 | // the top of the AST. The desired result requires lifting a copy of the left part of the
|
10469 | // expression up to test it for blank before generating the unguarded version.
|
10470 | // Consider, for example the following expression: a?.b.c?.d.e
|
10471 | // This results in the ast:
|
10472 | // .
|
10473 | // / \
|
10474 | // ?. e
|
10475 | // / \
|
10476 | // . d
|
10477 | // / \
|
10478 | // ?. c
|
10479 | // / \
|
10480 | // a b
|
10481 | // The following tree should be generated:
|
10482 | //
|
10483 | // /---- ? ----\
|
10484 | // / | \
|
10485 | // a /--- ? ---\ null
|
10486 | // / | \
|
10487 | // . . null
|
10488 | // / \ / \
|
10489 | // . c . e
|
10490 | // / \ / \
|
10491 | // a b . d
|
10492 | // / \
|
10493 | // . c
|
10494 | // / \
|
10495 | // a b
|
10496 | //
|
10497 | // Notice that the first guard condition is the left hand of the left most safe access node
|
10498 | // which comes in as leftMostSafe to this routine.
|
10499 | let guardedExpression = this._visit(leftMostSafe.receiver, _Mode.Expression);
|
10500 | let temporary = undefined;
|
10501 | if (this.needsTemporary(leftMostSafe.receiver)) {
|
10502 | // If the expression has method calls or pipes then we need to save the result into a
|
10503 | // temporary variable to avoid calling stateful or impure code more than once.
|
10504 | temporary = this.allocateTemporary();
|
10505 | // Preserve the result in the temporary variable
|
10506 | guardedExpression = temporary.set(guardedExpression);
|
10507 | // Ensure all further references to the guarded expression refer to the temporary instead.
|
10508 | this._resultMap.set(leftMostSafe.receiver, temporary);
|
10509 | }
|
10510 | const condition = guardedExpression.isBlank();
|
10511 | // Convert the ast to an unguarded access to the receiver's member. The map will substitute
|
10512 | // leftMostNode with its unguarded version in the call to `this.visit()`.
|
10513 | if (leftMostSafe instanceof SafeMethodCall) {
|
10514 | this._nodeMap.set(leftMostSafe, new MethodCall(leftMostSafe.span, leftMostSafe.sourceSpan, leftMostSafe.nameSpan, leftMostSafe.receiver, leftMostSafe.name, leftMostSafe.args));
|
10515 | }
|
10516 | else {
|
10517 | this._nodeMap.set(leftMostSafe, new PropertyRead(leftMostSafe.span, leftMostSafe.sourceSpan, leftMostSafe.nameSpan, leftMostSafe.receiver, leftMostSafe.name));
|
10518 | }
|
10519 | // Recursively convert the node now without the guarded member access.
|
10520 | const access = this._visit(ast, _Mode.Expression);
|
10521 | // Remove the mapping. This is not strictly required as the converter only traverses each node
|
10522 | // once but is safer if the conversion is changed to traverse the nodes more than once.
|
10523 | this._nodeMap.delete(leftMostSafe);
|
10524 | // If we allocated a temporary, release it.
|
10525 | if (temporary) {
|
10526 | this.releaseTemporary(temporary);
|
10527 | }
|
10528 | // Produce the conditional
|
10529 | return convertToStatementIfNeeded(mode, condition.conditional(literal(null), access));
|
10530 | }
|
10531 | // Given an expression of the form a?.b.c?.d.e then the left most safe node is
|
10532 | // the (a?.b). The . and ?. are left associative thus can be rewritten as:
|
10533 | // ((((a?.c).b).c)?.d).e. This returns the most deeply nested safe read or
|
10534 | // safe method call as this needs to be transformed initially to:
|
10535 | // a == null ? null : a.c.b.c?.d.e
|
10536 | // then to:
|
10537 | // a == null ? null : a.b.c == null ? null : a.b.c.d.e
|
10538 | leftMostSafeNode(ast) {
|
10539 | const visit = (visitor, ast) => {
|
10540 | return (this._nodeMap.get(ast) || ast).visit(visitor);
|
10541 | };
|
10542 | return ast.visit({
|
10543 | visitUnary(ast) {
|
10544 | return null;
|
10545 | },
|
10546 | visitBinary(ast) {
|
10547 | return null;
|
10548 | },
|
10549 | visitChain(ast) {
|
10550 | return null;
|
10551 | },
|
10552 | visitConditional(ast) {
|
10553 | return null;
|
10554 | },
|
10555 | visitFunctionCall(ast) {
|
10556 | return null;
|
10557 | },
|
10558 | visitImplicitReceiver(ast) {
|
10559 | return null;
|
10560 | },
|
10561 | visitThisReceiver(ast) {
|
10562 | return null;
|
10563 | },
|
10564 | visitInterpolation(ast) {
|
10565 | return null;
|
10566 | },
|
10567 | visitKeyedRead(ast) {
|
10568 | return visit(this, ast.obj);
|
10569 | },
|
10570 | visitKeyedWrite(ast) {
|
10571 | return null;
|
10572 | },
|
10573 | visitLiteralArray(ast) {
|
10574 | return null;
|
10575 | },
|
10576 | visitLiteralMap(ast) {
|
10577 | return null;
|
10578 | },
|
10579 | visitLiteralPrimitive(ast) {
|
10580 | return null;
|
10581 | },
|
10582 | visitMethodCall(ast) {
|
10583 | return visit(this, ast.receiver);
|
10584 | },
|
10585 | visitPipe(ast) {
|
10586 | return null;
|
10587 | },
|
10588 | visitPrefixNot(ast) {
|
10589 | return null;
|
10590 | },
|
10591 | visitNonNullAssert(ast) {
|
10592 | return null;
|
10593 | },
|
10594 | visitPropertyRead(ast) {
|
10595 | return visit(this, ast.receiver);
|
10596 | },
|
10597 | visitPropertyWrite(ast) {
|
10598 | return null;
|
10599 | },
|
10600 | visitQuote(ast) {
|
10601 | return null;
|
10602 | },
|
10603 | visitSafeMethodCall(ast) {
|
10604 | return visit(this, ast.receiver) || ast;
|
10605 | },
|
10606 | visitSafePropertyRead(ast) {
|
10607 | return visit(this, ast.receiver) || ast;
|
10608 | }
|
10609 | });
|
10610 | }
|
10611 | // Returns true of the AST includes a method or a pipe indicating that, if the
|
10612 | // expression is used as the target of a safe property or method access then
|
10613 | // the expression should be stored into a temporary variable.
|
10614 | needsTemporary(ast) {
|
10615 | const visit = (visitor, ast) => {
|
10616 | return ast && (this._nodeMap.get(ast) || ast).visit(visitor);
|
10617 | };
|
10618 | const visitSome = (visitor, ast) => {
|
10619 | return ast.some(ast => visit(visitor, ast));
|
10620 | };
|
10621 | return ast.visit({
|
10622 | visitUnary(ast) {
|
10623 | return visit(this, ast.expr);
|
10624 | },
|
10625 | visitBinary(ast) {
|
10626 | return visit(this, ast.left) || visit(this, ast.right);
|
10627 | },
|
10628 | visitChain(ast) {
|
10629 | return false;
|
10630 | },
|
10631 | visitConditional(ast) {
|
10632 | return visit(this, ast.condition) || visit(this, ast.trueExp) || visit(this, ast.falseExp);
|
10633 | },
|
10634 | visitFunctionCall(ast) {
|
10635 | return true;
|
10636 | },
|
10637 | visitImplicitReceiver(ast) {
|
10638 | return false;
|
10639 | },
|
10640 | visitThisReceiver(ast) {
|
10641 | return false;
|
10642 | },
|
10643 | visitInterpolation(ast) {
|
10644 | return visitSome(this, ast.expressions);
|
10645 | },
|
10646 | visitKeyedRead(ast) {
|
10647 | return false;
|
10648 | },
|
10649 | visitKeyedWrite(ast) {
|
10650 | return false;
|
10651 | },
|
10652 | visitLiteralArray(ast) {
|
10653 | return true;
|
10654 | },
|
10655 | visitLiteralMap(ast) {
|
10656 | return true;
|
10657 | },
|
10658 | visitLiteralPrimitive(ast) {
|
10659 | return false;
|
10660 | },
|
10661 | visitMethodCall(ast) {
|
10662 | return true;
|
10663 | },
|
10664 | visitPipe(ast) {
|
10665 | return true;
|
10666 | },
|
10667 | visitPrefixNot(ast) {
|
10668 | return visit(this, ast.expression);
|
10669 | },
|
10670 | visitNonNullAssert(ast) {
|
10671 | return visit(this, ast.expression);
|
10672 | },
|
10673 | visitPropertyRead(ast) {
|
10674 | return false;
|
10675 | },
|
10676 | visitPropertyWrite(ast) {
|
10677 | return false;
|
10678 | },
|
10679 | visitQuote(ast) {
|
10680 | return false;
|
10681 | },
|
10682 | visitSafeMethodCall(ast) {
|
10683 | return true;
|
10684 | },
|
10685 | visitSafePropertyRead(ast) {
|
10686 | return false;
|
10687 | }
|
10688 | });
|
10689 | }
|
10690 | allocateTemporary() {
|
10691 | const tempNumber = this._currentTemporary++;
|
10692 | this.temporaryCount = Math.max(this._currentTemporary, this.temporaryCount);
|
10693 | return new ReadVarExpr(temporaryName(this.bindingId, tempNumber));
|
10694 | }
|
10695 | releaseTemporary(temporary) {
|
10696 | this._currentTemporary--;
|
10697 | if (temporary.name != temporaryName(this.bindingId, this._currentTemporary)) {
|
10698 | throw new Error(`Temporary ${temporary.name} released out of order`);
|
10699 | }
|
10700 | }
|
10701 | /**
|
10702 | * Creates an absolute `ParseSourceSpan` from the relative `ParseSpan`.
|
10703 | *
|
10704 | * `ParseSpan` objects are relative to the start of the expression.
|
10705 | * This method converts these to full `ParseSourceSpan` objects that
|
10706 | * show where the span is within the overall source file.
|
10707 | *
|
10708 | * @param span the relative span to convert.
|
10709 | * @returns a `ParseSourceSpan` for the given span or null if no
|
10710 | * `baseSourceSpan` was provided to this class.
|
10711 | */
|
10712 | convertSourceSpan(span) {
|
10713 | if (this.baseSourceSpan) {
|
10714 | const start = this.baseSourceSpan.start.moveBy(span.start);
|
10715 | const end = this.baseSourceSpan.start.moveBy(span.end);
|
10716 | const fullStart = this.baseSourceSpan.fullStart.moveBy(span.start);
|
10717 | return new ParseSourceSpan(start, end, fullStart);
|
10718 | }
|
10719 | else {
|
10720 | return null;
|
10721 | }
|
10722 | }
|
10723 | /** Adds the name of an AST to the list of implicit receiver accesses. */
|
10724 | addImplicitReceiverAccess(name) {
|
10725 | if (this.implicitReceiverAccesses) {
|
10726 | this.implicitReceiverAccesses.add(name);
|
10727 | }
|
10728 | }
|
10729 | }
|
10730 | function flattenStatements(arg, output) {
|
10731 | if (Array.isArray(arg)) {
|
10732 | arg.forEach((entry) => flattenStatements(entry, output));
|
10733 | }
|
10734 | else {
|
10735 | output.push(arg);
|
10736 | }
|
10737 | }
|
10738 | class DefaultLocalResolver {
|
10739 | constructor(globals) {
|
10740 | this.globals = globals;
|
10741 | }
|
10742 | notifyImplicitReceiverUse() { }
|
10743 | getLocal(name) {
|
10744 | if (name === EventHandlerVars.event.name) {
|
10745 | return EventHandlerVars.event;
|
10746 | }
|
10747 | return null;
|
10748 | }
|
10749 | }
|
10750 | function createCurrValueExpr(bindingId) {
|
10751 | return variable(`currVal_${bindingId}`); // fix syntax highlighting: `
|
10752 | }
|
10753 | function createPreventDefaultVar(bindingId) {
|
10754 | return variable(`pd_${bindingId}`);
|
10755 | }
|
10756 | function convertStmtIntoExpression(stmt) {
|
10757 | if (stmt instanceof ExpressionStatement) {
|
10758 | return stmt.expr;
|
10759 | }
|
10760 | else if (stmt instanceof ReturnStatement) {
|
10761 | return stmt.value;
|
10762 | }
|
10763 | return null;
|
10764 | }
|
10765 | class BuiltinFunctionCall extends FunctionCall {
|
10766 | constructor(span, sourceSpan, args, converter) {
|
10767 | super(span, sourceSpan, null, args);
|
10768 | this.args = args;
|
10769 | this.converter = converter;
|
10770 | }
|
10771 | }
|
10772 |
|
10773 | /**
|
10774 | * @license
|
10775 | * Copyright Google LLC All Rights Reserved.
|
10776 | *
|
10777 | * Use of this source code is governed by an MIT-style license that can be
|
10778 | * found in the LICENSE file at https://angular.io/license
|
10779 | */
|
10780 | /**
|
10781 | * This file is a port of shadowCSS from webcomponents.js to TypeScript.
|
10782 | *
|
10783 | * Please make sure to keep to edits in sync with the source file.
|
10784 | *
|
10785 | * Source:
|
10786 | * https://github.com/webcomponents/webcomponentsjs/blob/4efecd7e0e/src/ShadowCSS/ShadowCSS.js
|
10787 | *
|
10788 | * The original file level comment is reproduced below
|
10789 | */
|
10790 | /*
|
10791 | This is a limited shim for ShadowDOM css styling.
|
10792 | https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#styles
|
10793 |
|
10794 | The intention here is to support only the styling features which can be
|
10795 | relatively simply implemented. The goal is to allow users to avoid the
|
10796 | most obvious pitfalls and do so without compromising performance significantly.
|
10797 | For ShadowDOM styling that's not covered here, a set of best practices
|
10798 | can be provided that should allow users to accomplish more complex styling.
|
10799 |
|
10800 | The following is a list of specific ShadowDOM styling features and a brief
|
10801 | discussion of the approach used to shim.
|
10802 |
|
10803 | Shimmed features:
|
10804 |
|
10805 | * :host, :host-context: ShadowDOM allows styling of the shadowRoot's host
|
10806 | element using the :host rule. To shim this feature, the :host styles are
|
10807 | reformatted and prefixed with a given scope name and promoted to a
|
10808 | document level stylesheet.
|
10809 | For example, given a scope name of .foo, a rule like this:
|
10810 |
|
10811 | :host {
|
10812 | background: red;
|
10813 | }
|
10814 | }
|
10815 |
|
10816 | becomes:
|
10817 |
|
10818 | .foo {
|
10819 | background: red;
|
10820 | }
|
10821 |
|
10822 | * encapsulation: Styles defined within ShadowDOM, apply only to
|
10823 | dom inside the ShadowDOM. Polymer uses one of two techniques to implement
|
10824 | this feature.
|
10825 |
|
10826 | By default, rules are prefixed with the host element tag name
|
10827 | as a descendant selector. This ensures styling does not leak out of the 'top'
|
10828 | of the element's ShadowDOM. For example,
|
10829 |
|
10830 | div {
|
10831 | font-weight: bold;
|
10832 | }
|
10833 |
|
10834 | becomes:
|
10835 |
|
10836 | x-foo div {
|
10837 | font-weight: bold;
|
10838 | }
|
10839 |
|
10840 | becomes:
|
10841 |
|
10842 |
|
10843 | Alternatively, if WebComponents.ShadowCSS.strictStyling is set to true then
|
10844 | selectors are scoped by adding an attribute selector suffix to each
|
10845 | simple selector that contains the host element tag name. Each element
|
10846 | in the element's ShadowDOM template is also given the scope attribute.
|
10847 | Thus, these rules match only elements that have the scope attribute.
|
10848 | For example, given a scope name of x-foo, a rule like this:
|
10849 |
|
10850 | div {
|
10851 | font-weight: bold;
|
10852 | }
|
10853 |
|
10854 | becomes:
|
10855 |
|
10856 | div[x-foo] {
|
10857 | font-weight: bold;
|
10858 | }
|
10859 |
|
10860 | Note that elements that are dynamically added to a scope must have the scope
|
10861 | selector added to them manually.
|
10862 |
|
10863 | * upper/lower bound encapsulation: Styles which are defined outside a
|
10864 | shadowRoot should not cross the ShadowDOM boundary and should not apply
|
10865 | inside a shadowRoot.
|
10866 |
|
10867 | This styling behavior is not emulated. Some possible ways to do this that
|
10868 | were rejected due to complexity and/or performance concerns include: (1) reset
|
10869 | every possible property for every possible selector for a given scope name;
|
10870 | (2) re-implement css in javascript.
|
10871 |
|
10872 | As an alternative, users should make sure to use selectors
|
10873 | specific to the scope in which they are working.
|
10874 |
|
10875 | * ::distributed: This behavior is not emulated. It's often not necessary
|
10876 | to style the contents of a specific insertion point and instead, descendants
|
10877 | of the host element can be styled selectively. Users can also create an
|
10878 | extra node around an insertion point and style that node's contents
|
10879 | via descendent selectors. For example, with a shadowRoot like this:
|
10880 |
|
10881 | <style>
|
10882 | ::content(div) {
|
10883 | background: red;
|
10884 | }
|
10885 | </style>
|
10886 | <content></content>
|
10887 |
|
10888 | could become:
|
10889 |
|
10890 | <style>
|
10891 | / *@polyfill .content-container div * /
|
10892 | ::content(div) {
|
10893 | background: red;
|
10894 | }
|
10895 | </style>
|
10896 | <div class="content-container">
|
10897 | <content></content>
|
10898 | </div>
|
10899 |
|
10900 | Note the use of @polyfill in the comment above a ShadowDOM specific style
|
10901 | declaration. This is a directive to the styling shim to use the selector
|
10902 | in comments in lieu of the next selector when running under polyfill.
|
10903 | */
|
10904 | class ShadowCss {
|
10905 | constructor() {
|
10906 | this.strictStyling = true;
|
10907 | }
|
10908 | /*
|
10909 | * Shim some cssText with the given selector. Returns cssText that can
|
10910 | * be included in the document via WebComponents.ShadowCSS.addCssToDocument(css).
|
10911 | *
|
10912 | * When strictStyling is true:
|
10913 | * - selector is the attribute added to all elements inside the host,
|
10914 | * - hostSelector is the attribute added to the host itself.
|
10915 | */
|
10916 | shimCssText(cssText, selector, hostSelector = '') {
|
10917 | const commentsWithHash = extractCommentsWithHash(cssText);
|
10918 | cssText = stripComments(cssText);
|
10919 | cssText = this._insertDirectives(cssText);
|
10920 | const scopedCssText = this._scopeCssText(cssText, selector, hostSelector);
|
10921 | return [scopedCssText, ...commentsWithHash].join('\n');
|
10922 | }
|
10923 | _insertDirectives(cssText) {
|
10924 | cssText = this._insertPolyfillDirectivesInCssText(cssText);
|
10925 | return this._insertPolyfillRulesInCssText(cssText);
|
10926 | }
|
10927 | /*
|
10928 | * Process styles to convert native ShadowDOM rules that will trip
|
10929 | * up the css parser; we rely on decorating the stylesheet with inert rules.
|
10930 | *
|
10931 | * For example, we convert this rule:
|
10932 | *
|
10933 | * polyfill-next-selector { content: ':host menu-item'; }
|
10934 | * ::content menu-item {
|
10935 | *
|
10936 | * to this:
|
10937 | *
|
10938 | * scopeName menu-item {
|
10939 | *
|
10940 | **/
|
10941 | _insertPolyfillDirectivesInCssText(cssText) {
|
10942 | // Difference with webcomponents.js: does not handle comments
|
10943 | return cssText.replace(_cssContentNextSelectorRe, function (...m) {
|
10944 | return m[2] + '{';
|
10945 | });
|
10946 | }
|
10947 | /*
|
10948 | * Process styles to add rules which will only apply under the polyfill
|
10949 | *
|
10950 | * For example, we convert this rule:
|
10951 | *
|
10952 | * polyfill-rule {
|
10953 | * content: ':host menu-item';
|
10954 | * ...
|
10955 | * }
|
10956 | *
|
10957 | * to this:
|
10958 | *
|
10959 | * scopeName menu-item {...}
|
10960 | *
|
10961 | **/
|
10962 | _insertPolyfillRulesInCssText(cssText) {
|
10963 | // Difference with webcomponents.js: does not handle comments
|
10964 | return cssText.replace(_cssContentRuleRe, (...m) => {
|
10965 | const rule = m[0].replace(m[1], '').replace(m[2], '');
|
10966 | return m[4] + rule;
|
10967 | });
|
10968 | }
|
10969 | /* Ensure styles are scoped. Pseudo-scoping takes a rule like:
|
10970 | *
|
10971 | * .foo {... }
|
10972 | *
|
10973 | * and converts this to
|
10974 | *
|
10975 | * scopeName .foo { ... }
|
10976 | */
|
10977 | _scopeCssText(cssText, scopeSelector, hostSelector) {
|
10978 | const unscopedRules = this._extractUnscopedRulesFromCssText(cssText);
|
10979 | // replace :host and :host-context -shadowcsshost and -shadowcsshost respectively
|
10980 | cssText = this._insertPolyfillHostInCssText(cssText);
|
10981 | cssText = this._convertColonHost(cssText);
|
10982 | cssText = this._convertColonHostContext(cssText);
|
10983 | cssText = this._convertShadowDOMSelectors(cssText);
|
10984 | if (scopeSelector) {
|
10985 | cssText = this._scopeSelectors(cssText, scopeSelector, hostSelector);
|
10986 | }
|
10987 | cssText = cssText + '\n' + unscopedRules;
|
10988 | return cssText.trim();
|
10989 | }
|
10990 | /*
|
10991 | * Process styles to add rules which will only apply under the polyfill
|
10992 | * and do not process via CSSOM. (CSSOM is destructive to rules on rare
|
10993 | * occasions, e.g. -webkit-calc on Safari.)
|
10994 | * For example, we convert this rule:
|
10995 | *
|
10996 | * @polyfill-unscoped-rule {
|
10997 | * content: 'menu-item';
|
10998 | * ... }
|
10999 | *
|
11000 | * to this:
|
11001 | *
|
11002 | * menu-item {...}
|
11003 | *
|
11004 | **/
|
11005 | _extractUnscopedRulesFromCssText(cssText) {
|
11006 | // Difference with webcomponents.js: does not handle comments
|
11007 | let r = '';
|
11008 | let m;
|
11009 | _cssContentUnscopedRuleRe.lastIndex = 0;
|
11010 | while ((m = _cssContentUnscopedRuleRe.exec(cssText)) !== null) {
|
11011 | const rule = m[0].replace(m[2], '').replace(m[1], m[4]);
|
11012 | r += rule + '\n\n';
|
11013 | }
|
11014 | return r;
|
11015 | }
|
11016 | /*
|
11017 | * convert a rule like :host(.foo) > .bar { }
|
11018 | *
|
11019 | * to
|
11020 | *
|
11021 | * .foo<scopeName> > .bar
|
11022 | */
|
11023 | _convertColonHost(cssText) {
|
11024 | return cssText.replace(_cssColonHostRe, (_, hostSelectors, otherSelectors) => {
|
11025 | if (hostSelectors) {
|
11026 | const convertedSelectors = [];
|
11027 | const hostSelectorArray = hostSelectors.split(',').map(p => p.trim());
|
11028 | for (const hostSelector of hostSelectorArray) {
|
11029 | if (!hostSelector)
|
11030 | break;
|
11031 | const convertedSelector = _polyfillHostNoCombinator + hostSelector.replace(_polyfillHost, '') + otherSelectors;
|
11032 | convertedSelectors.push(convertedSelector);
|
11033 | }
|
11034 | return convertedSelectors.join(',');
|
11035 | }
|
11036 | else {
|
11037 | return _polyfillHostNoCombinator + otherSelectors;
|
11038 | }
|
11039 | });
|
11040 | }
|
11041 | /*
|
11042 | * convert a rule like :host-context(.foo) > .bar { }
|
11043 | *
|
11044 | * to
|
11045 | *
|
11046 | * .foo<scopeName> > .bar, .foo <scopeName> > .bar { }
|
11047 | *
|
11048 | * and
|
11049 | *
|
11050 | * :host-context(.foo:host) .bar { ... }
|
11051 | *
|
11052 | * to
|
11053 | *
|
11054 | * .foo<scopeName> .bar { ... }
|
11055 | */
|
11056 | _convertColonHostContext(cssText) {
|
11057 | return cssText.replace(_cssColonHostContextReGlobal, selectorText => {
|
11058 | // We have captured a selector that contains a `:host-context` rule.
|
11059 | var _a;
|
11060 | // For backward compatibility `:host-context` may contain a comma separated list of selectors.
|
11061 | // Each context selector group will contain a list of host-context selectors that must match
|
11062 | // an ancestor of the host.
|
11063 | // (Normally `contextSelectorGroups` will only contain a single array of context selectors.)
|
11064 | const contextSelectorGroups = [[]];
|
11065 | // There may be more than `:host-context` in this selector so `selectorText` could look like:
|
11066 | // `:host-context(.one):host-context(.two)`.
|
11067 | // Execute `_cssColonHostContextRe` over and over until we have extracted all the
|
11068 | // `:host-context` selectors from this selector.
|
11069 | let match;
|
11070 | while (match = _cssColonHostContextRe.exec(selectorText)) {
|
11071 | // `match` = [':host-context(<selectors>)<rest>', <selectors>, <rest>]
|
11072 | // The `<selectors>` could actually be a comma separated list: `:host-context(.one, .two)`.
|
11073 | const newContextSelectors = ((_a = match[1]) !== null && _a !== void 0 ? _a : '').trim().split(',').map(m => m.trim()).filter(m => m !== '');
|
11074 | // We must duplicate the current selector group for each of these new selectors.
|
11075 | // For example if the current groups are:
|
11076 | // ```
|
11077 | // [
|
11078 | // ['a', 'b', 'c'],
|
11079 | // ['x', 'y', 'z'],
|
11080 | // ]
|
11081 | // ```
|
11082 | // And we have a new set of comma separated selectors: `:host-context(m,n)` then the new
|
11083 | // groups are:
|
11084 | // ```
|
11085 | // [
|
11086 | // ['a', 'b', 'c', 'm'],
|
11087 | // ['x', 'y', 'z', 'm'],
|
11088 | // ['a', 'b', 'c', 'n'],
|
11089 | // ['x', 'y', 'z', 'n'],
|
11090 | // ]
|
11091 | // ```
|
11092 | const contextSelectorGroupsLength = contextSelectorGroups.length;
|
11093 | repeatGroups(contextSelectorGroups, newContextSelectors.length);
|
11094 | for (let i = 0; i < newContextSelectors.length; i++) {
|
11095 | for (let j = 0; j < contextSelectorGroupsLength; j++) {
|
11096 | contextSelectorGroups[j + (i * contextSelectorGroupsLength)].push(newContextSelectors[i]);
|
11097 | }
|
11098 | }
|
11099 | // Update the `selectorText` and see repeat to see if there are more `:host-context`s.
|
11100 | selectorText = match[2];
|
11101 | }
|
11102 | // The context selectors now must be combined with each other to capture all the possible
|
11103 | // selectors that `:host-context` can match. See `combineHostContextSelectors()` for more
|
11104 | // info about how this is done.
|
11105 | return contextSelectorGroups
|
11106 | .map(contextSelectors => combineHostContextSelectors(contextSelectors, selectorText))
|
11107 | .join(', ');
|
11108 | });
|
11109 | }
|
11110 | /*
|
11111 | * Convert combinators like ::shadow and pseudo-elements like ::content
|
11112 | * by replacing with space.
|
11113 | */
|
11114 | _convertShadowDOMSelectors(cssText) {
|
11115 | return _shadowDOMSelectorsRe.reduce((result, pattern) => result.replace(pattern, ' '), cssText);
|
11116 | }
|
11117 | // change a selector like 'div' to 'name div'
|
11118 | _scopeSelectors(cssText, scopeSelector, hostSelector) {
|
11119 | return processRules(cssText, (rule) => {
|
11120 | let selector = rule.selector;
|
11121 | let content = rule.content;
|
11122 | if (rule.selector[0] != '@') {
|
11123 | selector =
|
11124 | this._scopeSelector(rule.selector, scopeSelector, hostSelector, this.strictStyling);
|
11125 | }
|
11126 | else if (rule.selector.startsWith('@media') || rule.selector.startsWith('@supports') ||
|
11127 | rule.selector.startsWith('@page') || rule.selector.startsWith('@document')) {
|
11128 | content = this._scopeSelectors(rule.content, scopeSelector, hostSelector);
|
11129 | }
|
11130 | return new CssRule(selector, content);
|
11131 | });
|
11132 | }
|
11133 | _scopeSelector(selector, scopeSelector, hostSelector, strict) {
|
11134 | return selector.split(',')
|
11135 | .map(part => part.trim().split(_shadowDeepSelectors))
|
11136 | .map((deepParts) => {
|
11137 | const [shallowPart, ...otherParts] = deepParts;
|
11138 | const applyScope = (shallowPart) => {
|
11139 | if (this._selectorNeedsScoping(shallowPart, scopeSelector)) {
|
11140 | return strict ?
|
11141 | this._applyStrictSelectorScope(shallowPart, scopeSelector, hostSelector) :
|
11142 | this._applySelectorScope(shallowPart, scopeSelector, hostSelector);
|
11143 | }
|
11144 | else {
|
11145 | return shallowPart;
|
11146 | }
|
11147 | };
|
11148 | return [applyScope(shallowPart), ...otherParts].join(' ');
|
11149 | })
|
11150 | .join(', ');
|
11151 | }
|
11152 | _selectorNeedsScoping(selector, scopeSelector) {
|
11153 | const re = this._makeScopeMatcher(scopeSelector);
|
11154 | return !re.test(selector);
|
11155 | }
|
11156 | _makeScopeMatcher(scopeSelector) {
|
11157 | const lre = /\[/g;
|
11158 | const rre = /\]/g;
|
11159 | scopeSelector = scopeSelector.replace(lre, '\\[').replace(rre, '\\]');
|
11160 | return new RegExp('^(' + scopeSelector + ')' + _selectorReSuffix, 'm');
|
11161 | }
|
11162 | _applySelectorScope(selector, scopeSelector, hostSelector) {
|
11163 | // Difference from webcomponents.js: scopeSelector could not be an array
|
11164 | return this._applySimpleSelectorScope(selector, scopeSelector, hostSelector);
|
11165 | }
|
11166 | // scope via name and [is=name]
|
11167 | _applySimpleSelectorScope(selector, scopeSelector, hostSelector) {
|
11168 | // In Android browser, the lastIndex is not reset when the regex is used in String.replace()
|
11169 | _polyfillHostRe.lastIndex = 0;
|
11170 | if (_polyfillHostRe.test(selector)) {
|
11171 | const replaceBy = this.strictStyling ? `[${hostSelector}]` : scopeSelector;
|
11172 | return selector
|
11173 | .replace(_polyfillHostNoCombinatorRe, (hnc, selector) => {
|
11174 | return selector.replace(/([^:]*)(:*)(.*)/, (_, before, colon, after) => {
|
11175 | return before + replaceBy + colon + after;
|
11176 | });
|
11177 | })
|
11178 | .replace(_polyfillHostRe, replaceBy + ' ');
|
11179 | }
|
11180 | return scopeSelector + ' ' + selector;
|
11181 | }
|
11182 | // return a selector with [name] suffix on each simple selector
|
11183 | // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name] /** @internal */
|
11184 | _applyStrictSelectorScope(selector, scopeSelector, hostSelector) {
|
11185 | const isRe = /\[is=([^\]]*)\]/g;
|
11186 | scopeSelector = scopeSelector.replace(isRe, (_, ...parts) => parts[0]);
|
11187 | const attrName = '[' + scopeSelector + ']';
|
11188 | const _scopeSelectorPart = (p) => {
|
11189 | let scopedP = p.trim();
|
11190 | if (!scopedP) {
|
11191 | return '';
|
11192 | }
|
11193 | if (p.indexOf(_polyfillHostNoCombinator) > -1) {
|
11194 | scopedP = this._applySimpleSelectorScope(p, scopeSelector, hostSelector);
|
11195 | }
|
11196 | else {
|
11197 | // remove :host since it should be unnecessary
|
11198 | const t = p.replace(_polyfillHostRe, '');
|
11199 | if (t.length > 0) {
|
11200 | const matches = t.match(/([^:]*)(:*)(.*)/);
|
11201 | if (matches) {
|
11202 | scopedP = matches[1] + attrName + matches[2] + matches[3];
|
11203 | }
|
11204 | }
|
11205 | }
|
11206 | return scopedP;
|
11207 | };
|
11208 | const safeContent = new SafeSelector(selector);
|
11209 | selector = safeContent.content();
|
11210 | let scopedSelector = '';
|
11211 | let startIndex = 0;
|
11212 | let res;
|
11213 | const sep = /( |>|\+|~(?!=))\s*/g;
|
11214 | // If a selector appears before :host it should not be shimmed as it
|
11215 | // matches on ancestor elements and not on elements in the host's shadow
|
11216 | // `:host-context(div)` is transformed to
|
11217 | // `-shadowcsshost-no-combinatordiv, div -shadowcsshost-no-combinator`
|
11218 | // the `div` is not part of the component in the 2nd selectors and should not be scoped.
|
11219 | // Historically `component-tag:host` was matching the component so we also want to preserve
|
11220 | // this behavior to avoid breaking legacy apps (it should not match).
|
11221 | // The behavior should be:
|
11222 | // - `tag:host` -> `tag[h]` (this is to avoid breaking legacy apps, should not match anything)
|
11223 | // - `tag :host` -> `tag [h]` (`tag` is not scoped because it's considered part of a
|
11224 | // `:host-context(tag)`)
|
11225 | const hasHost = selector.indexOf(_polyfillHostNoCombinator) > -1;
|
11226 | // Only scope parts after the first `-shadowcsshost-no-combinator` when it is present
|
11227 | let shouldScope = !hasHost;
|
11228 | while ((res = sep.exec(selector)) !== null) {
|
11229 | const separator = res[1];
|
11230 | const part = selector.slice(startIndex, res.index).trim();
|
11231 | shouldScope = shouldScope || part.indexOf(_polyfillHostNoCombinator) > -1;
|
11232 | const scopedPart = shouldScope ? _scopeSelectorPart(part) : part;
|
11233 | scopedSelector += `${scopedPart} ${separator} `;
|
11234 | startIndex = sep.lastIndex;
|
11235 | }
|
11236 | const part = selector.substring(startIndex);
|
11237 | shouldScope = shouldScope || part.indexOf(_polyfillHostNoCombinator) > -1;
|
11238 | scopedSelector += shouldScope ? _scopeSelectorPart(part) : part;
|
11239 | // replace the placeholders with their original values
|
11240 | return safeContent.restore(scopedSelector);
|
11241 | }
|
11242 | _insertPolyfillHostInCssText(selector) {
|
11243 | return selector.replace(_colonHostContextRe, _polyfillHostContext)
|
11244 | .replace(_colonHostRe, _polyfillHost);
|
11245 | }
|
11246 | }
|
11247 | class SafeSelector {
|
11248 | constructor(selector) {
|
11249 | this.placeholders = [];
|
11250 | this.index = 0;
|
11251 | // Replaces attribute selectors with placeholders.
|
11252 | // The WS in [attr="va lue"] would otherwise be interpreted as a selector separator.
|
11253 | selector = this._escapeRegexMatches(selector, /(\[[^\]]*\])/g);
|
11254 | // CSS allows for certain special characters to be used in selectors if they're escaped.
|
11255 | // E.g. `.foo:blue` won't match a class called `foo:blue`, because the colon denotes a
|
11256 | // pseudo-class, but writing `.foo\:blue` will match, because the colon was escaped.
|
11257 | // Replace all escape sequences (`\` followed by a character) with a placeholder so
|
11258 | // that our handling of pseudo-selectors doesn't mess with them.
|
11259 | selector = this._escapeRegexMatches(selector, /(\\.)/g);
|
11260 | // Replaces the expression in `:nth-child(2n + 1)` with a placeholder.
|
11261 | // WS and "+" would otherwise be interpreted as selector separators.
|
11262 | this._content = selector.replace(/(:nth-[-\w]+)(\([^)]+\))/g, (_, pseudo, exp) => {
|
11263 | const replaceBy = `__ph-${this.index}__`;
|
11264 | this.placeholders.push(exp);
|
11265 | this.index++;
|
11266 | return pseudo + replaceBy;
|
11267 | });
|
11268 | }
|
11269 | restore(content) {
|
11270 | return content.replace(/__ph-(\d+)__/g, (_ph, index) => this.placeholders[+index]);
|
11271 | }
|
11272 | content() {
|
11273 | return this._content;
|
11274 | }
|
11275 | /**
|
11276 | * Replaces all of the substrings that match a regex within a
|
11277 | * special string (e.g. `__ph-0__`, `__ph-1__`, etc).
|
11278 | */
|
11279 | _escapeRegexMatches(content, pattern) {
|
11280 | return content.replace(pattern, (_, keep) => {
|
11281 | const replaceBy = `__ph-${this.index}__`;
|
11282 | this.placeholders.push(keep);
|
11283 | this.index++;
|
11284 | return replaceBy;
|
11285 | });
|
11286 | }
|
11287 | }
|
11288 | const _cssContentNextSelectorRe = /polyfill-next-selector[^}]*content:[\s]*?(['"])(.*?)\1[;\s]*}([^{]*?){/gim;
|
11289 | const _cssContentRuleRe = /(polyfill-rule)[^}]*(content:[\s]*(['"])(.*?)\3)[;\s]*[^}]*}/gim;
|
11290 | const _cssContentUnscopedRuleRe = /(polyfill-unscoped-rule)[^}]*(content:[\s]*(['"])(.*?)\3)[;\s]*[^}]*}/gim;
|
11291 | const _polyfillHost = '-shadowcsshost';
|
11292 | // note: :host-context pre-processed to -shadowcsshostcontext.
|
11293 | const _polyfillHostContext = '-shadowcsscontext';
|
11294 | const _parenSuffix = '(?:\\((' +
|
11295 | '(?:\\([^)(]*\\)|[^)(]*)+?' +
|
11296 | ')\\))?([^,{]*)';
|
11297 | const _cssColonHostRe = new RegExp(_polyfillHost + _parenSuffix, 'gim');
|
11298 | const _cssColonHostContextReGlobal = new RegExp(_polyfillHostContext + _parenSuffix, 'gim');
|
11299 | const _cssColonHostContextRe = new RegExp(_polyfillHostContext + _parenSuffix, 'im');
|
11300 | const _polyfillHostNoCombinator = _polyfillHost + '-no-combinator';
|
11301 | const _polyfillHostNoCombinatorRe = /-shadowcsshost-no-combinator([^\s]*)/;
|
11302 | const _shadowDOMSelectorsRe = [
|
11303 | /::shadow/g,
|
11304 | /::content/g,
|
11305 | // Deprecated selectors
|
11306 | /\/shadow-deep\//g,
|
11307 | /\/shadow\//g,
|
11308 | ];
|
11309 | // The deep combinator is deprecated in the CSS spec
|
11310 | // Support for `>>>`, `deep`, `::ng-deep` is then also deprecated and will be removed in the future.
|
11311 | // see https://github.com/angular/angular/pull/17677
|
11312 | const _shadowDeepSelectors = /(?:>>>)|(?:\/deep\/)|(?:::ng-deep)/g;
|
11313 | const _selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$';
|
11314 | const _polyfillHostRe = /-shadowcsshost/gim;
|
11315 | const _colonHostRe = /:host/gim;
|
11316 | const _colonHostContextRe = /:host-context/gim;
|
11317 | const _commentRe = /\/\*\s*[\s\S]*?\*\//g;
|
11318 | function stripComments(input) {
|
11319 | return input.replace(_commentRe, '');
|
11320 | }
|
11321 | const _commentWithHashRe = /\/\*\s*#\s*source(Mapping)?URL=[\s\S]+?\*\//g;
|
11322 | function extractCommentsWithHash(input) {
|
11323 | return input.match(_commentWithHashRe) || [];
|
11324 | }
|
11325 | const BLOCK_PLACEHOLDER = '%BLOCK%';
|
11326 | const QUOTE_PLACEHOLDER = '%QUOTED%';
|
11327 | const _ruleRe = /(\s*)([^;\{\}]+?)(\s*)((?:{%BLOCK%}?\s*;?)|(?:\s*;))/g;
|
11328 | const _quotedRe = /%QUOTED%/g;
|
11329 | const CONTENT_PAIRS = new Map([['{', '}']]);
|
11330 | const QUOTE_PAIRS = new Map([[`"`, `"`], [`'`, `'`]]);
|
11331 | class CssRule {
|
11332 | constructor(selector, content) {
|
11333 | this.selector = selector;
|
11334 | this.content = content;
|
11335 | }
|
11336 | }
|
11337 | function processRules(input, ruleCallback) {
|
11338 | const inputWithEscapedQuotes = escapeBlocks(input, QUOTE_PAIRS, QUOTE_PLACEHOLDER);
|
11339 | const inputWithEscapedBlocks = escapeBlocks(inputWithEscapedQuotes.escapedString, CONTENT_PAIRS, BLOCK_PLACEHOLDER);
|
11340 | let nextBlockIndex = 0;
|
11341 | let nextQuoteIndex = 0;
|
11342 | return inputWithEscapedBlocks.escapedString
|
11343 | .replace(_ruleRe, (...m) => {
|
11344 | const selector = m[2];
|
11345 | let content = '';
|
11346 | let suffix = m[4];
|
11347 | let contentPrefix = '';
|
11348 | if (suffix && suffix.startsWith('{' + BLOCK_PLACEHOLDER)) {
|
11349 | content = inputWithEscapedBlocks.blocks[nextBlockIndex++];
|
11350 | suffix = suffix.substring(BLOCK_PLACEHOLDER.length + 1);
|
11351 | contentPrefix = '{';
|
11352 | }
|
11353 | const rule = ruleCallback(new CssRule(selector, content));
|
11354 | return `${m[1]}${rule.selector}${m[3]}${contentPrefix}${rule.content}${suffix}`;
|
11355 | })
|
11356 | .replace(_quotedRe, () => inputWithEscapedQuotes.blocks[nextQuoteIndex++]);
|
11357 | }
|
11358 | class StringWithEscapedBlocks {
|
11359 | constructor(escapedString, blocks) {
|
11360 | this.escapedString = escapedString;
|
11361 | this.blocks = blocks;
|
11362 | }
|
11363 | }
|
11364 | function escapeBlocks(input, charPairs, placeholder) {
|
11365 | const resultParts = [];
|
11366 | const escapedBlocks = [];
|
11367 | let openCharCount = 0;
|
11368 | let nonBlockStartIndex = 0;
|
11369 | let blockStartIndex = -1;
|
11370 | let openChar;
|
11371 | let closeChar;
|
11372 | for (let i = 0; i < input.length; i++) {
|
11373 | const char = input[i];
|
11374 | if (char === '\\') {
|
11375 | i++;
|
11376 | }
|
11377 | else if (char === closeChar) {
|
11378 | openCharCount--;
|
11379 | if (openCharCount === 0) {
|
11380 | escapedBlocks.push(input.substring(blockStartIndex, i));
|
11381 | resultParts.push(placeholder);
|
11382 | nonBlockStartIndex = i;
|
11383 | blockStartIndex = -1;
|
11384 | openChar = closeChar = undefined;
|
11385 | }
|
11386 | }
|
11387 | else if (char === openChar) {
|
11388 | openCharCount++;
|
11389 | }
|
11390 | else if (openCharCount === 0 && charPairs.has(char)) {
|
11391 | openChar = char;
|
11392 | closeChar = charPairs.get(char);
|
11393 | openCharCount = 1;
|
11394 | blockStartIndex = i + 1;
|
11395 | resultParts.push(input.substring(nonBlockStartIndex, blockStartIndex));
|
11396 | }
|
11397 | }
|
11398 | if (blockStartIndex !== -1) {
|
11399 | escapedBlocks.push(input.substring(blockStartIndex));
|
11400 | resultParts.push(placeholder);
|
11401 | }
|
11402 | else {
|
11403 | resultParts.push(input.substring(nonBlockStartIndex));
|
11404 | }
|
11405 | return new StringWithEscapedBlocks(resultParts.join(''), escapedBlocks);
|
11406 | }
|
11407 | /**
|
11408 | * Combine the `contextSelectors` with the `hostMarker` and the `otherSelectors`
|
11409 | * to create a selector that matches the same as `:host-context()`.
|
11410 | *
|
11411 | * Given a single context selector `A` we need to output selectors that match on the host and as an
|
11412 | * ancestor of the host:
|
11413 | *
|
11414 | * ```
|
11415 | * A <hostMarker>, A<hostMarker> {}
|
11416 | * ```
|
11417 | *
|
11418 | * When there is more than one context selector we also have to create combinations of those
|
11419 | * selectors with each other. For example if there are `A` and `B` selectors the output is:
|
11420 | *
|
11421 | * ```
|
11422 | * AB<hostMarker>, AB <hostMarker>, A B<hostMarker>,
|
11423 | * B A<hostMarker>, A B <hostMarker>, B A <hostMarker> {}
|
11424 | * ```
|
11425 | *
|
11426 | * And so on...
|
11427 | *
|
11428 | * @param hostMarker the string that selects the host element.
|
11429 | * @param contextSelectors an array of context selectors that will be combined.
|
11430 | * @param otherSelectors the rest of the selectors that are not context selectors.
|
11431 | */
|
11432 | function combineHostContextSelectors(contextSelectors, otherSelectors) {
|
11433 | const hostMarker = _polyfillHostNoCombinator;
|
11434 | const otherSelectorsHasHost = _polyfillHostRe.test(otherSelectors);
|
11435 | // If there are no context selectors then just output a host marker
|
11436 | if (contextSelectors.length === 0) {
|
11437 | return hostMarker + otherSelectors;
|
11438 | }
|
11439 | const combined = [contextSelectors.pop() || ''];
|
11440 | while (contextSelectors.length > 0) {
|
11441 | const length = combined.length;
|
11442 | const contextSelector = contextSelectors.pop();
|
11443 | for (let i = 0; i < length; i++) {
|
11444 | const previousSelectors = combined[i];
|
11445 | // Add the new selector as a descendant of the previous selectors
|
11446 | combined[length * 2 + i] = previousSelectors + ' ' + contextSelector;
|
11447 | // Add the new selector as an ancestor of the previous selectors
|
11448 | combined[length + i] = contextSelector + ' ' + previousSelectors;
|
11449 | // Add the new selector to act on the same element as the previous selectors
|
11450 | combined[i] = contextSelector + previousSelectors;
|
11451 | }
|
11452 | }
|
11453 | // Finally connect the selector to the `hostMarker`s: either acting directly on the host
|
11454 | // (A<hostMarker>) or as an ancestor (A <hostMarker>).
|
11455 | return combined
|
11456 | .map(s => otherSelectorsHasHost ?
|
11457 | `${s}${otherSelectors}` :
|
11458 | `${s}${hostMarker}${otherSelectors}, ${s} ${hostMarker}${otherSelectors}`)
|
11459 | .join(',');
|
11460 | }
|
11461 | /**
|
11462 | * Mutate the given `groups` array so that there are `multiples` clones of the original array
|
11463 | * stored.
|
11464 | *
|
11465 | * For example `repeatGroups([a, b], 3)` will result in `[a, b, a, b, a, b]` - but importantly the
|
11466 | * newly added groups will be clones of the original.
|
11467 | *
|
11468 | * @param groups An array of groups of strings that will be repeated. This array is mutated
|
11469 | * in-place.
|
11470 | * @param multiples The number of times the current groups should appear.
|
11471 | */
|
11472 | function repeatGroups(groups, multiples) {
|
11473 | const length = groups.length;
|
11474 | for (let i = 1; i < multiples; i++) {
|
11475 | for (let j = 0; j < length; j++) {
|
11476 | groups[j + (i * length)] = groups[j].slice(0);
|
11477 | }
|
11478 | }
|
11479 | }
|
11480 |
|
11481 | /**
|
11482 | * @license
|
11483 | * Copyright Google LLC All Rights Reserved.
|
11484 | *
|
11485 | * Use of this source code is governed by an MIT-style license that can be
|
11486 | * found in the LICENSE file at https://angular.io/license
|
11487 | */
|
11488 | const COMPONENT_VARIABLE = '%COMP%';
|
11489 | const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`;
|
11490 | const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`;
|
11491 |
|
11492 | /**
|
11493 | * @license
|
11494 | * Copyright Google LLC All Rights Reserved.
|
11495 | *
|
11496 | * Use of this source code is governed by an MIT-style license that can be
|
11497 | * found in the LICENSE file at https://angular.io/license
|
11498 | */
|
11499 | class NodeWithI18n {
|
11500 | constructor(sourceSpan, i18n) {
|
11501 | this.sourceSpan = sourceSpan;
|
11502 | this.i18n = i18n;
|
11503 | }
|
11504 | }
|
11505 | class Text$2 extends NodeWithI18n {
|
11506 | constructor(value, sourceSpan, i18n) {
|
11507 | super(sourceSpan, i18n);
|
11508 | this.value = value;
|
11509 | }
|
11510 | visit(visitor, context) {
|
11511 | return visitor.visitText(this, context);
|
11512 | }
|
11513 | }
|
11514 | class Expansion extends NodeWithI18n {
|
11515 | constructor(switchValue, type, cases, sourceSpan, switchValueSourceSpan, i18n) {
|
11516 | super(sourceSpan, i18n);
|
11517 | this.switchValue = switchValue;
|
11518 | this.type = type;
|
11519 | this.cases = cases;
|
11520 | this.switchValueSourceSpan = switchValueSourceSpan;
|
11521 | }
|
11522 | visit(visitor, context) {
|
11523 | return visitor.visitExpansion(this, context);
|
11524 | }
|
11525 | }
|
11526 | class ExpansionCase {
|
11527 | constructor(value, expression, sourceSpan, valueSourceSpan, expSourceSpan) {
|
11528 | this.value = value;
|
11529 | this.expression = expression;
|
11530 | this.sourceSpan = sourceSpan;
|
11531 | this.valueSourceSpan = valueSourceSpan;
|
11532 | this.expSourceSpan = expSourceSpan;
|
11533 | }
|
11534 | visit(visitor, context) {
|
11535 | return visitor.visitExpansionCase(this, context);
|
11536 | }
|
11537 | }
|
11538 | class Attribute extends NodeWithI18n {
|
11539 | constructor(name, value, sourceSpan, keySpan, valueSpan, i18n) {
|
11540 | super(sourceSpan, i18n);
|
11541 | this.name = name;
|
11542 | this.value = value;
|
11543 | this.keySpan = keySpan;
|
11544 | this.valueSpan = valueSpan;
|
11545 | }
|
11546 | visit(visitor, context) {
|
11547 | return visitor.visitAttribute(this, context);
|
11548 | }
|
11549 | }
|
11550 | class Element$1 extends NodeWithI18n {
|
11551 | constructor(name, attrs, children, sourceSpan, startSourceSpan, endSourceSpan = null, i18n) {
|
11552 | super(sourceSpan, i18n);
|
11553 | this.name = name;
|
11554 | this.attrs = attrs;
|
11555 | this.children = children;
|
11556 | this.startSourceSpan = startSourceSpan;
|
11557 | this.endSourceSpan = endSourceSpan;
|
11558 | }
|
11559 | visit(visitor, context) {
|
11560 | return visitor.visitElement(this, context);
|
11561 | }
|
11562 | }
|
11563 | class Comment {
|
11564 | constructor(value, sourceSpan) {
|
11565 | this.value = value;
|
11566 | this.sourceSpan = sourceSpan;
|
11567 | }
|
11568 | visit(visitor, context) {
|
11569 | return visitor.visitComment(this, context);
|
11570 | }
|
11571 | }
|
11572 | function visitAll$1(visitor, nodes, context = null) {
|
11573 | const result = [];
|
11574 | const visit = visitor.visit ?
|
11575 | (ast) => visitor.visit(ast, context) || ast.visit(visitor, context) :
|
11576 | (ast) => ast.visit(visitor, context);
|
11577 | nodes.forEach(ast => {
|
11578 | const astResult = visit(ast);
|
11579 | if (astResult) {
|
11580 | result.push(astResult);
|
11581 | }
|
11582 | });
|
11583 | return result;
|
11584 | }
|
11585 |
|
11586 | /**
|
11587 | * @license
|
11588 | * Copyright Google LLC All Rights Reserved.
|
11589 | *
|
11590 | * Use of this source code is governed by an MIT-style license that can be
|
11591 | * found in the LICENSE file at https://angular.io/license
|
11592 | */
|
11593 | var TokenType;
|
11594 | (function (TokenType) {
|
11595 | TokenType[TokenType["TAG_OPEN_START"] = 0] = "TAG_OPEN_START";
|
11596 | TokenType[TokenType["TAG_OPEN_END"] = 1] = "TAG_OPEN_END";
|
11597 | TokenType[TokenType["TAG_OPEN_END_VOID"] = 2] = "TAG_OPEN_END_VOID";
|
11598 | TokenType[TokenType["TAG_CLOSE"] = 3] = "TAG_CLOSE";
|
11599 | TokenType[TokenType["INCOMPLETE_TAG_OPEN"] = 4] = "INCOMPLETE_TAG_OPEN";
|
11600 | TokenType[TokenType["TEXT"] = 5] = "TEXT";
|
11601 | TokenType[TokenType["ESCAPABLE_RAW_TEXT"] = 6] = "ESCAPABLE_RAW_TEXT";
|
11602 | TokenType[TokenType["RAW_TEXT"] = 7] = "RAW_TEXT";
|
11603 | TokenType[TokenType["COMMENT_START"] = 8] = "COMMENT_START";
|
11604 | TokenType[TokenType["COMMENT_END"] = 9] = "COMMENT_END";
|
11605 | TokenType[TokenType["CDATA_START"] = 10] = "CDATA_START";
|
11606 | TokenType[TokenType["CDATA_END"] = 11] = "CDATA_END";
|
11607 | TokenType[TokenType["ATTR_NAME"] = 12] = "ATTR_NAME";
|
11608 | TokenType[TokenType["ATTR_QUOTE"] = 13] = "ATTR_QUOTE";
|
11609 | TokenType[TokenType["ATTR_VALUE"] = 14] = "ATTR_VALUE";
|
11610 | TokenType[TokenType["DOC_TYPE"] = 15] = "DOC_TYPE";
|
11611 | TokenType[TokenType["EXPANSION_FORM_START"] = 16] = "EXPANSION_FORM_START";
|
11612 | TokenType[TokenType["EXPANSION_CASE_VALUE"] = 17] = "EXPANSION_CASE_VALUE";
|
11613 | TokenType[TokenType["EXPANSION_CASE_EXP_START"] = 18] = "EXPANSION_CASE_EXP_START";
|
11614 | TokenType[TokenType["EXPANSION_CASE_EXP_END"] = 19] = "EXPANSION_CASE_EXP_END";
|
11615 | TokenType[TokenType["EXPANSION_FORM_END"] = 20] = "EXPANSION_FORM_END";
|
11616 | TokenType[TokenType["EOF"] = 21] = "EOF";
|
11617 | })(TokenType || (TokenType = {}));
|
11618 | class Token {
|
11619 | constructor(type, parts, sourceSpan) {
|
11620 | this.type = type;
|
11621 | this.parts = parts;
|
11622 | this.sourceSpan = sourceSpan;
|
11623 | }
|
11624 | }
|
11625 | class TokenError extends ParseError {
|
11626 | constructor(errorMsg, tokenType, span) {
|
11627 | super(span, errorMsg);
|
11628 | this.tokenType = tokenType;
|
11629 | }
|
11630 | }
|
11631 | class TokenizeResult {
|
11632 | constructor(tokens, errors, nonNormalizedIcuExpressions) {
|
11633 | this.tokens = tokens;
|
11634 | this.errors = errors;
|
11635 | this.nonNormalizedIcuExpressions = nonNormalizedIcuExpressions;
|
11636 | }
|
11637 | }
|
11638 | function tokenize(source, url, getTagDefinition, options = {}) {
|
11639 | const tokenizer = new _Tokenizer(new ParseSourceFile(source, url), getTagDefinition, options);
|
11640 | tokenizer.tokenize();
|
11641 | return new TokenizeResult(mergeTextTokens(tokenizer.tokens), tokenizer.errors, tokenizer.nonNormalizedIcuExpressions);
|
11642 | }
|
11643 | const _CR_OR_CRLF_REGEXP = /\r\n?/g;
|
11644 | function _unexpectedCharacterErrorMsg(charCode) {
|
11645 | const char = charCode === $EOF ? 'EOF' : String.fromCharCode(charCode);
|
11646 | return `Unexpected character "${char}"`;
|
11647 | }
|
11648 | function _unknownEntityErrorMsg(entitySrc) {
|
11649 | return `Unknown entity "${entitySrc}" - use the "&#<decimal>;" or "&#x<hex>;" syntax`;
|
11650 | }
|
11651 | function _unparsableEntityErrorMsg(type, entityStr) {
|
11652 | return `Unable to parse entity "${entityStr}" - ${type} character reference entities must end with ";"`;
|
11653 | }
|
11654 | var CharacterReferenceType;
|
11655 | (function (CharacterReferenceType) {
|
11656 | CharacterReferenceType["HEX"] = "hexadecimal";
|
11657 | CharacterReferenceType["DEC"] = "decimal";
|
11658 | })(CharacterReferenceType || (CharacterReferenceType = {}));
|
11659 | class _ControlFlowError {
|
11660 | constructor(error) {
|
11661 | this.error = error;
|
11662 | }
|
11663 | }
|
11664 | // See https://www.w3.org/TR/html51/syntax.html#writing-html-documents
|
11665 | class _Tokenizer {
|
11666 | /**
|
11667 | * @param _file The html source file being tokenized.
|
11668 | * @param _getTagDefinition A function that will retrieve a tag definition for a given tag name.
|
11669 | * @param options Configuration of the tokenization.
|
11670 | */
|
11671 | constructor(_file, _getTagDefinition, options) {
|
11672 | this._getTagDefinition = _getTagDefinition;
|
11673 | this._currentTokenStart = null;
|
11674 | this._currentTokenType = null;
|
11675 | this._expansionCaseStack = [];
|
11676 | this._inInterpolation = false;
|
11677 | this.tokens = [];
|
11678 | this.errors = [];
|
11679 | this.nonNormalizedIcuExpressions = [];
|
11680 | this._tokenizeIcu = options.tokenizeExpansionForms || false;
|
11681 | this._interpolationConfig = options.interpolationConfig || DEFAULT_INTERPOLATION_CONFIG;
|
11682 | this._leadingTriviaCodePoints =
|
11683 | options.leadingTriviaChars && options.leadingTriviaChars.map(c => c.codePointAt(0) || 0);
|
11684 | const range = options.range || { endPos: _file.content.length, startPos: 0, startLine: 0, startCol: 0 };
|
11685 | this._cursor = options.escapedString ? new EscapedCharacterCursor(_file, range) :
|
11686 | new PlainCharacterCursor(_file, range);
|
11687 | this._preserveLineEndings = options.preserveLineEndings || false;
|
11688 | this._escapedString = options.escapedString || false;
|
11689 | this._i18nNormalizeLineEndingsInICUs = options.i18nNormalizeLineEndingsInICUs || false;
|
11690 | try {
|
11691 | this._cursor.init();
|
11692 | }
|
11693 | catch (e) {
|
11694 | this.handleError(e);
|
11695 | }
|
11696 | }
|
11697 | _processCarriageReturns(content) {
|
11698 | if (this._preserveLineEndings) {
|
11699 | return content;
|
11700 | }
|
11701 | // https://www.w3.org/TR/html51/syntax.html#preprocessing-the-input-stream
|
11702 | // In order to keep the original position in the source, we can not
|
11703 | // pre-process it.
|
11704 | // Instead CRs are processed right before instantiating the tokens.
|
11705 | return content.replace(_CR_OR_CRLF_REGEXP, '\n');
|
11706 | }
|
11707 | tokenize() {
|
11708 | while (this._cursor.peek() !== $EOF) {
|
11709 | const start = this._cursor.clone();
|
11710 | try {
|
11711 | if (this._attemptCharCode($LT)) {
|
11712 | if (this._attemptCharCode($BANG)) {
|
11713 | if (this._attemptCharCode($LBRACKET)) {
|
11714 | this._consumeCdata(start);
|
11715 | }
|
11716 | else if (this._attemptCharCode($MINUS)) {
|
11717 | this._consumeComment(start);
|
11718 | }
|
11719 | else {
|
11720 | this._consumeDocType(start);
|
11721 | }
|
11722 | }
|
11723 | else if (this._attemptCharCode($SLASH)) {
|
11724 | this._consumeTagClose(start);
|
11725 | }
|
11726 | else {
|
11727 | this._consumeTagOpen(start);
|
11728 | }
|
11729 | }
|
11730 | else if (!(this._tokenizeIcu && this._tokenizeExpansionForm())) {
|
11731 | this._consumeText();
|
11732 | }
|
11733 | }
|
11734 | catch (e) {
|
11735 | this.handleError(e);
|
11736 | }
|
11737 | }
|
11738 | this._beginToken(TokenType.EOF);
|
11739 | this._endToken([]);
|
11740 | }
|
11741 | /**
|
11742 | * @returns whether an ICU token has been created
|
11743 | * @internal
|
11744 | */
|
11745 | _tokenizeExpansionForm() {
|
11746 | if (this.isExpansionFormStart()) {
|
11747 | this._consumeExpansionFormStart();
|
11748 | return true;
|
11749 | }
|
11750 | if (isExpansionCaseStart(this._cursor.peek()) && this._isInExpansionForm()) {
|
11751 | this._consumeExpansionCaseStart();
|
11752 | return true;
|
11753 | }
|
11754 | if (this._cursor.peek() === $RBRACE) {
|
11755 | if (this._isInExpansionCase()) {
|
11756 | this._consumeExpansionCaseEnd();
|
11757 | return true;
|
11758 | }
|
11759 | if (this._isInExpansionForm()) {
|
11760 | this._consumeExpansionFormEnd();
|
11761 | return true;
|
11762 | }
|
11763 | }
|
11764 | return false;
|
11765 | }
|
11766 | _beginToken(type, start = this._cursor.clone()) {
|
11767 | this._currentTokenStart = start;
|
11768 | this._currentTokenType = type;
|
11769 | }
|
11770 | _endToken(parts, end) {
|
11771 | if (this._currentTokenStart === null) {
|
11772 | throw new TokenError('Programming error - attempted to end a token when there was no start to the token', this._currentTokenType, this._cursor.getSpan(end));
|
11773 | }
|
11774 | if (this._currentTokenType === null) {
|
11775 | throw new TokenError('Programming error - attempted to end a token which has no token type', null, this._cursor.getSpan(this._currentTokenStart));
|
11776 | }
|
11777 | const token = new Token(this._currentTokenType, parts, this._cursor.getSpan(this._currentTokenStart, this._leadingTriviaCodePoints));
|
11778 | this.tokens.push(token);
|
11779 | this._currentTokenStart = null;
|
11780 | this._currentTokenType = null;
|
11781 | return token;
|
11782 | }
|
11783 | _createError(msg, span) {
|
11784 | if (this._isInExpansionForm()) {
|
11785 | msg += ` (Do you have an unescaped "{" in your template? Use "{{ '{' }}") to escape it.)`;
|
11786 | }
|
11787 | const error = new TokenError(msg, this._currentTokenType, span);
|
11788 | this._currentTokenStart = null;
|
11789 | this._currentTokenType = null;
|
11790 | return new _ControlFlowError(error);
|
11791 | }
|
11792 | handleError(e) {
|
11793 | if (e instanceof CursorError) {
|
11794 | e = this._createError(e.msg, this._cursor.getSpan(e.cursor));
|
11795 | }
|
11796 | if (e instanceof _ControlFlowError) {
|
11797 | this.errors.push(e.error);
|
11798 | }
|
11799 | else {
|
11800 | throw e;
|
11801 | }
|
11802 | }
|
11803 | _attemptCharCode(charCode) {
|
11804 | if (this._cursor.peek() === charCode) {
|
11805 | this._cursor.advance();
|
11806 | return true;
|
11807 | }
|
11808 | return false;
|
11809 | }
|
11810 | _attemptCharCodeCaseInsensitive(charCode) {
|
11811 | if (compareCharCodeCaseInsensitive(this._cursor.peek(), charCode)) {
|
11812 | this._cursor.advance();
|
11813 | return true;
|
11814 | }
|
11815 | return false;
|
11816 | }
|
11817 | _requireCharCode(charCode) {
|
11818 | const location = this._cursor.clone();
|
11819 | if (!this._attemptCharCode(charCode)) {
|
11820 | throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(location));
|
11821 | }
|
11822 | }
|
11823 | _attemptStr(chars) {
|
11824 | const len = chars.length;
|
11825 | if (this._cursor.charsLeft() < len) {
|
11826 | return false;
|
11827 | }
|
11828 | const initialPosition = this._cursor.clone();
|
11829 | for (let i = 0; i < len; i++) {
|
11830 | if (!this._attemptCharCode(chars.charCodeAt(i))) {
|
11831 | // If attempting to parse the string fails, we want to reset the parser
|
11832 | // to where it was before the attempt
|
11833 | this._cursor = initialPosition;
|
11834 | return false;
|
11835 | }
|
11836 | }
|
11837 | return true;
|
11838 | }
|
11839 | _attemptStrCaseInsensitive(chars) {
|
11840 | for (let i = 0; i < chars.length; i++) {
|
11841 | if (!this._attemptCharCodeCaseInsensitive(chars.charCodeAt(i))) {
|
11842 | return false;
|
11843 | }
|
11844 | }
|
11845 | return true;
|
11846 | }
|
11847 | _requireStr(chars) {
|
11848 | const location = this._cursor.clone();
|
11849 | if (!this._attemptStr(chars)) {
|
11850 | throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(location));
|
11851 | }
|
11852 | }
|
11853 | _attemptCharCodeUntilFn(predicate) {
|
11854 | while (!predicate(this._cursor.peek())) {
|
11855 | this._cursor.advance();
|
11856 | }
|
11857 | }
|
11858 | _requireCharCodeUntilFn(predicate, len) {
|
11859 | const start = this._cursor.clone();
|
11860 | this._attemptCharCodeUntilFn(predicate);
|
11861 | if (this._cursor.diff(start) < len) {
|
11862 | throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(start));
|
11863 | }
|
11864 | }
|
11865 | _attemptUntilChar(char) {
|
11866 | while (this._cursor.peek() !== char) {
|
11867 | this._cursor.advance();
|
11868 | }
|
11869 | }
|
11870 | _readChar(decodeEntities) {
|
11871 | if (decodeEntities && this._cursor.peek() === $AMPERSAND) {
|
11872 | return this._decodeEntity();
|
11873 | }
|
11874 | else {
|
11875 | // Don't rely upon reading directly from `_input` as the actual char value
|
11876 | // may have been generated from an escape sequence.
|
11877 | const char = String.fromCodePoint(this._cursor.peek());
|
11878 | this._cursor.advance();
|
11879 | return char;
|
11880 | }
|
11881 | }
|
11882 | _decodeEntity() {
|
11883 | const start = this._cursor.clone();
|
11884 | this._cursor.advance();
|
11885 | if (this._attemptCharCode($HASH)) {
|
11886 | const isHex = this._attemptCharCode($x) || this._attemptCharCode($X);
|
11887 | const codeStart = this._cursor.clone();
|
11888 | this._attemptCharCodeUntilFn(isDigitEntityEnd);
|
11889 | if (this._cursor.peek() != $SEMICOLON) {
|
11890 | // Advance cursor to include the peeked character in the string provided to the error
|
11891 | // message.
|
11892 | this._cursor.advance();
|
11893 | const entityType = isHex ? CharacterReferenceType.HEX : CharacterReferenceType.DEC;
|
11894 | throw this._createError(_unparsableEntityErrorMsg(entityType, this._cursor.getChars(start)), this._cursor.getSpan());
|
11895 | }
|
11896 | const strNum = this._cursor.getChars(codeStart);
|
11897 | this._cursor.advance();
|
11898 | try {
|
11899 | const charCode = parseInt(strNum, isHex ? 16 : 10);
|
11900 | return String.fromCharCode(charCode);
|
11901 | }
|
11902 | catch (_a) {
|
11903 | throw this._createError(_unknownEntityErrorMsg(this._cursor.getChars(start)), this._cursor.getSpan());
|
11904 | }
|
11905 | }
|
11906 | else {
|
11907 | const nameStart = this._cursor.clone();
|
11908 | this._attemptCharCodeUntilFn(isNamedEntityEnd);
|
11909 | if (this._cursor.peek() != $SEMICOLON) {
|
11910 | this._cursor = nameStart;
|
11911 | return '&';
|
11912 | }
|
11913 | const name = this._cursor.getChars(nameStart);
|
11914 | this._cursor.advance();
|
11915 | const char = NAMED_ENTITIES[name];
|
11916 | if (!char) {
|
11917 | throw this._createError(_unknownEntityErrorMsg(name), this._cursor.getSpan(start));
|
11918 | }
|
11919 | return char;
|
11920 | }
|
11921 | }
|
11922 | _consumeRawText(decodeEntities, endMarkerPredicate) {
|
11923 | this._beginToken(decodeEntities ? TokenType.ESCAPABLE_RAW_TEXT : TokenType.RAW_TEXT);
|
11924 | const parts = [];
|
11925 | while (true) {
|
11926 | const tagCloseStart = this._cursor.clone();
|
11927 | const foundEndMarker = endMarkerPredicate();
|
11928 | this._cursor = tagCloseStart;
|
11929 | if (foundEndMarker) {
|
11930 | break;
|
11931 | }
|
11932 | parts.push(this._readChar(decodeEntities));
|
11933 | }
|
11934 | return this._endToken([this._processCarriageReturns(parts.join(''))]);
|
11935 | }
|
11936 | _consumeComment(start) {
|
11937 | this._beginToken(TokenType.COMMENT_START, start);
|
11938 | this._requireCharCode($MINUS);
|
11939 | this._endToken([]);
|
11940 | this._consumeRawText(false, () => this._attemptStr('-->'));
|
11941 | this._beginToken(TokenType.COMMENT_END);
|
11942 | this._requireStr('-->');
|
11943 | this._endToken([]);
|
11944 | }
|
11945 | _consumeCdata(start) {
|
11946 | this._beginToken(TokenType.CDATA_START, start);
|
11947 | this._requireStr('CDATA[');
|
11948 | this._endToken([]);
|
11949 | this._consumeRawText(false, () => this._attemptStr(']]>'));
|
11950 | this._beginToken(TokenType.CDATA_END);
|
11951 | this._requireStr(']]>');
|
11952 | this._endToken([]);
|
11953 | }
|
11954 | _consumeDocType(start) {
|
11955 | this._beginToken(TokenType.DOC_TYPE, start);
|
11956 | const contentStart = this._cursor.clone();
|
11957 | this._attemptUntilChar($GT);
|
11958 | const content = this._cursor.getChars(contentStart);
|
11959 | this._cursor.advance();
|
11960 | this._endToken([content]);
|
11961 | }
|
11962 | _consumePrefixAndName() {
|
11963 | const nameOrPrefixStart = this._cursor.clone();
|
11964 | let prefix = '';
|
11965 | while (this._cursor.peek() !== $COLON && !isPrefixEnd(this._cursor.peek())) {
|
11966 | this._cursor.advance();
|
11967 | }
|
11968 | let nameStart;
|
11969 | if (this._cursor.peek() === $COLON) {
|
11970 | prefix = this._cursor.getChars(nameOrPrefixStart);
|
11971 | this._cursor.advance();
|
11972 | nameStart = this._cursor.clone();
|
11973 | }
|
11974 | else {
|
11975 | nameStart = nameOrPrefixStart;
|
11976 | }
|
11977 | this._requireCharCodeUntilFn(isNameEnd, prefix === '' ? 0 : 1);
|
11978 | const name = this._cursor.getChars(nameStart);
|
11979 | return [prefix, name];
|
11980 | }
|
11981 | _consumeTagOpen(start) {
|
11982 | let tagName;
|
11983 | let prefix;
|
11984 | let openTagToken;
|
11985 | try {
|
11986 | if (!isAsciiLetter(this._cursor.peek())) {
|
11987 | throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(start));
|
11988 | }
|
11989 | openTagToken = this._consumeTagOpenStart(start);
|
11990 | prefix = openTagToken.parts[0];
|
11991 | tagName = openTagToken.parts[1];
|
11992 | this._attemptCharCodeUntilFn(isNotWhitespace);
|
11993 | while (this._cursor.peek() !== $SLASH && this._cursor.peek() !== $GT &&
|
11994 | this._cursor.peek() !== $LT && this._cursor.peek() !== $EOF) {
|
11995 | this._consumeAttributeName();
|
11996 | this._attemptCharCodeUntilFn(isNotWhitespace);
|
11997 | if (this._attemptCharCode($EQ)) {
|
11998 | this._attemptCharCodeUntilFn(isNotWhitespace);
|
11999 | this._consumeAttributeValue();
|
12000 | }
|
12001 | this._attemptCharCodeUntilFn(isNotWhitespace);
|
12002 | }
|
12003 | this._consumeTagOpenEnd();
|
12004 | }
|
12005 | catch (e) {
|
12006 | if (e instanceof _ControlFlowError) {
|
12007 | if (openTagToken) {
|
12008 | // We errored before we could close the opening tag, so it is incomplete.
|
12009 | openTagToken.type = TokenType.INCOMPLETE_TAG_OPEN;
|
12010 | }
|
12011 | else {
|
12012 | // When the start tag is invalid, assume we want a "<" as text.
|
12013 | // Back to back text tokens are merged at the end.
|
12014 | this._beginToken(TokenType.TEXT, start);
|
12015 | this._endToken(['<']);
|
12016 | }
|
12017 | return;
|
12018 | }
|
12019 | throw e;
|
12020 | }
|
12021 | const contentTokenType = this._getTagDefinition(tagName).getContentType(prefix);
|
12022 | if (contentTokenType === TagContentType.RAW_TEXT) {
|
12023 | this._consumeRawTextWithTagClose(prefix, tagName, false);
|
12024 | }
|
12025 | else if (contentTokenType === TagContentType.ESCAPABLE_RAW_TEXT) {
|
12026 | this._consumeRawTextWithTagClose(prefix, tagName, true);
|
12027 | }
|
12028 | }
|
12029 | _consumeRawTextWithTagClose(prefix, tagName, decodeEntities) {
|
12030 | this._consumeRawText(decodeEntities, () => {
|
12031 | if (!this._attemptCharCode($LT))
|
12032 | return false;
|
12033 | if (!this._attemptCharCode($SLASH))
|
12034 | return false;
|
12035 | this._attemptCharCodeUntilFn(isNotWhitespace);
|
12036 | if (!this._attemptStrCaseInsensitive(tagName))
|
12037 | return false;
|
12038 | this._attemptCharCodeUntilFn(isNotWhitespace);
|
12039 | return this._attemptCharCode($GT);
|
12040 | });
|
12041 | this._beginToken(TokenType.TAG_CLOSE);
|
12042 | this._requireCharCodeUntilFn(code => code === $GT, 3);
|
12043 | this._cursor.advance(); // Consume the `>`
|
12044 | this._endToken([prefix, tagName]);
|
12045 | }
|
12046 | _consumeTagOpenStart(start) {
|
12047 | this._beginToken(TokenType.TAG_OPEN_START, start);
|
12048 | const parts = this._consumePrefixAndName();
|
12049 | return this._endToken(parts);
|
12050 | }
|
12051 | _consumeAttributeName() {
|
12052 | const attrNameStart = this._cursor.peek();
|
12053 | if (attrNameStart === $SQ || attrNameStart === $DQ) {
|
12054 | throw this._createError(_unexpectedCharacterErrorMsg(attrNameStart), this._cursor.getSpan());
|
12055 | }
|
12056 | this._beginToken(TokenType.ATTR_NAME);
|
12057 | const prefixAndName = this._consumePrefixAndName();
|
12058 | this._endToken(prefixAndName);
|
12059 | }
|
12060 | _consumeAttributeValue() {
|
12061 | let value;
|
12062 | if (this._cursor.peek() === $SQ || this._cursor.peek() === $DQ) {
|
12063 | this._beginToken(TokenType.ATTR_QUOTE);
|
12064 | const quoteChar = this._cursor.peek();
|
12065 | this._cursor.advance();
|
12066 | this._endToken([String.fromCodePoint(quoteChar)]);
|
12067 | this._beginToken(TokenType.ATTR_VALUE);
|
12068 | const parts = [];
|
12069 | while (this._cursor.peek() !== quoteChar) {
|
12070 | parts.push(this._readChar(true));
|
12071 | }
|
12072 | value = parts.join('');
|
12073 | this._endToken([this._processCarriageReturns(value)]);
|
12074 | this._beginToken(TokenType.ATTR_QUOTE);
|
12075 | this._cursor.advance();
|
12076 | this._endToken([String.fromCodePoint(quoteChar)]);
|
12077 | }
|
12078 | else {
|
12079 | this._beginToken(TokenType.ATTR_VALUE);
|
12080 | const valueStart = this._cursor.clone();
|
12081 | this._requireCharCodeUntilFn(isNameEnd, 1);
|
12082 | value = this._cursor.getChars(valueStart);
|
12083 | this._endToken([this._processCarriageReturns(value)]);
|
12084 | }
|
12085 | }
|
12086 | _consumeTagOpenEnd() {
|
12087 | const tokenType = this._attemptCharCode($SLASH) ? TokenType.TAG_OPEN_END_VOID : TokenType.TAG_OPEN_END;
|
12088 | this._beginToken(tokenType);
|
12089 | this._requireCharCode($GT);
|
12090 | this._endToken([]);
|
12091 | }
|
12092 | _consumeTagClose(start) {
|
12093 | this._beginToken(TokenType.TAG_CLOSE, start);
|
12094 | this._attemptCharCodeUntilFn(isNotWhitespace);
|
12095 | const prefixAndName = this._consumePrefixAndName();
|
12096 | this._attemptCharCodeUntilFn(isNotWhitespace);
|
12097 | this._requireCharCode($GT);
|
12098 | this._endToken(prefixAndName);
|
12099 | }
|
12100 | _consumeExpansionFormStart() {
|
12101 | this._beginToken(TokenType.EXPANSION_FORM_START);
|
12102 | this._requireCharCode($LBRACE);
|
12103 | this._endToken([]);
|
12104 | this._expansionCaseStack.push(TokenType.EXPANSION_FORM_START);
|
12105 | this._beginToken(TokenType.RAW_TEXT);
|
12106 | const condition = this._readUntil($COMMA);
|
12107 | const normalizedCondition = this._processCarriageReturns(condition);
|
12108 | if (this._i18nNormalizeLineEndingsInICUs) {
|
12109 | // We explicitly want to normalize line endings for this text.
|
12110 | this._endToken([normalizedCondition]);
|
12111 | }
|
12112 | else {
|
12113 | // We are not normalizing line endings.
|
12114 | const conditionToken = this._endToken([condition]);
|
12115 | if (normalizedCondition !== condition) {
|
12116 | this.nonNormalizedIcuExpressions.push(conditionToken);
|
12117 | }
|
12118 | }
|
12119 | this._requireCharCode($COMMA);
|
12120 | this._attemptCharCodeUntilFn(isNotWhitespace);
|
12121 | this._beginToken(TokenType.RAW_TEXT);
|
12122 | const type = this._readUntil($COMMA);
|
12123 | this._endToken([type]);
|
12124 | this._requireCharCode($COMMA);
|
12125 | this._attemptCharCodeUntilFn(isNotWhitespace);
|
12126 | }
|
12127 | _consumeExpansionCaseStart() {
|
12128 | this._beginToken(TokenType.EXPANSION_CASE_VALUE);
|
12129 | const value = this._readUntil($LBRACE).trim();
|
12130 | this._endToken([value]);
|
12131 | this._attemptCharCodeUntilFn(isNotWhitespace);
|
12132 | this._beginToken(TokenType.EXPANSION_CASE_EXP_START);
|
12133 | this._requireCharCode($LBRACE);
|
12134 | this._endToken([]);
|
12135 | this._attemptCharCodeUntilFn(isNotWhitespace);
|
12136 | this._expansionCaseStack.push(TokenType.EXPANSION_CASE_EXP_START);
|
12137 | }
|
12138 | _consumeExpansionCaseEnd() {
|
12139 | this._beginToken(TokenType.EXPANSION_CASE_EXP_END);
|
12140 | this._requireCharCode($RBRACE);
|
12141 | this._endToken([]);
|
12142 | this._attemptCharCodeUntilFn(isNotWhitespace);
|
12143 | this._expansionCaseStack.pop();
|
12144 | }
|
12145 | _consumeExpansionFormEnd() {
|
12146 | this._beginToken(TokenType.EXPANSION_FORM_END);
|
12147 | this._requireCharCode($RBRACE);
|
12148 | this._endToken([]);
|
12149 | this._expansionCaseStack.pop();
|
12150 | }
|
12151 | _consumeText() {
|
12152 | const start = this._cursor.clone();
|
12153 | this._beginToken(TokenType.TEXT, start);
|
12154 | const parts = [];
|
12155 | do {
|
12156 | if (this._interpolationConfig && this._attemptStr(this._interpolationConfig.start)) {
|
12157 | parts.push(this._interpolationConfig.start);
|
12158 | this._inInterpolation = true;
|
12159 | }
|
12160 | else if (this._interpolationConfig && this._inInterpolation &&
|
12161 | this._attemptStr(this._interpolationConfig.end)) {
|
12162 | parts.push(this._interpolationConfig.end);
|
12163 | this._inInterpolation = false;
|
12164 | }
|
12165 | else {
|
12166 | parts.push(this._readChar(true));
|
12167 | }
|
12168 | } while (!this._isTextEnd());
|
12169 | this._endToken([this._processCarriageReturns(parts.join(''))]);
|
12170 | }
|
12171 | _isTextEnd() {
|
12172 | if (this._cursor.peek() === $LT || this._cursor.peek() === $EOF) {
|
12173 | return true;
|
12174 | }
|
12175 | if (this._tokenizeIcu && !this._inInterpolation) {
|
12176 | if (this.isExpansionFormStart()) {
|
12177 | // start of an expansion form
|
12178 | return true;
|
12179 | }
|
12180 | if (this._cursor.peek() === $RBRACE && this._isInExpansionCase()) {
|
12181 | // end of and expansion case
|
12182 | return true;
|
12183 | }
|
12184 | }
|
12185 | return false;
|
12186 | }
|
12187 | _readUntil(char) {
|
12188 | const start = this._cursor.clone();
|
12189 | this._attemptUntilChar(char);
|
12190 | return this._cursor.getChars(start);
|
12191 | }
|
12192 | _isInExpansionCase() {
|
12193 | return this._expansionCaseStack.length > 0 &&
|
12194 | this._expansionCaseStack[this._expansionCaseStack.length - 1] ===
|
12195 | TokenType.EXPANSION_CASE_EXP_START;
|
12196 | }
|
12197 | _isInExpansionForm() {
|
12198 | return this._expansionCaseStack.length > 0 &&
|
12199 | this._expansionCaseStack[this._expansionCaseStack.length - 1] ===
|
12200 | TokenType.EXPANSION_FORM_START;
|
12201 | }
|
12202 | isExpansionFormStart() {
|
12203 | if (this._cursor.peek() !== $LBRACE) {
|
12204 | return false;
|
12205 | }
|
12206 | if (this._interpolationConfig) {
|
12207 | const start = this._cursor.clone();
|
12208 | const isInterpolation = this._attemptStr(this._interpolationConfig.start);
|
12209 | this._cursor = start;
|
12210 | return !isInterpolation;
|
12211 | }
|
12212 | return true;
|
12213 | }
|
12214 | }
|
12215 | function isNotWhitespace(code) {
|
12216 | return !isWhitespace(code) || code === $EOF;
|
12217 | }
|
12218 | function isNameEnd(code) {
|
12219 | return isWhitespace(code) || code === $GT || code === $LT ||
|
12220 | code === $SLASH || code === $SQ || code === $DQ || code === $EQ ||
|
12221 | code === $EOF;
|
12222 | }
|
12223 | function isPrefixEnd(code) {
|
12224 | return (code < $a || $z < code) && (code < $A || $Z < code) &&
|
12225 | (code < $0 || code > $9);
|
12226 | }
|
12227 | function isDigitEntityEnd(code) {
|
12228 | return code == $SEMICOLON || code == $EOF || !isAsciiHexDigit(code);
|
12229 | }
|
12230 | function isNamedEntityEnd(code) {
|
12231 | return code == $SEMICOLON || code == $EOF || !isAsciiLetter(code);
|
12232 | }
|
12233 | function isExpansionCaseStart(peek) {
|
12234 | return peek !== $RBRACE;
|
12235 | }
|
12236 | function compareCharCodeCaseInsensitive(code1, code2) {
|
12237 | return toUpperCaseCharCode(code1) == toUpperCaseCharCode(code2);
|
12238 | }
|
12239 | function toUpperCaseCharCode(code) {
|
12240 | return code >= $a && code <= $z ? code - $a + $A : code;
|
12241 | }
|
12242 | function mergeTextTokens(srcTokens) {
|
12243 | const dstTokens = [];
|
12244 | let lastDstToken = undefined;
|
12245 | for (let i = 0; i < srcTokens.length; i++) {
|
12246 | const token = srcTokens[i];
|
12247 | if (lastDstToken && lastDstToken.type == TokenType.TEXT && token.type == TokenType.TEXT) {
|
12248 | lastDstToken.parts[0] += token.parts[0];
|
12249 | lastDstToken.sourceSpan.end = token.sourceSpan.end;
|
12250 | }
|
12251 | else {
|
12252 | lastDstToken = token;
|
12253 | dstTokens.push(lastDstToken);
|
12254 | }
|
12255 | }
|
12256 | return dstTokens;
|
12257 | }
|
12258 | class PlainCharacterCursor {
|
12259 | constructor(fileOrCursor, range) {
|
12260 | if (fileOrCursor instanceof PlainCharacterCursor) {
|
12261 | this.file = fileOrCursor.file;
|
12262 | this.input = fileOrCursor.input;
|
12263 | this.end = fileOrCursor.end;
|
12264 | const state = fileOrCursor.state;
|
12265 | // Note: avoid using `{...fileOrCursor.state}` here as that has a severe performance penalty.
|
12266 | // In ES5 bundles the object spread operator is translated into the `__assign` helper, which
|
12267 | // is not optimized by VMs as efficiently as a raw object literal. Since this constructor is
|
12268 | // called in tight loops, this difference matters.
|
12269 | this.state = {
|
12270 | peek: state.peek,
|
12271 | offset: state.offset,
|
12272 | line: state.line,
|
12273 | column: state.column,
|
12274 | };
|
12275 | }
|
12276 | else {
|
12277 | if (!range) {
|
12278 | throw new Error('Programming error: the range argument must be provided with a file argument.');
|
12279 | }
|
12280 | this.file = fileOrCursor;
|
12281 | this.input = fileOrCursor.content;
|
12282 | this.end = range.endPos;
|
12283 | this.state = {
|
12284 | peek: -1,
|
12285 | offset: range.startPos,
|
12286 | line: range.startLine,
|
12287 | column: range.startCol,
|
12288 | };
|
12289 | }
|
12290 | }
|
12291 | clone() {
|
12292 | return new PlainCharacterCursor(this);
|
12293 | }
|
12294 | peek() {
|
12295 | return this.state.peek;
|
12296 | }
|
12297 | charsLeft() {
|
12298 | return this.end - this.state.offset;
|
12299 | }
|
12300 | diff(other) {
|
12301 | return this.state.offset - other.state.offset;
|
12302 | }
|
12303 | advance() {
|
12304 | this.advanceState(this.state);
|
12305 | }
|
12306 | init() {
|
12307 | this.updatePeek(this.state);
|
12308 | }
|
12309 | getSpan(start, leadingTriviaCodePoints) {
|
12310 | start = start || this;
|
12311 | let fullStart = start;
|
12312 | if (leadingTriviaCodePoints) {
|
12313 | while (this.diff(start) > 0 && leadingTriviaCodePoints.indexOf(start.peek()) !== -1) {
|
12314 | if (fullStart === start) {
|
12315 | start = start.clone();
|
12316 | }
|
12317 | start.advance();
|
12318 | }
|
12319 | }
|
12320 | const startLocation = this.locationFromCursor(start);
|
12321 | const endLocation = this.locationFromCursor(this);
|
12322 | const fullStartLocation = fullStart !== start ? this.locationFromCursor(fullStart) : startLocation;
|
12323 | return new ParseSourceSpan(startLocation, endLocation, fullStartLocation);
|
12324 | }
|
12325 | getChars(start) {
|
12326 | return this.input.substring(start.state.offset, this.state.offset);
|
12327 | }
|
12328 | charAt(pos) {
|
12329 | return this.input.charCodeAt(pos);
|
12330 | }
|
12331 | advanceState(state) {
|
12332 | if (state.offset >= this.end) {
|
12333 | this.state = state;
|
12334 | throw new CursorError('Unexpected character "EOF"', this);
|
12335 | }
|
12336 | const currentChar = this.charAt(state.offset);
|
12337 | if (currentChar === $LF) {
|
12338 | state.line++;
|
12339 | state.column = 0;
|
12340 | }
|
12341 | else if (!isNewLine(currentChar)) {
|
12342 | state.column++;
|
12343 | }
|
12344 | state.offset++;
|
12345 | this.updatePeek(state);
|
12346 | }
|
12347 | updatePeek(state) {
|
12348 | state.peek = state.offset >= this.end ? $EOF : this.charAt(state.offset);
|
12349 | }
|
12350 | locationFromCursor(cursor) {
|
12351 | return new ParseLocation(cursor.file, cursor.state.offset, cursor.state.line, cursor.state.column);
|
12352 | }
|
12353 | }
|
12354 | class EscapedCharacterCursor extends PlainCharacterCursor {
|
12355 | constructor(fileOrCursor, range) {
|
12356 | if (fileOrCursor instanceof EscapedCharacterCursor) {
|
12357 | super(fileOrCursor);
|
12358 | this.internalState = Object.assign({}, fileOrCursor.internalState);
|
12359 | }
|
12360 | else {
|
12361 | super(fileOrCursor, range);
|
12362 | this.internalState = this.state;
|
12363 | }
|
12364 | }
|
12365 | advance() {
|
12366 | this.state = this.internalState;
|
12367 | super.advance();
|
12368 | this.processEscapeSequence();
|
12369 | }
|
12370 | init() {
|
12371 | super.init();
|
12372 | this.processEscapeSequence();
|
12373 | }
|
12374 | clone() {
|
12375 | return new EscapedCharacterCursor(this);
|
12376 | }
|
12377 | getChars(start) {
|
12378 | const cursor = start.clone();
|
12379 | let chars = '';
|
12380 | while (cursor.internalState.offset < this.internalState.offset) {
|
12381 | chars += String.fromCodePoint(cursor.peek());
|
12382 | cursor.advance();
|
12383 | }
|
12384 | return chars;
|
12385 | }
|
12386 | /**
|
12387 | * Process the escape sequence that starts at the current position in the text.
|
12388 | *
|
12389 | * This method is called to ensure that `peek` has the unescaped value of escape sequences.
|
12390 | */
|
12391 | processEscapeSequence() {
|
12392 | const peek = () => this.internalState.peek;
|
12393 | if (peek() === $BACKSLASH) {
|
12394 | // We have hit an escape sequence so we need the internal state to become independent
|
12395 | // of the external state.
|
12396 | this.internalState = Object.assign({}, this.state);
|
12397 | // Move past the backslash
|
12398 | this.advanceState(this.internalState);
|
12399 | // First check for standard control char sequences
|
12400 | if (peek() === $n) {
|
12401 | this.state.peek = $LF;
|
12402 | }
|
12403 | else if (peek() === $r) {
|
12404 | this.state.peek = $CR;
|
12405 | }
|
12406 | else if (peek() === $v) {
|
12407 | this.state.peek = $VTAB;
|
12408 | }
|
12409 | else if (peek() === $t) {
|
12410 | this.state.peek = $TAB;
|
12411 | }
|
12412 | else if (peek() === $b) {
|
12413 | this.state.peek = $BSPACE;
|
12414 | }
|
12415 | else if (peek() === $f) {
|
12416 | this.state.peek = $FF;
|
12417 | }
|
12418 | // Now consider more complex sequences
|
12419 | else if (peek() === $u) {
|
12420 | // Unicode code-point sequence
|
12421 | this.advanceState(this.internalState); // advance past the `u` char
|
12422 | if (peek() === $LBRACE) {
|
12423 | // Variable length Unicode, e.g. `\x{123}`
|
12424 | this.advanceState(this.internalState); // advance past the `{` char
|
12425 | // Advance past the variable number of hex digits until we hit a `}` char
|
12426 | const digitStart = this.clone();
|
12427 | let length = 0;
|
12428 | while (peek() !== $RBRACE) {
|
12429 | this.advanceState(this.internalState);
|
12430 | length++;
|
12431 | }
|
12432 | this.state.peek = this.decodeHexDigits(digitStart, length);
|
12433 | }
|
12434 | else {
|
12435 | // Fixed length Unicode, e.g. `\u1234`
|
12436 | const digitStart = this.clone();
|
12437 | this.advanceState(this.internalState);
|
12438 | this.advanceState(this.internalState);
|
12439 | this.advanceState(this.internalState);
|
12440 | this.state.peek = this.decodeHexDigits(digitStart, 4);
|
12441 | }
|
12442 | }
|
12443 | else if (peek() === $x) {
|
12444 | // Hex char code, e.g. `\x2F`
|
12445 | this.advanceState(this.internalState); // advance past the `x` char
|
12446 | const digitStart = this.clone();
|
12447 | this.advanceState(this.internalState);
|
12448 | this.state.peek = this.decodeHexDigits(digitStart, 2);
|
12449 | }
|
12450 | else if (isOctalDigit(peek())) {
|
12451 | // Octal char code, e.g. `\012`,
|
12452 | let octal = '';
|
12453 | let length = 0;
|
12454 | let previous = this.clone();
|
12455 | while (isOctalDigit(peek()) && length < 3) {
|
12456 | previous = this.clone();
|
12457 | octal += String.fromCodePoint(peek());
|
12458 | this.advanceState(this.internalState);
|
12459 | length++;
|
12460 | }
|
12461 | this.state.peek = parseInt(octal, 8);
|
12462 | // Backup one char
|
12463 | this.internalState = previous.internalState;
|
12464 | }
|
12465 | else if (isNewLine(this.internalState.peek)) {
|
12466 | // Line continuation `\` followed by a new line
|
12467 | this.advanceState(this.internalState); // advance over the newline
|
12468 | this.state = this.internalState;
|
12469 | }
|
12470 | else {
|
12471 | // If none of the `if` blocks were executed then we just have an escaped normal character.
|
12472 | // In that case we just, effectively, skip the backslash from the character.
|
12473 | this.state.peek = this.internalState.peek;
|
12474 | }
|
12475 | }
|
12476 | }
|
12477 | decodeHexDigits(start, length) {
|
12478 | const hex = this.input.substr(start.internalState.offset, length);
|
12479 | const charCode = parseInt(hex, 16);
|
12480 | if (!isNaN(charCode)) {
|
12481 | return charCode;
|
12482 | }
|
12483 | else {
|
12484 | start.state = start.internalState;
|
12485 | throw new CursorError('Invalid hexadecimal escape sequence', start);
|
12486 | }
|
12487 | }
|
12488 | }
|
12489 | class CursorError {
|
12490 | constructor(msg, cursor) {
|
12491 | this.msg = msg;
|
12492 | this.cursor = cursor;
|
12493 | }
|
12494 | }
|
12495 |
|
12496 | /**
|
12497 | * @license
|
12498 | * Copyright Google LLC All Rights Reserved.
|
12499 | *
|
12500 | * Use of this source code is governed by an MIT-style license that can be
|
12501 | * found in the LICENSE file at https://angular.io/license
|
12502 | */
|
12503 | class TreeError extends ParseError {
|
12504 | constructor(elementName, span, msg) {
|
12505 | super(span, msg);
|
12506 | this.elementName = elementName;
|
12507 | }
|
12508 | static create(elementName, span, msg) {
|
12509 | return new TreeError(elementName, span, msg);
|
12510 | }
|
12511 | }
|
12512 | class ParseTreeResult {
|
12513 | constructor(rootNodes, errors) {
|
12514 | this.rootNodes = rootNodes;
|
12515 | this.errors = errors;
|
12516 | }
|
12517 | }
|
12518 | class Parser {
|
12519 | constructor(getTagDefinition) {
|
12520 | this.getTagDefinition = getTagDefinition;
|
12521 | }
|
12522 | parse(source, url, options) {
|
12523 | const tokenizeResult = tokenize(source, url, this.getTagDefinition, options);
|
12524 | const parser = new _TreeBuilder(tokenizeResult.tokens, this.getTagDefinition);
|
12525 | parser.build();
|
12526 | return new ParseTreeResult(parser.rootNodes, tokenizeResult.errors.concat(parser.errors));
|
12527 | }
|
12528 | }
|
12529 | class _TreeBuilder {
|
12530 | constructor(tokens, getTagDefinition) {
|
12531 | this.tokens = tokens;
|
12532 | this.getTagDefinition = getTagDefinition;
|
12533 | this._index = -1;
|
12534 | this._elementStack = [];
|
12535 | this.rootNodes = [];
|
12536 | this.errors = [];
|
12537 | this._advance();
|
12538 | }
|
12539 | build() {
|
12540 | while (this._peek.type !== TokenType.EOF) {
|
12541 | if (this._peek.type === TokenType.TAG_OPEN_START ||
|
12542 | this._peek.type === TokenType.INCOMPLETE_TAG_OPEN) {
|
12543 | this._consumeStartTag(this._advance());
|
12544 | }
|
12545 | else if (this._peek.type === TokenType.TAG_CLOSE) {
|
12546 | this._consumeEndTag(this._advance());
|
12547 | }
|
12548 | else if (this._peek.type === TokenType.CDATA_START) {
|
12549 | this._closeVoidElement();
|
12550 | this._consumeCdata(this._advance());
|
12551 | }
|
12552 | else if (this._peek.type === TokenType.COMMENT_START) {
|
12553 | this._closeVoidElement();
|
12554 | this._consumeComment(this._advance());
|
12555 | }
|
12556 | else if (this._peek.type === TokenType.TEXT || this._peek.type === TokenType.RAW_TEXT ||
|
12557 | this._peek.type === TokenType.ESCAPABLE_RAW_TEXT) {
|
12558 | this._closeVoidElement();
|
12559 | this._consumeText(this._advance());
|
12560 | }
|
12561 | else if (this._peek.type === TokenType.EXPANSION_FORM_START) {
|
12562 | this._consumeExpansion(this._advance());
|
12563 | }
|
12564 | else {
|
12565 | // Skip all other tokens...
|
12566 | this._advance();
|
12567 | }
|
12568 | }
|
12569 | }
|
12570 | _advance() {
|
12571 | const prev = this._peek;
|
12572 | if (this._index < this.tokens.length - 1) {
|
12573 | // Note: there is always an EOF token at the end
|
12574 | this._index++;
|
12575 | }
|
12576 | this._peek = this.tokens[this._index];
|
12577 | return prev;
|
12578 | }
|
12579 | _advanceIf(type) {
|
12580 | if (this._peek.type === type) {
|
12581 | return this._advance();
|
12582 | }
|
12583 | return null;
|
12584 | }
|
12585 | _consumeCdata(_startToken) {
|
12586 | this._consumeText(this._advance());
|
12587 | this._advanceIf(TokenType.CDATA_END);
|
12588 | }
|
12589 | _consumeComment(token) {
|
12590 | const text = this._advanceIf(TokenType.RAW_TEXT);
|
12591 | this._advanceIf(TokenType.COMMENT_END);
|
12592 | const value = text != null ? text.parts[0].trim() : null;
|
12593 | this._addToParent(new Comment(value, token.sourceSpan));
|
12594 | }
|
12595 | _consumeExpansion(token) {
|
12596 | const switchValue = this._advance();
|
12597 | const type = this._advance();
|
12598 | const cases = [];
|
12599 | // read =
|
12600 | while (this._peek.type === TokenType.EXPANSION_CASE_VALUE) {
|
12601 | const expCase = this._parseExpansionCase();
|
12602 | if (!expCase)
|
12603 | return; // error
|
12604 | cases.push(expCase);
|
12605 | }
|
12606 | // read the final }
|
12607 | if (this._peek.type !== TokenType.EXPANSION_FORM_END) {
|
12608 | this.errors.push(TreeError.create(null, this._peek.sourceSpan, `Invalid ICU message. Missing '}'.`));
|
12609 | return;
|
12610 | }
|
12611 | const sourceSpan = new ParseSourceSpan(token.sourceSpan.start, this._peek.sourceSpan.end, token.sourceSpan.fullStart);
|
12612 | this._addToParent(new Expansion(switchValue.parts[0], type.parts[0], cases, sourceSpan, switchValue.sourceSpan));
|
12613 | this._advance();
|
12614 | }
|
12615 | _parseExpansionCase() {
|
12616 | const value = this._advance();
|
12617 | // read {
|
12618 | if (this._peek.type !== TokenType.EXPANSION_CASE_EXP_START) {
|
12619 | this.errors.push(TreeError.create(null, this._peek.sourceSpan, `Invalid ICU message. Missing '{'.`));
|
12620 | return null;
|
12621 | }
|
12622 | // read until }
|
12623 | const start = this._advance();
|
12624 | const exp = this._collectExpansionExpTokens(start);
|
12625 | if (!exp)
|
12626 | return null;
|
12627 | const end = this._advance();
|
12628 | exp.push(new Token(TokenType.EOF, [], end.sourceSpan));
|
12629 | // parse everything in between { and }
|
12630 | const expansionCaseParser = new _TreeBuilder(exp, this.getTagDefinition);
|
12631 | expansionCaseParser.build();
|
12632 | if (expansionCaseParser.errors.length > 0) {
|
12633 | this.errors = this.errors.concat(expansionCaseParser.errors);
|
12634 | return null;
|
12635 | }
|
12636 | const sourceSpan = new ParseSourceSpan(value.sourceSpan.start, end.sourceSpan.end, value.sourceSpan.fullStart);
|
12637 | const expSourceSpan = new ParseSourceSpan(start.sourceSpan.start, end.sourceSpan.end, start.sourceSpan.fullStart);
|
12638 | return new ExpansionCase(value.parts[0], expansionCaseParser.rootNodes, sourceSpan, value.sourceSpan, expSourceSpan);
|
12639 | }
|
12640 | _collectExpansionExpTokens(start) {
|
12641 | const exp = [];
|
12642 | const expansionFormStack = [TokenType.EXPANSION_CASE_EXP_START];
|
12643 | while (true) {
|
12644 | if (this._peek.type === TokenType.EXPANSION_FORM_START ||
|
12645 | this._peek.type === TokenType.EXPANSION_CASE_EXP_START) {
|
12646 | expansionFormStack.push(this._peek.type);
|
12647 | }
|
12648 | if (this._peek.type === TokenType.EXPANSION_CASE_EXP_END) {
|
12649 | if (lastOnStack(expansionFormStack, TokenType.EXPANSION_CASE_EXP_START)) {
|
12650 | expansionFormStack.pop();
|
12651 | if (expansionFormStack.length == 0)
|
12652 | return exp;
|
12653 | }
|
12654 | else {
|
12655 | this.errors.push(TreeError.create(null, start.sourceSpan, `Invalid ICU message. Missing '}'.`));
|
12656 | return null;
|
12657 | }
|
12658 | }
|
12659 | if (this._peek.type === TokenType.EXPANSION_FORM_END) {
|
12660 | if (lastOnStack(expansionFormStack, TokenType.EXPANSION_FORM_START)) {
|
12661 | expansionFormStack.pop();
|
12662 | }
|
12663 | else {
|
12664 | this.errors.push(TreeError.create(null, start.sourceSpan, `Invalid ICU message. Missing '}'.`));
|
12665 | return null;
|
12666 | }
|
12667 | }
|
12668 | if (this._peek.type === TokenType.EOF) {
|
12669 | this.errors.push(TreeError.create(null, start.sourceSpan, `Invalid ICU message. Missing '}'.`));
|
12670 | return null;
|
12671 | }
|
12672 | exp.push(this._advance());
|
12673 | }
|
12674 | }
|
12675 | _consumeText(token) {
|
12676 | let text = token.parts[0];
|
12677 | if (text.length > 0 && text[0] == '\n') {
|
12678 | const parent = this._getParentElement();
|
12679 | if (parent != null && parent.children.length == 0 &&
|
12680 | this.getTagDefinition(parent.name).ignoreFirstLf) {
|
12681 | text = text.substring(1);
|
12682 | }
|
12683 | }
|
12684 | if (text.length > 0) {
|
12685 | this._addToParent(new Text$2(text, token.sourceSpan));
|
12686 | }
|
12687 | }
|
12688 | _closeVoidElement() {
|
12689 | const el = this._getParentElement();
|
12690 | if (el && this.getTagDefinition(el.name).isVoid) {
|
12691 | this._elementStack.pop();
|
12692 | }
|
12693 | }
|
12694 | _consumeStartTag(startTagToken) {
|
12695 | const [prefix, name] = startTagToken.parts;
|
12696 | const attrs = [];
|
12697 | while (this._peek.type === TokenType.ATTR_NAME) {
|
12698 | attrs.push(this._consumeAttr(this._advance()));
|
12699 | }
|
12700 | const fullName = this._getElementFullName(prefix, name, this._getParentElement());
|
12701 | let selfClosing = false;
|
12702 | // Note: There could have been a tokenizer error
|
12703 | // so that we don't get a token for the end tag...
|
12704 | if (this._peek.type === TokenType.TAG_OPEN_END_VOID) {
|
12705 | this._advance();
|
12706 | selfClosing = true;
|
12707 | const tagDef = this.getTagDefinition(fullName);
|
12708 | if (!(tagDef.canSelfClose || getNsPrefix(fullName) !== null || tagDef.isVoid)) {
|
12709 | this.errors.push(TreeError.create(fullName, startTagToken.sourceSpan, `Only void and foreign elements can be self closed "${startTagToken.parts[1]}"`));
|
12710 | }
|
12711 | }
|
12712 | else if (this._peek.type === TokenType.TAG_OPEN_END) {
|
12713 | this._advance();
|
12714 | selfClosing = false;
|
12715 | }
|
12716 | const end = this._peek.sourceSpan.fullStart;
|
12717 | const span = new ParseSourceSpan(startTagToken.sourceSpan.start, end, startTagToken.sourceSpan.fullStart);
|
12718 | // Create a separate `startSpan` because `span` will be modified when there is an `end` span.
|
12719 | const startSpan = new ParseSourceSpan(startTagToken.sourceSpan.start, end, startTagToken.sourceSpan.fullStart);
|
12720 | const el = new Element$1(fullName, attrs, [], span, startSpan, undefined);
|
12721 | this._pushElement(el);
|
12722 | if (selfClosing) {
|
12723 | // Elements that are self-closed have their `endSourceSpan` set to the full span, as the
|
12724 | // element start tag also represents the end tag.
|
12725 | this._popElement(fullName, span);
|
12726 | }
|
12727 | else if (startTagToken.type === TokenType.INCOMPLETE_TAG_OPEN) {
|
12728 | // We already know the opening tag is not complete, so it is unlikely it has a corresponding
|
12729 | // close tag. Let's optimistically parse it as a full element and emit an error.
|
12730 | this._popElement(fullName, null);
|
12731 | this.errors.push(TreeError.create(fullName, span, `Opening tag "${fullName}" not terminated.`));
|
12732 | }
|
12733 | }
|
12734 | _pushElement(el) {
|
12735 | const parentEl = this._getParentElement();
|
12736 | if (parentEl && this.getTagDefinition(parentEl.name).isClosedByChild(el.name)) {
|
12737 | this._elementStack.pop();
|
12738 | }
|
12739 | this._addToParent(el);
|
12740 | this._elementStack.push(el);
|
12741 | }
|
12742 | _consumeEndTag(endTagToken) {
|
12743 | const fullName = this._getElementFullName(endTagToken.parts[0], endTagToken.parts[1], this._getParentElement());
|
12744 | if (this.getTagDefinition(fullName).isVoid) {
|
12745 | this.errors.push(TreeError.create(fullName, endTagToken.sourceSpan, `Void elements do not have end tags "${endTagToken.parts[1]}"`));
|
12746 | }
|
12747 | else if (!this._popElement(fullName, endTagToken.sourceSpan)) {
|
12748 | const errMsg = `Unexpected closing tag "${fullName}". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags`;
|
12749 | this.errors.push(TreeError.create(fullName, endTagToken.sourceSpan, errMsg));
|
12750 | }
|
12751 | }
|
12752 | /**
|
12753 | * Closes the nearest element with the tag name `fullName` in the parse tree.
|
12754 | * `endSourceSpan` is the span of the closing tag, or null if the element does
|
12755 | * not have a closing tag (for example, this happens when an incomplete
|
12756 | * opening tag is recovered).
|
12757 | */
|
12758 | _popElement(fullName, endSourceSpan) {
|
12759 | for (let stackIndex = this._elementStack.length - 1; stackIndex >= 0; stackIndex--) {
|
12760 | const el = this._elementStack[stackIndex];
|
12761 | if (el.name == fullName) {
|
12762 | // Record the parse span with the element that is being closed. Any elements that are
|
12763 | // removed from the element stack at this point are closed implicitly, so they won't get
|
12764 | // an end source span (as there is no explicit closing element).
|
12765 | el.endSourceSpan = endSourceSpan;
|
12766 | el.sourceSpan.end = endSourceSpan !== null ? endSourceSpan.end : el.sourceSpan.end;
|
12767 | this._elementStack.splice(stackIndex, this._elementStack.length - stackIndex);
|
12768 | return true;
|
12769 | }
|
12770 | if (!this.getTagDefinition(el.name).closedByParent) {
|
12771 | return false;
|
12772 | }
|
12773 | }
|
12774 | return false;
|
12775 | }
|
12776 | _consumeAttr(attrName) {
|
12777 | const fullName = mergeNsAndName(attrName.parts[0], attrName.parts[1]);
|
12778 | let end = attrName.sourceSpan.end;
|
12779 | let value = '';
|
12780 | let valueSpan = undefined;
|
12781 | if (this._peek.type === TokenType.ATTR_QUOTE) {
|
12782 | this._advance();
|
12783 | }
|
12784 | if (this._peek.type === TokenType.ATTR_VALUE) {
|
12785 | const valueToken = this._advance();
|
12786 | value = valueToken.parts[0];
|
12787 | end = valueToken.sourceSpan.end;
|
12788 | valueSpan = valueToken.sourceSpan;
|
12789 | }
|
12790 | if (this._peek.type === TokenType.ATTR_QUOTE) {
|
12791 | const quoteToken = this._advance();
|
12792 | end = quoteToken.sourceSpan.end;
|
12793 | }
|
12794 | const keySpan = new ParseSourceSpan(attrName.sourceSpan.start, attrName.sourceSpan.end);
|
12795 | return new Attribute(fullName, value, new ParseSourceSpan(attrName.sourceSpan.start, end, attrName.sourceSpan.fullStart), keySpan, valueSpan);
|
12796 | }
|
12797 | _getParentElement() {
|
12798 | return this._elementStack.length > 0 ? this._elementStack[this._elementStack.length - 1] : null;
|
12799 | }
|
12800 | _addToParent(node) {
|
12801 | const parent = this._getParentElement();
|
12802 | if (parent != null) {
|
12803 | parent.children.push(node);
|
12804 | }
|
12805 | else {
|
12806 | this.rootNodes.push(node);
|
12807 | }
|
12808 | }
|
12809 | _getElementFullName(prefix, localName, parentElement) {
|
12810 | if (prefix === '') {
|
12811 | prefix = this.getTagDefinition(localName).implicitNamespacePrefix || '';
|
12812 | if (prefix === '' && parentElement != null) {
|
12813 | const parentTagName = splitNsName(parentElement.name)[1];
|
12814 | const parentTagDefinition = this.getTagDefinition(parentTagName);
|
12815 | if (!parentTagDefinition.preventNamespaceInheritance) {
|
12816 | prefix = getNsPrefix(parentElement.name);
|
12817 | }
|
12818 | }
|
12819 | }
|
12820 | return mergeNsAndName(prefix, localName);
|
12821 | }
|
12822 | }
|
12823 | function lastOnStack(stack, element) {
|
12824 | return stack.length > 0 && stack[stack.length - 1] === element;
|
12825 | }
|
12826 |
|
12827 | /**
|
12828 | * @license
|
12829 | * Copyright Google LLC All Rights Reserved.
|
12830 | *
|
12831 | * Use of this source code is governed by an MIT-style license that can be
|
12832 | * found in the LICENSE file at https://angular.io/license
|
12833 | */
|
12834 | class HtmlParser extends Parser {
|
12835 | constructor() {
|
12836 | super(getHtmlTagDefinition);
|
12837 | }
|
12838 | parse(source, url, options) {
|
12839 | return super.parse(source, url, options);
|
12840 | }
|
12841 | }
|
12842 |
|
12843 | /**
|
12844 | * @license
|
12845 | * Copyright Google LLC All Rights Reserved.
|
12846 | *
|
12847 | * Use of this source code is governed by an MIT-style license that can be
|
12848 | * found in the LICENSE file at https://angular.io/license
|
12849 | */
|
12850 | const PRESERVE_WS_ATTR_NAME = 'ngPreserveWhitespaces';
|
12851 | const SKIP_WS_TRIM_TAGS = new Set(['pre', 'template', 'textarea', 'script', 'style']);
|
12852 | // Equivalent to \s with \u00a0 (non-breaking space) excluded.
|
12853 | // Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
|
12854 | const WS_CHARS = ' \f\n\r\t\v\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff';
|
12855 | const NO_WS_REGEXP = new RegExp(`[^${WS_CHARS}]`);
|
12856 | const WS_REPLACE_REGEXP = new RegExp(`[${WS_CHARS}]{2,}`, 'g');
|
12857 | function hasPreserveWhitespacesAttr(attrs) {
|
12858 | return attrs.some((attr) => attr.name === PRESERVE_WS_ATTR_NAME);
|
12859 | }
|
12860 | /**
|
12861 | * Angular Dart introduced &ngsp; as a placeholder for non-removable space, see:
|
12862 | * https://github.com/dart-lang/angular/blob/0bb611387d29d65b5af7f9d2515ab571fd3fbee4/_tests/test/compiler/preserve_whitespace_test.dart#L25-L32
|
12863 | * In Angular Dart &ngsp; is converted to the 0xE500 PUA (Private Use Areas) unicode character
|
12864 | * and later on replaced by a space. We are re-implementing the same idea here.
|
12865 | */
|
12866 | function replaceNgsp(value) {
|
12867 | // lexer is replacing the &ngsp; pseudo-entity with NGSP_UNICODE
|
12868 | return value.replace(new RegExp(NGSP_UNICODE, 'g'), ' ');
|
12869 | }
|
12870 | /**
|
12871 | * This visitor can walk HTML parse tree and remove / trim text nodes using the following rules:
|
12872 | * - consider spaces, tabs and new lines as whitespace characters;
|
12873 | * - drop text nodes consisting of whitespace characters only;
|
12874 | * - for all other text nodes replace consecutive whitespace characters with one space;
|
12875 | * - convert &ngsp; pseudo-entity to a single space;
|
12876 | *
|
12877 | * Removal and trimming of whitespaces have positive performance impact (less code to generate
|
12878 | * while compiling templates, faster view creation). At the same time it can be "destructive"
|
12879 | * in some cases (whitespaces can influence layout). Because of the potential of breaking layout
|
12880 | * this visitor is not activated by default in Angular 5 and people need to explicitly opt-in for
|
12881 | * whitespace removal. The default option for whitespace removal will be revisited in Angular 6
|
12882 | * and might be changed to "on" by default.
|
12883 | */
|
12884 | class WhitespaceVisitor {
|
12885 | visitElement(element, context) {
|
12886 | if (SKIP_WS_TRIM_TAGS.has(element.name) || hasPreserveWhitespacesAttr(element.attrs)) {
|
12887 | // don't descent into elements where we need to preserve whitespaces
|
12888 | // but still visit all attributes to eliminate one used as a market to preserve WS
|
12889 | return new Element$1(element.name, visitAll$1(this, element.attrs), element.children, element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
|
12890 | }
|
12891 | return new Element$1(element.name, element.attrs, visitAllWithSiblings(this, element.children), element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
|
12892 | }
|
12893 | visitAttribute(attribute, context) {
|
12894 | return attribute.name !== PRESERVE_WS_ATTR_NAME ? attribute : null;
|
12895 | }
|
12896 | visitText(text, context) {
|
12897 | const isNotBlank = text.value.match(NO_WS_REGEXP);
|
12898 | const hasExpansionSibling = context &&
|
12899 | (context.prev instanceof Expansion || context.next instanceof Expansion);
|
12900 | if (isNotBlank || hasExpansionSibling) {
|
12901 | return new Text$2(replaceNgsp(text.value).replace(WS_REPLACE_REGEXP, ' '), text.sourceSpan, text.i18n);
|
12902 | }
|
12903 | return null;
|
12904 | }
|
12905 | visitComment(comment, context) {
|
12906 | return comment;
|
12907 | }
|
12908 | visitExpansion(expansion, context) {
|
12909 | return expansion;
|
12910 | }
|
12911 | visitExpansionCase(expansionCase, context) {
|
12912 | return expansionCase;
|
12913 | }
|
12914 | }
|
12915 | function visitAllWithSiblings(visitor, nodes) {
|
12916 | const result = [];
|
12917 | nodes.forEach((ast, i) => {
|
12918 | const context = { prev: nodes[i - 1], next: nodes[i + 1] };
|
12919 | const astResult = ast.visit(visitor, context);
|
12920 | if (astResult) {
|
12921 | result.push(astResult);
|
12922 | }
|
12923 | });
|
12924 | return result;
|
12925 | }
|
12926 |
|
12927 | /**
|
12928 | * @license
|
12929 | * Copyright Google LLC All Rights Reserved.
|
12930 | *
|
12931 | * Use of this source code is governed by an MIT-style license that can be
|
12932 | * found in the LICENSE file at https://angular.io/license
|
12933 | */
|
12934 | var ProviderAstType;
|
12935 | (function (ProviderAstType) {
|
12936 | ProviderAstType[ProviderAstType["PublicService"] = 0] = "PublicService";
|
12937 | ProviderAstType[ProviderAstType["PrivateService"] = 1] = "PrivateService";
|
12938 | ProviderAstType[ProviderAstType["Component"] = 2] = "Component";
|
12939 | ProviderAstType[ProviderAstType["Directive"] = 3] = "Directive";
|
12940 | ProviderAstType[ProviderAstType["Builtin"] = 4] = "Builtin";
|
12941 | })(ProviderAstType || (ProviderAstType = {}));
|
12942 |
|
12943 | /**
|
12944 | * @license
|
12945 | * Copyright Google LLC All Rights Reserved.
|
12946 | *
|
12947 | * Use of this source code is governed by an MIT-style license that can be
|
12948 | * found in the LICENSE file at https://angular.io/license
|
12949 | */
|
12950 | function isStyleUrlResolvable(url) {
|
12951 | if (url == null || url.length === 0 || url[0] == '/')
|
12952 | return false;
|
12953 | const schemeMatch = url.match(URL_WITH_SCHEMA_REGEXP);
|
12954 | return schemeMatch === null || schemeMatch[1] == 'package' || schemeMatch[1] == 'asset';
|
12955 | }
|
12956 | const URL_WITH_SCHEMA_REGEXP = /^([^:/?#]+):/;
|
12957 |
|
12958 | /**
|
12959 | * @license
|
12960 | * Copyright Google LLC All Rights Reserved.
|
12961 | *
|
12962 | * Use of this source code is governed by an MIT-style license that can be
|
12963 | * found in the LICENSE file at https://angular.io/license
|
12964 | */
|
12965 | const PROPERTY_PARTS_SEPARATOR = '.';
|
12966 | const ATTRIBUTE_PREFIX = 'attr';
|
12967 | const CLASS_PREFIX = 'class';
|
12968 | const STYLE_PREFIX = 'style';
|
12969 | const TEMPLATE_ATTR_PREFIX = '*';
|
12970 | const ANIMATE_PROP_PREFIX = 'animate-';
|
12971 | /**
|
12972 | * Parses bindings in templates and in the directive host area.
|
12973 | */
|
12974 | class BindingParser {
|
12975 | constructor(_exprParser, _interpolationConfig, _schemaRegistry, pipes, errors) {
|
12976 | this._exprParser = _exprParser;
|
12977 | this._interpolationConfig = _interpolationConfig;
|
12978 | this._schemaRegistry = _schemaRegistry;
|
12979 | this.errors = errors;
|
12980 | this.pipesByName = null;
|
12981 | this._usedPipes = new Map();
|
12982 | // When the `pipes` parameter is `null`, do not check for used pipes
|
12983 | // This is used in IVY when we might not know the available pipes at compile time
|
12984 | if (pipes) {
|
12985 | const pipesByName = new Map();
|
12986 | pipes.forEach(pipe => pipesByName.set(pipe.name, pipe));
|
12987 | this.pipesByName = pipesByName;
|
12988 | }
|
12989 | }
|
12990 | get interpolationConfig() {
|
12991 | return this._interpolationConfig;
|
12992 | }
|
12993 | getUsedPipes() {
|
12994 | return Array.from(this._usedPipes.values());
|
12995 | }
|
12996 | createBoundHostProperties(dirMeta, sourceSpan) {
|
12997 | if (dirMeta.hostProperties) {
|
12998 | const boundProps = [];
|
12999 | Object.keys(dirMeta.hostProperties).forEach(propName => {
|
13000 | const expression = dirMeta.hostProperties[propName];
|
13001 | if (typeof expression === 'string') {
|
13002 | this.parsePropertyBinding(propName, expression, true, sourceSpan, sourceSpan.start.offset, undefined, [],
|
13003 | // Use the `sourceSpan` for `keySpan`. This isn't really accurate, but neither is the
|
13004 | // sourceSpan, as it represents the sourceSpan of the host itself rather than the
|
13005 | // source of the host binding (which doesn't exist in the template). Regardless,
|
13006 | // neither of these values are used in Ivy but are only here to satisfy the function
|
13007 | // signature. This should likely be refactored in the future so that `sourceSpan`
|
13008 | // isn't being used inaccurately.
|
13009 | boundProps, sourceSpan);
|
13010 | }
|
13011 | else {
|
13012 | this._reportError(`Value of the host property binding "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`, sourceSpan);
|
13013 | }
|
13014 | });
|
13015 | return boundProps;
|
13016 | }
|
13017 | return null;
|
13018 | }
|
13019 | createDirectiveHostPropertyAsts(dirMeta, elementSelector, sourceSpan) {
|
13020 | const boundProps = this.createBoundHostProperties(dirMeta, sourceSpan);
|
13021 | return boundProps &&
|
13022 | boundProps.map((prop) => this.createBoundElementProperty(elementSelector, prop));
|
13023 | }
|
13024 | createDirectiveHostEventAsts(dirMeta, sourceSpan) {
|
13025 | if (dirMeta.hostListeners) {
|
13026 | const targetEvents = [];
|
13027 | Object.keys(dirMeta.hostListeners).forEach(propName => {
|
13028 | const expression = dirMeta.hostListeners[propName];
|
13029 | if (typeof expression === 'string') {
|
13030 | // Use the `sourceSpan` for `keySpan` and `handlerSpan`. This isn't really accurate, but
|
13031 | // neither is the `sourceSpan`, as it represents the `sourceSpan` of the host itself
|
13032 | // rather than the source of the host binding (which doesn't exist in the template).
|
13033 | // Regardless, neither of these values are used in Ivy but are only here to satisfy the
|
13034 | // function signature. This should likely be refactored in the future so that `sourceSpan`
|
13035 | // isn't being used inaccurately.
|
13036 | this.parseEvent(propName, expression, sourceSpan, sourceSpan, [], targetEvents, sourceSpan);
|
13037 | }
|
13038 | else {
|
13039 | this._reportError(`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`, sourceSpan);
|
13040 | }
|
13041 | });
|
13042 | return targetEvents;
|
13043 | }
|
13044 | return null;
|
13045 | }
|
13046 | parseInterpolation(value, sourceSpan) {
|
13047 | const sourceInfo = sourceSpan.start.toString();
|
13048 | const absoluteOffset = sourceSpan.fullStart.offset;
|
13049 | try {
|
13050 | const ast = this._exprParser.parseInterpolation(value, sourceInfo, absoluteOffset, this._interpolationConfig);
|
13051 | if (ast)
|
13052 | this._reportExpressionParserErrors(ast.errors, sourceSpan);
|
13053 | this._checkPipes(ast, sourceSpan);
|
13054 | return ast;
|
13055 | }
|
13056 | catch (e) {
|
13057 | this._reportError(`${e}`, sourceSpan);
|
13058 | return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
|
13059 | }
|
13060 | }
|
13061 | /**
|
13062 | * Similar to `parseInterpolation`, but treats the provided string as a single expression
|
13063 | * element that would normally appear within the interpolation prefix and suffix (`{{` and `}}`).
|
13064 | * This is used for parsing the switch expression in ICUs.
|
13065 | */
|
13066 | parseInterpolationExpression(expression, sourceSpan) {
|
13067 | const sourceInfo = sourceSpan.start.toString();
|
13068 | const absoluteOffset = sourceSpan.start.offset;
|
13069 | try {
|
13070 | const ast = this._exprParser.parseInterpolationExpression(expression, sourceInfo, absoluteOffset);
|
13071 | if (ast)
|
13072 | this._reportExpressionParserErrors(ast.errors, sourceSpan);
|
13073 | this._checkPipes(ast, sourceSpan);
|
13074 | return ast;
|
13075 | }
|
13076 | catch (e) {
|
13077 | this._reportError(`${e}`, sourceSpan);
|
13078 | return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
|
13079 | }
|
13080 | }
|
13081 | /**
|
13082 | * Parses the bindings in a microsyntax expression, and converts them to
|
13083 | * `ParsedProperty` or `ParsedVariable`.
|
13084 | *
|
13085 | * @param tplKey template binding name
|
13086 | * @param tplValue template binding value
|
13087 | * @param sourceSpan span of template binding relative to entire the template
|
13088 | * @param absoluteValueOffset start of the tplValue relative to the entire template
|
13089 | * @param targetMatchableAttrs potential attributes to match in the template
|
13090 | * @param targetProps target property bindings in the template
|
13091 | * @param targetVars target variables in the template
|
13092 | */
|
13093 | parseInlineTemplateBinding(tplKey, tplValue, sourceSpan, absoluteValueOffset, targetMatchableAttrs, targetProps, targetVars, isIvyAst) {
|
13094 | const absoluteKeyOffset = sourceSpan.start.offset + TEMPLATE_ATTR_PREFIX.length;
|
13095 | const bindings = this._parseTemplateBindings(tplKey, tplValue, sourceSpan, absoluteKeyOffset, absoluteValueOffset);
|
13096 | for (const binding of bindings) {
|
13097 | // sourceSpan is for the entire HTML attribute. bindingSpan is for a particular
|
13098 | // binding within the microsyntax expression so it's more narrow than sourceSpan.
|
13099 | const bindingSpan = moveParseSourceSpan(sourceSpan, binding.sourceSpan);
|
13100 | const key = binding.key.source;
|
13101 | const keySpan = moveParseSourceSpan(sourceSpan, binding.key.span);
|
13102 | if (binding instanceof VariableBinding) {
|
13103 | const value = binding.value ? binding.value.source : '$implicit';
|
13104 | const valueSpan = binding.value ? moveParseSourceSpan(sourceSpan, binding.value.span) : undefined;
|
13105 | targetVars.push(new ParsedVariable(key, value, bindingSpan, keySpan, valueSpan));
|
13106 | }
|
13107 | else if (binding.value) {
|
13108 | const srcSpan = isIvyAst ? bindingSpan : sourceSpan;
|
13109 | const valueSpan = moveParseSourceSpan(sourceSpan, binding.value.ast.sourceSpan);
|
13110 | this._parsePropertyAst(key, binding.value, srcSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
|
13111 | }
|
13112 | else {
|
13113 | targetMatchableAttrs.push([key, '' /* value */]);
|
13114 | // Since this is a literal attribute with no RHS, source span should be
|
13115 | // just the key span.
|
13116 | this.parseLiteralAttr(key, null /* value */, keySpan, absoluteValueOffset, undefined /* valueSpan */, targetMatchableAttrs, targetProps, keySpan);
|
13117 | }
|
13118 | }
|
13119 | }
|
13120 | /**
|
13121 | * Parses the bindings in a microsyntax expression, e.g.
|
13122 | * ```
|
13123 | * <tag *tplKey="let value1 = prop; let value2 = localVar">
|
13124 | * ```
|
13125 | *
|
13126 | * @param tplKey template binding name
|
13127 | * @param tplValue template binding value
|
13128 | * @param sourceSpan span of template binding relative to entire the template
|
13129 | * @param absoluteKeyOffset start of the `tplKey`
|
13130 | * @param absoluteValueOffset start of the `tplValue`
|
13131 | */
|
13132 | _parseTemplateBindings(tplKey, tplValue, sourceSpan, absoluteKeyOffset, absoluteValueOffset) {
|
13133 | const sourceInfo = sourceSpan.start.toString();
|
13134 | try {
|
13135 | const bindingsResult = this._exprParser.parseTemplateBindings(tplKey, tplValue, sourceInfo, absoluteKeyOffset, absoluteValueOffset);
|
13136 | this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
13137 | bindingsResult.templateBindings.forEach((binding) => {
|
13138 | if (binding.value instanceof ASTWithSource) {
|
13139 | this._checkPipes(binding.value, sourceSpan);
|
13140 | }
|
13141 | });
|
13142 | bindingsResult.warnings.forEach((warning) => {
|
13143 | this._reportError(warning, sourceSpan, ParseErrorLevel.WARNING);
|
13144 | });
|
13145 | return bindingsResult.templateBindings;
|
13146 | }
|
13147 | catch (e) {
|
13148 | this._reportError(`${e}`, sourceSpan);
|
13149 | return [];
|
13150 | }
|
13151 | }
|
13152 | parseLiteralAttr(name, value, sourceSpan, absoluteOffset, valueSpan, targetMatchableAttrs,
|
13153 | // TODO(atscott): keySpan is only optional here so VE template parser implementation does not
|
13154 | // have to change This should be required when VE is removed.
|
13155 | targetProps, keySpan) {
|
13156 | if (isAnimationLabel(name)) {
|
13157 | name = name.substring(1);
|
13158 | if (keySpan !== undefined) {
|
13159 | keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset));
|
13160 | }
|
13161 | if (value) {
|
13162 | this._reportError(`Assigning animation triggers via @prop="exp" attributes with an expression is invalid.` +
|
13163 | ` Use property bindings (e.g. [@prop]="exp") or use an attribute without a value (e.g. @prop) instead.`, sourceSpan, ParseErrorLevel.ERROR);
|
13164 | }
|
13165 | this._parseAnimation(name, value, sourceSpan, absoluteOffset, keySpan, valueSpan, targetMatchableAttrs, targetProps);
|
13166 | }
|
13167 | else {
|
13168 | targetProps.push(new ParsedProperty(name, this._exprParser.wrapLiteralPrimitive(value, '', absoluteOffset), ParsedPropertyType.LITERAL_ATTR, sourceSpan, keySpan, valueSpan));
|
13169 | }
|
13170 | }
|
13171 | parsePropertyBinding(name, expression, isHost, sourceSpan, absoluteOffset, valueSpan,
|
13172 | // TODO(atscott): keySpan is only optional here so VE template parser implementation does not
|
13173 | // have to change This should be required when VE is removed.
|
13174 | targetMatchableAttrs, targetProps, keySpan) {
|
13175 | if (name.length === 0) {
|
13176 | this._reportError(`Property name is missing in binding`, sourceSpan);
|
13177 | }
|
13178 | let isAnimationProp = false;
|
13179 | if (name.startsWith(ANIMATE_PROP_PREFIX)) {
|
13180 | isAnimationProp = true;
|
13181 | name = name.substring(ANIMATE_PROP_PREFIX.length);
|
13182 | if (keySpan !== undefined) {
|
13183 | keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + ANIMATE_PROP_PREFIX.length, keySpan.end.offset));
|
13184 | }
|
13185 | }
|
13186 | else if (isAnimationLabel(name)) {
|
13187 | isAnimationProp = true;
|
13188 | name = name.substring(1);
|
13189 | if (keySpan !== undefined) {
|
13190 | keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset));
|
13191 | }
|
13192 | }
|
13193 | if (isAnimationProp) {
|
13194 | this._parseAnimation(name, expression, sourceSpan, absoluteOffset, keySpan, valueSpan, targetMatchableAttrs, targetProps);
|
13195 | }
|
13196 | else {
|
13197 | this._parsePropertyAst(name, this._parseBinding(expression, isHost, valueSpan || sourceSpan, absoluteOffset), sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
|
13198 | }
|
13199 | }
|
13200 | parsePropertyInterpolation(name, value, sourceSpan, valueSpan, targetMatchableAttrs,
|
13201 | // TODO(atscott): keySpan is only optional here so VE template parser implementation does not
|
13202 | // have to change This should be required when VE is removed.
|
13203 | targetProps, keySpan) {
|
13204 | const expr = this.parseInterpolation(value, valueSpan || sourceSpan);
|
13205 | if (expr) {
|
13206 | this._parsePropertyAst(name, expr, sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps);
|
13207 | return true;
|
13208 | }
|
13209 | return false;
|
13210 | }
|
13211 | _parsePropertyAst(name, ast, sourceSpan, keySpan, valueSpan, targetMatchableAttrs, targetProps) {
|
13212 | targetMatchableAttrs.push([name, ast.source]);
|
13213 | targetProps.push(new ParsedProperty(name, ast, ParsedPropertyType.DEFAULT, sourceSpan, keySpan, valueSpan));
|
13214 | }
|
13215 | _parseAnimation(name, expression, sourceSpan, absoluteOffset, keySpan, valueSpan, targetMatchableAttrs, targetProps) {
|
13216 | if (name.length === 0) {
|
13217 | this._reportError('Animation trigger is missing', sourceSpan);
|
13218 | }
|
13219 | // This will occur when a @trigger is not paired with an expression.
|
13220 | // For animations it is valid to not have an expression since */void
|
13221 | // states will be applied by angular when the element is attached/detached
|
13222 | const ast = this._parseBinding(expression || 'undefined', false, valueSpan || sourceSpan, absoluteOffset);
|
13223 | targetMatchableAttrs.push([name, ast.source]);
|
13224 | targetProps.push(new ParsedProperty(name, ast, ParsedPropertyType.ANIMATION, sourceSpan, keySpan, valueSpan));
|
13225 | }
|
13226 | _parseBinding(value, isHostBinding, sourceSpan, absoluteOffset) {
|
13227 | const sourceInfo = (sourceSpan && sourceSpan.start || '(unknown)').toString();
|
13228 | try {
|
13229 | const ast = isHostBinding ?
|
13230 | this._exprParser.parseSimpleBinding(value, sourceInfo, absoluteOffset, this._interpolationConfig) :
|
13231 | this._exprParser.parseBinding(value, sourceInfo, absoluteOffset, this._interpolationConfig);
|
13232 | if (ast)
|
13233 | this._reportExpressionParserErrors(ast.errors, sourceSpan);
|
13234 | this._checkPipes(ast, sourceSpan);
|
13235 | return ast;
|
13236 | }
|
13237 | catch (e) {
|
13238 | this._reportError(`${e}`, sourceSpan);
|
13239 | return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
|
13240 | }
|
13241 | }
|
13242 | createBoundElementProperty(elementSelector, boundProp, skipValidation = false, mapPropertyName = true) {
|
13243 | if (boundProp.isAnimation) {
|
13244 | return new BoundElementProperty(boundProp.name, 4 /* Animation */, SecurityContext.NONE, boundProp.expression, null, boundProp.sourceSpan, boundProp.keySpan, boundProp.valueSpan);
|
13245 | }
|
13246 | let unit = null;
|
13247 | let bindingType = undefined;
|
13248 | let boundPropertyName = null;
|
13249 | const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR);
|
13250 | let securityContexts = undefined;
|
13251 | // Check for special cases (prefix style, attr, class)
|
13252 | if (parts.length > 1) {
|
13253 | if (parts[0] == ATTRIBUTE_PREFIX) {
|
13254 | boundPropertyName = parts.slice(1).join(PROPERTY_PARTS_SEPARATOR);
|
13255 | if (!skipValidation) {
|
13256 | this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, true);
|
13257 | }
|
13258 | securityContexts = calcPossibleSecurityContexts(this._schemaRegistry, elementSelector, boundPropertyName, true);
|
13259 | const nsSeparatorIdx = boundPropertyName.indexOf(':');
|
13260 | if (nsSeparatorIdx > -1) {
|
13261 | const ns = boundPropertyName.substring(0, nsSeparatorIdx);
|
13262 | const name = boundPropertyName.substring(nsSeparatorIdx + 1);
|
13263 | boundPropertyName = mergeNsAndName(ns, name);
|
13264 | }
|
13265 | bindingType = 1 /* Attribute */;
|
13266 | }
|
13267 | else if (parts[0] == CLASS_PREFIX) {
|
13268 | boundPropertyName = parts[1];
|
13269 | bindingType = 2 /* Class */;
|
13270 | securityContexts = [SecurityContext.NONE];
|
13271 | }
|
13272 | else if (parts[0] == STYLE_PREFIX) {
|
13273 | unit = parts.length > 2 ? parts[2] : null;
|
13274 | boundPropertyName = parts[1];
|
13275 | bindingType = 3 /* Style */;
|
13276 | securityContexts = [SecurityContext.STYLE];
|
13277 | }
|
13278 | }
|
13279 | // If not a special case, use the full property name
|
13280 | if (boundPropertyName === null) {
|
13281 | const mappedPropName = this._schemaRegistry.getMappedPropName(boundProp.name);
|
13282 | boundPropertyName = mapPropertyName ? mappedPropName : boundProp.name;
|
13283 | securityContexts = calcPossibleSecurityContexts(this._schemaRegistry, elementSelector, mappedPropName, false);
|
13284 | bindingType = 0 /* Property */;
|
13285 | if (!skipValidation) {
|
13286 | this._validatePropertyOrAttributeName(mappedPropName, boundProp.sourceSpan, false);
|
13287 | }
|
13288 | }
|
13289 | return new BoundElementProperty(boundPropertyName, bindingType, securityContexts[0], boundProp.expression, unit, boundProp.sourceSpan, boundProp.keySpan, boundProp.valueSpan);
|
13290 | }
|
13291 | // TODO: keySpan should be required but was made optional to avoid changing VE parser.
|
13292 | parseEvent(name, expression, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan) {
|
13293 | if (name.length === 0) {
|
13294 | this._reportError(`Event name is missing in binding`, sourceSpan);
|
13295 | }
|
13296 | if (isAnimationLabel(name)) {
|
13297 | name = name.substr(1);
|
13298 | if (keySpan !== undefined) {
|
13299 | keySpan = moveParseSourceSpan(keySpan, new AbsoluteSourceSpan(keySpan.start.offset + 1, keySpan.end.offset));
|
13300 | }
|
13301 | this._parseAnimationEvent(name, expression, sourceSpan, handlerSpan, targetEvents, keySpan);
|
13302 | }
|
13303 | else {
|
13304 | this._parseRegularEvent(name, expression, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan);
|
13305 | }
|
13306 | }
|
13307 | calcPossibleSecurityContexts(selector, propName, isAttribute) {
|
13308 | const prop = this._schemaRegistry.getMappedPropName(propName);
|
13309 | return calcPossibleSecurityContexts(this._schemaRegistry, selector, prop, isAttribute);
|
13310 | }
|
13311 | _parseAnimationEvent(name, expression, sourceSpan, handlerSpan, targetEvents, keySpan) {
|
13312 | const matches = splitAtPeriod(name, [name, '']);
|
13313 | const eventName = matches[0];
|
13314 | const phase = matches[1].toLowerCase();
|
13315 | const ast = this._parseAction(expression, handlerSpan);
|
13316 | targetEvents.push(new ParsedEvent(eventName, phase, 1 /* Animation */, ast, sourceSpan, handlerSpan, keySpan));
|
13317 | if (eventName.length === 0) {
|
13318 | this._reportError(`Animation event name is missing in binding`, sourceSpan);
|
13319 | }
|
13320 | if (phase) {
|
13321 | if (phase !== 'start' && phase !== 'done') {
|
13322 | this._reportError(`The provided animation output phase value "${phase}" for "@${eventName}" is not supported (use start or done)`, sourceSpan);
|
13323 | }
|
13324 | }
|
13325 | else {
|
13326 | this._reportError(`The animation trigger output event (@${eventName}) is missing its phase value name (start or done are currently supported)`, sourceSpan);
|
13327 | }
|
13328 | }
|
13329 | _parseRegularEvent(name, expression, sourceSpan, handlerSpan, targetMatchableAttrs, targetEvents, keySpan) {
|
13330 | // long format: 'target: eventName'
|
13331 | const [target, eventName] = splitAtColon(name, [null, name]);
|
13332 | const ast = this._parseAction(expression, handlerSpan);
|
13333 | targetMatchableAttrs.push([name, ast.source]);
|
13334 | targetEvents.push(new ParsedEvent(eventName, target, 0 /* Regular */, ast, sourceSpan, handlerSpan, keySpan));
|
13335 | // Don't detect directives for event names for now,
|
13336 | // so don't add the event name to the matchableAttrs
|
13337 | }
|
13338 | _parseAction(value, sourceSpan) {
|
13339 | const sourceInfo = (sourceSpan && sourceSpan.start || '(unknown').toString();
|
13340 | const absoluteOffset = (sourceSpan && sourceSpan.start) ? sourceSpan.start.offset : 0;
|
13341 | try {
|
13342 | const ast = this._exprParser.parseAction(value, sourceInfo, absoluteOffset, this._interpolationConfig);
|
13343 | if (ast) {
|
13344 | this._reportExpressionParserErrors(ast.errors, sourceSpan);
|
13345 | }
|
13346 | if (!ast || ast.ast instanceof EmptyExpr) {
|
13347 | this._reportError(`Empty expressions are not allowed`, sourceSpan);
|
13348 | return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
|
13349 | }
|
13350 | this._checkPipes(ast, sourceSpan);
|
13351 | return ast;
|
13352 | }
|
13353 | catch (e) {
|
13354 | this._reportError(`${e}`, sourceSpan);
|
13355 | return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset);
|
13356 | }
|
13357 | }
|
13358 | _reportError(message, sourceSpan, level = ParseErrorLevel.ERROR) {
|
13359 | this.errors.push(new ParseError(sourceSpan, message, level));
|
13360 | }
|
13361 | _reportExpressionParserErrors(errors, sourceSpan) {
|
13362 | for (const error of errors) {
|
13363 | this._reportError(error.message, sourceSpan);
|
13364 | }
|
13365 | }
|
13366 | // Make sure all the used pipes are known in `this.pipesByName`
|
13367 | _checkPipes(ast, sourceSpan) {
|
13368 | if (ast && this.pipesByName) {
|
13369 | const collector = new PipeCollector();
|
13370 | ast.visit(collector);
|
13371 | collector.pipes.forEach((ast, pipeName) => {
|
13372 | const pipeMeta = this.pipesByName.get(pipeName);
|
13373 | if (!pipeMeta) {
|
13374 | this._reportError(`The pipe '${pipeName}' could not be found`, new ParseSourceSpan(sourceSpan.start.moveBy(ast.span.start), sourceSpan.start.moveBy(ast.span.end)));
|
13375 | }
|
13376 | else {
|
13377 | this._usedPipes.set(pipeName, pipeMeta);
|
13378 | }
|
13379 | });
|
13380 | }
|
13381 | }
|
13382 | /**
|
13383 | * @param propName the name of the property / attribute
|
13384 | * @param sourceSpan
|
13385 | * @param isAttr true when binding to an attribute
|
13386 | */
|
13387 | _validatePropertyOrAttributeName(propName, sourceSpan, isAttr) {
|
13388 | const report = isAttr ? this._schemaRegistry.validateAttribute(propName) :
|
13389 | this._schemaRegistry.validateProperty(propName);
|
13390 | if (report.error) {
|
13391 | this._reportError(report.msg, sourceSpan, ParseErrorLevel.ERROR);
|
13392 | }
|
13393 | }
|
13394 | }
|
13395 | class PipeCollector extends RecursiveAstVisitor {
|
13396 | constructor() {
|
13397 | super(...arguments);
|
13398 | this.pipes = new Map();
|
13399 | }
|
13400 | visitPipe(ast, context) {
|
13401 | this.pipes.set(ast.name, ast);
|
13402 | ast.exp.visit(this);
|
13403 | this.visitAll(ast.args, context);
|
13404 | return null;
|
13405 | }
|
13406 | }
|
13407 | function isAnimationLabel(name) {
|
13408 | return name[0] == '@';
|
13409 | }
|
13410 | function calcPossibleSecurityContexts(registry, selector, propName, isAttribute) {
|
13411 | const ctxs = [];
|
13412 | CssSelector.parse(selector).forEach((selector) => {
|
13413 | const elementNames = selector.element ? [selector.element] : registry.allKnownElementNames();
|
13414 | const notElementNames = new Set(selector.notSelectors.filter(selector => selector.isElementSelector())
|
13415 | .map((selector) => selector.element));
|
13416 | const possibleElementNames = elementNames.filter(elementName => !notElementNames.has(elementName));
|
13417 | ctxs.push(...possibleElementNames.map(elementName => registry.securityContext(elementName, propName, isAttribute)));
|
13418 | });
|
13419 | return ctxs.length === 0 ? [SecurityContext.NONE] : Array.from(new Set(ctxs)).sort();
|
13420 | }
|
13421 | /**
|
13422 | * Compute a new ParseSourceSpan based off an original `sourceSpan` by using
|
13423 | * absolute offsets from the specified `absoluteSpan`.
|
13424 | *
|
13425 | * @param sourceSpan original source span
|
13426 | * @param absoluteSpan absolute source span to move to
|
13427 | */
|
13428 | function moveParseSourceSpan(sourceSpan, absoluteSpan) {
|
13429 | // The difference of two absolute offsets provide the relative offset
|
13430 | const startDiff = absoluteSpan.start - sourceSpan.start.offset;
|
13431 | const endDiff = absoluteSpan.end - sourceSpan.end.offset;
|
13432 | return new ParseSourceSpan(sourceSpan.start.moveBy(startDiff), sourceSpan.end.moveBy(endDiff), sourceSpan.fullStart.moveBy(startDiff), sourceSpan.details);
|
13433 | }
|
13434 |
|
13435 | /**
|
13436 | * @license
|
13437 | * Copyright Google LLC All Rights Reserved.
|
13438 | *
|
13439 | * Use of this source code is governed by an MIT-style license that can be
|
13440 | * found in the LICENSE file at https://angular.io/license
|
13441 | */
|
13442 | const NG_CONTENT_SELECT_ATTR = 'select';
|
13443 | const LINK_ELEMENT = 'link';
|
13444 | const LINK_STYLE_REL_ATTR = 'rel';
|
13445 | const LINK_STYLE_HREF_ATTR = 'href';
|
13446 | const LINK_STYLE_REL_VALUE = 'stylesheet';
|
13447 | const STYLE_ELEMENT = 'style';
|
13448 | const SCRIPT_ELEMENT = 'script';
|
13449 | const NG_NON_BINDABLE_ATTR = 'ngNonBindable';
|
13450 | const NG_PROJECT_AS = 'ngProjectAs';
|
13451 | function preparseElement(ast) {
|
13452 | let selectAttr = null;
|
13453 | let hrefAttr = null;
|
13454 | let relAttr = null;
|
13455 | let nonBindable = false;
|
13456 | let projectAs = '';
|
13457 | ast.attrs.forEach(attr => {
|
13458 | const lcAttrName = attr.name.toLowerCase();
|
13459 | if (lcAttrName == NG_CONTENT_SELECT_ATTR) {
|
13460 | selectAttr = attr.value;
|
13461 | }
|
13462 | else if (lcAttrName == LINK_STYLE_HREF_ATTR) {
|
13463 | hrefAttr = attr.value;
|
13464 | }
|
13465 | else if (lcAttrName == LINK_STYLE_REL_ATTR) {
|
13466 | relAttr = attr.value;
|
13467 | }
|
13468 | else if (attr.name == NG_NON_BINDABLE_ATTR) {
|
13469 | nonBindable = true;
|
13470 | }
|
13471 | else if (attr.name == NG_PROJECT_AS) {
|
13472 | if (attr.value.length > 0) {
|
13473 | projectAs = attr.value;
|
13474 | }
|
13475 | }
|
13476 | });
|
13477 | selectAttr = normalizeNgContentSelect(selectAttr);
|
13478 | const nodeName = ast.name.toLowerCase();
|
13479 | let type = PreparsedElementType.OTHER;
|
13480 | if (isNgContent(nodeName)) {
|
13481 | type = PreparsedElementType.NG_CONTENT;
|
13482 | }
|
13483 | else if (nodeName == STYLE_ELEMENT) {
|
13484 | type = PreparsedElementType.STYLE;
|
13485 | }
|
13486 | else if (nodeName == SCRIPT_ELEMENT) {
|
13487 | type = PreparsedElementType.SCRIPT;
|
13488 | }
|
13489 | else if (nodeName == LINK_ELEMENT && relAttr == LINK_STYLE_REL_VALUE) {
|
13490 | type = PreparsedElementType.STYLESHEET;
|
13491 | }
|
13492 | return new PreparsedElement(type, selectAttr, hrefAttr, nonBindable, projectAs);
|
13493 | }
|
13494 | var PreparsedElementType;
|
13495 | (function (PreparsedElementType) {
|
13496 | PreparsedElementType[PreparsedElementType["NG_CONTENT"] = 0] = "NG_CONTENT";
|
13497 | PreparsedElementType[PreparsedElementType["STYLE"] = 1] = "STYLE";
|
13498 | PreparsedElementType[PreparsedElementType["STYLESHEET"] = 2] = "STYLESHEET";
|
13499 | PreparsedElementType[PreparsedElementType["SCRIPT"] = 3] = "SCRIPT";
|
13500 | PreparsedElementType[PreparsedElementType["OTHER"] = 4] = "OTHER";
|
13501 | })(PreparsedElementType || (PreparsedElementType = {}));
|
13502 | class PreparsedElement {
|
13503 | constructor(type, selectAttr, hrefAttr, nonBindable, projectAs) {
|
13504 | this.type = type;
|
13505 | this.selectAttr = selectAttr;
|
13506 | this.hrefAttr = hrefAttr;
|
13507 | this.nonBindable = nonBindable;
|
13508 | this.projectAs = projectAs;
|
13509 | }
|
13510 | }
|
13511 | function normalizeNgContentSelect(selectAttr) {
|
13512 | if (selectAttr === null || selectAttr.length === 0) {
|
13513 | return '*';
|
13514 | }
|
13515 | return selectAttr;
|
13516 | }
|
13517 |
|
13518 | /**
|
13519 | * @license
|
13520 | * Copyright Google LLC All Rights Reserved.
|
13521 | *
|
13522 | * Use of this source code is governed by an MIT-style license that can be
|
13523 | * found in the LICENSE file at https://angular.io/license
|
13524 | */
|
13525 | function isEmptyExpression(ast) {
|
13526 | if (ast instanceof ASTWithSource) {
|
13527 | ast = ast.ast;
|
13528 | }
|
13529 | return ast instanceof EmptyExpr;
|
13530 | }
|
13531 |
|
13532 | /**
|
13533 | * @license
|
13534 | * Copyright Google LLC All Rights Reserved.
|
13535 | *
|
13536 | * Use of this source code is governed by an MIT-style license that can be
|
13537 | * found in the LICENSE file at https://angular.io/license
|
13538 | */
|
13539 | /**
|
13540 | * Parses string representation of a style and converts it into object literal.
|
13541 | *
|
13542 | * @param value string representation of style as used in the `style` attribute in HTML.
|
13543 | * Example: `color: red; height: auto`.
|
13544 | * @returns An array of style property name and value pairs, e.g. `['color', 'red', 'height',
|
13545 | * 'auto']`
|
13546 | */
|
13547 | function parse(value) {
|
13548 | // we use a string array here instead of a string map
|
13549 | // because a string-map is not guaranteed to retain the
|
13550 | // order of the entries whereas a string array can be
|
13551 | // constructed in a [key, value, key, value] format.
|
13552 | const styles = [];
|
13553 | let i = 0;
|
13554 | let parenDepth = 0;
|
13555 | let quote = 0 /* QuoteNone */;
|
13556 | let valueStart = 0;
|
13557 | let propStart = 0;
|
13558 | let currentProp = null;
|
13559 | let valueHasQuotes = false;
|
13560 | while (i < value.length) {
|
13561 | const token = value.charCodeAt(i++);
|
13562 | switch (token) {
|
13563 | case 40 /* OpenParen */:
|
13564 | parenDepth++;
|
13565 | break;
|
13566 | case 41 /* CloseParen */:
|
13567 | parenDepth--;
|
13568 | break;
|
13569 | case 39 /* QuoteSingle */:
|
13570 | // valueStart needs to be there since prop values don't
|
13571 | // have quotes in CSS
|
13572 | valueHasQuotes = valueHasQuotes || valueStart > 0;
|
13573 | if (quote === 0 /* QuoteNone */) {
|
13574 | quote = 39 /* QuoteSingle */;
|
13575 | }
|
13576 | else if (quote === 39 /* QuoteSingle */ && value.charCodeAt(i - 1) !== 92 /* BackSlash */) {
|
13577 | quote = 0 /* QuoteNone */;
|
13578 | }
|
13579 | break;
|
13580 | case 34 /* QuoteDouble */:
|
13581 | // same logic as above
|
13582 | valueHasQuotes = valueHasQuotes || valueStart > 0;
|
13583 | if (quote === 0 /* QuoteNone */) {
|
13584 | quote = 34 /* QuoteDouble */;
|
13585 | }
|
13586 | else if (quote === 34 /* QuoteDouble */ && value.charCodeAt(i - 1) !== 92 /* BackSlash */) {
|
13587 | quote = 0 /* QuoteNone */;
|
13588 | }
|
13589 | break;
|
13590 | case 58 /* Colon */:
|
13591 | if (!currentProp && parenDepth === 0 && quote === 0 /* QuoteNone */) {
|
13592 | currentProp = hyphenate(value.substring(propStart, i - 1).trim());
|
13593 | valueStart = i;
|
13594 | }
|
13595 | break;
|
13596 | case 59 /* Semicolon */:
|
13597 | if (currentProp && valueStart > 0 && parenDepth === 0 && quote === 0 /* QuoteNone */) {
|
13598 | const styleVal = value.substring(valueStart, i - 1).trim();
|
13599 | styles.push(currentProp, valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal);
|
13600 | propStart = i;
|
13601 | valueStart = 0;
|
13602 | currentProp = null;
|
13603 | valueHasQuotes = false;
|
13604 | }
|
13605 | break;
|
13606 | }
|
13607 | }
|
13608 | if (currentProp && valueStart) {
|
13609 | const styleVal = value.substr(valueStart).trim();
|
13610 | styles.push(currentProp, valueHasQuotes ? stripUnnecessaryQuotes(styleVal) : styleVal);
|
13611 | }
|
13612 | return styles;
|
13613 | }
|
13614 | function stripUnnecessaryQuotes(value) {
|
13615 | const qS = value.charCodeAt(0);
|
13616 | const qE = value.charCodeAt(value.length - 1);
|
13617 | if (qS == qE && (qS == 39 /* QuoteSingle */ || qS == 34 /* QuoteDouble */)) {
|
13618 | const tempValue = value.substring(1, value.length - 1);
|
13619 | // special case to avoid using a multi-quoted string that was just chomped
|
13620 | // (e.g. `font-family: "Verdana", "sans-serif"`)
|
13621 | if (tempValue.indexOf('\'') == -1 && tempValue.indexOf('"') == -1) {
|
13622 | value = tempValue;
|
13623 | }
|
13624 | }
|
13625 | return value;
|
13626 | }
|
13627 | function hyphenate(value) {
|
13628 | return value
|
13629 | .replace(/[a-z][A-Z]/g, v => {
|
13630 | return v.charAt(0) + '-' + v.charAt(1);
|
13631 | })
|
13632 | .toLowerCase();
|
13633 | }
|
13634 |
|
13635 | const IMPORTANT_FLAG = '!important';
|
13636 | /**
|
13637 | * Minimum amount of binding slots required in the runtime for style/class bindings.
|
13638 | *
|
13639 | * Styling in Angular uses up two slots in the runtime LView/TData data structures to
|
13640 | * record binding data, property information and metadata.
|
13641 | *
|
13642 | * When a binding is registered it will place the following information in the `LView`:
|
13643 | *
|
13644 | * slot 1) binding value
|
13645 | * slot 2) cached value (all other values collected before it in string form)
|
13646 | *
|
13647 | * When a binding is registered it will place the following information in the `TData`:
|
13648 | *
|
13649 | * slot 1) prop name
|
13650 | * slot 2) binding index that points to the previous style/class binding (and some extra config
|
13651 | * values)
|
13652 | *
|
13653 | * Let's imagine we have a binding that looks like so:
|
13654 | *
|
13655 | * ```
|
13656 | * <div [style.width]="x" [style.height]="y">
|
13657 | * ```
|
13658 | *
|
13659 | * Our `LView` and `TData` data-structures look like so:
|
13660 | *
|
13661 | * ```typescript
|
13662 | * LView = [
|
13663 | * // ...
|
13664 | * x, // value of x
|
13665 | * "width: x",
|
13666 | *
|
13667 | * y, // value of y
|
13668 | * "width: x; height: y",
|
13669 | * // ...
|
13670 | * ];
|
13671 | *
|
13672 | * TData = [
|
13673 | * // ...
|
13674 | * "width", // binding slot 20
|
13675 | * 0,
|
13676 | *
|
13677 | * "height",
|
13678 | * 20,
|
13679 | * // ...
|
13680 | * ];
|
13681 | * ```
|
13682 | *
|
13683 | * */
|
13684 | const MIN_STYLING_BINDING_SLOTS_REQUIRED = 2;
|
13685 | /**
|
13686 | * Produces creation/update instructions for all styling bindings (class and style)
|
13687 | *
|
13688 | * It also produces the creation instruction to register all initial styling values
|
13689 | * (which are all the static class="..." and style="..." attribute values that exist
|
13690 | * on an element within a template).
|
13691 | *
|
13692 | * The builder class below handles producing instructions for the following cases:
|
13693 | *
|
13694 | * - Static style/class attributes (style="..." and class="...")
|
13695 | * - Dynamic style/class map bindings ([style]="map" and [class]="map|string")
|
13696 | * - Dynamic style/class property bindings ([style.prop]="exp" and [class.name]="exp")
|
13697 | *
|
13698 | * Due to the complex relationship of all of these cases, the instructions generated
|
13699 | * for these attributes/properties/bindings must be done so in the correct order. The
|
13700 | * order which these must be generated is as follows:
|
13701 | *
|
13702 | * if (createMode) {
|
13703 | * styling(...)
|
13704 | * }
|
13705 | * if (updateMode) {
|
13706 | * styleMap(...)
|
13707 | * classMap(...)
|
13708 | * styleProp(...)
|
13709 | * classProp(...)
|
13710 | * }
|
13711 | *
|
13712 | * The creation/update methods within the builder class produce these instructions.
|
13713 | */
|
13714 | class StylingBuilder {
|
13715 | constructor(_directiveExpr) {
|
13716 | this._directiveExpr = _directiveExpr;
|
13717 | /** Whether or not there are any static styling values present */
|
13718 | this._hasInitialValues = false;
|
13719 | /**
|
13720 | * Whether or not there are any styling bindings present
|
13721 | * (i.e. `[style]`, `[class]`, `[style.prop]` or `[class.name]`)
|
13722 | */
|
13723 | this.hasBindings = false;
|
13724 | this.hasBindingsWithPipes = false;
|
13725 | /** the input for [class] (if it exists) */
|
13726 | this._classMapInput = null;
|
13727 | /** the input for [style] (if it exists) */
|
13728 | this._styleMapInput = null;
|
13729 | /** an array of each [style.prop] input */
|
13730 | this._singleStyleInputs = null;
|
13731 | /** an array of each [class.name] input */
|
13732 | this._singleClassInputs = null;
|
13733 | this._lastStylingInput = null;
|
13734 | this._firstStylingInput = null;
|
13735 | // maps are used instead of hash maps because a Map will
|
13736 | // retain the ordering of the keys
|
13737 | /**
|
13738 | * Represents the location of each style binding in the template
|
13739 | * (e.g. `<div [style.width]="w" [style.height]="h">` implies
|
13740 | * that `width=0` and `height=1`)
|
13741 | */
|
13742 | this._stylesIndex = new Map();
|
13743 | /**
|
13744 | * Represents the location of each class binding in the template
|
13745 | * (e.g. `<div [class.big]="b" [class.hidden]="h">` implies
|
13746 | * that `big=0` and `hidden=1`)
|
13747 | */
|
13748 | this._classesIndex = new Map();
|
13749 | this._initialStyleValues = [];
|
13750 | this._initialClassValues = [];
|
13751 | }
|
13752 | /**
|
13753 | * Registers a given input to the styling builder to be later used when producing AOT code.
|
13754 | *
|
13755 | * The code below will only accept the input if it is somehow tied to styling (whether it be
|
13756 | * style/class bindings or static style/class attributes).
|
13757 | */
|
13758 | registerBoundInput(input) {
|
13759 | // [attr.style] or [attr.class] are skipped in the code below,
|
13760 | // they should not be treated as styling-based bindings since
|
13761 | // they are intended to be written directly to the attr and
|
13762 | // will therefore skip all style/class resolution that is present
|
13763 | // with style="", [style]="" and [style.prop]="", class="",
|
13764 | // [class.prop]="". [class]="" assignments
|
13765 | let binding = null;
|
13766 | let name = input.name;
|
13767 | switch (input.type) {
|
13768 | case 0 /* Property */:
|
13769 | binding = this.registerInputBasedOnName(name, input.value, input.sourceSpan);
|
13770 | break;
|
13771 | case 3 /* Style */:
|
13772 | binding = this.registerStyleInput(name, false, input.value, input.sourceSpan, input.unit);
|
13773 | break;
|
13774 | case 2 /* Class */:
|
13775 | binding = this.registerClassInput(name, false, input.value, input.sourceSpan);
|
13776 | break;
|
13777 | }
|
13778 | return binding ? true : false;
|
13779 | }
|
13780 | registerInputBasedOnName(name, expression, sourceSpan) {
|
13781 | let binding = null;
|
13782 | const prefix = name.substring(0, 6);
|
13783 | const isStyle = name === 'style' || prefix === 'style.' || prefix === 'style!';
|
13784 | const isClass = !isStyle && (name === 'class' || prefix === 'class.' || prefix === 'class!');
|
13785 | if (isStyle || isClass) {
|
13786 | const isMapBased = name.charAt(5) !== '.'; // style.prop or class.prop makes this a no
|
13787 | const property = name.substr(isMapBased ? 5 : 6); // the dot explains why there's a +1
|
13788 | if (isStyle) {
|
13789 | binding = this.registerStyleInput(property, isMapBased, expression, sourceSpan);
|
13790 | }
|
13791 | else {
|
13792 | binding = this.registerClassInput(property, isMapBased, expression, sourceSpan);
|
13793 | }
|
13794 | }
|
13795 | return binding;
|
13796 | }
|
13797 | registerStyleInput(name, isMapBased, value, sourceSpan, suffix) {
|
13798 | if (isEmptyExpression(value)) {
|
13799 | return null;
|
13800 | }
|
13801 | name = normalizePropName(name);
|
13802 | const { property, hasOverrideFlag, suffix: bindingSuffix } = parseProperty(name);
|
13803 | suffix = typeof suffix === 'string' && suffix.length !== 0 ? suffix : bindingSuffix;
|
13804 | const entry = { name: property, suffix: suffix, value, sourceSpan, hasOverrideFlag };
|
13805 | if (isMapBased) {
|
13806 | this._styleMapInput = entry;
|
13807 | }
|
13808 | else {
|
13809 | (this._singleStyleInputs = this._singleStyleInputs || []).push(entry);
|
13810 | registerIntoMap(this._stylesIndex, property);
|
13811 | }
|
13812 | this._lastStylingInput = entry;
|
13813 | this._firstStylingInput = this._firstStylingInput || entry;
|
13814 | this._checkForPipes(value);
|
13815 | this.hasBindings = true;
|
13816 | return entry;
|
13817 | }
|
13818 | registerClassInput(name, isMapBased, value, sourceSpan) {
|
13819 | if (isEmptyExpression(value)) {
|
13820 | return null;
|
13821 | }
|
13822 | const { property, hasOverrideFlag } = parseProperty(name);
|
13823 | const entry = { name: property, value, sourceSpan, hasOverrideFlag, suffix: null };
|
13824 | if (isMapBased) {
|
13825 | if (this._classMapInput) {
|
13826 | throw new Error('[class] and [className] bindings cannot be used on the same element simultaneously');
|
13827 | }
|
13828 | this._classMapInput = entry;
|
13829 | }
|
13830 | else {
|
13831 | (this._singleClassInputs = this._singleClassInputs || []).push(entry);
|
13832 | registerIntoMap(this._classesIndex, property);
|
13833 | }
|
13834 | this._lastStylingInput = entry;
|
13835 | this._firstStylingInput = this._firstStylingInput || entry;
|
13836 | this._checkForPipes(value);
|
13837 | this.hasBindings = true;
|
13838 | return entry;
|
13839 | }
|
13840 | _checkForPipes(value) {
|
13841 | if ((value instanceof ASTWithSource) && (value.ast instanceof BindingPipe)) {
|
13842 | this.hasBindingsWithPipes = true;
|
13843 | }
|
13844 | }
|
13845 | /**
|
13846 | * Registers the element's static style string value to the builder.
|
13847 | *
|
13848 | * @param value the style string (e.g. `width:100px; height:200px;`)
|
13849 | */
|
13850 | registerStyleAttr(value) {
|
13851 | this._initialStyleValues = parse(value);
|
13852 | this._hasInitialValues = true;
|
13853 | }
|
13854 | /**
|
13855 | * Registers the element's static class string value to the builder.
|
13856 | *
|
13857 | * @param value the className string (e.g. `disabled gold zoom`)
|
13858 | */
|
13859 | registerClassAttr(value) {
|
13860 | this._initialClassValues = value.trim().split(/\s+/g);
|
13861 | this._hasInitialValues = true;
|
13862 | }
|
13863 | /**
|
13864 | * Appends all styling-related expressions to the provided attrs array.
|
13865 | *
|
13866 | * @param attrs an existing array where each of the styling expressions
|
13867 | * will be inserted into.
|
13868 | */
|
13869 | populateInitialStylingAttrs(attrs) {
|
13870 | // [CLASS_MARKER, 'foo', 'bar', 'baz' ...]
|
13871 | if (this._initialClassValues.length) {
|
13872 | attrs.push(literal(1 /* Classes */));
|
13873 | for (let i = 0; i < this._initialClassValues.length; i++) {
|
13874 | attrs.push(literal(this._initialClassValues[i]));
|
13875 | }
|
13876 | }
|
13877 | // [STYLE_MARKER, 'width', '200px', 'height', '100px', ...]
|
13878 | if (this._initialStyleValues.length) {
|
13879 | attrs.push(literal(2 /* Styles */));
|
13880 | for (let i = 0; i < this._initialStyleValues.length; i += 2) {
|
13881 | attrs.push(literal(this._initialStyleValues[i]), literal(this._initialStyleValues[i + 1]));
|
13882 | }
|
13883 | }
|
13884 | }
|
13885 | /**
|
13886 | * Builds an instruction with all the expressions and parameters for `elementHostAttrs`.
|
13887 | *
|
13888 | * The instruction generation code below is used for producing the AOT statement code which is
|
13889 | * responsible for registering initial styles (within a directive hostBindings' creation block),
|
13890 | * as well as any of the provided attribute values, to the directive host element.
|
13891 | */
|
13892 | assignHostAttrs(attrs, definitionMap) {
|
13893 | if (this._directiveExpr && (attrs.length || this._hasInitialValues)) {
|
13894 | this.populateInitialStylingAttrs(attrs);
|
13895 | definitionMap.set('hostAttrs', literalArr(attrs));
|
13896 | }
|
13897 | }
|
13898 | /**
|
13899 | * Builds an instruction with all the expressions and parameters for `classMap`.
|
13900 | *
|
13901 | * The instruction data will contain all expressions for `classMap` to function
|
13902 | * which includes the `[class]` expression params.
|
13903 | */
|
13904 | buildClassMapInstruction(valueConverter) {
|
13905 | if (this._classMapInput) {
|
13906 | return this._buildMapBasedInstruction(valueConverter, true, this._classMapInput);
|
13907 | }
|
13908 | return null;
|
13909 | }
|
13910 | /**
|
13911 | * Builds an instruction with all the expressions and parameters for `styleMap`.
|
13912 | *
|
13913 | * The instruction data will contain all expressions for `styleMap` to function
|
13914 | * which includes the `[style]` expression params.
|
13915 | */
|
13916 | buildStyleMapInstruction(valueConverter) {
|
13917 | if (this._styleMapInput) {
|
13918 | return this._buildMapBasedInstruction(valueConverter, false, this._styleMapInput);
|
13919 | }
|
13920 | return null;
|
13921 | }
|
13922 | _buildMapBasedInstruction(valueConverter, isClassBased, stylingInput) {
|
13923 | // each styling binding value is stored in the LView
|
13924 | // map-based bindings allocate two slots: one for the
|
13925 | // previous binding value and another for the previous
|
13926 | // className or style attribute value.
|
13927 | let totalBindingSlotsRequired = MIN_STYLING_BINDING_SLOTS_REQUIRED;
|
13928 | // these values must be outside of the update block so that they can
|
13929 | // be evaluated (the AST visit call) during creation time so that any
|
13930 | // pipes can be picked up in time before the template is built
|
13931 | const mapValue = stylingInput.value.visit(valueConverter);
|
13932 | let reference;
|
13933 | if (mapValue instanceof Interpolation) {
|
13934 | totalBindingSlotsRequired += mapValue.expressions.length;
|
13935 | reference = isClassBased ? getClassMapInterpolationExpression(mapValue) :
|
13936 | getStyleMapInterpolationExpression(mapValue);
|
13937 | }
|
13938 | else {
|
13939 | reference = isClassBased ? Identifiers$1.classMap : Identifiers$1.styleMap;
|
13940 | }
|
13941 | return {
|
13942 | reference,
|
13943 | calls: [{
|
13944 | supportsInterpolation: true,
|
13945 | sourceSpan: stylingInput.sourceSpan,
|
13946 | allocateBindingSlots: totalBindingSlotsRequired,
|
13947 | params: (convertFn) => {
|
13948 | const convertResult = convertFn(mapValue);
|
13949 | const params = Array.isArray(convertResult) ? convertResult : [convertResult];
|
13950 | return params;
|
13951 | }
|
13952 | }]
|
13953 | };
|
13954 | }
|
13955 | _buildSingleInputs(reference, inputs, valueConverter, getInterpolationExpressionFn, isClassBased) {
|
13956 | const instructions = [];
|
13957 | inputs.forEach(input => {
|
13958 | const previousInstruction = instructions[instructions.length - 1];
|
13959 | const value = input.value.visit(valueConverter);
|
13960 | let referenceForCall = reference;
|
13961 | // each styling binding value is stored in the LView
|
13962 | // but there are two values stored for each binding:
|
13963 | // 1) the value itself
|
13964 | // 2) an intermediate value (concatenation of style up to this point).
|
13965 | // We need to store the intermediate value so that we don't allocate
|
13966 | // the strings on each CD.
|
13967 | let totalBindingSlotsRequired = MIN_STYLING_BINDING_SLOTS_REQUIRED;
|
13968 | if (value instanceof Interpolation) {
|
13969 | totalBindingSlotsRequired += value.expressions.length;
|
13970 | if (getInterpolationExpressionFn) {
|
13971 | referenceForCall = getInterpolationExpressionFn(value);
|
13972 | }
|
13973 | }
|
13974 | const call = {
|
13975 | sourceSpan: input.sourceSpan,
|
13976 | allocateBindingSlots: totalBindingSlotsRequired,
|
13977 | supportsInterpolation: !!getInterpolationExpressionFn,
|
13978 | params: (convertFn) => {
|
13979 | // params => stylingProp(propName, value, suffix)
|
13980 | const params = [];
|
13981 | params.push(literal(input.name));
|
13982 | const convertResult = convertFn(value);
|
13983 | if (Array.isArray(convertResult)) {
|
13984 | params.push(...convertResult);
|
13985 | }
|
13986 | else {
|
13987 | params.push(convertResult);
|
13988 | }
|
13989 | // [style.prop] bindings may use suffix values (e.g. px, em, etc...), therefore,
|
13990 | // if that is detected then we need to pass that in as an optional param.
|
13991 | if (!isClassBased && input.suffix !== null) {
|
13992 | params.push(literal(input.suffix));
|
13993 | }
|
13994 | return params;
|
13995 | }
|
13996 | };
|
13997 | // If we ended up generating a call to the same instruction as the previous styling property
|
13998 | // we can chain the calls together safely to save some bytes, otherwise we have to generate
|
13999 | // a separate instruction call. This is primarily a concern with interpolation instructions
|
14000 | // where we may start off with one `reference`, but end up using another based on the
|
14001 | // number of interpolations.
|
14002 | if (previousInstruction && previousInstruction.reference === referenceForCall) {
|
14003 | previousInstruction.calls.push(call);
|
14004 | }
|
14005 | else {
|
14006 | instructions.push({ reference: referenceForCall, calls: [call] });
|
14007 | }
|
14008 | });
|
14009 | return instructions;
|
14010 | }
|
14011 | _buildClassInputs(valueConverter) {
|
14012 | if (this._singleClassInputs) {
|
14013 | return this._buildSingleInputs(Identifiers$1.classProp, this._singleClassInputs, valueConverter, null, true);
|
14014 | }
|
14015 | return [];
|
14016 | }
|
14017 | _buildStyleInputs(valueConverter) {
|
14018 | if (this._singleStyleInputs) {
|
14019 | return this._buildSingleInputs(Identifiers$1.styleProp, this._singleStyleInputs, valueConverter, getStylePropInterpolationExpression, false);
|
14020 | }
|
14021 | return [];
|
14022 | }
|
14023 | /**
|
14024 | * Constructs all instructions which contain the expressions that will be placed
|
14025 | * into the update block of a template function or a directive hostBindings function.
|
14026 | */
|
14027 | buildUpdateLevelInstructions(valueConverter) {
|
14028 | const instructions = [];
|
14029 | if (this.hasBindings) {
|
14030 | const styleMapInstruction = this.buildStyleMapInstruction(valueConverter);
|
14031 | if (styleMapInstruction) {
|
14032 | instructions.push(styleMapInstruction);
|
14033 | }
|
14034 | const classMapInstruction = this.buildClassMapInstruction(valueConverter);
|
14035 | if (classMapInstruction) {
|
14036 | instructions.push(classMapInstruction);
|
14037 | }
|
14038 | instructions.push(...this._buildStyleInputs(valueConverter));
|
14039 | instructions.push(...this._buildClassInputs(valueConverter));
|
14040 | }
|
14041 | return instructions;
|
14042 | }
|
14043 | }
|
14044 | function registerIntoMap(map, key) {
|
14045 | if (!map.has(key)) {
|
14046 | map.set(key, map.size);
|
14047 | }
|
14048 | }
|
14049 | function parseProperty(name) {
|
14050 | let hasOverrideFlag = false;
|
14051 | const overrideIndex = name.indexOf(IMPORTANT_FLAG);
|
14052 | if (overrideIndex !== -1) {
|
14053 | name = overrideIndex > 0 ? name.substring(0, overrideIndex) : '';
|
14054 | hasOverrideFlag = true;
|
14055 | }
|
14056 | let suffix = null;
|
14057 | let property = name;
|
14058 | const unitIndex = name.lastIndexOf('.');
|
14059 | if (unitIndex > 0) {
|
14060 | suffix = name.substr(unitIndex + 1);
|
14061 | property = name.substring(0, unitIndex);
|
14062 | }
|
14063 | return { property, suffix, hasOverrideFlag };
|
14064 | }
|
14065 | /**
|
14066 | * Gets the instruction to generate for an interpolated class map.
|
14067 | * @param interpolation An Interpolation AST
|
14068 | */
|
14069 | function getClassMapInterpolationExpression(interpolation) {
|
14070 | switch (getInterpolationArgsLength(interpolation)) {
|
14071 | case 1:
|
14072 | return Identifiers$1.classMap;
|
14073 | case 3:
|
14074 | return Identifiers$1.classMapInterpolate1;
|
14075 | case 5:
|
14076 | return Identifiers$1.classMapInterpolate2;
|
14077 | case 7:
|
14078 | return Identifiers$1.classMapInterpolate3;
|
14079 | case 9:
|
14080 | return Identifiers$1.classMapInterpolate4;
|
14081 | case 11:
|
14082 | return Identifiers$1.classMapInterpolate5;
|
14083 | case 13:
|
14084 | return Identifiers$1.classMapInterpolate6;
|
14085 | case 15:
|
14086 | return Identifiers$1.classMapInterpolate7;
|
14087 | case 17:
|
14088 | return Identifiers$1.classMapInterpolate8;
|
14089 | default:
|
14090 | return Identifiers$1.classMapInterpolateV;
|
14091 | }
|
14092 | }
|
14093 | /**
|
14094 | * Gets the instruction to generate for an interpolated style map.
|
14095 | * @param interpolation An Interpolation AST
|
14096 | */
|
14097 | function getStyleMapInterpolationExpression(interpolation) {
|
14098 | switch (getInterpolationArgsLength(interpolation)) {
|
14099 | case 1:
|
14100 | return Identifiers$1.styleMap;
|
14101 | case 3:
|
14102 | return Identifiers$1.styleMapInterpolate1;
|
14103 | case 5:
|
14104 | return Identifiers$1.styleMapInterpolate2;
|
14105 | case 7:
|
14106 | return Identifiers$1.styleMapInterpolate3;
|
14107 | case 9:
|
14108 | return Identifiers$1.styleMapInterpolate4;
|
14109 | case 11:
|
14110 | return Identifiers$1.styleMapInterpolate5;
|
14111 | case 13:
|
14112 | return Identifiers$1.styleMapInterpolate6;
|
14113 | case 15:
|
14114 | return Identifiers$1.styleMapInterpolate7;
|
14115 | case 17:
|
14116 | return Identifiers$1.styleMapInterpolate8;
|
14117 | default:
|
14118 | return Identifiers$1.styleMapInterpolateV;
|
14119 | }
|
14120 | }
|
14121 | /**
|
14122 | * Gets the instruction to generate for an interpolated style prop.
|
14123 | * @param interpolation An Interpolation AST
|
14124 | */
|
14125 | function getStylePropInterpolationExpression(interpolation) {
|
14126 | switch (getInterpolationArgsLength(interpolation)) {
|
14127 | case 1:
|
14128 | return Identifiers$1.styleProp;
|
14129 | case 3:
|
14130 | return Identifiers$1.stylePropInterpolate1;
|
14131 | case 5:
|
14132 | return Identifiers$1.stylePropInterpolate2;
|
14133 | case 7:
|
14134 | return Identifiers$1.stylePropInterpolate3;
|
14135 | case 9:
|
14136 | return Identifiers$1.stylePropInterpolate4;
|
14137 | case 11:
|
14138 | return Identifiers$1.stylePropInterpolate5;
|
14139 | case 13:
|
14140 | return Identifiers$1.stylePropInterpolate6;
|
14141 | case 15:
|
14142 | return Identifiers$1.stylePropInterpolate7;
|
14143 | case 17:
|
14144 | return Identifiers$1.stylePropInterpolate8;
|
14145 | default:
|
14146 | return Identifiers$1.stylePropInterpolateV;
|
14147 | }
|
14148 | }
|
14149 | function normalizePropName(prop) {
|
14150 | return hyphenate(prop);
|
14151 | }
|
14152 |
|
14153 | /**
|
14154 | * @license
|
14155 | * Copyright Google LLC All Rights Reserved.
|
14156 | *
|
14157 | * Use of this source code is governed by an MIT-style license that can be
|
14158 | * found in the LICENSE file at https://angular.io/license
|
14159 | */
|
14160 | var TokenType$1;
|
14161 | (function (TokenType) {
|
14162 | TokenType[TokenType["Character"] = 0] = "Character";
|
14163 | TokenType[TokenType["Identifier"] = 1] = "Identifier";
|
14164 | TokenType[TokenType["Keyword"] = 2] = "Keyword";
|
14165 | TokenType[TokenType["String"] = 3] = "String";
|
14166 | TokenType[TokenType["Operator"] = 4] = "Operator";
|
14167 | TokenType[TokenType["Number"] = 5] = "Number";
|
14168 | TokenType[TokenType["Error"] = 6] = "Error";
|
14169 | })(TokenType$1 || (TokenType$1 = {}));
|
14170 | const KEYWORDS = ['var', 'let', 'as', 'null', 'undefined', 'true', 'false', 'if', 'else', 'this'];
|
14171 | class Lexer {
|
14172 | tokenize(text) {
|
14173 | const scanner = new _Scanner(text);
|
14174 | const tokens = [];
|
14175 | let token = scanner.scanToken();
|
14176 | while (token != null) {
|
14177 | tokens.push(token);
|
14178 | token = scanner.scanToken();
|
14179 | }
|
14180 | return tokens;
|
14181 | }
|
14182 | }
|
14183 | class Token$1 {
|
14184 | constructor(index, end, type, numValue, strValue) {
|
14185 | this.index = index;
|
14186 | this.end = end;
|
14187 | this.type = type;
|
14188 | this.numValue = numValue;
|
14189 | this.strValue = strValue;
|
14190 | }
|
14191 | isCharacter(code) {
|
14192 | return this.type == TokenType$1.Character && this.numValue == code;
|
14193 | }
|
14194 | isNumber() {
|
14195 | return this.type == TokenType$1.Number;
|
14196 | }
|
14197 | isString() {
|
14198 | return this.type == TokenType$1.String;
|
14199 | }
|
14200 | isOperator(operator) {
|
14201 | return this.type == TokenType$1.Operator && this.strValue == operator;
|
14202 | }
|
14203 | isIdentifier() {
|
14204 | return this.type == TokenType$1.Identifier;
|
14205 | }
|
14206 | isKeyword() {
|
14207 | return this.type == TokenType$1.Keyword;
|
14208 | }
|
14209 | isKeywordLet() {
|
14210 | return this.type == TokenType$1.Keyword && this.strValue == 'let';
|
14211 | }
|
14212 | isKeywordAs() {
|
14213 | return this.type == TokenType$1.Keyword && this.strValue == 'as';
|
14214 | }
|
14215 | isKeywordNull() {
|
14216 | return this.type == TokenType$1.Keyword && this.strValue == 'null';
|
14217 | }
|
14218 | isKeywordUndefined() {
|
14219 | return this.type == TokenType$1.Keyword && this.strValue == 'undefined';
|
14220 | }
|
14221 | isKeywordTrue() {
|
14222 | return this.type == TokenType$1.Keyword && this.strValue == 'true';
|
14223 | }
|
14224 | isKeywordFalse() {
|
14225 | return this.type == TokenType$1.Keyword && this.strValue == 'false';
|
14226 | }
|
14227 | isKeywordThis() {
|
14228 | return this.type == TokenType$1.Keyword && this.strValue == 'this';
|
14229 | }
|
14230 | isError() {
|
14231 | return this.type == TokenType$1.Error;
|
14232 | }
|
14233 | toNumber() {
|
14234 | return this.type == TokenType$1.Number ? this.numValue : -1;
|
14235 | }
|
14236 | toString() {
|
14237 | switch (this.type) {
|
14238 | case TokenType$1.Character:
|
14239 | case TokenType$1.Identifier:
|
14240 | case TokenType$1.Keyword:
|
14241 | case TokenType$1.Operator:
|
14242 | case TokenType$1.String:
|
14243 | case TokenType$1.Error:
|
14244 | return this.strValue;
|
14245 | case TokenType$1.Number:
|
14246 | return this.numValue.toString();
|
14247 | default:
|
14248 | return null;
|
14249 | }
|
14250 | }
|
14251 | }
|
14252 | function newCharacterToken(index, end, code) {
|
14253 | return new Token$1(index, end, TokenType$1.Character, code, String.fromCharCode(code));
|
14254 | }
|
14255 | function newIdentifierToken(index, end, text) {
|
14256 | return new Token$1(index, end, TokenType$1.Identifier, 0, text);
|
14257 | }
|
14258 | function newKeywordToken(index, end, text) {
|
14259 | return new Token$1(index, end, TokenType$1.Keyword, 0, text);
|
14260 | }
|
14261 | function newOperatorToken(index, end, text) {
|
14262 | return new Token$1(index, end, TokenType$1.Operator, 0, text);
|
14263 | }
|
14264 | function newStringToken(index, end, text) {
|
14265 | return new Token$1(index, end, TokenType$1.String, 0, text);
|
14266 | }
|
14267 | function newNumberToken(index, end, n) {
|
14268 | return new Token$1(index, end, TokenType$1.Number, n, '');
|
14269 | }
|
14270 | function newErrorToken(index, end, message) {
|
14271 | return new Token$1(index, end, TokenType$1.Error, 0, message);
|
14272 | }
|
14273 | const EOF = new Token$1(-1, -1, TokenType$1.Character, 0, '');
|
14274 | class _Scanner {
|
14275 | constructor(input) {
|
14276 | this.input = input;
|
14277 | this.peek = 0;
|
14278 | this.index = -1;
|
14279 | this.length = input.length;
|
14280 | this.advance();
|
14281 | }
|
14282 | advance() {
|
14283 | this.peek = ++this.index >= this.length ? $EOF : this.input.charCodeAt(this.index);
|
14284 | }
|
14285 | scanToken() {
|
14286 | const input = this.input, length = this.length;
|
14287 | let peek = this.peek, index = this.index;
|
14288 | // Skip whitespace.
|
14289 | while (peek <= $SPACE) {
|
14290 | if (++index >= length) {
|
14291 | peek = $EOF;
|
14292 | break;
|
14293 | }
|
14294 | else {
|
14295 | peek = input.charCodeAt(index);
|
14296 | }
|
14297 | }
|
14298 | this.peek = peek;
|
14299 | this.index = index;
|
14300 | if (index >= length) {
|
14301 | return null;
|
14302 | }
|
14303 | // Handle identifiers and numbers.
|
14304 | if (isIdentifierStart(peek))
|
14305 | return this.scanIdentifier();
|
14306 | if (isDigit(peek))
|
14307 | return this.scanNumber(index);
|
14308 | const start = index;
|
14309 | switch (peek) {
|
14310 | case $PERIOD:
|
14311 | this.advance();
|
14312 | return isDigit(this.peek) ? this.scanNumber(start) :
|
14313 | newCharacterToken(start, this.index, $PERIOD);
|
14314 | case $LPAREN:
|
14315 | case $RPAREN:
|
14316 | case $LBRACE:
|
14317 | case $RBRACE:
|
14318 | case $LBRACKET:
|
14319 | case $RBRACKET:
|
14320 | case $COMMA:
|
14321 | case $COLON:
|
14322 | case $SEMICOLON:
|
14323 | return this.scanCharacter(start, peek);
|
14324 | case $SQ:
|
14325 | case $DQ:
|
14326 | return this.scanString();
|
14327 | case $HASH:
|
14328 | case $PLUS:
|
14329 | case $MINUS:
|
14330 | case $STAR:
|
14331 | case $SLASH:
|
14332 | case $PERCENT:
|
14333 | case $CARET:
|
14334 | return this.scanOperator(start, String.fromCharCode(peek));
|
14335 | case $QUESTION:
|
14336 | return this.scanComplexOperator(start, '?', $PERIOD, '.');
|
14337 | case $LT:
|
14338 | case $GT:
|
14339 | return this.scanComplexOperator(start, String.fromCharCode(peek), $EQ, '=');
|
14340 | case $BANG:
|
14341 | case $EQ:
|
14342 | return this.scanComplexOperator(start, String.fromCharCode(peek), $EQ, '=', $EQ, '=');
|
14343 | case $AMPERSAND:
|
14344 | return this.scanComplexOperator(start, '&', $AMPERSAND, '&');
|
14345 | case $BAR:
|
14346 | return this.scanComplexOperator(start, '|', $BAR, '|');
|
14347 | case $NBSP:
|
14348 | while (isWhitespace(this.peek))
|
14349 | this.advance();
|
14350 | return this.scanToken();
|
14351 | }
|
14352 | this.advance();
|
14353 | return this.error(`Unexpected character [${String.fromCharCode(peek)}]`, 0);
|
14354 | }
|
14355 | scanCharacter(start, code) {
|
14356 | this.advance();
|
14357 | return newCharacterToken(start, this.index, code);
|
14358 | }
|
14359 | scanOperator(start, str) {
|
14360 | this.advance();
|
14361 | return newOperatorToken(start, this.index, str);
|
14362 | }
|
14363 | /**
|
14364 | * Tokenize a 2/3 char long operator
|
14365 | *
|
14366 | * @param start start index in the expression
|
14367 | * @param one first symbol (always part of the operator)
|
14368 | * @param twoCode code point for the second symbol
|
14369 | * @param two second symbol (part of the operator when the second code point matches)
|
14370 | * @param threeCode code point for the third symbol
|
14371 | * @param three third symbol (part of the operator when provided and matches source expression)
|
14372 | */
|
14373 | scanComplexOperator(start, one, twoCode, two, threeCode, three) {
|
14374 | this.advance();
|
14375 | let str = one;
|
14376 | if (this.peek == twoCode) {
|
14377 | this.advance();
|
14378 | str += two;
|
14379 | }
|
14380 | if (threeCode != null && this.peek == threeCode) {
|
14381 | this.advance();
|
14382 | str += three;
|
14383 | }
|
14384 | return newOperatorToken(start, this.index, str);
|
14385 | }
|
14386 | scanIdentifier() {
|
14387 | const start = this.index;
|
14388 | this.advance();
|
14389 | while (isIdentifierPart(this.peek))
|
14390 | this.advance();
|
14391 | const str = this.input.substring(start, this.index);
|
14392 | return KEYWORDS.indexOf(str) > -1 ? newKeywordToken(start, this.index, str) :
|
14393 | newIdentifierToken(start, this.index, str);
|
14394 | }
|
14395 | scanNumber(start) {
|
14396 | let simple = (this.index === start);
|
14397 | this.advance(); // Skip initial digit.
|
14398 | while (true) {
|
14399 | if (isDigit(this.peek)) ;
|
14400 | else if (this.peek == $PERIOD) {
|
14401 | simple = false;
|
14402 | }
|
14403 | else if (isExponentStart(this.peek)) {
|
14404 | this.advance();
|
14405 | if (isExponentSign(this.peek))
|
14406 | this.advance();
|
14407 | if (!isDigit(this.peek))
|
14408 | return this.error('Invalid exponent', -1);
|
14409 | simple = false;
|
14410 | }
|
14411 | else {
|
14412 | break;
|
14413 | }
|
14414 | this.advance();
|
14415 | }
|
14416 | const str = this.input.substring(start, this.index);
|
14417 | const value = simple ? parseIntAutoRadix(str) : parseFloat(str);
|
14418 | return newNumberToken(start, this.index, value);
|
14419 | }
|
14420 | scanString() {
|
14421 | const start = this.index;
|
14422 | const quote = this.peek;
|
14423 | this.advance(); // Skip initial quote.
|
14424 | let buffer = '';
|
14425 | let marker = this.index;
|
14426 | const input = this.input;
|
14427 | while (this.peek != quote) {
|
14428 | if (this.peek == $BACKSLASH) {
|
14429 | buffer += input.substring(marker, this.index);
|
14430 | this.advance();
|
14431 | let unescapedCode;
|
14432 | // Workaround for TS2.1-introduced type strictness
|
14433 | this.peek = this.peek;
|
14434 | if (this.peek == $u) {
|
14435 | // 4 character hex code for unicode character.
|
14436 | const hex = input.substring(this.index + 1, this.index + 5);
|
14437 | if (/^[0-9a-f]+$/i.test(hex)) {
|
14438 | unescapedCode = parseInt(hex, 16);
|
14439 | }
|
14440 | else {
|
14441 | return this.error(`Invalid unicode escape [\\u${hex}]`, 0);
|
14442 | }
|
14443 | for (let i = 0; i < 5; i++) {
|
14444 | this.advance();
|
14445 | }
|
14446 | }
|
14447 | else {
|
14448 | unescapedCode = unescape(this.peek);
|
14449 | this.advance();
|
14450 | }
|
14451 | buffer += String.fromCharCode(unescapedCode);
|
14452 | marker = this.index;
|
14453 | }
|
14454 | else if (this.peek == $EOF) {
|
14455 | return this.error('Unterminated quote', 0);
|
14456 | }
|
14457 | else {
|
14458 | this.advance();
|
14459 | }
|
14460 | }
|
14461 | const last = input.substring(marker, this.index);
|
14462 | this.advance(); // Skip terminating quote.
|
14463 | return newStringToken(start, this.index, buffer + last);
|
14464 | }
|
14465 | error(message, offset) {
|
14466 | const position = this.index + offset;
|
14467 | return newErrorToken(position, this.index, `Lexer Error: ${message} at column ${position} in expression [${this.input}]`);
|
14468 | }
|
14469 | }
|
14470 | function isIdentifierStart(code) {
|
14471 | return ($a <= code && code <= $z) || ($A <= code && code <= $Z) ||
|
14472 | (code == $_) || (code == $$);
|
14473 | }
|
14474 | function isIdentifier(input) {
|
14475 | if (input.length == 0)
|
14476 | return false;
|
14477 | const scanner = new _Scanner(input);
|
14478 | if (!isIdentifierStart(scanner.peek))
|
14479 | return false;
|
14480 | scanner.advance();
|
14481 | while (scanner.peek !== $EOF) {
|
14482 | if (!isIdentifierPart(scanner.peek))
|
14483 | return false;
|
14484 | scanner.advance();
|
14485 | }
|
14486 | return true;
|
14487 | }
|
14488 | function isIdentifierPart(code) {
|
14489 | return isAsciiLetter(code) || isDigit(code) || (code == $_) ||
|
14490 | (code == $$);
|
14491 | }
|
14492 | function isExponentStart(code) {
|
14493 | return code == $e || code == $E;
|
14494 | }
|
14495 | function isExponentSign(code) {
|
14496 | return code == $MINUS || code == $PLUS;
|
14497 | }
|
14498 | function isQuote(code) {
|
14499 | return code === $SQ || code === $DQ || code === $BT;
|
14500 | }
|
14501 | function unescape(code) {
|
14502 | switch (code) {
|
14503 | case $n:
|
14504 | return $LF;
|
14505 | case $f:
|
14506 | return $FF;
|
14507 | case $r:
|
14508 | return $CR;
|
14509 | case $t:
|
14510 | return $TAB;
|
14511 | case $v:
|
14512 | return $VTAB;
|
14513 | default:
|
14514 | return code;
|
14515 | }
|
14516 | }
|
14517 | function parseIntAutoRadix(text) {
|
14518 | const result = parseInt(text);
|
14519 | if (isNaN(result)) {
|
14520 | throw new Error('Invalid integer literal when parsing ' + text);
|
14521 | }
|
14522 | return result;
|
14523 | }
|
14524 |
|
14525 | /**
|
14526 | * @license
|
14527 | * Copyright Google LLC All Rights Reserved.
|
14528 | *
|
14529 | * Use of this source code is governed by an MIT-style license that can be
|
14530 | * found in the LICENSE file at https://angular.io/license
|
14531 | */
|
14532 | class SplitInterpolation {
|
14533 | constructor(strings, expressions, offsets) {
|
14534 | this.strings = strings;
|
14535 | this.expressions = expressions;
|
14536 | this.offsets = offsets;
|
14537 | }
|
14538 | }
|
14539 | class TemplateBindingParseResult {
|
14540 | constructor(templateBindings, warnings, errors) {
|
14541 | this.templateBindings = templateBindings;
|
14542 | this.warnings = warnings;
|
14543 | this.errors = errors;
|
14544 | }
|
14545 | }
|
14546 | class Parser$1 {
|
14547 | constructor(_lexer) {
|
14548 | this._lexer = _lexer;
|
14549 | this.errors = [];
|
14550 | this.simpleExpressionChecker = SimpleExpressionChecker;
|
14551 | }
|
14552 | parseAction(input, location, absoluteOffset, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
|
14553 | this._checkNoInterpolation(input, location, interpolationConfig);
|
14554 | const sourceToLex = this._stripComments(input);
|
14555 | const tokens = this._lexer.tokenize(this._stripComments(input));
|
14556 | const ast = new _ParseAST(input, location, absoluteOffset, tokens, sourceToLex.length, true, this.errors, input.length - sourceToLex.length)
|
14557 | .parseChain();
|
14558 | return new ASTWithSource(ast, input, location, absoluteOffset, this.errors);
|
14559 | }
|
14560 | parseBinding(input, location, absoluteOffset, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
|
14561 | const ast = this._parseBindingAst(input, location, absoluteOffset, interpolationConfig);
|
14562 | return new ASTWithSource(ast, input, location, absoluteOffset, this.errors);
|
14563 | }
|
14564 | checkSimpleExpression(ast) {
|
14565 | const checker = new this.simpleExpressionChecker();
|
14566 | ast.visit(checker);
|
14567 | return checker.errors;
|
14568 | }
|
14569 | parseSimpleBinding(input, location, absoluteOffset, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
|
14570 | const ast = this._parseBindingAst(input, location, absoluteOffset, interpolationConfig);
|
14571 | const errors = this.checkSimpleExpression(ast);
|
14572 | if (errors.length > 0) {
|
14573 | this._reportError(`Host binding expression cannot contain ${errors.join(' ')}`, input, location);
|
14574 | }
|
14575 | return new ASTWithSource(ast, input, location, absoluteOffset, this.errors);
|
14576 | }
|
14577 | _reportError(message, input, errLocation, ctxLocation) {
|
14578 | this.errors.push(new ParserError(message, input, errLocation, ctxLocation));
|
14579 | }
|
14580 | _parseBindingAst(input, location, absoluteOffset, interpolationConfig) {
|
14581 | // Quotes expressions use 3rd-party expression language. We don't want to use
|
14582 | // our lexer or parser for that, so we check for that ahead of time.
|
14583 | const quote = this._parseQuote(input, location, absoluteOffset);
|
14584 | if (quote != null) {
|
14585 | return quote;
|
14586 | }
|
14587 | this._checkNoInterpolation(input, location, interpolationConfig);
|
14588 | const sourceToLex = this._stripComments(input);
|
14589 | const tokens = this._lexer.tokenize(sourceToLex);
|
14590 | return new _ParseAST(input, location, absoluteOffset, tokens, sourceToLex.length, false, this.errors, input.length - sourceToLex.length)
|
14591 | .parseChain();
|
14592 | }
|
14593 | _parseQuote(input, location, absoluteOffset) {
|
14594 | if (input == null)
|
14595 | return null;
|
14596 | const prefixSeparatorIndex = input.indexOf(':');
|
14597 | if (prefixSeparatorIndex == -1)
|
14598 | return null;
|
14599 | const prefix = input.substring(0, prefixSeparatorIndex).trim();
|
14600 | if (!isIdentifier(prefix))
|
14601 | return null;
|
14602 | const uninterpretedExpression = input.substring(prefixSeparatorIndex + 1);
|
14603 | const span = new ParseSpan(0, input.length);
|
14604 | return new Quote(span, span.toAbsolute(absoluteOffset), prefix, uninterpretedExpression, location);
|
14605 | }
|
14606 | /**
|
14607 | * Parse microsyntax template expression and return a list of bindings or
|
14608 | * parsing errors in case the given expression is invalid.
|
14609 | *
|
14610 | * For example,
|
14611 | * ```
|
14612 | * <div *ngFor="let item of items">
|
14613 | * ^ ^ absoluteValueOffset for `templateValue`
|
14614 | * absoluteKeyOffset for `templateKey`
|
14615 | * ```
|
14616 | * contains three bindings:
|
14617 | * 1. ngFor -> null
|
14618 | * 2. item -> NgForOfContext.$implicit
|
14619 | * 3. ngForOf -> items
|
14620 | *
|
14621 | * This is apparent from the de-sugared template:
|
14622 | * ```
|
14623 | * <ng-template ngFor let-item [ngForOf]="items">
|
14624 | * ```
|
14625 | *
|
14626 | * @param templateKey name of directive, without the * prefix. For example: ngIf, ngFor
|
14627 | * @param templateValue RHS of the microsyntax attribute
|
14628 | * @param templateUrl template filename if it's external, component filename if it's inline
|
14629 | * @param absoluteKeyOffset start of the `templateKey`
|
14630 | * @param absoluteValueOffset start of the `templateValue`
|
14631 | */
|
14632 | parseTemplateBindings(templateKey, templateValue, templateUrl, absoluteKeyOffset, absoluteValueOffset) {
|
14633 | const tokens = this._lexer.tokenize(templateValue);
|
14634 | const parser = new _ParseAST(templateValue, templateUrl, absoluteValueOffset, tokens, templateValue.length, false /* parseAction */, this.errors, 0 /* relative offset */);
|
14635 | return parser.parseTemplateBindings({
|
14636 | source: templateKey,
|
14637 | span: new AbsoluteSourceSpan(absoluteKeyOffset, absoluteKeyOffset + templateKey.length),
|
14638 | });
|
14639 | }
|
14640 | parseInterpolation(input, location, absoluteOffset, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
|
14641 | const { strings, expressions, offsets } = this.splitInterpolation(input, location, interpolationConfig);
|
14642 | if (expressions.length === 0)
|
14643 | return null;
|
14644 | const expressionNodes = [];
|
14645 | for (let i = 0; i < expressions.length; ++i) {
|
14646 | const expressionText = expressions[i].text;
|
14647 | const sourceToLex = this._stripComments(expressionText);
|
14648 | const tokens = this._lexer.tokenize(sourceToLex);
|
14649 | const ast = new _ParseAST(input, location, absoluteOffset, tokens, sourceToLex.length, false, this.errors, offsets[i] + (expressionText.length - sourceToLex.length))
|
14650 | .parseChain();
|
14651 | expressionNodes.push(ast);
|
14652 | }
|
14653 | return this.createInterpolationAst(strings.map(s => s.text), expressionNodes, input, location, absoluteOffset);
|
14654 | }
|
14655 | /**
|
14656 | * Similar to `parseInterpolation`, but treats the provided string as a single expression
|
14657 | * element that would normally appear within the interpolation prefix and suffix (`{{` and `}}`).
|
14658 | * This is used for parsing the switch expression in ICUs.
|
14659 | */
|
14660 | parseInterpolationExpression(expression, location, absoluteOffset) {
|
14661 | const sourceToLex = this._stripComments(expression);
|
14662 | const tokens = this._lexer.tokenize(sourceToLex);
|
14663 | const ast = new _ParseAST(expression, location, absoluteOffset, tokens, sourceToLex.length,
|
14664 | /* parseAction */ false, this.errors, 0)
|
14665 | .parseChain();
|
14666 | const strings = ['', '']; // The prefix and suffix strings are both empty
|
14667 | return this.createInterpolationAst(strings, [ast], expression, location, absoluteOffset);
|
14668 | }
|
14669 | createInterpolationAst(strings, expressions, input, location, absoluteOffset) {
|
14670 | const span = new ParseSpan(0, input.length);
|
14671 | const interpolation = new Interpolation(span, span.toAbsolute(absoluteOffset), strings, expressions);
|
14672 | return new ASTWithSource(interpolation, input, location, absoluteOffset, this.errors);
|
14673 | }
|
14674 | /**
|
14675 | * Splits a string of text into "raw" text segments and expressions present in interpolations in
|
14676 | * the string.
|
14677 | * Returns `null` if there are no interpolations, otherwise a
|
14678 | * `SplitInterpolation` with splits that look like
|
14679 | * <raw text> <expression> <raw text> ... <raw text> <expression> <raw text>
|
14680 | */
|
14681 | splitInterpolation(input, location, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
|
14682 | const strings = [];
|
14683 | const expressions = [];
|
14684 | const offsets = [];
|
14685 | let i = 0;
|
14686 | let atInterpolation = false;
|
14687 | let extendLastString = false;
|
14688 | let { start: interpStart, end: interpEnd } = interpolationConfig;
|
14689 | while (i < input.length) {
|
14690 | if (!atInterpolation) {
|
14691 | // parse until starting {{
|
14692 | const start = i;
|
14693 | i = input.indexOf(interpStart, i);
|
14694 | if (i === -1) {
|
14695 | i = input.length;
|
14696 | }
|
14697 | const text = input.substring(start, i);
|
14698 | strings.push({ text, start, end: i });
|
14699 | atInterpolation = true;
|
14700 | }
|
14701 | else {
|
14702 | // parse from starting {{ to ending }} while ignoring content inside quotes.
|
14703 | const fullStart = i;
|
14704 | const exprStart = fullStart + interpStart.length;
|
14705 | const exprEnd = this._getInterpolationEndIndex(input, interpEnd, exprStart);
|
14706 | if (exprEnd === -1) {
|
14707 | // Could not find the end of the interpolation; do not parse an expression.
|
14708 | // Instead we should extend the content on the last raw string.
|
14709 | atInterpolation = false;
|
14710 | extendLastString = true;
|
14711 | break;
|
14712 | }
|
14713 | const fullEnd = exprEnd + interpEnd.length;
|
14714 | const text = input.substring(exprStart, exprEnd);
|
14715 | if (text.trim().length === 0) {
|
14716 | this._reportError('Blank expressions are not allowed in interpolated strings', input, `at column ${i} in`, location);
|
14717 | }
|
14718 | expressions.push({ text, start: fullStart, end: fullEnd });
|
14719 | offsets.push(exprStart);
|
14720 | i = fullEnd;
|
14721 | atInterpolation = false;
|
14722 | }
|
14723 | }
|
14724 | if (!atInterpolation) {
|
14725 | // If we are now at a text section, add the remaining content as a raw string.
|
14726 | if (extendLastString) {
|
14727 | const piece = strings[strings.length - 1];
|
14728 | piece.text += input.substring(i);
|
14729 | piece.end = input.length;
|
14730 | }
|
14731 | else {
|
14732 | strings.push({ text: input.substring(i), start: i, end: input.length });
|
14733 | }
|
14734 | }
|
14735 | return new SplitInterpolation(strings, expressions, offsets);
|
14736 | }
|
14737 | wrapLiteralPrimitive(input, location, absoluteOffset) {
|
14738 | const span = new ParseSpan(0, input == null ? 0 : input.length);
|
14739 | return new ASTWithSource(new LiteralPrimitive(span, span.toAbsolute(absoluteOffset), input), input, location, absoluteOffset, this.errors);
|
14740 | }
|
14741 | _stripComments(input) {
|
14742 | const i = this._commentStart(input);
|
14743 | return i != null ? input.substring(0, i).trim() : input;
|
14744 | }
|
14745 | _commentStart(input) {
|
14746 | let outerQuote = null;
|
14747 | for (let i = 0; i < input.length - 1; i++) {
|
14748 | const char = input.charCodeAt(i);
|
14749 | const nextChar = input.charCodeAt(i + 1);
|
14750 | if (char === $SLASH && nextChar == $SLASH && outerQuote == null)
|
14751 | return i;
|
14752 | if (outerQuote === char) {
|
14753 | outerQuote = null;
|
14754 | }
|
14755 | else if (outerQuote == null && isQuote(char)) {
|
14756 | outerQuote = char;
|
14757 | }
|
14758 | }
|
14759 | return null;
|
14760 | }
|
14761 | _checkNoInterpolation(input, location, { start, end }) {
|
14762 | let startIndex = -1;
|
14763 | let endIndex = -1;
|
14764 | for (const charIndex of this._forEachUnquotedChar(input, 0)) {
|
14765 | if (startIndex === -1) {
|
14766 | if (input.startsWith(start)) {
|
14767 | startIndex = charIndex;
|
14768 | }
|
14769 | }
|
14770 | else {
|
14771 | endIndex = this._getInterpolationEndIndex(input, end, charIndex);
|
14772 | if (endIndex > -1) {
|
14773 | break;
|
14774 | }
|
14775 | }
|
14776 | }
|
14777 | if (startIndex > -1 && endIndex > -1) {
|
14778 | this._reportError(`Got interpolation (${start}${end}) where expression was expected`, input, `at column ${startIndex} in`, location);
|
14779 | }
|
14780 | }
|
14781 | /**
|
14782 | * Finds the index of the end of an interpolation expression
|
14783 | * while ignoring comments and quoted content.
|
14784 | */
|
14785 | _getInterpolationEndIndex(input, expressionEnd, start) {
|
14786 | for (const charIndex of this._forEachUnquotedChar(input, start)) {
|
14787 | if (input.startsWith(expressionEnd, charIndex)) {
|
14788 | return charIndex;
|
14789 | }
|
14790 | // Nothing else in the expression matters after we've
|
14791 | // hit a comment so look directly for the end token.
|
14792 | if (input.startsWith('//', charIndex)) {
|
14793 | return input.indexOf(expressionEnd, charIndex);
|
14794 | }
|
14795 | }
|
14796 | return -1;
|
14797 | }
|
14798 | /**
|
14799 | * Generator used to iterate over the character indexes of a string that are outside of quotes.
|
14800 | * @param input String to loop through.
|
14801 | * @param start Index within the string at which to start.
|
14802 | */
|
14803 | *_forEachUnquotedChar(input, start) {
|
14804 | let currentQuote = null;
|
14805 | let escapeCount = 0;
|
14806 | for (let i = start; i < input.length; i++) {
|
14807 | const char = input[i];
|
14808 | // Skip the characters inside quotes. Note that we only care about the outer-most
|
14809 | // quotes matching up and we need to account for escape characters.
|
14810 | if (isQuote(input.charCodeAt(i)) && (currentQuote === null || currentQuote === char) &&
|
14811 | escapeCount % 2 === 0) {
|
14812 | currentQuote = currentQuote === null ? char : null;
|
14813 | }
|
14814 | else if (currentQuote === null) {
|
14815 | yield i;
|
14816 | }
|
14817 | escapeCount = char === '\\' ? escapeCount + 1 : 0;
|
14818 | }
|
14819 | }
|
14820 | }
|
14821 | class IvyParser extends Parser$1 {
|
14822 | constructor() {
|
14823 | super(...arguments);
|
14824 | this.simpleExpressionChecker = IvySimpleExpressionChecker;
|
14825 | }
|
14826 | }
|
14827 | /** Describes a stateful context an expression parser is in. */
|
14828 | var ParseContextFlags;
|
14829 | (function (ParseContextFlags) {
|
14830 | ParseContextFlags[ParseContextFlags["None"] = 0] = "None";
|
14831 | /**
|
14832 | * A Writable context is one in which a value may be written to an lvalue.
|
14833 | * For example, after we see a property access, we may expect a write to the
|
14834 | * property via the "=" operator.
|
14835 | * prop
|
14836 | * ^ possible "=" after
|
14837 | */
|
14838 | ParseContextFlags[ParseContextFlags["Writable"] = 1] = "Writable";
|
14839 | })(ParseContextFlags || (ParseContextFlags = {}));
|
14840 | class _ParseAST {
|
14841 | constructor(input, location, absoluteOffset, tokens, inputLength, parseAction, errors, offset) {
|
14842 | this.input = input;
|
14843 | this.location = location;
|
14844 | this.absoluteOffset = absoluteOffset;
|
14845 | this.tokens = tokens;
|
14846 | this.inputLength = inputLength;
|
14847 | this.parseAction = parseAction;
|
14848 | this.errors = errors;
|
14849 | this.offset = offset;
|
14850 | this.rparensExpected = 0;
|
14851 | this.rbracketsExpected = 0;
|
14852 | this.rbracesExpected = 0;
|
14853 | this.context = ParseContextFlags.None;
|
14854 | // Cache of expression start and input indeces to the absolute source span they map to, used to
|
14855 | // prevent creating superfluous source spans in `sourceSpan`.
|
14856 | // A serial of the expression start and input index is used for mapping because both are stateful
|
14857 | // and may change for subsequent expressions visited by the parser.
|
14858 | this.sourceSpanCache = new Map();
|
14859 | this.index = 0;
|
14860 | }
|
14861 | peek(offset) {
|
14862 | const i = this.index + offset;
|
14863 | return i < this.tokens.length ? this.tokens[i] : EOF;
|
14864 | }
|
14865 | get next() {
|
14866 | return this.peek(0);
|
14867 | }
|
14868 | /** Whether all the parser input has been processed. */
|
14869 | get atEOF() {
|
14870 | return this.index >= this.tokens.length;
|
14871 | }
|
14872 | /**
|
14873 | * Index of the next token to be processed, or the end of the last token if all have been
|
14874 | * processed.
|
14875 | */
|
14876 | get inputIndex() {
|
14877 | return this.atEOF ? this.currentEndIndex : this.next.index + this.offset;
|
14878 | }
|
14879 | /**
|
14880 | * End index of the last processed token, or the start of the first token if none have been
|
14881 | * processed.
|
14882 | */
|
14883 | get currentEndIndex() {
|
14884 | if (this.index > 0) {
|
14885 | const curToken = this.peek(-1);
|
14886 | return curToken.end + this.offset;
|
14887 | }
|
14888 | // No tokens have been processed yet; return the next token's start or the length of the input
|
14889 | // if there is no token.
|
14890 | if (this.tokens.length === 0) {
|
14891 | return this.inputLength + this.offset;
|
14892 | }
|
14893 | return this.next.index + this.offset;
|
14894 | }
|
14895 | /**
|
14896 | * Returns the absolute offset of the start of the current token.
|
14897 | */
|
14898 | get currentAbsoluteOffset() {
|
14899 | return this.absoluteOffset + this.inputIndex;
|
14900 | }
|
14901 | /**
|
14902 | * Retrieve a `ParseSpan` from `start` to the current position (or to `artificialEndIndex` if
|
14903 | * provided).
|
14904 | *
|
14905 | * @param start Position from which the `ParseSpan` will start.
|
14906 | * @param artificialEndIndex Optional ending index to be used if provided (and if greater than the
|
14907 | * natural ending index)
|
14908 | */
|
14909 | span(start, artificialEndIndex) {
|
14910 | let endIndex = this.currentEndIndex;
|
14911 | if (artificialEndIndex !== undefined && artificialEndIndex > this.currentEndIndex) {
|
14912 | endIndex = artificialEndIndex;
|
14913 | }
|
14914 | return new ParseSpan(start, endIndex);
|
14915 | }
|
14916 | sourceSpan(start, artificialEndIndex) {
|
14917 | const serial = `${start}@${this.inputIndex}:${artificialEndIndex}`;
|
14918 | if (!this.sourceSpanCache.has(serial)) {
|
14919 | this.sourceSpanCache.set(serial, this.span(start, artificialEndIndex).toAbsolute(this.absoluteOffset));
|
14920 | }
|
14921 | return this.sourceSpanCache.get(serial);
|
14922 | }
|
14923 | advance() {
|
14924 | this.index++;
|
14925 | }
|
14926 | /**
|
14927 | * Executes a callback in the provided context.
|
14928 | */
|
14929 | withContext(context, cb) {
|
14930 | this.context |= context;
|
14931 | const ret = cb();
|
14932 | this.context ^= context;
|
14933 | return ret;
|
14934 | }
|
14935 | consumeOptionalCharacter(code) {
|
14936 | if (this.next.isCharacter(code)) {
|
14937 | this.advance();
|
14938 | return true;
|
14939 | }
|
14940 | else {
|
14941 | return false;
|
14942 | }
|
14943 | }
|
14944 | peekKeywordLet() {
|
14945 | return this.next.isKeywordLet();
|
14946 | }
|
14947 | peekKeywordAs() {
|
14948 | return this.next.isKeywordAs();
|
14949 | }
|
14950 | /**
|
14951 | * Consumes an expected character, otherwise emits an error about the missing expected character
|
14952 | * and skips over the token stream until reaching a recoverable point.
|
14953 | *
|
14954 | * See `this.error` and `this.skip` for more details.
|
14955 | */
|
14956 | expectCharacter(code) {
|
14957 | if (this.consumeOptionalCharacter(code))
|
14958 | return;
|
14959 | this.error(`Missing expected ${String.fromCharCode(code)}`);
|
14960 | }
|
14961 | consumeOptionalOperator(op) {
|
14962 | if (this.next.isOperator(op)) {
|
14963 | this.advance();
|
14964 | return true;
|
14965 | }
|
14966 | else {
|
14967 | return false;
|
14968 | }
|
14969 | }
|
14970 | expectOperator(operator) {
|
14971 | if (this.consumeOptionalOperator(operator))
|
14972 | return;
|
14973 | this.error(`Missing expected operator ${operator}`);
|
14974 | }
|
14975 | prettyPrintToken(tok) {
|
14976 | return tok === EOF ? 'end of input' : `token ${tok}`;
|
14977 | }
|
14978 | expectIdentifierOrKeyword() {
|
14979 | const n = this.next;
|
14980 | if (!n.isIdentifier() && !n.isKeyword()) {
|
14981 | this.error(`Unexpected ${this.prettyPrintToken(n)}, expected identifier or keyword`);
|
14982 | return null;
|
14983 | }
|
14984 | this.advance();
|
14985 | return n.toString();
|
14986 | }
|
14987 | expectIdentifierOrKeywordOrString() {
|
14988 | const n = this.next;
|
14989 | if (!n.isIdentifier() && !n.isKeyword() && !n.isString()) {
|
14990 | this.error(`Unexpected ${this.prettyPrintToken(n)}, expected identifier, keyword, or string`);
|
14991 | return '';
|
14992 | }
|
14993 | this.advance();
|
14994 | return n.toString();
|
14995 | }
|
14996 | parseChain() {
|
14997 | const exprs = [];
|
14998 | const start = this.inputIndex;
|
14999 | while (this.index < this.tokens.length) {
|
15000 | const expr = this.parsePipe();
|
15001 | exprs.push(expr);
|
15002 | if (this.consumeOptionalCharacter($SEMICOLON)) {
|
15003 | if (!this.parseAction) {
|
15004 | this.error('Binding expression cannot contain chained expression');
|
15005 | }
|
15006 | while (this.consumeOptionalCharacter($SEMICOLON)) {
|
15007 | } // read all semicolons
|
15008 | }
|
15009 | else if (this.index < this.tokens.length) {
|
15010 | this.error(`Unexpected token '${this.next}'`);
|
15011 | }
|
15012 | }
|
15013 | if (exprs.length == 0) {
|
15014 | // We have no expressions so create an empty expression that spans the entire input length
|
15015 | const artificialStart = this.offset;
|
15016 | const artificialEnd = this.offset + this.inputLength;
|
15017 | return new EmptyExpr(this.span(artificialStart, artificialEnd), this.sourceSpan(artificialStart, artificialEnd));
|
15018 | }
|
15019 | if (exprs.length == 1)
|
15020 | return exprs[0];
|
15021 | return new Chain(this.span(start), this.sourceSpan(start), exprs);
|
15022 | }
|
15023 | parsePipe() {
|
15024 | const start = this.inputIndex;
|
15025 | let result = this.parseExpression();
|
15026 | if (this.consumeOptionalOperator('|')) {
|
15027 | if (this.parseAction) {
|
15028 | this.error('Cannot have a pipe in an action expression');
|
15029 | }
|
15030 | do {
|
15031 | const nameStart = this.inputIndex;
|
15032 | let nameId = this.expectIdentifierOrKeyword();
|
15033 | let nameSpan;
|
15034 | let fullSpanEnd = undefined;
|
15035 | if (nameId !== null) {
|
15036 | nameSpan = this.sourceSpan(nameStart);
|
15037 | }
|
15038 | else {
|
15039 | // No valid identifier was found, so we'll assume an empty pipe name ('').
|
15040 | nameId = '';
|
15041 | // However, there may have been whitespace present between the pipe character and the next
|
15042 | // token in the sequence (or the end of input). We want to track this whitespace so that
|
15043 | // the `BindingPipe` we produce covers not just the pipe character, but any trailing
|
15044 | // whitespace beyond it. Another way of thinking about this is that the zero-length name
|
15045 | // is assumed to be at the end of any whitespace beyond the pipe character.
|
15046 | //
|
15047 | // Therefore, we push the end of the `ParseSpan` for this pipe all the way up to the
|
15048 | // beginning of the next token, or until the end of input if the next token is EOF.
|
15049 | fullSpanEnd = this.next.index !== -1 ? this.next.index : this.inputLength + this.offset;
|
15050 | // The `nameSpan` for an empty pipe name is zero-length at the end of any whitespace
|
15051 | // beyond the pipe character.
|
15052 | nameSpan = new ParseSpan(fullSpanEnd, fullSpanEnd).toAbsolute(this.absoluteOffset);
|
15053 | }
|
15054 | const args = [];
|
15055 | while (this.consumeOptionalCharacter($COLON)) {
|
15056 | args.push(this.parseExpression());
|
15057 | // If there are additional expressions beyond the name, then the artificial end for the
|
15058 | // name is no longer relevant.
|
15059 | }
|
15060 | result = new BindingPipe(this.span(start), this.sourceSpan(start, fullSpanEnd), result, nameId, args, nameSpan);
|
15061 | } while (this.consumeOptionalOperator('|'));
|
15062 | }
|
15063 | return result;
|
15064 | }
|
15065 | parseExpression() {
|
15066 | return this.parseConditional();
|
15067 | }
|
15068 | parseConditional() {
|
15069 | const start = this.inputIndex;
|
15070 | const result = this.parseLogicalOr();
|
15071 | if (this.consumeOptionalOperator('?')) {
|
15072 | const yes = this.parsePipe();
|
15073 | let no;
|
15074 | if (!this.consumeOptionalCharacter($COLON)) {
|
15075 | const end = this.inputIndex;
|
15076 | const expression = this.input.substring(start, end);
|
15077 | this.error(`Conditional expression ${expression} requires all 3 expressions`);
|
15078 | no = new EmptyExpr(this.span(start), this.sourceSpan(start));
|
15079 | }
|
15080 | else {
|
15081 | no = this.parsePipe();
|
15082 | }
|
15083 | return new Conditional(this.span(start), this.sourceSpan(start), result, yes, no);
|
15084 | }
|
15085 | else {
|
15086 | return result;
|
15087 | }
|
15088 | }
|
15089 | parseLogicalOr() {
|
15090 | // '||'
|
15091 | const start = this.inputIndex;
|
15092 | let result = this.parseLogicalAnd();
|
15093 | while (this.consumeOptionalOperator('||')) {
|
15094 | const right = this.parseLogicalAnd();
|
15095 | result = new Binary(this.span(start), this.sourceSpan(start), '||', result, right);
|
15096 | }
|
15097 | return result;
|
15098 | }
|
15099 | parseLogicalAnd() {
|
15100 | // '&&'
|
15101 | const start = this.inputIndex;
|
15102 | let result = this.parseEquality();
|
15103 | while (this.consumeOptionalOperator('&&')) {
|
15104 | const right = this.parseEquality();
|
15105 | result = new Binary(this.span(start), this.sourceSpan(start), '&&', result, right);
|
15106 | }
|
15107 | return result;
|
15108 | }
|
15109 | parseEquality() {
|
15110 | // '==','!=','===','!=='
|
15111 | const start = this.inputIndex;
|
15112 | let result = this.parseRelational();
|
15113 | while (this.next.type == TokenType$1.Operator) {
|
15114 | const operator = this.next.strValue;
|
15115 | switch (operator) {
|
15116 | case '==':
|
15117 | case '===':
|
15118 | case '!=':
|
15119 | case '!==':
|
15120 | this.advance();
|
15121 | const right = this.parseRelational();
|
15122 | result = new Binary(this.span(start), this.sourceSpan(start), operator, result, right);
|
15123 | continue;
|
15124 | }
|
15125 | break;
|
15126 | }
|
15127 | return result;
|
15128 | }
|
15129 | parseRelational() {
|
15130 | // '<', '>', '<=', '>='
|
15131 | const start = this.inputIndex;
|
15132 | let result = this.parseAdditive();
|
15133 | while (this.next.type == TokenType$1.Operator) {
|
15134 | const operator = this.next.strValue;
|
15135 | switch (operator) {
|
15136 | case '<':
|
15137 | case '>':
|
15138 | case '<=':
|
15139 | case '>=':
|
15140 | this.advance();
|
15141 | const right = this.parseAdditive();
|
15142 | result = new Binary(this.span(start), this.sourceSpan(start), operator, result, right);
|
15143 | continue;
|
15144 | }
|
15145 | break;
|
15146 | }
|
15147 | return result;
|
15148 | }
|
15149 | parseAdditive() {
|
15150 | // '+', '-'
|
15151 | const start = this.inputIndex;
|
15152 | let result = this.parseMultiplicative();
|
15153 | while (this.next.type == TokenType$1.Operator) {
|
15154 | const operator = this.next.strValue;
|
15155 | switch (operator) {
|
15156 | case '+':
|
15157 | case '-':
|
15158 | this.advance();
|
15159 | let right = this.parseMultiplicative();
|
15160 | result = new Binary(this.span(start), this.sourceSpan(start), operator, result, right);
|
15161 | continue;
|
15162 | }
|
15163 | break;
|
15164 | }
|
15165 | return result;
|
15166 | }
|
15167 | parseMultiplicative() {
|
15168 | // '*', '%', '/'
|
15169 | const start = this.inputIndex;
|
15170 | let result = this.parsePrefix();
|
15171 | while (this.next.type == TokenType$1.Operator) {
|
15172 | const operator = this.next.strValue;
|
15173 | switch (operator) {
|
15174 | case '*':
|
15175 | case '%':
|
15176 | case '/':
|
15177 | this.advance();
|
15178 | let right = this.parsePrefix();
|
15179 | result = new Binary(this.span(start), this.sourceSpan(start), operator, result, right);
|
15180 | continue;
|
15181 | }
|
15182 | break;
|
15183 | }
|
15184 | return result;
|
15185 | }
|
15186 | parsePrefix() {
|
15187 | if (this.next.type == TokenType$1.Operator) {
|
15188 | const start = this.inputIndex;
|
15189 | const operator = this.next.strValue;
|
15190 | let result;
|
15191 | switch (operator) {
|
15192 | case '+':
|
15193 | this.advance();
|
15194 | result = this.parsePrefix();
|
15195 | return Unary.createPlus(this.span(start), this.sourceSpan(start), result);
|
15196 | case '-':
|
15197 | this.advance();
|
15198 | result = this.parsePrefix();
|
15199 | return Unary.createMinus(this.span(start), this.sourceSpan(start), result);
|
15200 | case '!':
|
15201 | this.advance();
|
15202 | result = this.parsePrefix();
|
15203 | return new PrefixNot(this.span(start), this.sourceSpan(start), result);
|
15204 | }
|
15205 | }
|
15206 | return this.parseCallChain();
|
15207 | }
|
15208 | parseCallChain() {
|
15209 | const start = this.inputIndex;
|
15210 | let result = this.parsePrimary();
|
15211 | while (true) {
|
15212 | if (this.consumeOptionalCharacter($PERIOD)) {
|
15213 | result = this.parseAccessMemberOrMethodCall(result, start, false);
|
15214 | }
|
15215 | else if (this.consumeOptionalOperator('?.')) {
|
15216 | result = this.parseAccessMemberOrMethodCall(result, start, true);
|
15217 | }
|
15218 | else if (this.consumeOptionalCharacter($LBRACKET)) {
|
15219 | this.withContext(ParseContextFlags.Writable, () => {
|
15220 | this.rbracketsExpected++;
|
15221 | const key = this.parsePipe();
|
15222 | if (key instanceof EmptyExpr) {
|
15223 | this.error(`Key access cannot be empty`);
|
15224 | }
|
15225 | this.rbracketsExpected--;
|
15226 | this.expectCharacter($RBRACKET);
|
15227 | if (this.consumeOptionalOperator('=')) {
|
15228 | const value = this.parseConditional();
|
15229 | result = new KeyedWrite(this.span(start), this.sourceSpan(start), result, key, value);
|
15230 | }
|
15231 | else {
|
15232 | result = new KeyedRead(this.span(start), this.sourceSpan(start), result, key);
|
15233 | }
|
15234 | });
|
15235 | }
|
15236 | else if (this.consumeOptionalCharacter($LPAREN)) {
|
15237 | this.rparensExpected++;
|
15238 | const args = this.parseCallArguments();
|
15239 | this.rparensExpected--;
|
15240 | this.expectCharacter($RPAREN);
|
15241 | result = new FunctionCall(this.span(start), this.sourceSpan(start), result, args);
|
15242 | }
|
15243 | else if (this.consumeOptionalOperator('!')) {
|
15244 | result = new NonNullAssert(this.span(start), this.sourceSpan(start), result);
|
15245 | }
|
15246 | else {
|
15247 | return result;
|
15248 | }
|
15249 | }
|
15250 | }
|
15251 | parsePrimary() {
|
15252 | const start = this.inputIndex;
|
15253 | if (this.consumeOptionalCharacter($LPAREN)) {
|
15254 | this.rparensExpected++;
|
15255 | const result = this.parsePipe();
|
15256 | this.rparensExpected--;
|
15257 | this.expectCharacter($RPAREN);
|
15258 | return result;
|
15259 | }
|
15260 | else if (this.next.isKeywordNull()) {
|
15261 | this.advance();
|
15262 | return new LiteralPrimitive(this.span(start), this.sourceSpan(start), null);
|
15263 | }
|
15264 | else if (this.next.isKeywordUndefined()) {
|
15265 | this.advance();
|
15266 | return new LiteralPrimitive(this.span(start), this.sourceSpan(start), void 0);
|
15267 | }
|
15268 | else if (this.next.isKeywordTrue()) {
|
15269 | this.advance();
|
15270 | return new LiteralPrimitive(this.span(start), this.sourceSpan(start), true);
|
15271 | }
|
15272 | else if (this.next.isKeywordFalse()) {
|
15273 | this.advance();
|
15274 | return new LiteralPrimitive(this.span(start), this.sourceSpan(start), false);
|
15275 | }
|
15276 | else if (this.next.isKeywordThis()) {
|
15277 | this.advance();
|
15278 | return new ThisReceiver(this.span(start), this.sourceSpan(start));
|
15279 | }
|
15280 | else if (this.consumeOptionalCharacter($LBRACKET)) {
|
15281 | this.rbracketsExpected++;
|
15282 | const elements = this.parseExpressionList($RBRACKET);
|
15283 | this.rbracketsExpected--;
|
15284 | this.expectCharacter($RBRACKET);
|
15285 | return new LiteralArray(this.span(start), this.sourceSpan(start), elements);
|
15286 | }
|
15287 | else if (this.next.isCharacter($LBRACE)) {
|
15288 | return this.parseLiteralMap();
|
15289 | }
|
15290 | else if (this.next.isIdentifier()) {
|
15291 | return this.parseAccessMemberOrMethodCall(new ImplicitReceiver(this.span(start), this.sourceSpan(start)), start, false);
|
15292 | }
|
15293 | else if (this.next.isNumber()) {
|
15294 | const value = this.next.toNumber();
|
15295 | this.advance();
|
15296 | return new LiteralPrimitive(this.span(start), this.sourceSpan(start), value);
|
15297 | }
|
15298 | else if (this.next.isString()) {
|
15299 | const literalValue = this.next.toString();
|
15300 | this.advance();
|
15301 | return new LiteralPrimitive(this.span(start), this.sourceSpan(start), literalValue);
|
15302 | }
|
15303 | else if (this.index >= this.tokens.length) {
|
15304 | this.error(`Unexpected end of expression: ${this.input}`);
|
15305 | return new EmptyExpr(this.span(start), this.sourceSpan(start));
|
15306 | }
|
15307 | else {
|
15308 | this.error(`Unexpected token ${this.next}`);
|
15309 | return new EmptyExpr(this.span(start), this.sourceSpan(start));
|
15310 | }
|
15311 | }
|
15312 | parseExpressionList(terminator) {
|
15313 | const result = [];
|
15314 | do {
|
15315 | if (!this.next.isCharacter(terminator)) {
|
15316 | result.push(this.parsePipe());
|
15317 | }
|
15318 | else {
|
15319 | break;
|
15320 | }
|
15321 | } while (this.consumeOptionalCharacter($COMMA));
|
15322 | return result;
|
15323 | }
|
15324 | parseLiteralMap() {
|
15325 | const keys = [];
|
15326 | const values = [];
|
15327 | const start = this.inputIndex;
|
15328 | this.expectCharacter($LBRACE);
|
15329 | if (!this.consumeOptionalCharacter($RBRACE)) {
|
15330 | this.rbracesExpected++;
|
15331 | do {
|
15332 | const quoted = this.next.isString();
|
15333 | const key = this.expectIdentifierOrKeywordOrString();
|
15334 | keys.push({ key, quoted });
|
15335 | this.expectCharacter($COLON);
|
15336 | values.push(this.parsePipe());
|
15337 | } while (this.consumeOptionalCharacter($COMMA));
|
15338 | this.rbracesExpected--;
|
15339 | this.expectCharacter($RBRACE);
|
15340 | }
|
15341 | return new LiteralMap(this.span(start), this.sourceSpan(start), keys, values);
|
15342 | }
|
15343 | parseAccessMemberOrMethodCall(receiver, start, isSafe = false) {
|
15344 | const nameStart = this.inputIndex;
|
15345 | const id = this.withContext(ParseContextFlags.Writable, () => {
|
15346 | var _a;
|
15347 | const id = (_a = this.expectIdentifierOrKeyword()) !== null && _a !== void 0 ? _a : '';
|
15348 | if (id.length === 0) {
|
15349 | this.error(`Expected identifier for property access`, receiver.span.end);
|
15350 | }
|
15351 | return id;
|
15352 | });
|
15353 | const nameSpan = this.sourceSpan(nameStart);
|
15354 | if (this.consumeOptionalCharacter($LPAREN)) {
|
15355 | this.rparensExpected++;
|
15356 | const args = this.parseCallArguments();
|
15357 | this.expectCharacter($RPAREN);
|
15358 | this.rparensExpected--;
|
15359 | const span = this.span(start);
|
15360 | const sourceSpan = this.sourceSpan(start);
|
15361 | return isSafe ? new SafeMethodCall(span, sourceSpan, nameSpan, receiver, id, args) :
|
15362 | new MethodCall(span, sourceSpan, nameSpan, receiver, id, args);
|
15363 | }
|
15364 | else {
|
15365 | if (isSafe) {
|
15366 | if (this.consumeOptionalOperator('=')) {
|
15367 | this.error('The \'?.\' operator cannot be used in the assignment');
|
15368 | return new EmptyExpr(this.span(start), this.sourceSpan(start));
|
15369 | }
|
15370 | else {
|
15371 | return new SafePropertyRead(this.span(start), this.sourceSpan(start), nameSpan, receiver, id);
|
15372 | }
|
15373 | }
|
15374 | else {
|
15375 | if (this.consumeOptionalOperator('=')) {
|
15376 | if (!this.parseAction) {
|
15377 | this.error('Bindings cannot contain assignments');
|
15378 | return new EmptyExpr(this.span(start), this.sourceSpan(start));
|
15379 | }
|
15380 | const value = this.parseConditional();
|
15381 | return new PropertyWrite(this.span(start), this.sourceSpan(start), nameSpan, receiver, id, value);
|
15382 | }
|
15383 | else {
|
15384 | return new PropertyRead(this.span(start), this.sourceSpan(start), nameSpan, receiver, id);
|
15385 | }
|
15386 | }
|
15387 | }
|
15388 | }
|
15389 | parseCallArguments() {
|
15390 | if (this.next.isCharacter($RPAREN))
|
15391 | return [];
|
15392 | const positionals = [];
|
15393 | do {
|
15394 | positionals.push(this.parsePipe());
|
15395 | } while (this.consumeOptionalCharacter($COMMA));
|
15396 | return positionals;
|
15397 | }
|
15398 | /**
|
15399 | * Parses an identifier, a keyword, a string with an optional `-` in between,
|
15400 | * and returns the string along with its absolute source span.
|
15401 | */
|
15402 | expectTemplateBindingKey() {
|
15403 | let result = '';
|
15404 | let operatorFound = false;
|
15405 | const start = this.currentAbsoluteOffset;
|
15406 | do {
|
15407 | result += this.expectIdentifierOrKeywordOrString();
|
15408 | operatorFound = this.consumeOptionalOperator('-');
|
15409 | if (operatorFound) {
|
15410 | result += '-';
|
15411 | }
|
15412 | } while (operatorFound);
|
15413 | return {
|
15414 | source: result,
|
15415 | span: new AbsoluteSourceSpan(start, start + result.length),
|
15416 | };
|
15417 | }
|
15418 | /**
|
15419 | * Parse microsyntax template expression and return a list of bindings or
|
15420 | * parsing errors in case the given expression is invalid.
|
15421 | *
|
15422 | * For example,
|
15423 | * ```
|
15424 | * <div *ngFor="let item of items; index as i; trackBy: func">
|
15425 | * ```
|
15426 | * contains five bindings:
|
15427 | * 1. ngFor -> null
|
15428 | * 2. item -> NgForOfContext.$implicit
|
15429 | * 3. ngForOf -> items
|
15430 | * 4. i -> NgForOfContext.index
|
15431 | * 5. ngForTrackBy -> func
|
15432 | *
|
15433 | * For a full description of the microsyntax grammar, see
|
15434 | * https://gist.github.com/mhevery/d3530294cff2e4a1b3fe15ff75d08855
|
15435 | *
|
15436 | * @param templateKey name of the microsyntax directive, like ngIf, ngFor,
|
15437 | * without the *, along with its absolute span.
|
15438 | */
|
15439 | parseTemplateBindings(templateKey) {
|
15440 | const bindings = [];
|
15441 | // The first binding is for the template key itself
|
15442 | // In *ngFor="let item of items", key = "ngFor", value = null
|
15443 | // In *ngIf="cond | pipe", key = "ngIf", value = "cond | pipe"
|
15444 | bindings.push(...this.parseDirectiveKeywordBindings(templateKey));
|
15445 | while (this.index < this.tokens.length) {
|
15446 | // If it starts with 'let', then this must be variable declaration
|
15447 | const letBinding = this.parseLetBinding();
|
15448 | if (letBinding) {
|
15449 | bindings.push(letBinding);
|
15450 | }
|
15451 | else {
|
15452 | // Two possible cases here, either `value "as" key` or
|
15453 | // "directive-keyword expression". We don't know which case, but both
|
15454 | // "value" and "directive-keyword" are template binding key, so consume
|
15455 | // the key first.
|
15456 | const key = this.expectTemplateBindingKey();
|
15457 | // Peek at the next token, if it is "as" then this must be variable
|
15458 | // declaration.
|
15459 | const binding = this.parseAsBinding(key);
|
15460 | if (binding) {
|
15461 | bindings.push(binding);
|
15462 | }
|
15463 | else {
|
15464 | // Otherwise the key must be a directive keyword, like "of". Transform
|
15465 | // the key to actual key. Eg. of -> ngForOf, trackBy -> ngForTrackBy
|
15466 | key.source =
|
15467 | templateKey.source + key.source.charAt(0).toUpperCase() + key.source.substring(1);
|
15468 | bindings.push(...this.parseDirectiveKeywordBindings(key));
|
15469 | }
|
15470 | }
|
15471 | this.consumeStatementTerminator();
|
15472 | }
|
15473 | return new TemplateBindingParseResult(bindings, [] /* warnings */, this.errors);
|
15474 | }
|
15475 | /**
|
15476 | * Parse a directive keyword, followed by a mandatory expression.
|
15477 | * For example, "of items", "trackBy: func".
|
15478 | * The bindings are: ngForOf -> items, ngForTrackBy -> func
|
15479 | * There could be an optional "as" binding that follows the expression.
|
15480 | * For example,
|
15481 | * ```
|
15482 | * *ngFor="let item of items | slice:0:1 as collection".
|
15483 | * ^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^
|
15484 | * keyword bound target optional 'as' binding
|
15485 | * ```
|
15486 | *
|
15487 | * @param key binding key, for example, ngFor, ngIf, ngForOf, along with its
|
15488 | * absolute span.
|
15489 | */
|
15490 | parseDirectiveKeywordBindings(key) {
|
15491 | const bindings = [];
|
15492 | this.consumeOptionalCharacter($COLON); // trackBy: trackByFunction
|
15493 | const value = this.getDirectiveBoundTarget();
|
15494 | let spanEnd = this.currentAbsoluteOffset;
|
15495 | // The binding could optionally be followed by "as". For example,
|
15496 | // *ngIf="cond | pipe as x". In this case, the key in the "as" binding
|
15497 | // is "x" and the value is the template key itself ("ngIf"). Note that the
|
15498 | // 'key' in the current context now becomes the "value" in the next binding.
|
15499 | const asBinding = this.parseAsBinding(key);
|
15500 | if (!asBinding) {
|
15501 | this.consumeStatementTerminator();
|
15502 | spanEnd = this.currentAbsoluteOffset;
|
15503 | }
|
15504 | const sourceSpan = new AbsoluteSourceSpan(key.span.start, spanEnd);
|
15505 | bindings.push(new ExpressionBinding(sourceSpan, key, value));
|
15506 | if (asBinding) {
|
15507 | bindings.push(asBinding);
|
15508 | }
|
15509 | return bindings;
|
15510 | }
|
15511 | /**
|
15512 | * Return the expression AST for the bound target of a directive keyword
|
15513 | * binding. For example,
|
15514 | * ```
|
15515 | * *ngIf="condition | pipe"
|
15516 | * ^^^^^^^^^^^^^^^^ bound target for "ngIf"
|
15517 | * *ngFor="let item of items"
|
15518 | * ^^^^^ bound target for "ngForOf"
|
15519 | * ```
|
15520 | */
|
15521 | getDirectiveBoundTarget() {
|
15522 | if (this.next === EOF || this.peekKeywordAs() || this.peekKeywordLet()) {
|
15523 | return null;
|
15524 | }
|
15525 | const ast = this.parsePipe(); // example: "condition | async"
|
15526 | const { start, end } = ast.span;
|
15527 | const value = this.input.substring(start, end);
|
15528 | return new ASTWithSource(ast, value, this.location, this.absoluteOffset + start, this.errors);
|
15529 | }
|
15530 | /**
|
15531 | * Return the binding for a variable declared using `as`. Note that the order
|
15532 | * of the key-value pair in this declaration is reversed. For example,
|
15533 | * ```
|
15534 | * *ngFor="let item of items; index as i"
|
15535 | * ^^^^^ ^
|
15536 | * value key
|
15537 | * ```
|
15538 | *
|
15539 | * @param value name of the value in the declaration, "ngIf" in the example
|
15540 | * above, along with its absolute span.
|
15541 | */
|
15542 | parseAsBinding(value) {
|
15543 | if (!this.peekKeywordAs()) {
|
15544 | return null;
|
15545 | }
|
15546 | this.advance(); // consume the 'as' keyword
|
15547 | const key = this.expectTemplateBindingKey();
|
15548 | this.consumeStatementTerminator();
|
15549 | const sourceSpan = new AbsoluteSourceSpan(value.span.start, this.currentAbsoluteOffset);
|
15550 | return new VariableBinding(sourceSpan, key, value);
|
15551 | }
|
15552 | /**
|
15553 | * Return the binding for a variable declared using `let`. For example,
|
15554 | * ```
|
15555 | * *ngFor="let item of items; let i=index;"
|
15556 | * ^^^^^^^^ ^^^^^^^^^^^
|
15557 | * ```
|
15558 | * In the first binding, `item` is bound to `NgForOfContext.$implicit`.
|
15559 | * In the second binding, `i` is bound to `NgForOfContext.index`.
|
15560 | */
|
15561 | parseLetBinding() {
|
15562 | if (!this.peekKeywordLet()) {
|
15563 | return null;
|
15564 | }
|
15565 | const spanStart = this.currentAbsoluteOffset;
|
15566 | this.advance(); // consume the 'let' keyword
|
15567 | const key = this.expectTemplateBindingKey();
|
15568 | let value = null;
|
15569 | if (this.consumeOptionalOperator('=')) {
|
15570 | value = this.expectTemplateBindingKey();
|
15571 | }
|
15572 | this.consumeStatementTerminator();
|
15573 | const sourceSpan = new AbsoluteSourceSpan(spanStart, this.currentAbsoluteOffset);
|
15574 | return new VariableBinding(sourceSpan, key, value);
|
15575 | }
|
15576 | /**
|
15577 | * Consume the optional statement terminator: semicolon or comma.
|
15578 | */
|
15579 | consumeStatementTerminator() {
|
15580 | this.consumeOptionalCharacter($SEMICOLON) || this.consumeOptionalCharacter($COMMA);
|
15581 | }
|
15582 | /**
|
15583 | * Records an error and skips over the token stream until reaching a recoverable point. See
|
15584 | * `this.skip` for more details on token skipping.
|
15585 | */
|
15586 | error(message, index = null) {
|
15587 | this.errors.push(new ParserError(message, this.input, this.locationText(index), this.location));
|
15588 | this.skip();
|
15589 | }
|
15590 | locationText(index = null) {
|
15591 | if (index == null)
|
15592 | index = this.index;
|
15593 | return (index < this.tokens.length) ? `at column ${this.tokens[index].index + 1} in` :
|
15594 | `at the end of the expression`;
|
15595 | }
|
15596 | /**
|
15597 | * Error recovery should skip tokens until it encounters a recovery point.
|
15598 | *
|
15599 | * The following are treated as unconditional recovery points:
|
15600 | * - end of input
|
15601 | * - ';' (parseChain() is always the root production, and it expects a ';')
|
15602 | * - '|' (since pipes may be chained and each pipe expression may be treated independently)
|
15603 | *
|
15604 | * The following are conditional recovery points:
|
15605 | * - ')', '}', ']' if one of calling productions is expecting one of these symbols
|
15606 | * - This allows skip() to recover from errors such as '(a.) + 1' allowing more of the AST to
|
15607 | * be retained (it doesn't skip any tokens as the ')' is retained because of the '(' begins
|
15608 | * an '(' <expr> ')' production).
|
15609 | * The recovery points of grouping symbols must be conditional as they must be skipped if
|
15610 | * none of the calling productions are not expecting the closing token else we will never
|
15611 | * make progress in the case of an extraneous group closing symbol (such as a stray ')').
|
15612 | * That is, we skip a closing symbol if we are not in a grouping production.
|
15613 | * - '=' in a `Writable` context
|
15614 | * - In this context, we are able to recover after seeing the `=` operator, which
|
15615 | * signals the presence of an independent rvalue expression following the `=` operator.
|
15616 | *
|
15617 | * If a production expects one of these token it increments the corresponding nesting count,
|
15618 | * and then decrements it just prior to checking if the token is in the input.
|
15619 | */
|
15620 | skip() {
|
15621 | let n = this.next;
|
15622 | while (this.index < this.tokens.length && !n.isCharacter($SEMICOLON) &&
|
15623 | !n.isOperator('|') && (this.rparensExpected <= 0 || !n.isCharacter($RPAREN)) &&
|
15624 | (this.rbracesExpected <= 0 || !n.isCharacter($RBRACE)) &&
|
15625 | (this.rbracketsExpected <= 0 || !n.isCharacter($RBRACKET)) &&
|
15626 | (!(this.context & ParseContextFlags.Writable) || !n.isOperator('='))) {
|
15627 | if (this.next.isError()) {
|
15628 | this.errors.push(new ParserError(this.next.toString(), this.input, this.locationText(), this.location));
|
15629 | }
|
15630 | this.advance();
|
15631 | n = this.next;
|
15632 | }
|
15633 | }
|
15634 | }
|
15635 | class SimpleExpressionChecker {
|
15636 | constructor() {
|
15637 | this.errors = [];
|
15638 | }
|
15639 | visitImplicitReceiver(ast, context) { }
|
15640 | visitThisReceiver(ast, context) { }
|
15641 | visitInterpolation(ast, context) { }
|
15642 | visitLiteralPrimitive(ast, context) { }
|
15643 | visitPropertyRead(ast, context) { }
|
15644 | visitPropertyWrite(ast, context) { }
|
15645 | visitSafePropertyRead(ast, context) { }
|
15646 | visitMethodCall(ast, context) { }
|
15647 | visitSafeMethodCall(ast, context) { }
|
15648 | visitFunctionCall(ast, context) { }
|
15649 | visitLiteralArray(ast, context) {
|
15650 | this.visitAll(ast.expressions, context);
|
15651 | }
|
15652 | visitLiteralMap(ast, context) {
|
15653 | this.visitAll(ast.values, context);
|
15654 | }
|
15655 | visitUnary(ast, context) { }
|
15656 | visitBinary(ast, context) { }
|
15657 | visitPrefixNot(ast, context) { }
|
15658 | visitNonNullAssert(ast, context) { }
|
15659 | visitConditional(ast, context) { }
|
15660 | visitPipe(ast, context) {
|
15661 | this.errors.push('pipes');
|
15662 | }
|
15663 | visitKeyedRead(ast, context) { }
|
15664 | visitKeyedWrite(ast, context) { }
|
15665 | visitAll(asts, context) {
|
15666 | return asts.map(node => node.visit(this, context));
|
15667 | }
|
15668 | visitChain(ast, context) { }
|
15669 | visitQuote(ast, context) { }
|
15670 | }
|
15671 | /**
|
15672 | * This class implements SimpleExpressionChecker used in View Engine and performs more strict checks
|
15673 | * to make sure host bindings do not contain pipes. In View Engine, having pipes in host bindings is
|
15674 | * not supported as well, but in some cases (like `!(value | async)`) the error is not triggered at
|
15675 | * compile time. In order to preserve View Engine behavior, more strict checks are introduced for
|
15676 | * Ivy mode only.
|
15677 | */
|
15678 | class IvySimpleExpressionChecker extends RecursiveAstVisitor {
|
15679 | constructor() {
|
15680 | super(...arguments);
|
15681 | this.errors = [];
|
15682 | }
|
15683 | visitPipe() {
|
15684 | this.errors.push('pipes');
|
15685 | }
|
15686 | }
|
15687 |
|
15688 | /**
|
15689 | * @license
|
15690 | * Copyright Google LLC All Rights Reserved.
|
15691 | *
|
15692 | * Use of this source code is governed by an MIT-style license that can be
|
15693 | * found in the LICENSE file at https://angular.io/license
|
15694 | */
|
15695 | // =================================================================================================
|
15696 | // =================================================================================================
|
15697 | // =========== S T O P - S T O P - S T O P - S T O P - S T O P - S T O P ===========
|
15698 | // =================================================================================================
|
15699 | // =================================================================================================
|
15700 | //
|
15701 | // DO NOT EDIT THIS LIST OF SECURITY SENSITIVE PROPERTIES WITHOUT A SECURITY REVIEW!
|
15702 | // Reach out to mprobst for details.
|
15703 | //
|
15704 | // =================================================================================================
|
15705 | /** Map from tagName|propertyName to SecurityContext. Properties applying to all tags use '*'. */
|
15706 | let _SECURITY_SCHEMA;
|
15707 | function SECURITY_SCHEMA() {
|
15708 | if (!_SECURITY_SCHEMA) {
|
15709 | _SECURITY_SCHEMA = {};
|
15710 | // Case is insignificant below, all element and attribute names are lower-cased for lookup.
|
15711 | registerContext(SecurityContext.HTML, [
|
15712 | 'iframe|srcdoc',
|
15713 | '*|innerHTML',
|
15714 | '*|outerHTML',
|
15715 | ]);
|
15716 | registerContext(SecurityContext.STYLE, ['*|style']);
|
15717 | // NB: no SCRIPT contexts here, they are never allowed due to the parser stripping them.
|
15718 | registerContext(SecurityContext.URL, [
|
15719 | '*|formAction', 'area|href', 'area|ping', 'audio|src', 'a|href',
|
15720 | 'a|ping', 'blockquote|cite', 'body|background', 'del|cite', 'form|action',
|
15721 | 'img|src', 'img|srcset', 'input|src', 'ins|cite', 'q|cite',
|
15722 | 'source|src', 'source|srcset', 'track|src', 'video|poster', 'video|src',
|
15723 | ]);
|
15724 | registerContext(SecurityContext.RESOURCE_URL, [
|
15725 | 'applet|code',
|
15726 | 'applet|codebase',
|
15727 | 'base|href',
|
15728 | 'embed|src',
|
15729 | 'frame|src',
|
15730 | 'head|profile',
|
15731 | 'html|manifest',
|
15732 | 'iframe|src',
|
15733 | 'link|href',
|
15734 | 'media|src',
|
15735 | 'object|codebase',
|
15736 | 'object|data',
|
15737 | 'script|src',
|
15738 | ]);
|
15739 | }
|
15740 | return _SECURITY_SCHEMA;
|
15741 | }
|
15742 | function registerContext(ctx, specs) {
|
15743 | for (const spec of specs)
|
15744 | _SECURITY_SCHEMA[spec.toLowerCase()] = ctx;
|
15745 | }
|
15746 |
|
15747 | /**
|
15748 | * @license
|
15749 | * Copyright Google LLC All Rights Reserved.
|
15750 | *
|
15751 | * Use of this source code is governed by an MIT-style license that can be
|
15752 | * found in the LICENSE file at https://angular.io/license
|
15753 | */
|
15754 | class ElementSchemaRegistry {
|
15755 | }
|
15756 |
|
15757 | /**
|
15758 | * @license
|
15759 | * Copyright Google LLC All Rights Reserved.
|
15760 | *
|
15761 | * Use of this source code is governed by an MIT-style license that can be
|
15762 | * found in the LICENSE file at https://angular.io/license
|
15763 | */
|
15764 | const BOOLEAN = 'boolean';
|
15765 | const NUMBER = 'number';
|
15766 | const STRING = 'string';
|
15767 | const OBJECT = 'object';
|
15768 | /**
|
15769 | * This array represents the DOM schema. It encodes inheritance, properties, and events.
|
15770 | *
|
15771 | * ## Overview
|
15772 | *
|
15773 | * Each line represents one kind of element. The `element_inheritance` and properties are joined
|
15774 | * using `element_inheritance|properties` syntax.
|
15775 | *
|
15776 | * ## Element Inheritance
|
15777 | *
|
15778 | * The `element_inheritance` can be further subdivided as `element1,element2,...^parentElement`.
|
15779 | * Here the individual elements are separated by `,` (commas). Every element in the list
|
15780 | * has identical properties.
|
15781 | *
|
15782 | * An `element` may inherit additional properties from `parentElement` If no `^parentElement` is
|
15783 | * specified then `""` (blank) element is assumed.
|
15784 | *
|
15785 | * NOTE: The blank element inherits from root `[Element]` element, the super element of all
|
15786 | * elements.
|
15787 | *
|
15788 | * NOTE an element prefix such as `:svg:` has no special meaning to the schema.
|
15789 | *
|
15790 | * ## Properties
|
15791 | *
|
15792 | * Each element has a set of properties separated by `,` (commas). Each property can be prefixed
|
15793 | * by a special character designating its type:
|
15794 | *
|
15795 | * - (no prefix): property is a string.
|
15796 | * - `*`: property represents an event.
|
15797 | * - `!`: property is a boolean.
|
15798 | * - `#`: property is a number.
|
15799 | * - `%`: property is an object.
|
15800 | *
|
15801 | * ## Query
|
15802 | *
|
15803 | * The class creates an internal squas representation which allows to easily answer the query of
|
15804 | * if a given property exist on a given element.
|
15805 | *
|
15806 | * NOTE: We don't yet support querying for types or events.
|
15807 | * NOTE: This schema is auto extracted from `schema_extractor.ts` located in the test folder,
|
15808 | * see dom_element_schema_registry_spec.ts
|
15809 | */
|
15810 | // =================================================================================================
|
15811 | // =================================================================================================
|
15812 | // =========== S T O P - S T O P - S T O P - S T O P - S T O P - S T O P ===========
|
15813 | // =================================================================================================
|
15814 | // =================================================================================================
|
15815 | //
|
15816 | // DO NOT EDIT THIS DOM SCHEMA WITHOUT A SECURITY REVIEW!
|
15817 | //
|
15818 | // Newly added properties must be security reviewed and assigned an appropriate SecurityContext in
|
15819 | // dom_security_schema.ts. Reach out to mprobst & rjamet for details.
|
15820 | //
|
15821 | // =================================================================================================
|
15822 | const SCHEMA = [
|
15823 | '[Element]|textContent,%classList,className,id,innerHTML,*beforecopy,*beforecut,*beforepaste,*copy,*cut,*paste,*search,*selectstart,*webkitfullscreenchange,*webkitfullscreenerror,*wheel,outerHTML,#scrollLeft,#scrollTop,slot' +
|
15824 | /* added manually to avoid breaking changes */
|
15825 | ',*message,*mozfullscreenchange,*mozfullscreenerror,*mozpointerlockchange,*mozpointerlockerror,*webglcontextcreationerror,*webglcontextlost,*webglcontextrestored',
|
15826 | '[HTMLElement]^[Element]|accessKey,contentEditable,dir,!draggable,!hidden,innerText,lang,*abort,*auxclick,*blur,*cancel,*canplay,*canplaythrough,*change,*click,*close,*contextmenu,*cuechange,*dblclick,*drag,*dragend,*dragenter,*dragleave,*dragover,*dragstart,*drop,*durationchange,*emptied,*ended,*error,*focus,*gotpointercapture,*input,*invalid,*keydown,*keypress,*keyup,*load,*loadeddata,*loadedmetadata,*loadstart,*lostpointercapture,*mousedown,*mouseenter,*mouseleave,*mousemove,*mouseout,*mouseover,*mouseup,*mousewheel,*pause,*play,*playing,*pointercancel,*pointerdown,*pointerenter,*pointerleave,*pointermove,*pointerout,*pointerover,*pointerup,*progress,*ratechange,*reset,*resize,*scroll,*seeked,*seeking,*select,*show,*stalled,*submit,*suspend,*timeupdate,*toggle,*volumechange,*waiting,outerText,!spellcheck,%style,#tabIndex,title,!translate',
|
15827 | 'abbr,address,article,aside,b,bdi,bdo,cite,code,dd,dfn,dt,em,figcaption,figure,footer,header,i,kbd,main,mark,nav,noscript,rb,rp,rt,rtc,ruby,s,samp,section,small,strong,sub,sup,u,var,wbr^[HTMLElement]|accessKey,contentEditable,dir,!draggable,!hidden,innerText,lang,*abort,*auxclick,*blur,*cancel,*canplay,*canplaythrough,*change,*click,*close,*contextmenu,*cuechange,*dblclick,*drag,*dragend,*dragenter,*dragleave,*dragover,*dragstart,*drop,*durationchange,*emptied,*ended,*error,*focus,*gotpointercapture,*input,*invalid,*keydown,*keypress,*keyup,*load,*loadeddata,*loadedmetadata,*loadstart,*lostpointercapture,*mousedown,*mouseenter,*mouseleave,*mousemove,*mouseout,*mouseover,*mouseup,*mousewheel,*pause,*play,*playing,*pointercancel,*pointerdown,*pointerenter,*pointerleave,*pointermove,*pointerout,*pointerover,*pointerup,*progress,*ratechange,*reset,*resize,*scroll,*seeked,*seeking,*select,*show,*stalled,*submit,*suspend,*timeupdate,*toggle,*volumechange,*waiting,outerText,!spellcheck,%style,#tabIndex,title,!translate',
|
15828 | 'media^[HTMLElement]|!autoplay,!controls,%controlsList,%crossOrigin,#currentTime,!defaultMuted,#defaultPlaybackRate,!disableRemotePlayback,!loop,!muted,*encrypted,*waitingforkey,#playbackRate,preload,src,%srcObject,#volume',
|
15829 | ':svg:^[HTMLElement]|*abort,*auxclick,*blur,*cancel,*canplay,*canplaythrough,*change,*click,*close,*contextmenu,*cuechange,*dblclick,*drag,*dragend,*dragenter,*dragleave,*dragover,*dragstart,*drop,*durationchange,*emptied,*ended,*error,*focus,*gotpointercapture,*input,*invalid,*keydown,*keypress,*keyup,*load,*loadeddata,*loadedmetadata,*loadstart,*lostpointercapture,*mousedown,*mouseenter,*mouseleave,*mousemove,*mouseout,*mouseover,*mouseup,*mousewheel,*pause,*play,*playing,*pointercancel,*pointerdown,*pointerenter,*pointerleave,*pointermove,*pointerout,*pointerover,*pointerup,*progress,*ratechange,*reset,*resize,*scroll,*seeked,*seeking,*select,*show,*stalled,*submit,*suspend,*timeupdate,*toggle,*volumechange,*waiting,%style,#tabIndex',
|
15830 | ':svg:graphics^:svg:|',
|
15831 | ':svg:animation^:svg:|*begin,*end,*repeat',
|
15832 | ':svg:geometry^:svg:|',
|
15833 | ':svg:componentTransferFunction^:svg:|',
|
15834 | ':svg:gradient^:svg:|',
|
15835 | ':svg:textContent^:svg:graphics|',
|
15836 | ':svg:textPositioning^:svg:textContent|',
|
15837 | 'a^[HTMLElement]|charset,coords,download,hash,host,hostname,href,hreflang,name,password,pathname,ping,port,protocol,referrerPolicy,rel,rev,search,shape,target,text,type,username',
|
15838 | 'area^[HTMLElement]|alt,coords,download,hash,host,hostname,href,!noHref,password,pathname,ping,port,protocol,referrerPolicy,rel,search,shape,target,username',
|
15839 | 'audio^media|',
|
15840 | 'br^[HTMLElement]|clear',
|
15841 | 'base^[HTMLElement]|href,target',
|
15842 | 'body^[HTMLElement]|aLink,background,bgColor,link,*beforeunload,*blur,*error,*focus,*hashchange,*languagechange,*load,*message,*offline,*online,*pagehide,*pageshow,*popstate,*rejectionhandled,*resize,*scroll,*storage,*unhandledrejection,*unload,text,vLink',
|
15843 | 'button^[HTMLElement]|!autofocus,!disabled,formAction,formEnctype,formMethod,!formNoValidate,formTarget,name,type,value',
|
15844 | 'canvas^[HTMLElement]|#height,#width',
|
15845 | 'content^[HTMLElement]|select',
|
15846 | 'dl^[HTMLElement]|!compact',
|
15847 | 'datalist^[HTMLElement]|',
|
15848 | 'details^[HTMLElement]|!open',
|
15849 | 'dialog^[HTMLElement]|!open,returnValue',
|
15850 | 'dir^[HTMLElement]|!compact',
|
15851 | 'div^[HTMLElement]|align',
|
15852 | 'embed^[HTMLElement]|align,height,name,src,type,width',
|
15853 | 'fieldset^[HTMLElement]|!disabled,name',
|
15854 | 'font^[HTMLElement]|color,face,size',
|
15855 | 'form^[HTMLElement]|acceptCharset,action,autocomplete,encoding,enctype,method,name,!noValidate,target',
|
15856 | 'frame^[HTMLElement]|frameBorder,longDesc,marginHeight,marginWidth,name,!noResize,scrolling,src',
|
15857 | 'frameset^[HTMLElement]|cols,*beforeunload,*blur,*error,*focus,*hashchange,*languagechange,*load,*message,*offline,*online,*pagehide,*pageshow,*popstate,*rejectionhandled,*resize,*scroll,*storage,*unhandledrejection,*unload,rows',
|
15858 | 'hr^[HTMLElement]|align,color,!noShade,size,width',
|
15859 | 'head^[HTMLElement]|',
|
15860 | 'h1,h2,h3,h4,h5,h6^[HTMLElement]|align',
|
15861 | 'html^[HTMLElement]|version',
|
15862 | 'iframe^[HTMLElement]|align,!allowFullscreen,frameBorder,height,longDesc,marginHeight,marginWidth,name,referrerPolicy,%sandbox,scrolling,src,srcdoc,width',
|
15863 | 'img^[HTMLElement]|align,alt,border,%crossOrigin,#height,#hspace,!isMap,longDesc,lowsrc,name,referrerPolicy,sizes,src,srcset,useMap,#vspace,#width',
|
15864 | 'input^[HTMLElement]|accept,align,alt,autocapitalize,autocomplete,!autofocus,!checked,!defaultChecked,defaultValue,dirName,!disabled,%files,formAction,formEnctype,formMethod,!formNoValidate,formTarget,#height,!incremental,!indeterminate,max,#maxLength,min,#minLength,!multiple,name,pattern,placeholder,!readOnly,!required,selectionDirection,#selectionEnd,#selectionStart,#size,src,step,type,useMap,value,%valueAsDate,#valueAsNumber,#width',
|
15865 | 'li^[HTMLElement]|type,#value',
|
15866 | 'label^[HTMLElement]|htmlFor',
|
15867 | 'legend^[HTMLElement]|align',
|
15868 | 'link^[HTMLElement]|as,charset,%crossOrigin,!disabled,href,hreflang,integrity,media,referrerPolicy,rel,%relList,rev,%sizes,target,type',
|
15869 | 'map^[HTMLElement]|name',
|
15870 | 'marquee^[HTMLElement]|behavior,bgColor,direction,height,#hspace,#loop,#scrollAmount,#scrollDelay,!trueSpeed,#vspace,width',
|
15871 | 'menu^[HTMLElement]|!compact',
|
15872 | 'meta^[HTMLElement]|content,httpEquiv,name,scheme',
|
15873 | 'meter^[HTMLElement]|#high,#low,#max,#min,#optimum,#value',
|
15874 | 'ins,del^[HTMLElement]|cite,dateTime',
|
15875 | 'ol^[HTMLElement]|!compact,!reversed,#start,type',
|
15876 | 'object^[HTMLElement]|align,archive,border,code,codeBase,codeType,data,!declare,height,#hspace,name,standby,type,useMap,#vspace,width',
|
15877 | 'optgroup^[HTMLElement]|!disabled,label',
|
15878 | 'option^[HTMLElement]|!defaultSelected,!disabled,label,!selected,text,value',
|
15879 | 'output^[HTMLElement]|defaultValue,%htmlFor,name,value',
|
15880 | 'p^[HTMLElement]|align',
|
15881 | 'param^[HTMLElement]|name,type,value,valueType',
|
15882 | 'picture^[HTMLElement]|',
|
15883 | 'pre^[HTMLElement]|#width',
|
15884 | 'progress^[HTMLElement]|#max,#value',
|
15885 | 'q,blockquote,cite^[HTMLElement]|',
|
15886 | 'script^[HTMLElement]|!async,charset,%crossOrigin,!defer,event,htmlFor,integrity,src,text,type',
|
15887 | 'select^[HTMLElement]|autocomplete,!autofocus,!disabled,#length,!multiple,name,!required,#selectedIndex,#size,value',
|
15888 | 'shadow^[HTMLElement]|',
|
15889 | 'slot^[HTMLElement]|name',
|
15890 | 'source^[HTMLElement]|media,sizes,src,srcset,type',
|
15891 | 'span^[HTMLElement]|',
|
15892 | 'style^[HTMLElement]|!disabled,media,type',
|
15893 | 'caption^[HTMLElement]|align',
|
15894 | 'th,td^[HTMLElement]|abbr,align,axis,bgColor,ch,chOff,#colSpan,headers,height,!noWrap,#rowSpan,scope,vAlign,width',
|
15895 | 'col,colgroup^[HTMLElement]|align,ch,chOff,#span,vAlign,width',
|
15896 | 'table^[HTMLElement]|align,bgColor,border,%caption,cellPadding,cellSpacing,frame,rules,summary,%tFoot,%tHead,width',
|
15897 | 'tr^[HTMLElement]|align,bgColor,ch,chOff,vAlign',
|
15898 | 'tfoot,thead,tbody^[HTMLElement]|align,ch,chOff,vAlign',
|
15899 | 'template^[HTMLElement]|',
|
15900 | 'textarea^[HTMLElement]|autocapitalize,autocomplete,!autofocus,#cols,defaultValue,dirName,!disabled,#maxLength,#minLength,name,placeholder,!readOnly,!required,#rows,selectionDirection,#selectionEnd,#selectionStart,value,wrap',
|
15901 | 'title^[HTMLElement]|text',
|
15902 | 'track^[HTMLElement]|!default,kind,label,src,srclang',
|
15903 | 'ul^[HTMLElement]|!compact,type',
|
15904 | 'unknown^[HTMLElement]|',
|
15905 | 'video^media|#height,poster,#width',
|
15906 | ':svg:a^:svg:graphics|',
|
15907 | ':svg:animate^:svg:animation|',
|
15908 | ':svg:animateMotion^:svg:animation|',
|
15909 | ':svg:animateTransform^:svg:animation|',
|
15910 | ':svg:circle^:svg:geometry|',
|
15911 | ':svg:clipPath^:svg:graphics|',
|
15912 | ':svg:defs^:svg:graphics|',
|
15913 | ':svg:desc^:svg:|',
|
15914 | ':svg:discard^:svg:|',
|
15915 | ':svg:ellipse^:svg:geometry|',
|
15916 | ':svg:feBlend^:svg:|',
|
15917 | ':svg:feColorMatrix^:svg:|',
|
15918 | ':svg:feComponentTransfer^:svg:|',
|
15919 | ':svg:feComposite^:svg:|',
|
15920 | ':svg:feConvolveMatrix^:svg:|',
|
15921 | ':svg:feDiffuseLighting^:svg:|',
|
15922 | ':svg:feDisplacementMap^:svg:|',
|
15923 | ':svg:feDistantLight^:svg:|',
|
15924 | ':svg:feDropShadow^:svg:|',
|
15925 | ':svg:feFlood^:svg:|',
|
15926 | ':svg:feFuncA^:svg:componentTransferFunction|',
|
15927 | ':svg:feFuncB^:svg:componentTransferFunction|',
|
15928 | ':svg:feFuncG^:svg:componentTransferFunction|',
|
15929 | ':svg:feFuncR^:svg:componentTransferFunction|',
|
15930 | ':svg:feGaussianBlur^:svg:|',
|
15931 | ':svg:feImage^:svg:|',
|
15932 | ':svg:feMerge^:svg:|',
|
15933 | ':svg:feMergeNode^:svg:|',
|
15934 | ':svg:feMorphology^:svg:|',
|
15935 | ':svg:feOffset^:svg:|',
|
15936 | ':svg:fePointLight^:svg:|',
|
15937 | ':svg:feSpecularLighting^:svg:|',
|
15938 | ':svg:feSpotLight^:svg:|',
|
15939 | ':svg:feTile^:svg:|',
|
15940 | ':svg:feTurbulence^:svg:|',
|
15941 | ':svg:filter^:svg:|',
|
15942 | ':svg:foreignObject^:svg:graphics|',
|
15943 | ':svg:g^:svg:graphics|',
|
15944 | ':svg:image^:svg:graphics|',
|
15945 | ':svg:line^:svg:geometry|',
|
15946 | ':svg:linearGradient^:svg:gradient|',
|
15947 | ':svg:mpath^:svg:|',
|
15948 | ':svg:marker^:svg:|',
|
15949 | ':svg:mask^:svg:|',
|
15950 | ':svg:metadata^:svg:|',
|
15951 | ':svg:path^:svg:geometry|',
|
15952 | ':svg:pattern^:svg:|',
|
15953 | ':svg:polygon^:svg:geometry|',
|
15954 | ':svg:polyline^:svg:geometry|',
|
15955 | ':svg:radialGradient^:svg:gradient|',
|
15956 | ':svg:rect^:svg:geometry|',
|
15957 | ':svg:svg^:svg:graphics|#currentScale,#zoomAndPan',
|
15958 | ':svg:script^:svg:|type',
|
15959 | ':svg:set^:svg:animation|',
|
15960 | ':svg:stop^:svg:|',
|
15961 | ':svg:style^:svg:|!disabled,media,title,type',
|
15962 | ':svg:switch^:svg:graphics|',
|
15963 | ':svg:symbol^:svg:|',
|
15964 | ':svg:tspan^:svg:textPositioning|',
|
15965 | ':svg:text^:svg:textPositioning|',
|
15966 | ':svg:textPath^:svg:textContent|',
|
15967 | ':svg:title^:svg:|',
|
15968 | ':svg:use^:svg:graphics|',
|
15969 | ':svg:view^:svg:|#zoomAndPan',
|
15970 | 'data^[HTMLElement]|value',
|
15971 | 'keygen^[HTMLElement]|!autofocus,challenge,!disabled,form,keytype,name',
|
15972 | 'menuitem^[HTMLElement]|type,label,icon,!disabled,!checked,radiogroup,!default',
|
15973 | 'summary^[HTMLElement]|',
|
15974 | 'time^[HTMLElement]|dateTime',
|
15975 | ':svg:cursor^:svg:|',
|
15976 | ];
|
15977 | const _ATTR_TO_PROP = {
|
15978 | 'class': 'className',
|
15979 | 'for': 'htmlFor',
|
15980 | 'formaction': 'formAction',
|
15981 | 'innerHtml': 'innerHTML',
|
15982 | 'readonly': 'readOnly',
|
15983 | 'tabindex': 'tabIndex',
|
15984 | };
|
15985 | // Invert _ATTR_TO_PROP.
|
15986 | const _PROP_TO_ATTR = Object.keys(_ATTR_TO_PROP).reduce((inverted, attr) => {
|
15987 | inverted[_ATTR_TO_PROP[attr]] = attr;
|
15988 | return inverted;
|
15989 | }, {});
|
15990 | class DomElementSchemaRegistry extends ElementSchemaRegistry {
|
15991 | constructor() {
|
15992 | super();
|
15993 | this._schema = {};
|
15994 | SCHEMA.forEach(encodedType => {
|
15995 | const type = {};
|
15996 | const [strType, strProperties] = encodedType.split('|');
|
15997 | const properties = strProperties.split(',');
|
15998 | const [typeNames, superName] = strType.split('^');
|
15999 | typeNames.split(',').forEach(tag => this._schema[tag.toLowerCase()] = type);
|
16000 | const superType = superName && this._schema[superName.toLowerCase()];
|
16001 | if (superType) {
|
16002 | Object.keys(superType).forEach((prop) => {
|
16003 | type[prop] = superType[prop];
|
16004 | });
|
16005 | }
|
16006 | properties.forEach((property) => {
|
16007 | if (property.length > 0) {
|
16008 | switch (property[0]) {
|
16009 | case '*':
|
16010 | // We don't yet support events.
|
16011 | // If ever allowing to bind to events, GO THROUGH A SECURITY REVIEW, allowing events
|
16012 | // will
|
16013 | // almost certainly introduce bad XSS vulnerabilities.
|
16014 | // type[property.substring(1)] = EVENT;
|
16015 | break;
|
16016 | case '!':
|
16017 | type[property.substring(1)] = BOOLEAN;
|
16018 | break;
|
16019 | case '#':
|
16020 | type[property.substring(1)] = NUMBER;
|
16021 | break;
|
16022 | case '%':
|
16023 | type[property.substring(1)] = OBJECT;
|
16024 | break;
|
16025 | default:
|
16026 | type[property] = STRING;
|
16027 | }
|
16028 | }
|
16029 | });
|
16030 | });
|
16031 | }
|
16032 | hasProperty(tagName, propName, schemaMetas) {
|
16033 | if (schemaMetas.some((schema) => schema.name === NO_ERRORS_SCHEMA.name)) {
|
16034 | return true;
|
16035 | }
|
16036 | if (tagName.indexOf('-') > -1) {
|
16037 | if (isNgContainer(tagName) || isNgContent(tagName)) {
|
16038 | return false;
|
16039 | }
|
16040 | if (schemaMetas.some((schema) => schema.name === CUSTOM_ELEMENTS_SCHEMA.name)) {
|
16041 | // Can't tell now as we don't know which properties a custom element will get
|
16042 | // once it is instantiated
|
16043 | return true;
|
16044 | }
|
16045 | }
|
16046 | const elementProperties = this._schema[tagName.toLowerCase()] || this._schema['unknown'];
|
16047 | return !!elementProperties[propName];
|
16048 | }
|
16049 | hasElement(tagName, schemaMetas) {
|
16050 | if (schemaMetas.some((schema) => schema.name === NO_ERRORS_SCHEMA.name)) {
|
16051 | return true;
|
16052 | }
|
16053 | if (tagName.indexOf('-') > -1) {
|
16054 | if (isNgContainer(tagName) || isNgContent(tagName)) {
|
16055 | return true;
|
16056 | }
|
16057 | if (schemaMetas.some((schema) => schema.name === CUSTOM_ELEMENTS_SCHEMA.name)) {
|
16058 | // Allow any custom elements
|
16059 | return true;
|
16060 | }
|
16061 | }
|
16062 | return !!this._schema[tagName.toLowerCase()];
|
16063 | }
|
16064 | /**
|
16065 | * securityContext returns the security context for the given property on the given DOM tag.
|
16066 | *
|
16067 | * Tag and property name are statically known and cannot change at runtime, i.e. it is not
|
16068 | * possible to bind a value into a changing attribute or tag name.
|
16069 | *
|
16070 | * The filtering is based on a list of allowed tags|attributes. All attributes in the schema
|
16071 | * above are assumed to have the 'NONE' security context, i.e. that they are safe inert
|
16072 | * string values. Only specific well known attack vectors are assigned their appropriate context.
|
16073 | */
|
16074 | securityContext(tagName, propName, isAttribute) {
|
16075 | if (isAttribute) {
|
16076 | // NB: For security purposes, use the mapped property name, not the attribute name.
|
16077 | propName = this.getMappedPropName(propName);
|
16078 | }
|
16079 | // Make sure comparisons are case insensitive, so that case differences between attribute and
|
16080 | // property names do not have a security impact.
|
16081 | tagName = tagName.toLowerCase();
|
16082 | propName = propName.toLowerCase();
|
16083 | let ctx = SECURITY_SCHEMA()[tagName + '|' + propName];
|
16084 | if (ctx) {
|
16085 | return ctx;
|
16086 | }
|
16087 | ctx = SECURITY_SCHEMA()['*|' + propName];
|
16088 | return ctx ? ctx : SecurityContext.NONE;
|
16089 | }
|
16090 | getMappedPropName(propName) {
|
16091 | return _ATTR_TO_PROP[propName] || propName;
|
16092 | }
|
16093 | getDefaultComponentElementName() {
|
16094 | return 'ng-component';
|
16095 | }
|
16096 | validateProperty(name) {
|
16097 | if (name.toLowerCase().startsWith('on')) {
|
16098 | const msg = `Binding to event property '${name}' is disallowed for security reasons, ` +
|
16099 | `please use (${name.slice(2)})=...` +
|
16100 | `\nIf '${name}' is a directive input, make sure the directive is imported by the` +
|
16101 | ` current module.`;
|
16102 | return { error: true, msg: msg };
|
16103 | }
|
16104 | else {
|
16105 | return { error: false };
|
16106 | }
|
16107 | }
|
16108 | validateAttribute(name) {
|
16109 | if (name.toLowerCase().startsWith('on')) {
|
16110 | const msg = `Binding to event attribute '${name}' is disallowed for security reasons, ` +
|
16111 | `please use (${name.slice(2)})=...`;
|
16112 | return { error: true, msg: msg };
|
16113 | }
|
16114 | else {
|
16115 | return { error: false };
|
16116 | }
|
16117 | }
|
16118 | allKnownElementNames() {
|
16119 | return Object.keys(this._schema);
|
16120 | }
|
16121 | allKnownAttributesOfElement(tagName) {
|
16122 | const elementProperties = this._schema[tagName.toLowerCase()] || this._schema['unknown'];
|
16123 | // Convert properties to attributes.
|
16124 | return Object.keys(elementProperties).map(prop => { var _a; return (_a = _PROP_TO_ATTR[prop]) !== null && _a !== void 0 ? _a : prop; });
|
16125 | }
|
16126 | normalizeAnimationStyleProperty(propName) {
|
16127 | return dashCaseToCamelCase(propName);
|
16128 | }
|
16129 | normalizeAnimationStyleValue(camelCaseProp, userProvidedProp, val) {
|
16130 | let unit = '';
|
16131 | const strVal = val.toString().trim();
|
16132 | let errorMsg = null;
|
16133 | if (_isPixelDimensionStyle(camelCaseProp) && val !== 0 && val !== '0') {
|
16134 | if (typeof val === 'number') {
|
16135 | unit = 'px';
|
16136 | }
|
16137 | else {
|
16138 | const valAndSuffixMatch = val.match(/^[+-]?[\d\.]+([a-z]*)$/);
|
16139 | if (valAndSuffixMatch && valAndSuffixMatch[1].length == 0) {
|
16140 | errorMsg = `Please provide a CSS unit value for ${userProvidedProp}:${val}`;
|
16141 | }
|
16142 | }
|
16143 | }
|
16144 | return { error: errorMsg, value: strVal + unit };
|
16145 | }
|
16146 | }
|
16147 | function _isPixelDimensionStyle(prop) {
|
16148 | switch (prop) {
|
16149 | case 'width':
|
16150 | case 'height':
|
16151 | case 'minWidth':
|
16152 | case 'minHeight':
|
16153 | case 'maxWidth':
|
16154 | case 'maxHeight':
|
16155 | case 'left':
|
16156 | case 'top':
|
16157 | case 'bottom':
|
16158 | case 'right':
|
16159 | case 'fontSize':
|
16160 | case 'outlineWidth':
|
16161 | case 'outlineOffset':
|
16162 | case 'paddingTop':
|
16163 | case 'paddingLeft':
|
16164 | case 'paddingBottom':
|
16165 | case 'paddingRight':
|
16166 | case 'marginTop':
|
16167 | case 'marginLeft':
|
16168 | case 'marginBottom':
|
16169 | case 'marginRight':
|
16170 | case 'borderRadius':
|
16171 | case 'borderWidth':
|
16172 | case 'borderTopWidth':
|
16173 | case 'borderLeftWidth':
|
16174 | case 'borderRightWidth':
|
16175 | case 'borderBottomWidth':
|
16176 | case 'textIndent':
|
16177 | return true;
|
16178 | default:
|
16179 | return false;
|
16180 | }
|
16181 | }
|
16182 |
|
16183 | /**
|
16184 | * @license
|
16185 | * Copyright Google LLC All Rights Reserved.
|
16186 | *
|
16187 | * Use of this source code is governed by an MIT-style license that can be
|
16188 | * found in the LICENSE file at https://angular.io/license
|
16189 | */
|
16190 | /**
|
16191 | * Set of tagName|propertyName corresponding to Trusted Types sinks. Properties applying to all
|
16192 | * tags use '*'.
|
16193 | *
|
16194 | * Extracted from, and should be kept in sync with
|
16195 | * https://w3c.github.io/webappsec-trusted-types/dist/spec/#integrations
|
16196 | */
|
16197 | const TRUSTED_TYPES_SINKS = new Set([
|
16198 | // NOTE: All strings in this set *must* be lowercase!
|
16199 | // TrustedHTML
|
16200 | 'iframe|srcdoc',
|
16201 | '*|innerhtml',
|
16202 | '*|outerhtml',
|
16203 | // NB: no TrustedScript here, as the corresponding tags are stripped by the compiler.
|
16204 | // TrustedScriptURL
|
16205 | 'embed|src',
|
16206 | 'object|codebase',
|
16207 | 'object|data',
|
16208 | ]);
|
16209 | /**
|
16210 | * isTrustedTypesSink returns true if the given property on the given DOM tag is a Trusted Types
|
16211 | * sink. In that case, use `ElementSchemaRegistry.securityContext` to determine which particular
|
16212 | * Trusted Type is required for values passed to the sink:
|
16213 | * - SecurityContext.HTML corresponds to TrustedHTML
|
16214 | * - SecurityContext.RESOURCE_URL corresponds to TrustedScriptURL
|
16215 | */
|
16216 | function isTrustedTypesSink(tagName, propName) {
|
16217 | // Make sure comparisons are case insensitive, so that case differences between attribute and
|
16218 | // property names do not have a security impact.
|
16219 | tagName = tagName.toLowerCase();
|
16220 | propName = propName.toLowerCase();
|
16221 | return TRUSTED_TYPES_SINKS.has(tagName + '|' + propName) ||
|
16222 | TRUSTED_TYPES_SINKS.has('*|' + propName);
|
16223 | }
|
16224 |
|
16225 | /**
|
16226 | * @license
|
16227 | * Copyright Google LLC All Rights Reserved.
|
16228 | *
|
16229 | * Use of this source code is governed by an MIT-style license that can be
|
16230 | * found in the LICENSE file at https://angular.io/license
|
16231 | */
|
16232 | const BIND_NAME_REGEXP = /^(?:(bind-)|(let-)|(ref-|#)|(on-)|(bindon-)|(@))(.*)$/;
|
16233 | // Group 1 = "bind-"
|
16234 | const KW_BIND_IDX = 1;
|
16235 | // Group 2 = "let-"
|
16236 | const KW_LET_IDX = 2;
|
16237 | // Group 3 = "ref-/#"
|
16238 | const KW_REF_IDX = 3;
|
16239 | // Group 4 = "on-"
|
16240 | const KW_ON_IDX = 4;
|
16241 | // Group 5 = "bindon-"
|
16242 | const KW_BINDON_IDX = 5;
|
16243 | // Group 6 = "@"
|
16244 | const KW_AT_IDX = 6;
|
16245 | // Group 7 = the identifier after "bind-", "let-", "ref-/#", "on-", "bindon-" or "@"
|
16246 | const IDENT_KW_IDX = 7;
|
16247 | const BINDING_DELIMS = {
|
16248 | BANANA_BOX: { start: '[(', end: ')]' },
|
16249 | PROPERTY: { start: '[', end: ']' },
|
16250 | EVENT: { start: '(', end: ')' },
|
16251 | };
|
16252 | const TEMPLATE_ATTR_PREFIX$1 = '*';
|
16253 | function htmlAstToRender3Ast(htmlNodes, bindingParser) {
|
16254 | const transformer = new HtmlAstToIvyAst(bindingParser);
|
16255 | const ivyNodes = visitAll$1(transformer, htmlNodes);
|
16256 | // Errors might originate in either the binding parser or the html to ivy transformer
|
16257 | const allErrors = bindingParser.errors.concat(transformer.errors);
|
16258 | return {
|
16259 | nodes: ivyNodes,
|
16260 | errors: allErrors,
|
16261 | styleUrls: transformer.styleUrls,
|
16262 | styles: transformer.styles,
|
16263 | ngContentSelectors: transformer.ngContentSelectors,
|
16264 | };
|
16265 | }
|
16266 | class HtmlAstToIvyAst {
|
16267 | constructor(bindingParser) {
|
16268 | this.bindingParser = bindingParser;
|
16269 | this.errors = [];
|
16270 | this.styles = [];
|
16271 | this.styleUrls = [];
|
16272 | this.ngContentSelectors = [];
|
16273 | this.inI18nBlock = false;
|
16274 | }
|
16275 | // HTML visitor
|
16276 | visitElement(element) {
|
16277 | const isI18nRootElement = isI18nRootNode(element.i18n);
|
16278 | if (isI18nRootElement) {
|
16279 | if (this.inI18nBlock) {
|
16280 | this.reportError('Cannot mark an element as translatable inside of a translatable section. Please remove the nested i18n marker.', element.sourceSpan);
|
16281 | }
|
16282 | this.inI18nBlock = true;
|
16283 | }
|
16284 | const preparsedElement = preparseElement(element);
|
16285 | if (preparsedElement.type === PreparsedElementType.SCRIPT) {
|
16286 | return null;
|
16287 | }
|
16288 | else if (preparsedElement.type === PreparsedElementType.STYLE) {
|
16289 | const contents = textContents(element);
|
16290 | if (contents !== null) {
|
16291 | this.styles.push(contents);
|
16292 | }
|
16293 | return null;
|
16294 | }
|
16295 | else if (preparsedElement.type === PreparsedElementType.STYLESHEET &&
|
16296 | isStyleUrlResolvable(preparsedElement.hrefAttr)) {
|
16297 | this.styleUrls.push(preparsedElement.hrefAttr);
|
16298 | return null;
|
16299 | }
|
16300 | // Whether the element is a `<ng-template>`
|
16301 | const isTemplateElement = isNgTemplate(element.name);
|
16302 | const parsedProperties = [];
|
16303 | const boundEvents = [];
|
16304 | const variables = [];
|
16305 | const references = [];
|
16306 | const attributes = [];
|
16307 | const i18nAttrsMeta = {};
|
16308 | const templateParsedProperties = [];
|
16309 | const templateVariables = [];
|
16310 | // Whether the element has any *-attribute
|
16311 | let elementHasInlineTemplate = false;
|
16312 | for (const attribute of element.attrs) {
|
16313 | let hasBinding = false;
|
16314 | const normalizedName = normalizeAttributeName(attribute.name);
|
16315 | // `*attr` defines template bindings
|
16316 | let isTemplateBinding = false;
|
16317 | if (attribute.i18n) {
|
16318 | i18nAttrsMeta[attribute.name] = attribute.i18n;
|
16319 | }
|
16320 | if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX$1)) {
|
16321 | // *-attributes
|
16322 | if (elementHasInlineTemplate) {
|
16323 | this.reportError(`Can't have multiple template bindings on one element. Use only one attribute prefixed with *`, attribute.sourceSpan);
|
16324 | }
|
16325 | isTemplateBinding = true;
|
16326 | elementHasInlineTemplate = true;
|
16327 | const templateValue = attribute.value;
|
16328 | const templateKey = normalizedName.substring(TEMPLATE_ATTR_PREFIX$1.length);
|
16329 | const parsedVariables = [];
|
16330 | const absoluteValueOffset = attribute.valueSpan ?
|
16331 | attribute.valueSpan.start.offset :
|
16332 | // If there is no value span the attribute does not have a value, like `attr` in
|
16333 | //`<div attr></div>`. In this case, point to one character beyond the last character of
|
16334 | // the attribute name.
|
16335 | attribute.sourceSpan.start.offset + attribute.name.length;
|
16336 | this.bindingParser.parseInlineTemplateBinding(templateKey, templateValue, attribute.sourceSpan, absoluteValueOffset, [], templateParsedProperties, parsedVariables, true /* isIvyAst */);
|
16337 | templateVariables.push(...parsedVariables.map(v => new Variable(v.name, v.value, v.sourceSpan, v.keySpan, v.valueSpan)));
|
16338 | }
|
16339 | else {
|
16340 | // Check for variables, events, property bindings, interpolation
|
16341 | hasBinding = this.parseAttribute(isTemplateElement, attribute, [], parsedProperties, boundEvents, variables, references);
|
16342 | }
|
16343 | if (!hasBinding && !isTemplateBinding) {
|
16344 | // don't include the bindings as attributes as well in the AST
|
16345 | attributes.push(this.visitAttribute(attribute));
|
16346 | }
|
16347 | }
|
16348 | const children = visitAll$1(preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children);
|
16349 | let parsedElement;
|
16350 | if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
|
16351 | // `<ng-content>`
|
16352 | if (element.children &&
|
16353 | !element.children.every((node) => isEmptyTextNode(node) || isCommentNode(node))) {
|
16354 | this.reportError(`<ng-content> element cannot have content.`, element.sourceSpan);
|
16355 | }
|
16356 | const selector = preparsedElement.selectAttr;
|
16357 | const attrs = element.attrs.map(attr => this.visitAttribute(attr));
|
16358 | parsedElement = new Content(selector, attrs, element.sourceSpan, element.i18n);
|
16359 | this.ngContentSelectors.push(selector);
|
16360 | }
|
16361 | else if (isTemplateElement) {
|
16362 | // `<ng-template>`
|
16363 | const attrs = this.extractAttributes(element.name, parsedProperties, i18nAttrsMeta);
|
16364 | parsedElement = new Template(element.name, attributes, attrs.bound, boundEvents, [ /* no template attributes */], children, references, variables, element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
|
16365 | }
|
16366 | else {
|
16367 | const attrs = this.extractAttributes(element.name, parsedProperties, i18nAttrsMeta);
|
16368 | parsedElement = new Element(element.name, attributes, attrs.bound, boundEvents, children, references, element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
|
16369 | }
|
16370 | if (elementHasInlineTemplate) {
|
16371 | // If this node is an inline-template (e.g. has *ngFor) then we need to create a template
|
16372 | // node that contains this node.
|
16373 | // Moreover, if the node is an element, then we need to hoist its attributes to the template
|
16374 | // node for matching against content projection selectors.
|
16375 | const attrs = this.extractAttributes('ng-template', templateParsedProperties, i18nAttrsMeta);
|
16376 | const templateAttrs = [];
|
16377 | attrs.literal.forEach(attr => templateAttrs.push(attr));
|
16378 | attrs.bound.forEach(attr => templateAttrs.push(attr));
|
16379 | const hoistedAttrs = parsedElement instanceof Element ?
|
16380 | {
|
16381 | attributes: parsedElement.attributes,
|
16382 | inputs: parsedElement.inputs,
|
16383 | outputs: parsedElement.outputs,
|
16384 | } :
|
16385 | { attributes: [], inputs: [], outputs: [] };
|
16386 | // For <ng-template>s with structural directives on them, avoid passing i18n information to
|
16387 | // the wrapping template to prevent unnecessary i18n instructions from being generated. The
|
16388 | // necessary i18n meta information will be extracted from child elements.
|
16389 | const i18n = isTemplateElement && isI18nRootElement ? undefined : element.i18n;
|
16390 | // TODO(pk): test for this case
|
16391 | parsedElement = new Template(parsedElement.name, hoistedAttrs.attributes, hoistedAttrs.inputs, hoistedAttrs.outputs, templateAttrs, [parsedElement], [ /* no references */], templateVariables, element.sourceSpan, element.startSourceSpan, element.endSourceSpan, i18n);
|
16392 | }
|
16393 | if (isI18nRootElement) {
|
16394 | this.inI18nBlock = false;
|
16395 | }
|
16396 | return parsedElement;
|
16397 | }
|
16398 | visitAttribute(attribute) {
|
16399 | return new TextAttribute(attribute.name, attribute.value, attribute.sourceSpan, attribute.keySpan, attribute.valueSpan, attribute.i18n);
|
16400 | }
|
16401 | visitText(text) {
|
16402 | return this._visitTextWithInterpolation(text.value, text.sourceSpan, text.i18n);
|
16403 | }
|
16404 | visitExpansion(expansion) {
|
16405 | if (!expansion.i18n) {
|
16406 | // do not generate Icu in case it was created
|
16407 | // outside of i18n block in a template
|
16408 | return null;
|
16409 | }
|
16410 | if (!isI18nRootNode(expansion.i18n)) {
|
16411 | throw new Error(`Invalid type "${expansion.i18n.constructor}" for "i18n" property of ${expansion.sourceSpan.toString()}. Expected a "Message"`);
|
16412 | }
|
16413 | const message = expansion.i18n;
|
16414 | const vars = {};
|
16415 | const placeholders = {};
|
16416 | // extract VARs from ICUs - we process them separately while
|
16417 | // assembling resulting message via goog.getMsg function, since
|
16418 | // we need to pass them to top-level goog.getMsg call
|
16419 | Object.keys(message.placeholders).forEach(key => {
|
16420 | const value = message.placeholders[key];
|
16421 | if (key.startsWith(I18N_ICU_VAR_PREFIX)) {
|
16422 | // Currently when the `plural` or `select` keywords in an ICU contain trailing spaces (e.g.
|
16423 | // `{count, select , ...}`), these spaces are also included into the key names in ICU vars
|
16424 | // (e.g. "VAR_SELECT "). These trailing spaces are not desirable, since they will later be
|
16425 | // converted into `_` symbols while normalizing placeholder names, which might lead to
|
16426 | // mismatches at runtime (i.e. placeholder will not be replaced with the correct value).
|
16427 | const formattedKey = key.trim();
|
16428 | const ast = this.bindingParser.parseInterpolationExpression(value.text, value.sourceSpan);
|
16429 | vars[formattedKey] = new BoundText(ast, value.sourceSpan);
|
16430 | }
|
16431 | else {
|
16432 | placeholders[key] = this._visitTextWithInterpolation(value.text, value.sourceSpan);
|
16433 | }
|
16434 | });
|
16435 | return new Icu(vars, placeholders, expansion.sourceSpan, message);
|
16436 | }
|
16437 | visitExpansionCase(expansionCase) {
|
16438 | return null;
|
16439 | }
|
16440 | visitComment(comment) {
|
16441 | return null;
|
16442 | }
|
16443 | // convert view engine `ParsedProperty` to a format suitable for IVY
|
16444 | extractAttributes(elementName, properties, i18nPropsMeta) {
|
16445 | const bound = [];
|
16446 | const literal = [];
|
16447 | properties.forEach(prop => {
|
16448 | const i18n = i18nPropsMeta[prop.name];
|
16449 | if (prop.isLiteral) {
|
16450 | literal.push(new TextAttribute(prop.name, prop.expression.source || '', prop.sourceSpan, prop.keySpan, prop.valueSpan, i18n));
|
16451 | }
|
16452 | else {
|
16453 | // Note that validation is skipped and property mapping is disabled
|
16454 | // due to the fact that we need to make sure a given prop is not an
|
16455 | // input of a directive and directive matching happens at runtime.
|
16456 | const bep = this.bindingParser.createBoundElementProperty(elementName, prop, /* skipValidation */ true, /* mapPropertyName */ false);
|
16457 | bound.push(BoundAttribute.fromBoundElementProperty(bep, i18n));
|
16458 | }
|
16459 | });
|
16460 | return { bound, literal };
|
16461 | }
|
16462 | parseAttribute(isTemplateElement, attribute, matchableAttributes, parsedProperties, boundEvents, variables, references) {
|
16463 | const name = normalizeAttributeName(attribute.name);
|
16464 | const value = attribute.value;
|
16465 | const srcSpan = attribute.sourceSpan;
|
16466 | const absoluteOffset = attribute.valueSpan ? attribute.valueSpan.start.offset : srcSpan.start.offset;
|
16467 | function createKeySpan(srcSpan, prefix, identifier) {
|
16468 | // We need to adjust the start location for the keySpan to account for the removed 'data-'
|
16469 | // prefix from `normalizeAttributeName`.
|
16470 | const normalizationAdjustment = attribute.name.length - name.length;
|
16471 | const keySpanStart = srcSpan.start.moveBy(prefix.length + normalizationAdjustment);
|
16472 | const keySpanEnd = keySpanStart.moveBy(identifier.length);
|
16473 | return new ParseSourceSpan(keySpanStart, keySpanEnd, keySpanStart, identifier);
|
16474 | }
|
16475 | const bindParts = name.match(BIND_NAME_REGEXP);
|
16476 | if (bindParts) {
|
16477 | if (bindParts[KW_BIND_IDX] != null) {
|
16478 | const identifier = bindParts[IDENT_KW_IDX];
|
16479 | const keySpan = createKeySpan(srcSpan, bindParts[KW_BIND_IDX], identifier);
|
16480 | this.bindingParser.parsePropertyBinding(identifier, value, false, srcSpan, absoluteOffset, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
|
16481 | }
|
16482 | else if (bindParts[KW_LET_IDX]) {
|
16483 | if (isTemplateElement) {
|
16484 | const identifier = bindParts[IDENT_KW_IDX];
|
16485 | const keySpan = createKeySpan(srcSpan, bindParts[KW_LET_IDX], identifier);
|
16486 | this.parseVariable(identifier, value, srcSpan, keySpan, attribute.valueSpan, variables);
|
16487 | }
|
16488 | else {
|
16489 | this.reportError(`"let-" is only supported on ng-template elements.`, srcSpan);
|
16490 | }
|
16491 | }
|
16492 | else if (bindParts[KW_REF_IDX]) {
|
16493 | const identifier = bindParts[IDENT_KW_IDX];
|
16494 | const keySpan = createKeySpan(srcSpan, bindParts[KW_REF_IDX], identifier);
|
16495 | this.parseReference(identifier, value, srcSpan, keySpan, attribute.valueSpan, references);
|
16496 | }
|
16497 | else if (bindParts[KW_ON_IDX]) {
|
16498 | const events = [];
|
16499 | const identifier = bindParts[IDENT_KW_IDX];
|
16500 | const keySpan = createKeySpan(srcSpan, bindParts[KW_ON_IDX], identifier);
|
16501 | this.bindingParser.parseEvent(identifier, value, srcSpan, attribute.valueSpan || srcSpan, matchableAttributes, events, keySpan);
|
16502 | addEvents(events, boundEvents);
|
16503 | }
|
16504 | else if (bindParts[KW_BINDON_IDX]) {
|
16505 | const identifier = bindParts[IDENT_KW_IDX];
|
16506 | const keySpan = createKeySpan(srcSpan, bindParts[KW_BINDON_IDX], identifier);
|
16507 | this.bindingParser.parsePropertyBinding(identifier, value, false, srcSpan, absoluteOffset, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
|
16508 | this.parseAssignmentEvent(identifier, value, srcSpan, attribute.valueSpan, matchableAttributes, boundEvents, keySpan);
|
16509 | }
|
16510 | else if (bindParts[KW_AT_IDX]) {
|
16511 | const keySpan = createKeySpan(srcSpan, '', name);
|
16512 | this.bindingParser.parseLiteralAttr(name, value, srcSpan, absoluteOffset, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
|
16513 | }
|
16514 | return true;
|
16515 | }
|
16516 | // We didn't see a kw-prefixed property binding, but we have not yet checked
|
16517 | // for the []/()/[()] syntax.
|
16518 | let delims = null;
|
16519 | if (name.startsWith(BINDING_DELIMS.BANANA_BOX.start)) {
|
16520 | delims = BINDING_DELIMS.BANANA_BOX;
|
16521 | }
|
16522 | else if (name.startsWith(BINDING_DELIMS.PROPERTY.start)) {
|
16523 | delims = BINDING_DELIMS.PROPERTY;
|
16524 | }
|
16525 | else if (name.startsWith(BINDING_DELIMS.EVENT.start)) {
|
16526 | delims = BINDING_DELIMS.EVENT;
|
16527 | }
|
16528 | if (delims !== null &&
|
16529 | // NOTE: older versions of the parser would match a start/end delimited
|
16530 | // binding iff the property name was terminated by the ending delimiter
|
16531 | // and the identifier in the binding was non-empty.
|
16532 | // TODO(ayazhafiz): update this to handle malformed bindings.
|
16533 | name.endsWith(delims.end) && name.length > delims.start.length + delims.end.length) {
|
16534 | const identifier = name.substring(delims.start.length, name.length - delims.end.length);
|
16535 | const keySpan = createKeySpan(srcSpan, delims.start, identifier);
|
16536 | if (delims.start === BINDING_DELIMS.BANANA_BOX.start) {
|
16537 | this.bindingParser.parsePropertyBinding(identifier, value, false, srcSpan, absoluteOffset, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
|
16538 | this.parseAssignmentEvent(identifier, value, srcSpan, attribute.valueSpan, matchableAttributes, boundEvents, keySpan);
|
16539 | }
|
16540 | else if (delims.start === BINDING_DELIMS.PROPERTY.start) {
|
16541 | this.bindingParser.parsePropertyBinding(identifier, value, false, srcSpan, absoluteOffset, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
|
16542 | }
|
16543 | else {
|
16544 | const events = [];
|
16545 | this.bindingParser.parseEvent(identifier, value, srcSpan, attribute.valueSpan || srcSpan, matchableAttributes, events, keySpan);
|
16546 | addEvents(events, boundEvents);
|
16547 | }
|
16548 | return true;
|
16549 | }
|
16550 | // No explicit binding found.
|
16551 | const keySpan = createKeySpan(srcSpan, '' /* prefix */, name);
|
16552 | const hasBinding = this.bindingParser.parsePropertyInterpolation(name, value, srcSpan, attribute.valueSpan, matchableAttributes, parsedProperties, keySpan);
|
16553 | return hasBinding;
|
16554 | }
|
16555 | _visitTextWithInterpolation(value, sourceSpan, i18n) {
|
16556 | const valueNoNgsp = replaceNgsp(value);
|
16557 | const expr = this.bindingParser.parseInterpolation(valueNoNgsp, sourceSpan);
|
16558 | return expr ? new BoundText(expr, sourceSpan, i18n) : new Text(valueNoNgsp, sourceSpan);
|
16559 | }
|
16560 | parseVariable(identifier, value, sourceSpan, keySpan, valueSpan, variables) {
|
16561 | if (identifier.indexOf('-') > -1) {
|
16562 | this.reportError(`"-" is not allowed in variable names`, sourceSpan);
|
16563 | }
|
16564 | else if (identifier.length === 0) {
|
16565 | this.reportError(`Variable does not have a name`, sourceSpan);
|
16566 | }
|
16567 | variables.push(new Variable(identifier, value, sourceSpan, keySpan, valueSpan));
|
16568 | }
|
16569 | parseReference(identifier, value, sourceSpan, keySpan, valueSpan, references) {
|
16570 | if (identifier.indexOf('-') > -1) {
|
16571 | this.reportError(`"-" is not allowed in reference names`, sourceSpan);
|
16572 | }
|
16573 | else if (identifier.length === 0) {
|
16574 | this.reportError(`Reference does not have a name`, sourceSpan);
|
16575 | }
|
16576 | else if (references.some(reference => reference.name === identifier)) {
|
16577 | this.reportError(`Reference "#${identifier}" is defined more than once`, sourceSpan);
|
16578 | }
|
16579 | references.push(new Reference(identifier, value, sourceSpan, keySpan, valueSpan));
|
16580 | }
|
16581 | parseAssignmentEvent(name, expression, sourceSpan, valueSpan, targetMatchableAttrs, boundEvents, keySpan) {
|
16582 | const events = [];
|
16583 | this.bindingParser.parseEvent(`${name}Change`, `${expression}=$event`, sourceSpan, valueSpan || sourceSpan, targetMatchableAttrs, events, keySpan);
|
16584 | addEvents(events, boundEvents);
|
16585 | }
|
16586 | reportError(message, sourceSpan, level = ParseErrorLevel.ERROR) {
|
16587 | this.errors.push(new ParseError(sourceSpan, message, level));
|
16588 | }
|
16589 | }
|
16590 | class NonBindableVisitor {
|
16591 | visitElement(ast) {
|
16592 | const preparsedElement = preparseElement(ast);
|
16593 | if (preparsedElement.type === PreparsedElementType.SCRIPT ||
|
16594 | preparsedElement.type === PreparsedElementType.STYLE ||
|
16595 | preparsedElement.type === PreparsedElementType.STYLESHEET) {
|
16596 | // Skipping <script> for security reasons
|
16597 | // Skipping <style> and stylesheets as we already processed them
|
16598 | // in the StyleCompiler
|
16599 | return null;
|
16600 | }
|
16601 | const children = visitAll$1(this, ast.children, null);
|
16602 | return new Element(ast.name, visitAll$1(this, ast.attrs),
|
16603 | /* inputs */ [], /* outputs */ [], children, /* references */ [], ast.sourceSpan, ast.startSourceSpan, ast.endSourceSpan);
|
16604 | }
|
16605 | visitComment(comment) {
|
16606 | return null;
|
16607 | }
|
16608 | visitAttribute(attribute) {
|
16609 | return new TextAttribute(attribute.name, attribute.value, attribute.sourceSpan, attribute.keySpan, attribute.valueSpan, attribute.i18n);
|
16610 | }
|
16611 | visitText(text) {
|
16612 | return new Text(text.value, text.sourceSpan);
|
16613 | }
|
16614 | visitExpansion(expansion) {
|
16615 | return null;
|
16616 | }
|
16617 | visitExpansionCase(expansionCase) {
|
16618 | return null;
|
16619 | }
|
16620 | }
|
16621 | const NON_BINDABLE_VISITOR = new NonBindableVisitor();
|
16622 | function normalizeAttributeName(attrName) {
|
16623 | return /^data-/i.test(attrName) ? attrName.substring(5) : attrName;
|
16624 | }
|
16625 | function addEvents(events, boundEvents) {
|
16626 | boundEvents.push(...events.map(e => BoundEvent.fromParsedEvent(e)));
|
16627 | }
|
16628 | function isEmptyTextNode(node) {
|
16629 | return node instanceof Text$2 && node.value.trim().length == 0;
|
16630 | }
|
16631 | function isCommentNode(node) {
|
16632 | return node instanceof Comment;
|
16633 | }
|
16634 | function textContents(node) {
|
16635 | if (node.children.length !== 1 || !(node.children[0] instanceof Text$2)) {
|
16636 | return null;
|
16637 | }
|
16638 | else {
|
16639 | return node.children[0].value;
|
16640 | }
|
16641 | }
|
16642 |
|
16643 | /**
|
16644 | * @license
|
16645 | * Copyright Google LLC All Rights Reserved.
|
16646 | *
|
16647 | * Use of this source code is governed by an MIT-style license that can be
|
16648 | * found in the LICENSE file at https://angular.io/license
|
16649 | */
|
16650 | var TagType;
|
16651 | (function (TagType) {
|
16652 | TagType[TagType["ELEMENT"] = 0] = "ELEMENT";
|
16653 | TagType[TagType["TEMPLATE"] = 1] = "TEMPLATE";
|
16654 | })(TagType || (TagType = {}));
|
16655 | /**
|
16656 | * Generates an object that is used as a shared state between parent and all child contexts.
|
16657 | */
|
16658 | function setupRegistry() {
|
16659 | return { getUniqueId: getSeqNumberGenerator(), icus: new Map() };
|
16660 | }
|
16661 | /**
|
16662 | * I18nContext is a helper class which keeps track of all i18n-related aspects
|
16663 | * (accumulates placeholders, bindings, etc) between i18nStart and i18nEnd instructions.
|
16664 | *
|
16665 | * When we enter a nested template, the top-level context is being passed down
|
16666 | * to the nested component, which uses this context to generate a child instance
|
16667 | * of I18nContext class (to handle nested template) and at the end, reconciles it back
|
16668 | * with the parent context.
|
16669 | *
|
16670 | * @param index Instruction index of i18nStart, which initiates this context
|
16671 | * @param ref Reference to a translation const that represents the content if thus context
|
16672 | * @param level Nestng level defined for child contexts
|
16673 | * @param templateIndex Instruction index of a template which this context belongs to
|
16674 | * @param meta Meta information (id, meaning, description, etc) associated with this context
|
16675 | */
|
16676 | class I18nContext {
|
16677 | constructor(index, ref, level = 0, templateIndex = null, meta, registry) {
|
16678 | this.index = index;
|
16679 | this.ref = ref;
|
16680 | this.level = level;
|
16681 | this.templateIndex = templateIndex;
|
16682 | this.meta = meta;
|
16683 | this.registry = registry;
|
16684 | this.bindings = new Set();
|
16685 | this.placeholders = new Map();
|
16686 | this.isEmitted = false;
|
16687 | this._unresolvedCtxCount = 0;
|
16688 | this._registry = registry || setupRegistry();
|
16689 | this.id = this._registry.getUniqueId();
|
16690 | }
|
16691 | appendTag(type, node, index, closed) {
|
16692 | if (node.isVoid && closed) {
|
16693 | return; // ignore "close" for void tags
|
16694 | }
|
16695 | const ph = node.isVoid || !closed ? node.startName : node.closeName;
|
16696 | const content = { type, index, ctx: this.id, isVoid: node.isVoid, closed };
|
16697 | updatePlaceholderMap(this.placeholders, ph, content);
|
16698 | }
|
16699 | get icus() {
|
16700 | return this._registry.icus;
|
16701 | }
|
16702 | get isRoot() {
|
16703 | return this.level === 0;
|
16704 | }
|
16705 | get isResolved() {
|
16706 | return this._unresolvedCtxCount === 0;
|
16707 | }
|
16708 | getSerializedPlaceholders() {
|
16709 | const result = new Map();
|
16710 | this.placeholders.forEach((values, key) => result.set(key, values.map(serializePlaceholderValue)));
|
16711 | return result;
|
16712 | }
|
16713 | // public API to accumulate i18n-related content
|
16714 | appendBinding(binding) {
|
16715 | this.bindings.add(binding);
|
16716 | }
|
16717 | appendIcu(name, ref) {
|
16718 | updatePlaceholderMap(this._registry.icus, name, ref);
|
16719 | }
|
16720 | appendBoundText(node) {
|
16721 | const phs = assembleBoundTextPlaceholders(node, this.bindings.size, this.id);
|
16722 | phs.forEach((values, key) => updatePlaceholderMap(this.placeholders, key, ...values));
|
16723 | }
|
16724 | appendTemplate(node, index) {
|
16725 | // add open and close tags at the same time,
|
16726 | // since we process nested templates separately
|
16727 | this.appendTag(TagType.TEMPLATE, node, index, false);
|
16728 | this.appendTag(TagType.TEMPLATE, node, index, true);
|
16729 | this._unresolvedCtxCount++;
|
16730 | }
|
16731 | appendElement(node, index, closed) {
|
16732 | this.appendTag(TagType.ELEMENT, node, index, closed);
|
16733 | }
|
16734 | appendProjection(node, index) {
|
16735 | // Add open and close tags at the same time, since `<ng-content>` has no content,
|
16736 | // so when we come across `<ng-content>` we can register both open and close tags.
|
16737 | // Note: runtime i18n logic doesn't distinguish `<ng-content>` tag placeholders and
|
16738 | // regular element tag placeholders, so we generate element placeholders for both types.
|
16739 | this.appendTag(TagType.ELEMENT, node, index, false);
|
16740 | this.appendTag(TagType.ELEMENT, node, index, true);
|
16741 | }
|
16742 | /**
|
16743 | * Generates an instance of a child context based on the root one,
|
16744 | * when we enter a nested template within I18n section.
|
16745 | *
|
16746 | * @param index Instruction index of corresponding i18nStart, which initiates this context
|
16747 | * @param templateIndex Instruction index of a template which this context belongs to
|
16748 | * @param meta Meta information (id, meaning, description, etc) associated with this context
|
16749 | *
|
16750 | * @returns I18nContext instance
|
16751 | */
|
16752 | forkChildContext(index, templateIndex, meta) {
|
16753 | return new I18nContext(index, this.ref, this.level + 1, templateIndex, meta, this._registry);
|
16754 | }
|
16755 | /**
|
16756 | * Reconciles child context into parent one once the end of the i18n block is reached (i18nEnd).
|
16757 | *
|
16758 | * @param context Child I18nContext instance to be reconciled with parent context.
|
16759 | */
|
16760 | reconcileChildContext(context) {
|
16761 | // set the right context id for open and close
|
16762 | // template tags, so we can use it as sub-block ids
|
16763 | ['start', 'close'].forEach((op) => {
|
16764 | const key = context.meta[`${op}Name`];
|
16765 | const phs = this.placeholders.get(key) || [];
|
16766 | const tag = phs.find(findTemplateFn(this.id, context.templateIndex));
|
16767 | if (tag) {
|
16768 | tag.ctx = context.id;
|
16769 | }
|
16770 | });
|
16771 | // reconcile placeholders
|
16772 | const childPhs = context.placeholders;
|
16773 | childPhs.forEach((values, key) => {
|
16774 | const phs = this.placeholders.get(key);
|
16775 | if (!phs) {
|
16776 | this.placeholders.set(key, values);
|
16777 | return;
|
16778 | }
|
16779 | // try to find matching template...
|
16780 | const tmplIdx = phs.findIndex(findTemplateFn(context.id, context.templateIndex));
|
16781 | if (tmplIdx >= 0) {
|
16782 | // ... if found - replace it with nested template content
|
16783 | const isCloseTag = key.startsWith('CLOSE');
|
16784 | const isTemplateTag = key.endsWith('NG-TEMPLATE');
|
16785 | if (isTemplateTag) {
|
16786 | // current template's content is placed before or after
|
16787 | // parent template tag, depending on the open/close atrribute
|
16788 | phs.splice(tmplIdx + (isCloseTag ? 0 : 1), 0, ...values);
|
16789 | }
|
16790 | else {
|
16791 | const idx = isCloseTag ? values.length - 1 : 0;
|
16792 | values[idx].tmpl = phs[tmplIdx];
|
16793 | phs.splice(tmplIdx, 1, ...values);
|
16794 | }
|
16795 | }
|
16796 | else {
|
16797 | // ... otherwise just append content to placeholder value
|
16798 | phs.push(...values);
|
16799 | }
|
16800 | this.placeholders.set(key, phs);
|
16801 | });
|
16802 | this._unresolvedCtxCount--;
|
16803 | }
|
16804 | }
|
16805 | //
|
16806 | // Helper methods
|
16807 | //
|
16808 | function wrap(symbol, index, contextId, closed) {
|
16809 | const state = closed ? '/' : '';
|
16810 | return wrapI18nPlaceholder(`${state}${symbol}${index}`, contextId);
|
16811 | }
|
16812 | function wrapTag(symbol, { index, ctx, isVoid }, closed) {
|
16813 | return isVoid ? wrap(symbol, index, ctx) + wrap(symbol, index, ctx, true) :
|
16814 | wrap(symbol, index, ctx, closed);
|
16815 | }
|
16816 | function findTemplateFn(ctx, templateIndex) {
|
16817 | return (token) => typeof token === 'object' && token.type === TagType.TEMPLATE &&
|
16818 | token.index === templateIndex && token.ctx === ctx;
|
16819 | }
|
16820 | function serializePlaceholderValue(value) {
|
16821 | const element = (data, closed) => wrapTag('#', data, closed);
|
16822 | const template = (data, closed) => wrapTag('*', data, closed);
|
16823 | switch (value.type) {
|
16824 | case TagType.ELEMENT:
|
16825 | // close element tag
|
16826 | if (value.closed) {
|
16827 | return element(value, true) + (value.tmpl ? template(value.tmpl, true) : '');
|
16828 | }
|
16829 | // open element tag that also initiates a template
|
16830 | if (value.tmpl) {
|
16831 | return template(value.tmpl) + element(value) +
|
16832 | (value.isVoid ? template(value.tmpl, true) : '');
|
16833 | }
|
16834 | return element(value);
|
16835 | case TagType.TEMPLATE:
|
16836 | return template(value, value.closed);
|
16837 | default:
|
16838 | return value;
|
16839 | }
|
16840 | }
|
16841 |
|
16842 | /**
|
16843 | * @license
|
16844 | * Copyright Google LLC All Rights Reserved.
|
16845 | *
|
16846 | * Use of this source code is governed by an MIT-style license that can be
|
16847 | * found in the LICENSE file at https://angular.io/license
|
16848 | */
|
16849 | class IcuSerializerVisitor {
|
16850 | visitText(text) {
|
16851 | return text.value;
|
16852 | }
|
16853 | visitContainer(container) {
|
16854 | return container.children.map(child => child.visit(this)).join('');
|
16855 | }
|
16856 | visitIcu(icu) {
|
16857 | const strCases = Object.keys(icu.cases).map((k) => `${k} {${icu.cases[k].visit(this)}}`);
|
16858 | const result = `{${icu.expressionPlaceholder}, ${icu.type}, ${strCases.join(' ')}}`;
|
16859 | return result;
|
16860 | }
|
16861 | visitTagPlaceholder(ph) {
|
16862 | return ph.isVoid ?
|
16863 | this.formatPh(ph.startName) :
|
16864 | `${this.formatPh(ph.startName)}${ph.children.map(child => child.visit(this)).join('')}${this.formatPh(ph.closeName)}`;
|
16865 | }
|
16866 | visitPlaceholder(ph) {
|
16867 | return this.formatPh(ph.name);
|
16868 | }
|
16869 | visitIcuPlaceholder(ph, context) {
|
16870 | return this.formatPh(ph.name);
|
16871 | }
|
16872 | formatPh(value) {
|
16873 | return `{${formatI18nPlaceholderName(value, /* useCamelCase */ false)}}`;
|
16874 | }
|
16875 | }
|
16876 | const serializer = new IcuSerializerVisitor();
|
16877 | function serializeIcuNode(icu) {
|
16878 | return icu.visit(serializer);
|
16879 | }
|
16880 |
|
16881 | /**
|
16882 | * @license
|
16883 | * Copyright Google LLC All Rights Reserved.
|
16884 | *
|
16885 | * Use of this source code is governed by an MIT-style license that can be
|
16886 | * found in the LICENSE file at https://angular.io/license
|
16887 | */
|
16888 | const TAG_TO_PLACEHOLDER_NAMES = {
|
16889 | 'A': 'LINK',
|
16890 | 'B': 'BOLD_TEXT',
|
16891 | 'BR': 'LINE_BREAK',
|
16892 | 'EM': 'EMPHASISED_TEXT',
|
16893 | 'H1': 'HEADING_LEVEL1',
|
16894 | 'H2': 'HEADING_LEVEL2',
|
16895 | 'H3': 'HEADING_LEVEL3',
|
16896 | 'H4': 'HEADING_LEVEL4',
|
16897 | 'H5': 'HEADING_LEVEL5',
|
16898 | 'H6': 'HEADING_LEVEL6',
|
16899 | 'HR': 'HORIZONTAL_RULE',
|
16900 | 'I': 'ITALIC_TEXT',
|
16901 | 'LI': 'LIST_ITEM',
|
16902 | 'LINK': 'MEDIA_LINK',
|
16903 | 'OL': 'ORDERED_LIST',
|
16904 | 'P': 'PARAGRAPH',
|
16905 | 'Q': 'QUOTATION',
|
16906 | 'S': 'STRIKETHROUGH_TEXT',
|
16907 | 'SMALL': 'SMALL_TEXT',
|
16908 | 'SUB': 'SUBSTRIPT',
|
16909 | 'SUP': 'SUPERSCRIPT',
|
16910 | 'TBODY': 'TABLE_BODY',
|
16911 | 'TD': 'TABLE_CELL',
|
16912 | 'TFOOT': 'TABLE_FOOTER',
|
16913 | 'TH': 'TABLE_HEADER_CELL',
|
16914 | 'THEAD': 'TABLE_HEADER',
|
16915 | 'TR': 'TABLE_ROW',
|
16916 | 'TT': 'MONOSPACED_TEXT',
|
16917 | 'U': 'UNDERLINED_TEXT',
|
16918 | 'UL': 'UNORDERED_LIST',
|
16919 | };
|
16920 | /**
|
16921 | * Creates unique names for placeholder with different content.
|
16922 | *
|
16923 | * Returns the same placeholder name when the content is identical.
|
16924 | */
|
16925 | class PlaceholderRegistry {
|
16926 | constructor() {
|
16927 | // Count the occurrence of the base name top generate a unique name
|
16928 | this._placeHolderNameCounts = {};
|
16929 | // Maps signature to placeholder names
|
16930 | this._signatureToName = {};
|
16931 | }
|
16932 | getStartTagPlaceholderName(tag, attrs, isVoid) {
|
16933 | const signature = this._hashTag(tag, attrs, isVoid);
|
16934 | if (this._signatureToName[signature]) {
|
16935 | return this._signatureToName[signature];
|
16936 | }
|
16937 | const upperTag = tag.toUpperCase();
|
16938 | const baseName = TAG_TO_PLACEHOLDER_NAMES[upperTag] || `TAG_${upperTag}`;
|
16939 | const name = this._generateUniqueName(isVoid ? baseName : `START_${baseName}`);
|
16940 | this._signatureToName[signature] = name;
|
16941 | return name;
|
16942 | }
|
16943 | getCloseTagPlaceholderName(tag) {
|
16944 | const signature = this._hashClosingTag(tag);
|
16945 | if (this._signatureToName[signature]) {
|
16946 | return this._signatureToName[signature];
|
16947 | }
|
16948 | const upperTag = tag.toUpperCase();
|
16949 | const baseName = TAG_TO_PLACEHOLDER_NAMES[upperTag] || `TAG_${upperTag}`;
|
16950 | const name = this._generateUniqueName(`CLOSE_${baseName}`);
|
16951 | this._signatureToName[signature] = name;
|
16952 | return name;
|
16953 | }
|
16954 | getPlaceholderName(name, content) {
|
16955 | const upperName = name.toUpperCase();
|
16956 | const signature = `PH: ${upperName}=${content}`;
|
16957 | if (this._signatureToName[signature]) {
|
16958 | return this._signatureToName[signature];
|
16959 | }
|
16960 | const uniqueName = this._generateUniqueName(upperName);
|
16961 | this._signatureToName[signature] = uniqueName;
|
16962 | return uniqueName;
|
16963 | }
|
16964 | getUniquePlaceholder(name) {
|
16965 | return this._generateUniqueName(name.toUpperCase());
|
16966 | }
|
16967 | // Generate a hash for a tag - does not take attribute order into account
|
16968 | _hashTag(tag, attrs, isVoid) {
|
16969 | const start = `<${tag}`;
|
16970 | const strAttrs = Object.keys(attrs).sort().map((name) => ` ${name}=${attrs[name]}`).join('');
|
16971 | const end = isVoid ? '/>' : `></${tag}>`;
|
16972 | return start + strAttrs + end;
|
16973 | }
|
16974 | _hashClosingTag(tag) {
|
16975 | return this._hashTag(`/${tag}`, {}, false);
|
16976 | }
|
16977 | _generateUniqueName(base) {
|
16978 | const seen = this._placeHolderNameCounts.hasOwnProperty(base);
|
16979 | if (!seen) {
|
16980 | this._placeHolderNameCounts[base] = 1;
|
16981 | return base;
|
16982 | }
|
16983 | const id = this._placeHolderNameCounts[base];
|
16984 | this._placeHolderNameCounts[base] = id + 1;
|
16985 | return `${base}_${id}`;
|
16986 | }
|
16987 | }
|
16988 |
|
16989 | /**
|
16990 | * @license
|
16991 | * Copyright Google LLC All Rights Reserved.
|
16992 | *
|
16993 | * Use of this source code is governed by an MIT-style license that can be
|
16994 | * found in the LICENSE file at https://angular.io/license
|
16995 | */
|
16996 | const _expParser = new Parser$1(new Lexer());
|
16997 | /**
|
16998 | * Returns a function converting html nodes to an i18n Message given an interpolationConfig
|
16999 | */
|
17000 | function createI18nMessageFactory(interpolationConfig) {
|
17001 | const visitor = new _I18nVisitor(_expParser, interpolationConfig);
|
17002 | return (nodes, meaning, description, customId, visitNodeFn) => visitor.toI18nMessage(nodes, meaning, description, customId, visitNodeFn);
|
17003 | }
|
17004 | function noopVisitNodeFn(_html, i18n) {
|
17005 | return i18n;
|
17006 | }
|
17007 | class _I18nVisitor {
|
17008 | constructor(_expressionParser, _interpolationConfig) {
|
17009 | this._expressionParser = _expressionParser;
|
17010 | this._interpolationConfig = _interpolationConfig;
|
17011 | }
|
17012 | toI18nMessage(nodes, meaning = '', description = '', customId = '', visitNodeFn) {
|
17013 | const context = {
|
17014 | isIcu: nodes.length == 1 && nodes[0] instanceof Expansion,
|
17015 | icuDepth: 0,
|
17016 | placeholderRegistry: new PlaceholderRegistry(),
|
17017 | placeholderToContent: {},
|
17018 | placeholderToMessage: {},
|
17019 | visitNodeFn: visitNodeFn || noopVisitNodeFn,
|
17020 | };
|
17021 | const i18nodes = visitAll$1(this, nodes, context);
|
17022 | return new Message(i18nodes, context.placeholderToContent, context.placeholderToMessage, meaning, description, customId);
|
17023 | }
|
17024 | visitElement(el, context) {
|
17025 | var _a;
|
17026 | const children = visitAll$1(this, el.children, context);
|
17027 | const attrs = {};
|
17028 | el.attrs.forEach(attr => {
|
17029 | // Do not visit the attributes, translatable ones are top-level ASTs
|
17030 | attrs[attr.name] = attr.value;
|
17031 | });
|
17032 | const isVoid = getHtmlTagDefinition(el.name).isVoid;
|
17033 | const startPhName = context.placeholderRegistry.getStartTagPlaceholderName(el.name, attrs, isVoid);
|
17034 | context.placeholderToContent[startPhName] = {
|
17035 | text: el.startSourceSpan.toString(),
|
17036 | sourceSpan: el.startSourceSpan,
|
17037 | };
|
17038 | let closePhName = '';
|
17039 | if (!isVoid) {
|
17040 | closePhName = context.placeholderRegistry.getCloseTagPlaceholderName(el.name);
|
17041 | context.placeholderToContent[closePhName] = {
|
17042 | text: `</${el.name}>`,
|
17043 | sourceSpan: (_a = el.endSourceSpan) !== null && _a !== void 0 ? _a : el.sourceSpan,
|
17044 | };
|
17045 | }
|
17046 | const node = new TagPlaceholder(el.name, attrs, startPhName, closePhName, children, isVoid, el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
|
17047 | return context.visitNodeFn(el, node);
|
17048 | }
|
17049 | visitAttribute(attribute, context) {
|
17050 | const node = this._visitTextWithInterpolation(attribute.value, attribute.valueSpan || attribute.sourceSpan, context, attribute.i18n);
|
17051 | return context.visitNodeFn(attribute, node);
|
17052 | }
|
17053 | visitText(text, context) {
|
17054 | const node = this._visitTextWithInterpolation(text.value, text.sourceSpan, context, text.i18n);
|
17055 | return context.visitNodeFn(text, node);
|
17056 | }
|
17057 | visitComment(comment, context) {
|
17058 | return null;
|
17059 | }
|
17060 | visitExpansion(icu, context) {
|
17061 | context.icuDepth++;
|
17062 | const i18nIcuCases = {};
|
17063 | const i18nIcu = new Icu$1(icu.switchValue, icu.type, i18nIcuCases, icu.sourceSpan);
|
17064 | icu.cases.forEach((caze) => {
|
17065 | i18nIcuCases[caze.value] = new Container(caze.expression.map((node) => node.visit(this, context)), caze.expSourceSpan);
|
17066 | });
|
17067 | context.icuDepth--;
|
17068 | if (context.isIcu || context.icuDepth > 0) {
|
17069 | // Returns an ICU node when:
|
17070 | // - the message (vs a part of the message) is an ICU message, or
|
17071 | // - the ICU message is nested.
|
17072 | const expPh = context.placeholderRegistry.getUniquePlaceholder(`VAR_${icu.type}`);
|
17073 | i18nIcu.expressionPlaceholder = expPh;
|
17074 | context.placeholderToContent[expPh] = {
|
17075 | text: icu.switchValue,
|
17076 | sourceSpan: icu.switchValueSourceSpan,
|
17077 | };
|
17078 | return context.visitNodeFn(icu, i18nIcu);
|
17079 | }
|
17080 | // Else returns a placeholder
|
17081 | // ICU placeholders should not be replaced with their original content but with the their
|
17082 | // translations.
|
17083 | // TODO(vicb): add a html.Node -> i18n.Message cache to avoid having to re-create the msg
|
17084 | const phName = context.placeholderRegistry.getPlaceholderName('ICU', icu.sourceSpan.toString());
|
17085 | context.placeholderToMessage[phName] = this.toI18nMessage([icu], '', '', '', undefined);
|
17086 | const node = new IcuPlaceholder(i18nIcu, phName, icu.sourceSpan);
|
17087 | return context.visitNodeFn(icu, node);
|
17088 | }
|
17089 | visitExpansionCase(_icuCase, _context) {
|
17090 | throw new Error('Unreachable code');
|
17091 | }
|
17092 | /**
|
17093 | * Split the, potentially interpolated, text up into text and placeholder pieces.
|
17094 | *
|
17095 | * @param text The potentially interpolated string to be split.
|
17096 | * @param sourceSpan The span of the whole of the `text` string.
|
17097 | * @param context The current context of the visitor, used to compute and store placeholders.
|
17098 | * @param previousI18n Any i18n metadata associated with this `text` from a previous pass.
|
17099 | */
|
17100 | _visitTextWithInterpolation(text, sourceSpan, context, previousI18n) {
|
17101 | const { strings, expressions } = this._expressionParser.splitInterpolation(text, sourceSpan.start.toString(), this._interpolationConfig);
|
17102 | // No expressions, return a single text.
|
17103 | if (expressions.length === 0) {
|
17104 | return new Text$1(text, sourceSpan);
|
17105 | }
|
17106 | // Return a sequence of `Text` and `Placeholder` nodes grouped in a `Container`.
|
17107 | const nodes = [];
|
17108 | for (let i = 0; i < strings.length - 1; i++) {
|
17109 | this._addText(nodes, strings[i], sourceSpan);
|
17110 | this._addPlaceholder(nodes, context, expressions[i], sourceSpan);
|
17111 | }
|
17112 | // The last index contains no expression
|
17113 | this._addText(nodes, strings[strings.length - 1], sourceSpan);
|
17114 | // Whitespace removal may have invalidated the interpolation source-spans.
|
17115 | reusePreviousSourceSpans(nodes, previousI18n);
|
17116 | return new Container(nodes, sourceSpan);
|
17117 | }
|
17118 | /**
|
17119 | * Create a new `Text` node from the `textPiece` and add it to the `nodes` collection.
|
17120 | *
|
17121 | * @param nodes The nodes to which the created `Text` node should be added.
|
17122 | * @param textPiece The text and relative span information for this `Text` node.
|
17123 | * @param interpolationSpan The span of the whole interpolated text.
|
17124 | */
|
17125 | _addText(nodes, textPiece, interpolationSpan) {
|
17126 | if (textPiece.text.length > 0) {
|
17127 | // No need to add empty strings
|
17128 | const stringSpan = getOffsetSourceSpan(interpolationSpan, textPiece);
|
17129 | nodes.push(new Text$1(textPiece.text, stringSpan));
|
17130 | }
|
17131 | }
|
17132 | /**
|
17133 | * Create a new `Placeholder` node from the `expression` and add it to the `nodes` collection.
|
17134 | *
|
17135 | * @param nodes The nodes to which the created `Text` node should be added.
|
17136 | * @param context The current context of the visitor, used to compute and store placeholders.
|
17137 | * @param expression The expression text and relative span information for this `Placeholder`
|
17138 | * node.
|
17139 | * @param interpolationSpan The span of the whole interpolated text.
|
17140 | */
|
17141 | _addPlaceholder(nodes, context, expression, interpolationSpan) {
|
17142 | const sourceSpan = getOffsetSourceSpan(interpolationSpan, expression);
|
17143 | const baseName = extractPlaceholderName(expression.text) || 'INTERPOLATION';
|
17144 | const phName = context.placeholderRegistry.getPlaceholderName(baseName, expression.text);
|
17145 | const text = this._interpolationConfig.start + expression.text + this._interpolationConfig.end;
|
17146 | context.placeholderToContent[phName] = { text, sourceSpan };
|
17147 | nodes.push(new Placeholder(expression.text, phName, sourceSpan));
|
17148 | }
|
17149 | }
|
17150 | /**
|
17151 | * Re-use the source-spans from `previousI18n` metadata for the `nodes`.
|
17152 | *
|
17153 | * Whitespace removal can invalidate the source-spans of interpolation nodes, so we
|
17154 | * reuse the source-span stored from a previous pass before the whitespace was removed.
|
17155 | *
|
17156 | * @param nodes The `Text` and `Placeholder` nodes to be processed.
|
17157 | * @param previousI18n Any i18n metadata for these `nodes` stored from a previous pass.
|
17158 | */
|
17159 | function reusePreviousSourceSpans(nodes, previousI18n) {
|
17160 | if (previousI18n instanceof Message) {
|
17161 | // The `previousI18n` is an i18n `Message`, so we are processing an `Attribute` with i18n
|
17162 | // metadata. The `Message` should consist only of a single `Container` that contains the
|
17163 | // parts (`Text` and `Placeholder`) to process.
|
17164 | assertSingleContainerMessage(previousI18n);
|
17165 | previousI18n = previousI18n.nodes[0];
|
17166 | }
|
17167 | if (previousI18n instanceof Container) {
|
17168 | // The `previousI18n` is a `Container`, which means that this is a second i18n extraction pass
|
17169 | // after whitespace has been removed from the AST ndoes.
|
17170 | assertEquivalentNodes(previousI18n.children, nodes);
|
17171 | // Reuse the source-spans from the first pass.
|
17172 | for (let i = 0; i < nodes.length; i++) {
|
17173 | nodes[i].sourceSpan = previousI18n.children[i].sourceSpan;
|
17174 | }
|
17175 | }
|
17176 | }
|
17177 | /**
|
17178 | * Asserts that the `message` contains exactly one `Container` node.
|
17179 | */
|
17180 | function assertSingleContainerMessage(message) {
|
17181 | const nodes = message.nodes;
|
17182 | if (nodes.length !== 1 || !(nodes[0] instanceof Container)) {
|
17183 | throw new Error('Unexpected previous i18n message - expected it to consist of only a single `Container` node.');
|
17184 | }
|
17185 | }
|
17186 | /**
|
17187 | * Asserts that the `previousNodes` and `node` collections have the same number of elements and
|
17188 | * corresponding elements have the same node type.
|
17189 | */
|
17190 | function assertEquivalentNodes(previousNodes, nodes) {
|
17191 | if (previousNodes.length !== nodes.length) {
|
17192 | throw new Error('The number of i18n message children changed between first and second pass.');
|
17193 | }
|
17194 | if (previousNodes.some((node, i) => nodes[i].constructor !== node.constructor)) {
|
17195 | throw new Error('The types of the i18n message children changed between first and second pass.');
|
17196 | }
|
17197 | }
|
17198 | /**
|
17199 | * Create a new `ParseSourceSpan` from the `sourceSpan`, offset by the `start` and `end` values.
|
17200 | */
|
17201 | function getOffsetSourceSpan(sourceSpan, { start, end }) {
|
17202 | return new ParseSourceSpan(sourceSpan.fullStart.moveBy(start), sourceSpan.fullStart.moveBy(end));
|
17203 | }
|
17204 | const _CUSTOM_PH_EXP = /\/\/[\s\S]*i18n[\s\S]*\([\s\S]*ph[\s\S]*=[\s\S]*("|')([\s\S]*?)\1[\s\S]*\)/g;
|
17205 | function extractPlaceholderName(input) {
|
17206 | return input.split(_CUSTOM_PH_EXP)[2];
|
17207 | }
|
17208 |
|
17209 | /**
|
17210 | * @license
|
17211 | * Copyright Google LLC All Rights Reserved.
|
17212 | *
|
17213 | * Use of this source code is governed by an MIT-style license that can be
|
17214 | * found in the LICENSE file at https://angular.io/license
|
17215 | */
|
17216 | /**
|
17217 | * An i18n error.
|
17218 | */
|
17219 | class I18nError extends ParseError {
|
17220 | constructor(span, msg) {
|
17221 | super(span, msg);
|
17222 | }
|
17223 | }
|
17224 |
|
17225 | /**
|
17226 | * @license
|
17227 | * Copyright Google LLC All Rights Reserved.
|
17228 | *
|
17229 | * Use of this source code is governed by an MIT-style license that can be
|
17230 | * found in the LICENSE file at https://angular.io/license
|
17231 | */
|
17232 | const setI18nRefs = (htmlNode, i18nNode) => {
|
17233 | if (htmlNode instanceof NodeWithI18n) {
|
17234 | if (i18nNode instanceof IcuPlaceholder && htmlNode.i18n instanceof Message) {
|
17235 | // This html node represents an ICU but this is a second processing pass, and the legacy id
|
17236 | // was computed in the previous pass and stored in the `i18n` property as a message.
|
17237 | // We are about to wipe out that property so capture the previous message to be reused when
|
17238 | // generating the message for this ICU later. See `_generateI18nMessage()`.
|
17239 | i18nNode.previousMessage = htmlNode.i18n;
|
17240 | }
|
17241 | htmlNode.i18n = i18nNode;
|
17242 | }
|
17243 | return i18nNode;
|
17244 | };
|
17245 | /**
|
17246 | * This visitor walks over HTML parse tree and converts information stored in
|
17247 | * i18n-related attributes ("i18n" and "i18n-*") into i18n meta object that is
|
17248 | * stored with other element's and attribute's information.
|
17249 | */
|
17250 | class I18nMetaVisitor {
|
17251 | constructor(interpolationConfig = DEFAULT_INTERPOLATION_CONFIG, keepI18nAttrs = false, enableI18nLegacyMessageIdFormat = false) {
|
17252 | this.interpolationConfig = interpolationConfig;
|
17253 | this.keepI18nAttrs = keepI18nAttrs;
|
17254 | this.enableI18nLegacyMessageIdFormat = enableI18nLegacyMessageIdFormat;
|
17255 | // whether visited nodes contain i18n information
|
17256 | this.hasI18nMeta = false;
|
17257 | this._errors = [];
|
17258 | // i18n message generation factory
|
17259 | this._createI18nMessage = createI18nMessageFactory(this.interpolationConfig);
|
17260 | }
|
17261 | _generateI18nMessage(nodes, meta = '', visitNodeFn) {
|
17262 | const { meaning, description, customId } = this._parseMetadata(meta);
|
17263 | const message = this._createI18nMessage(nodes, meaning, description, customId, visitNodeFn);
|
17264 | this._setMessageId(message, meta);
|
17265 | this._setLegacyIds(message, meta);
|
17266 | return message;
|
17267 | }
|
17268 | visitAllWithErrors(nodes) {
|
17269 | const result = nodes.map(node => node.visit(this, null));
|
17270 | return new ParseTreeResult(result, this._errors);
|
17271 | }
|
17272 | visitElement(element) {
|
17273 | if (hasI18nAttrs(element)) {
|
17274 | this.hasI18nMeta = true;
|
17275 | const attrs = [];
|
17276 | const attrsMeta = {};
|
17277 | for (const attr of element.attrs) {
|
17278 | if (attr.name === I18N_ATTR) {
|
17279 | // root 'i18n' node attribute
|
17280 | const i18n = element.i18n || attr.value;
|
17281 | const message = this._generateI18nMessage(element.children, i18n, setI18nRefs);
|
17282 | // do not assign empty i18n meta
|
17283 | if (message.nodes.length) {
|
17284 | element.i18n = message;
|
17285 | }
|
17286 | }
|
17287 | else if (attr.name.startsWith(I18N_ATTR_PREFIX)) {
|
17288 | // 'i18n-*' attributes
|
17289 | const name = attr.name.slice(I18N_ATTR_PREFIX.length);
|
17290 | if (isTrustedTypesSink(element.name, name)) {
|
17291 | this._reportError(attr, `Translating attribute '${name}' is disallowed for security reasons.`);
|
17292 | }
|
17293 | else {
|
17294 | attrsMeta[name] = attr.value;
|
17295 | }
|
17296 | }
|
17297 | else {
|
17298 | // non-i18n attributes
|
17299 | attrs.push(attr);
|
17300 | }
|
17301 | }
|
17302 | // set i18n meta for attributes
|
17303 | if (Object.keys(attrsMeta).length) {
|
17304 | for (const attr of attrs) {
|
17305 | const meta = attrsMeta[attr.name];
|
17306 | // do not create translation for empty attributes
|
17307 | if (meta !== undefined && attr.value) {
|
17308 | attr.i18n = this._generateI18nMessage([attr], attr.i18n || meta);
|
17309 | }
|
17310 | }
|
17311 | }
|
17312 | if (!this.keepI18nAttrs) {
|
17313 | // update element's attributes,
|
17314 | // keeping only non-i18n related ones
|
17315 | element.attrs = attrs;
|
17316 | }
|
17317 | }
|
17318 | visitAll$1(this, element.children, element.i18n);
|
17319 | return element;
|
17320 | }
|
17321 | visitExpansion(expansion, currentMessage) {
|
17322 | let message;
|
17323 | const meta = expansion.i18n;
|
17324 | this.hasI18nMeta = true;
|
17325 | if (meta instanceof IcuPlaceholder) {
|
17326 | // set ICU placeholder name (e.g. "ICU_1"),
|
17327 | // generated while processing root element contents,
|
17328 | // so we can reference it when we output translation
|
17329 | const name = meta.name;
|
17330 | message = this._generateI18nMessage([expansion], meta);
|
17331 | const icu = icuFromI18nMessage(message);
|
17332 | icu.name = name;
|
17333 | }
|
17334 | else {
|
17335 | // ICU is a top level message, try to use metadata from container element if provided via
|
17336 | // `context` argument. Note: context may not be available for standalone ICUs (without
|
17337 | // wrapping element), so fallback to ICU metadata in this case.
|
17338 | message = this._generateI18nMessage([expansion], currentMessage || meta);
|
17339 | }
|
17340 | expansion.i18n = message;
|
17341 | return expansion;
|
17342 | }
|
17343 | visitText(text) {
|
17344 | return text;
|
17345 | }
|
17346 | visitAttribute(attribute) {
|
17347 | return attribute;
|
17348 | }
|
17349 | visitComment(comment) {
|
17350 | return comment;
|
17351 | }
|
17352 | visitExpansionCase(expansionCase) {
|
17353 | return expansionCase;
|
17354 | }
|
17355 | /**
|
17356 | * Parse the general form `meta` passed into extract the explicit metadata needed to create a
|
17357 | * `Message`.
|
17358 | *
|
17359 | * There are three possibilities for the `meta` variable
|
17360 | * 1) a string from an `i18n` template attribute: parse it to extract the metadata values.
|
17361 | * 2) a `Message` from a previous processing pass: reuse the metadata values in the message.
|
17362 | * 4) other: ignore this and just process the message metadata as normal
|
17363 | *
|
17364 | * @param meta the bucket that holds information about the message
|
17365 | * @returns the parsed metadata.
|
17366 | */
|
17367 | _parseMetadata(meta) {
|
17368 | return typeof meta === 'string' ? parseI18nMeta(meta) :
|
17369 | meta instanceof Message ? meta : {};
|
17370 | }
|
17371 | /**
|
17372 | * Generate (or restore) message id if not specified already.
|
17373 | */
|
17374 | _setMessageId(message, meta) {
|
17375 | if (!message.id) {
|
17376 | message.id = meta instanceof Message && meta.id || decimalDigest(message);
|
17377 | }
|
17378 | }
|
17379 | /**
|
17380 | * Update the `message` with a `legacyId` if necessary.
|
17381 | *
|
17382 | * @param message the message whose legacy id should be set
|
17383 | * @param meta information about the message being processed
|
17384 | */
|
17385 | _setLegacyIds(message, meta) {
|
17386 | if (this.enableI18nLegacyMessageIdFormat) {
|
17387 | message.legacyIds = [computeDigest(message), computeDecimalDigest(message)];
|
17388 | }
|
17389 | else if (typeof meta !== 'string') {
|
17390 | // This occurs if we are doing the 2nd pass after whitespace removal (see `parseTemplate()` in
|
17391 | // `packages/compiler/src/render3/view/template.ts`).
|
17392 | // In that case we want to reuse the legacy message generated in the 1st pass (see
|
17393 | // `setI18nRefs()`).
|
17394 | const previousMessage = meta instanceof Message ?
|
17395 | meta :
|
17396 | meta instanceof IcuPlaceholder ? meta.previousMessage : undefined;
|
17397 | message.legacyIds = previousMessage ? previousMessage.legacyIds : [];
|
17398 | }
|
17399 | }
|
17400 | _reportError(node, msg) {
|
17401 | this._errors.push(new I18nError(node.sourceSpan, msg));
|
17402 | }
|
17403 | }
|
17404 | /** I18n separators for metadata **/
|
17405 | const I18N_MEANING_SEPARATOR = '|';
|
17406 | const I18N_ID_SEPARATOR = '@@';
|
17407 | /**
|
17408 | * Parses i18n metas like:
|
17409 | * - "@@id",
|
17410 | * - "description[@@id]",
|
17411 | * - "meaning|description[@@id]"
|
17412 | * and returns an object with parsed output.
|
17413 | *
|
17414 | * @param meta String that represents i18n meta
|
17415 | * @returns Object with id, meaning and description fields
|
17416 | */
|
17417 | function parseI18nMeta(meta = '') {
|
17418 | let customId;
|
17419 | let meaning;
|
17420 | let description;
|
17421 | meta = meta.trim();
|
17422 | if (meta) {
|
17423 | const idIndex = meta.indexOf(I18N_ID_SEPARATOR);
|
17424 | const descIndex = meta.indexOf(I18N_MEANING_SEPARATOR);
|
17425 | let meaningAndDesc;
|
17426 | [meaningAndDesc, customId] =
|
17427 | (idIndex > -1) ? [meta.slice(0, idIndex), meta.slice(idIndex + 2)] : [meta, ''];
|
17428 | [meaning, description] = (descIndex > -1) ?
|
17429 | [meaningAndDesc.slice(0, descIndex), meaningAndDesc.slice(descIndex + 1)] :
|
17430 | ['', meaningAndDesc];
|
17431 | }
|
17432 | return { customId, meaning, description };
|
17433 | }
|
17434 | // Converts i18n meta information for a message (id, description, meaning)
|
17435 | // to a JsDoc statement formatted as expected by the Closure compiler.
|
17436 | function i18nMetaToJSDoc(meta) {
|
17437 | const tags = [];
|
17438 | if (meta.description) {
|
17439 | tags.push({ tagName: "desc" /* Desc */, text: meta.description });
|
17440 | }
|
17441 | if (meta.meaning) {
|
17442 | tags.push({ tagName: "meaning" /* Meaning */, text: meta.meaning });
|
17443 | }
|
17444 | return tags.length == 0 ? null : jsDocComment(tags);
|
17445 | }
|
17446 |
|
17447 | /** Closure uses `goog.getMsg(message)` to lookup translations */
|
17448 | const GOOG_GET_MSG = 'goog.getMsg';
|
17449 | function createGoogleGetMsgStatements(variable$1, message, closureVar, params) {
|
17450 | const messageString = serializeI18nMessageForGetMsg(message);
|
17451 | const args = [literal(messageString)];
|
17452 | if (Object.keys(params).length) {
|
17453 | args.push(mapLiteral(params, true));
|
17454 | }
|
17455 | // /**
|
17456 | // * @desc description of message
|
17457 | // * @meaning meaning of message
|
17458 | // */
|
17459 | // const MSG_... = goog.getMsg(..);
|
17460 | // I18N_X = MSG_...;
|
17461 | const googGetMsgStmt = closureVar.set(variable(GOOG_GET_MSG).callFn(args)).toConstDecl();
|
17462 | const metaComment = i18nMetaToJSDoc(message);
|
17463 | if (metaComment !== null) {
|
17464 | googGetMsgStmt.addLeadingComment(metaComment);
|
17465 | }
|
17466 | const i18nAssignmentStmt = new ExpressionStatement(variable$1.set(closureVar));
|
17467 | return [googGetMsgStmt, i18nAssignmentStmt];
|
17468 | }
|
17469 | /**
|
17470 | * This visitor walks over i18n tree and generates its string representation, including ICUs and
|
17471 | * placeholders in `{$placeholder}` (for plain messages) or `{PLACEHOLDER}` (inside ICUs) format.
|
17472 | */
|
17473 | class GetMsgSerializerVisitor {
|
17474 | formatPh(value) {
|
17475 | return `{$${formatI18nPlaceholderName(value)}}`;
|
17476 | }
|
17477 | visitText(text) {
|
17478 | return text.value;
|
17479 | }
|
17480 | visitContainer(container) {
|
17481 | return container.children.map(child => child.visit(this)).join('');
|
17482 | }
|
17483 | visitIcu(icu) {
|
17484 | return serializeIcuNode(icu);
|
17485 | }
|
17486 | visitTagPlaceholder(ph) {
|
17487 | return ph.isVoid ?
|
17488 | this.formatPh(ph.startName) :
|
17489 | `${this.formatPh(ph.startName)}${ph.children.map(child => child.visit(this)).join('')}${this.formatPh(ph.closeName)}`;
|
17490 | }
|
17491 | visitPlaceholder(ph) {
|
17492 | return this.formatPh(ph.name);
|
17493 | }
|
17494 | visitIcuPlaceholder(ph, context) {
|
17495 | return this.formatPh(ph.name);
|
17496 | }
|
17497 | }
|
17498 | const serializerVisitor$1 = new GetMsgSerializerVisitor();
|
17499 | function serializeI18nMessageForGetMsg(message) {
|
17500 | return message.nodes.map(node => node.visit(serializerVisitor$1, null)).join('');
|
17501 | }
|
17502 |
|
17503 | function createLocalizeStatements(variable, message, params) {
|
17504 | const { messageParts, placeHolders } = serializeI18nMessageForLocalize(message);
|
17505 | const sourceSpan = getSourceSpan(message);
|
17506 | const expressions = placeHolders.map(ph => params[ph.text]);
|
17507 | const localizedString$1 = localizedString(message, messageParts, placeHolders, expressions, sourceSpan);
|
17508 | const variableInitialization = variable.set(localizedString$1);
|
17509 | return [new ExpressionStatement(variableInitialization)];
|
17510 | }
|
17511 | /**
|
17512 | * This visitor walks over an i18n tree, capturing literal strings and placeholders.
|
17513 | *
|
17514 | * The result can be used for generating the `$localize` tagged template literals.
|
17515 | */
|
17516 | class LocalizeSerializerVisitor {
|
17517 | visitText(text, context) {
|
17518 | if (context[context.length - 1] instanceof LiteralPiece) {
|
17519 | // Two literal pieces in a row means that there was some comment node in-between.
|
17520 | context[context.length - 1].text += text.value;
|
17521 | }
|
17522 | else {
|
17523 | context.push(new LiteralPiece(text.value, text.sourceSpan));
|
17524 | }
|
17525 | }
|
17526 | visitContainer(container, context) {
|
17527 | container.children.forEach(child => child.visit(this, context));
|
17528 | }
|
17529 | visitIcu(icu, context) {
|
17530 | context.push(new LiteralPiece(serializeIcuNode(icu), icu.sourceSpan));
|
17531 | }
|
17532 | visitTagPlaceholder(ph, context) {
|
17533 | var _a, _b;
|
17534 | context.push(this.createPlaceholderPiece(ph.startName, (_a = ph.startSourceSpan) !== null && _a !== void 0 ? _a : ph.sourceSpan));
|
17535 | if (!ph.isVoid) {
|
17536 | ph.children.forEach(child => child.visit(this, context));
|
17537 | context.push(this.createPlaceholderPiece(ph.closeName, (_b = ph.endSourceSpan) !== null && _b !== void 0 ? _b : ph.sourceSpan));
|
17538 | }
|
17539 | }
|
17540 | visitPlaceholder(ph, context) {
|
17541 | context.push(this.createPlaceholderPiece(ph.name, ph.sourceSpan));
|
17542 | }
|
17543 | visitIcuPlaceholder(ph, context) {
|
17544 | context.push(this.createPlaceholderPiece(ph.name, ph.sourceSpan));
|
17545 | }
|
17546 | createPlaceholderPiece(name, sourceSpan) {
|
17547 | return new PlaceholderPiece(formatI18nPlaceholderName(name, /* useCamelCase */ false), sourceSpan);
|
17548 | }
|
17549 | }
|
17550 | const serializerVisitor$2 = new LocalizeSerializerVisitor();
|
17551 | /**
|
17552 | * Serialize an i18n message into two arrays: messageParts and placeholders.
|
17553 | *
|
17554 | * These arrays will be used to generate `$localize` tagged template literals.
|
17555 | *
|
17556 | * @param message The message to be serialized.
|
17557 | * @returns an object containing the messageParts and placeholders.
|
17558 | */
|
17559 | function serializeI18nMessageForLocalize(message) {
|
17560 | const pieces = [];
|
17561 | message.nodes.forEach(node => node.visit(serializerVisitor$2, pieces));
|
17562 | return processMessagePieces(pieces);
|
17563 | }
|
17564 | function getSourceSpan(message) {
|
17565 | const startNode = message.nodes[0];
|
17566 | const endNode = message.nodes[message.nodes.length - 1];
|
17567 | return new ParseSourceSpan(startNode.sourceSpan.start, endNode.sourceSpan.end, startNode.sourceSpan.fullStart, startNode.sourceSpan.details);
|
17568 | }
|
17569 | /**
|
17570 | * Convert the list of serialized MessagePieces into two arrays.
|
17571 | *
|
17572 | * One contains the literal string pieces and the other the placeholders that will be replaced by
|
17573 | * expressions when rendering `$localize` tagged template literals.
|
17574 | *
|
17575 | * @param pieces The pieces to process.
|
17576 | * @returns an object containing the messageParts and placeholders.
|
17577 | */
|
17578 | function processMessagePieces(pieces) {
|
17579 | const messageParts = [];
|
17580 | const placeHolders = [];
|
17581 | if (pieces[0] instanceof PlaceholderPiece) {
|
17582 | // The first piece was a placeholder so we need to add an initial empty message part.
|
17583 | messageParts.push(createEmptyMessagePart(pieces[0].sourceSpan.start));
|
17584 | }
|
17585 | for (let i = 0; i < pieces.length; i++) {
|
17586 | const part = pieces[i];
|
17587 | if (part instanceof LiteralPiece) {
|
17588 | messageParts.push(part);
|
17589 | }
|
17590 | else {
|
17591 | placeHolders.push(part);
|
17592 | if (pieces[i - 1] instanceof PlaceholderPiece) {
|
17593 | // There were two placeholders in a row, so we need to add an empty message part.
|
17594 | messageParts.push(createEmptyMessagePart(pieces[i - 1].sourceSpan.end));
|
17595 | }
|
17596 | }
|
17597 | }
|
17598 | if (pieces[pieces.length - 1] instanceof PlaceholderPiece) {
|
17599 | // The last piece was a placeholder so we need to add a final empty message part.
|
17600 | messageParts.push(createEmptyMessagePart(pieces[pieces.length - 1].sourceSpan.end));
|
17601 | }
|
17602 | return { messageParts, placeHolders };
|
17603 | }
|
17604 | function createEmptyMessagePart(location) {
|
17605 | return new LiteralPiece('', new ParseSourceSpan(location, location));
|
17606 | }
|
17607 |
|
17608 | /**
|
17609 | * @license
|
17610 | * Copyright Google LLC All Rights Reserved.
|
17611 | *
|
17612 | * Use of this source code is governed by an MIT-style license that can be
|
17613 | * found in the LICENSE file at https://angular.io/license
|
17614 | */
|
17615 | // Selector attribute name of `<ng-content>`
|
17616 | const NG_CONTENT_SELECT_ATTR$1 = 'select';
|
17617 | // Attribute name of `ngProjectAs`.
|
17618 | const NG_PROJECT_AS_ATTR_NAME = 'ngProjectAs';
|
17619 | // Global symbols available only inside event bindings.
|
17620 | const EVENT_BINDING_SCOPE_GLOBALS = new Set(['$event']);
|
17621 | // List of supported global targets for event listeners
|
17622 | const GLOBAL_TARGET_RESOLVERS = new Map([['window', Identifiers$1.resolveWindow], ['document', Identifiers$1.resolveDocument], ['body', Identifiers$1.resolveBody]]);
|
17623 | const LEADING_TRIVIA_CHARS = [' ', '\n', '\r', '\t'];
|
17624 | // if (rf & flags) { .. }
|
17625 | function renderFlagCheckIfStmt(flags, statements) {
|
17626 | return ifStmt(variable(RENDER_FLAGS).bitwiseAnd(literal(flags), null, false), statements);
|
17627 | }
|
17628 | function prepareEventListenerParameters(eventAst, handlerName = null, scope = null) {
|
17629 | const { type, name, target, phase, handler } = eventAst;
|
17630 | if (target && !GLOBAL_TARGET_RESOLVERS.has(target)) {
|
17631 | throw new Error(`Unexpected global target '${target}' defined for '${name}' event.
|
17632 | Supported list of global targets: ${Array.from(GLOBAL_TARGET_RESOLVERS.keys())}.`);
|
17633 | }
|
17634 | const eventArgumentName = '$event';
|
17635 | const implicitReceiverAccesses = new Set();
|
17636 | const implicitReceiverExpr = (scope === null || scope.bindingLevel === 0) ?
|
17637 | variable(CONTEXT_NAME) :
|
17638 | scope.getOrCreateSharedContextVar(0);
|
17639 | const bindingExpr = convertActionBinding(scope, implicitReceiverExpr, handler, 'b', () => error('Unexpected interpolation'), eventAst.handlerSpan, implicitReceiverAccesses, EVENT_BINDING_SCOPE_GLOBALS);
|
17640 | const statements = [];
|
17641 | if (scope) {
|
17642 | statements.push(...scope.restoreViewStatement());
|
17643 | statements.push(...scope.variableDeclarations());
|
17644 | }
|
17645 | statements.push(...bindingExpr.render3Stmts);
|
17646 | const eventName = type === 1 /* Animation */ ? prepareSyntheticListenerName(name, phase) : name;
|
17647 | const fnName = handlerName && sanitizeIdentifier(handlerName);
|
17648 | const fnArgs = [];
|
17649 | if (implicitReceiverAccesses.has(eventArgumentName)) {
|
17650 | fnArgs.push(new FnParam(eventArgumentName, DYNAMIC_TYPE));
|
17651 | }
|
17652 | const handlerFn = fn(fnArgs, statements, INFERRED_TYPE, null, fnName);
|
17653 | const params = [literal(eventName), handlerFn];
|
17654 | if (target) {
|
17655 | params.push(literal(false), // `useCapture` flag, defaults to `false`
|
17656 | importExpr(GLOBAL_TARGET_RESOLVERS.get(target)));
|
17657 | }
|
17658 | return params;
|
17659 | }
|
17660 | function createComponentDefConsts() {
|
17661 | return {
|
17662 | prepareStatements: [],
|
17663 | constExpressions: [],
|
17664 | i18nVarRefsCache: new Map(),
|
17665 | };
|
17666 | }
|
17667 | class TemplateDefinitionBuilder {
|
17668 | constructor(constantPool, parentBindingScope, level = 0, contextName, i18nContext, templateIndex, templateName, directiveMatcher, directives, pipeTypeByName, pipes, _namespace, relativeContextFilePath, i18nUseExternalIds, _constants = createComponentDefConsts()) {
|
17669 | this.constantPool = constantPool;
|
17670 | this.level = level;
|
17671 | this.contextName = contextName;
|
17672 | this.i18nContext = i18nContext;
|
17673 | this.templateIndex = templateIndex;
|
17674 | this.templateName = templateName;
|
17675 | this.directiveMatcher = directiveMatcher;
|
17676 | this.directives = directives;
|
17677 | this.pipeTypeByName = pipeTypeByName;
|
17678 | this.pipes = pipes;
|
17679 | this._namespace = _namespace;
|
17680 | this.i18nUseExternalIds = i18nUseExternalIds;
|
17681 | this._constants = _constants;
|
17682 | this._dataIndex = 0;
|
17683 | this._bindingContext = 0;
|
17684 | this._prefixCode = [];
|
17685 | /**
|
17686 | * List of callbacks to generate creation mode instructions. We store them here as we process
|
17687 | * the template so bindings in listeners are resolved only once all nodes have been visited.
|
17688 | * This ensures all local refs and context variables are available for matching.
|
17689 | */
|
17690 | this._creationCodeFns = [];
|
17691 | /**
|
17692 | * List of callbacks to generate update mode instructions. We store them here as we process
|
17693 | * the template so bindings are resolved only once all nodes have been visited. This ensures
|
17694 | * all local refs and context variables are available for matching.
|
17695 | */
|
17696 | this._updateCodeFns = [];
|
17697 | /** Index of the currently-selected node. */
|
17698 | this._currentIndex = 0;
|
17699 | /** Temporary variable declarations generated from visiting pipes, literals, etc. */
|
17700 | this._tempVariables = [];
|
17701 | /**
|
17702 | * List of callbacks to build nested templates. Nested templates must not be visited until
|
17703 | * after the parent template has finished visiting all of its nodes. This ensures that all
|
17704 | * local ref bindings in nested templates are able to find local ref values if the refs
|
17705 | * are defined after the template declaration.
|
17706 | */
|
17707 | this._nestedTemplateFns = [];
|
17708 | this._unsupported = unsupported;
|
17709 | // i18n context local to this template
|
17710 | this.i18n = null;
|
17711 | // Number of slots to reserve for pureFunctions
|
17712 | this._pureFunctionSlots = 0;
|
17713 | // Number of binding slots
|
17714 | this._bindingSlots = 0;
|
17715 | // Projection slots found in the template. Projection slots can distribute projected
|
17716 | // nodes based on a selector, or can just use the wildcard selector to match
|
17717 | // all nodes which aren't matching any selector.
|
17718 | this._ngContentReservedSlots = [];
|
17719 | // Number of non-default selectors found in all parent templates of this template. We need to
|
17720 | // track it to properly adjust projection slot index in the `projection` instruction.
|
17721 | this._ngContentSelectorsOffset = 0;
|
17722 | // Expression that should be used as implicit receiver when converting template
|
17723 | // expressions to output AST.
|
17724 | this._implicitReceiverExpr = null;
|
17725 | // These should be handled in the template or element directly.
|
17726 | this.visitReference = invalid$1;
|
17727 | this.visitVariable = invalid$1;
|
17728 | this.visitTextAttribute = invalid$1;
|
17729 | this.visitBoundAttribute = invalid$1;
|
17730 | this.visitBoundEvent = invalid$1;
|
17731 | this._bindingScope = parentBindingScope.nestedScope(level);
|
17732 | // Turn the relative context file path into an identifier by replacing non-alphanumeric
|
17733 | // characters with underscores.
|
17734 | this.fileBasedI18nSuffix = relativeContextFilePath.replace(/[^A-Za-z0-9]/g, '_') + '_';
|
17735 | this._valueConverter = new ValueConverter(constantPool, () => this.allocateDataSlot(), (numSlots) => this.allocatePureFunctionSlots(numSlots), (name, localName, slot, value) => {
|
17736 | const pipeType = pipeTypeByName.get(name);
|
17737 | if (pipeType) {
|
17738 | this.pipes.add(pipeType);
|
17739 | }
|
17740 | this._bindingScope.set(this.level, localName, value);
|
17741 | this.creationInstruction(null, Identifiers$1.pipe, [literal(slot), literal(name)]);
|
17742 | });
|
17743 | }
|
17744 | buildTemplateFunction(nodes, variables, ngContentSelectorsOffset = 0, i18n) {
|
17745 | this._ngContentSelectorsOffset = ngContentSelectorsOffset;
|
17746 | if (this._namespace !== Identifiers$1.namespaceHTML) {
|
17747 | this.creationInstruction(null, this._namespace);
|
17748 | }
|
17749 | // Create variable bindings
|
17750 | variables.forEach(v => this.registerContextVariables(v));
|
17751 | // Initiate i18n context in case:
|
17752 | // - this template has parent i18n context
|
17753 | // - or the template has i18n meta associated with it,
|
17754 | // but it's not initiated by the Element (e.g. <ng-template i18n>)
|
17755 | const initI18nContext = this.i18nContext ||
|
17756 | (isI18nRootNode(i18n) && !isSingleI18nIcu(i18n) &&
|
17757 | !(isSingleElementTemplate(nodes) && nodes[0].i18n === i18n));
|
17758 | const selfClosingI18nInstruction = hasTextChildrenOnly(nodes);
|
17759 | if (initI18nContext) {
|
17760 | this.i18nStart(null, i18n, selfClosingI18nInstruction);
|
17761 | }
|
17762 | // This is the initial pass through the nodes of this template. In this pass, we
|
17763 | // queue all creation mode and update mode instructions for generation in the second
|
17764 | // pass. It's necessary to separate the passes to ensure local refs are defined before
|
17765 | // resolving bindings. We also count bindings in this pass as we walk bound expressions.
|
17766 | visitAll(this, nodes);
|
17767 | // Add total binding count to pure function count so pure function instructions are
|
17768 | // generated with the correct slot offset when update instructions are processed.
|
17769 | this._pureFunctionSlots += this._bindingSlots;
|
17770 | // Pipes are walked in the first pass (to enqueue `pipe()` creation instructions and
|
17771 | // `pipeBind` update instructions), so we have to update the slot offsets manually
|
17772 | // to account for bindings.
|
17773 | this._valueConverter.updatePipeSlotOffsets(this._bindingSlots);
|
17774 | // Nested templates must be processed before creation instructions so template()
|
17775 | // instructions can be generated with the correct internal const count.
|
17776 | this._nestedTemplateFns.forEach(buildTemplateFn => buildTemplateFn());
|
17777 | // Output the `projectionDef` instruction when some `<ng-content>` tags are present.
|
17778 | // The `projectionDef` instruction is only emitted for the component template and
|
17779 | // is skipped for nested templates (<ng-template> tags).
|
17780 | if (this.level === 0 && this._ngContentReservedSlots.length) {
|
17781 | const parameters = [];
|
17782 | // By default the `projectionDef` instructions creates one slot for the wildcard
|
17783 | // selector if no parameters are passed. Therefore we only want to allocate a new
|
17784 | // array for the projection slots if the default projection slot is not sufficient.
|
17785 | if (this._ngContentReservedSlots.length > 1 || this._ngContentReservedSlots[0] !== '*') {
|
17786 | const r3ReservedSlots = this._ngContentReservedSlots.map(s => s !== '*' ? parseSelectorToR3Selector(s) : s);
|
17787 | parameters.push(this.constantPool.getConstLiteral(asLiteral(r3ReservedSlots), true));
|
17788 | }
|
17789 | // Since we accumulate ngContent selectors while processing template elements,
|
17790 | // we *prepend* `projectionDef` to creation instructions block, to put it before
|
17791 | // any `projection` instructions
|
17792 | this.creationInstruction(null, Identifiers$1.projectionDef, parameters, /* prepend */ true);
|
17793 | }
|
17794 | if (initI18nContext) {
|
17795 | this.i18nEnd(null, selfClosingI18nInstruction);
|
17796 | }
|
17797 | // Generate all the creation mode instructions (e.g. resolve bindings in listeners)
|
17798 | const creationStatements = this._creationCodeFns.map((fn) => fn());
|
17799 | // Generate all the update mode instructions (e.g. resolve property or text bindings)
|
17800 | const updateStatements = this._updateCodeFns.map((fn) => fn());
|
17801 | // Variable declaration must occur after binding resolution so we can generate context
|
17802 | // instructions that build on each other.
|
17803 | // e.g. const b = nextContext().$implicit(); const b = nextContext();
|
17804 | const creationVariables = this._bindingScope.viewSnapshotStatements();
|
17805 | const updateVariables = this._bindingScope.variableDeclarations().concat(this._tempVariables);
|
17806 | const creationBlock = creationStatements.length > 0 ?
|
17807 | [renderFlagCheckIfStmt(1 /* Create */, creationVariables.concat(creationStatements))] :
|
17808 | [];
|
17809 | const updateBlock = updateStatements.length > 0 ?
|
17810 | [renderFlagCheckIfStmt(2 /* Update */, updateVariables.concat(updateStatements))] :
|
17811 | [];
|
17812 | return fn(
|
17813 | // i.e. (rf: RenderFlags, ctx: any)
|
17814 | [new FnParam(RENDER_FLAGS, NUMBER_TYPE), new FnParam(CONTEXT_NAME, null)], [
|
17815 | // Temporary variable declarations for query refresh (i.e. let _t: any;)
|
17816 | ...this._prefixCode,
|
17817 | // Creating mode (i.e. if (rf & RenderFlags.Create) { ... })
|
17818 | ...creationBlock,
|
17819 | // Binding and refresh mode (i.e. if (rf & RenderFlags.Update) {...})
|
17820 | ...updateBlock,
|
17821 | ], INFERRED_TYPE, null, this.templateName);
|
17822 | }
|
17823 | // LocalResolver
|
17824 | getLocal(name) {
|
17825 | return this._bindingScope.get(name);
|
17826 | }
|
17827 | // LocalResolver
|
17828 | notifyImplicitReceiverUse() {
|
17829 | this._bindingScope.notifyImplicitReceiverUse();
|
17830 | }
|
17831 | i18nTranslate(message, params = {}, ref, transformFn) {
|
17832 | const _ref = ref || this.i18nGenerateMainBlockVar();
|
17833 | // Closure Compiler requires const names to start with `MSG_` but disallows any other const to
|
17834 | // start with `MSG_`. We define a variable starting with `MSG_` just for the `goog.getMsg` call
|
17835 | const closureVar = this.i18nGenerateClosureVar(message.id);
|
17836 | const statements = getTranslationDeclStmts(message, _ref, closureVar, params, transformFn);
|
17837 | this._constants.prepareStatements.push(...statements);
|
17838 | return _ref;
|
17839 | }
|
17840 | registerContextVariables(variable$1) {
|
17841 | const scopedName = this._bindingScope.freshReferenceName();
|
17842 | const retrievalLevel = this.level;
|
17843 | const lhs = variable(variable$1.name + scopedName);
|
17844 | this._bindingScope.set(retrievalLevel, variable$1.name, lhs, 1 /* CONTEXT */, (scope, relativeLevel) => {
|
17845 | let rhs;
|
17846 | if (scope.bindingLevel === retrievalLevel) {
|
17847 | // e.g. ctx
|
17848 | rhs = variable(CONTEXT_NAME);
|
17849 | }
|
17850 | else {
|
17851 | const sharedCtxVar = scope.getSharedContextName(retrievalLevel);
|
17852 | // e.g. ctx_r0 OR x(2);
|
17853 | rhs = sharedCtxVar ? sharedCtxVar : generateNextContextExpr(relativeLevel);
|
17854 | }
|
17855 | // e.g. const $item$ = x(2).$implicit;
|
17856 | return [lhs.set(rhs.prop(variable$1.value || IMPLICIT_REFERENCE)).toConstDecl()];
|
17857 | });
|
17858 | }
|
17859 | i18nAppendBindings(expressions) {
|
17860 | if (expressions.length > 0) {
|
17861 | expressions.forEach(expression => this.i18n.appendBinding(expression));
|
17862 | }
|
17863 | }
|
17864 | i18nBindProps(props) {
|
17865 | const bound = {};
|
17866 | Object.keys(props).forEach(key => {
|
17867 | const prop = props[key];
|
17868 | if (prop instanceof Text) {
|
17869 | bound[key] = literal(prop.value);
|
17870 | }
|
17871 | else {
|
17872 | const value = prop.value.visit(this._valueConverter);
|
17873 | this.allocateBindingSlots(value);
|
17874 | if (value instanceof Interpolation) {
|
17875 | const { strings, expressions } = value;
|
17876 | const { id, bindings } = this.i18n;
|
17877 | const label = assembleI18nBoundString(strings, bindings.size, id);
|
17878 | this.i18nAppendBindings(expressions);
|
17879 | bound[key] = literal(label);
|
17880 | }
|
17881 | }
|
17882 | });
|
17883 | return bound;
|
17884 | }
|
17885 | // Generates top level vars for i18n blocks (i.e. `i18n_N`).
|
17886 | i18nGenerateMainBlockVar() {
|
17887 | return variable(this.constantPool.uniqueName(TRANSLATION_VAR_PREFIX));
|
17888 | }
|
17889 | // Generates vars with Closure-specific names for i18n blocks (i.e. `MSG_XXX`).
|
17890 | i18nGenerateClosureVar(messageId) {
|
17891 | let name;
|
17892 | const suffix = this.fileBasedI18nSuffix.toUpperCase();
|
17893 | if (this.i18nUseExternalIds) {
|
17894 | const prefix = getTranslationConstPrefix(`EXTERNAL_`);
|
17895 | const uniqueSuffix = this.constantPool.uniqueName(suffix);
|
17896 | name = `${prefix}${sanitizeIdentifier(messageId)}$$${uniqueSuffix}`;
|
17897 | }
|
17898 | else {
|
17899 | const prefix = getTranslationConstPrefix(suffix);
|
17900 | name = this.constantPool.uniqueName(prefix);
|
17901 | }
|
17902 | return variable(name);
|
17903 | }
|
17904 | i18nUpdateRef(context) {
|
17905 | const { icus, meta, isRoot, isResolved, isEmitted } = context;
|
17906 | if (isRoot && isResolved && !isEmitted && !isSingleI18nIcu(meta)) {
|
17907 | context.isEmitted = true;
|
17908 | const placeholders = context.getSerializedPlaceholders();
|
17909 | let icuMapping = {};
|
17910 | let params = placeholders.size ? placeholdersToParams(placeholders) : {};
|
17911 | if (icus.size) {
|
17912 | icus.forEach((refs, key) => {
|
17913 | if (refs.length === 1) {
|
17914 | // if we have one ICU defined for a given
|
17915 | // placeholder - just output its reference
|
17916 | params[key] = refs[0];
|
17917 | }
|
17918 | else {
|
17919 | // ... otherwise we need to activate post-processing
|
17920 | // to replace ICU placeholders with proper values
|
17921 | const placeholder = wrapI18nPlaceholder(`${I18N_ICU_MAPPING_PREFIX}${key}`);
|
17922 | params[key] = literal(placeholder);
|
17923 | icuMapping[key] = literalArr(refs);
|
17924 | }
|
17925 | });
|
17926 | }
|
17927 | // translation requires post processing in 2 cases:
|
17928 | // - if we have placeholders with multiple values (ex. `START_DIV`: [�#1�, �#2�, ...])
|
17929 | // - if we have multiple ICUs that refer to the same placeholder name
|
17930 | const needsPostprocessing = Array.from(placeholders.values()).some((value) => value.length > 1) ||
|
17931 | Object.keys(icuMapping).length;
|
17932 | let transformFn;
|
17933 | if (needsPostprocessing) {
|
17934 | transformFn = (raw) => {
|
17935 | const args = [raw];
|
17936 | if (Object.keys(icuMapping).length) {
|
17937 | args.push(mapLiteral(icuMapping, true));
|
17938 | }
|
17939 | return instruction(null, Identifiers$1.i18nPostprocess, args);
|
17940 | };
|
17941 | }
|
17942 | this.i18nTranslate(meta, params, context.ref, transformFn);
|
17943 | }
|
17944 | }
|
17945 | i18nStart(span = null, meta, selfClosing) {
|
17946 | const index = this.allocateDataSlot();
|
17947 | this.i18n = this.i18nContext ?
|
17948 | this.i18nContext.forkChildContext(index, this.templateIndex, meta) :
|
17949 | new I18nContext(index, this.i18nGenerateMainBlockVar(), 0, this.templateIndex, meta);
|
17950 | // generate i18nStart instruction
|
17951 | const { id, ref } = this.i18n;
|
17952 | const params = [literal(index), this.addToConsts(ref)];
|
17953 | if (id > 0) {
|
17954 | // do not push 3rd argument (sub-block id)
|
17955 | // into i18nStart call for top level i18n context
|
17956 | params.push(literal(id));
|
17957 | }
|
17958 | this.creationInstruction(span, selfClosing ? Identifiers$1.i18n : Identifiers$1.i18nStart, params);
|
17959 | }
|
17960 | i18nEnd(span = null, selfClosing) {
|
17961 | if (!this.i18n) {
|
17962 | throw new Error('i18nEnd is executed with no i18n context present');
|
17963 | }
|
17964 | if (this.i18nContext) {
|
17965 | this.i18nContext.reconcileChildContext(this.i18n);
|
17966 | this.i18nUpdateRef(this.i18nContext);
|
17967 | }
|
17968 | else {
|
17969 | this.i18nUpdateRef(this.i18n);
|
17970 | }
|
17971 | // setup accumulated bindings
|
17972 | const { index, bindings } = this.i18n;
|
17973 | if (bindings.size) {
|
17974 | const chainBindings = [];
|
17975 | bindings.forEach(binding => {
|
17976 | chainBindings.push({ sourceSpan: span, value: () => this.convertPropertyBinding(binding) });
|
17977 | });
|
17978 | // for i18n block, advance to the most recent element index (by taking the current number of
|
17979 | // elements and subtracting one) before invoking `i18nExp` instructions, to make sure the
|
17980 | // necessary lifecycle hooks of components/directives are properly flushed.
|
17981 | this.updateInstructionChainWithAdvance(this.getConstCount() - 1, Identifiers$1.i18nExp, chainBindings);
|
17982 | this.updateInstruction(span, Identifiers$1.i18nApply, [literal(index)]);
|
17983 | }
|
17984 | if (!selfClosing) {
|
17985 | this.creationInstruction(span, Identifiers$1.i18nEnd);
|
17986 | }
|
17987 | this.i18n = null; // reset local i18n context
|
17988 | }
|
17989 | i18nAttributesInstruction(nodeIndex, attrs, sourceSpan) {
|
17990 | let hasBindings = false;
|
17991 | const i18nAttrArgs = [];
|
17992 | const bindings = [];
|
17993 | attrs.forEach(attr => {
|
17994 | const message = attr.i18n;
|
17995 | const converted = attr.value.visit(this._valueConverter);
|
17996 | this.allocateBindingSlots(converted);
|
17997 | if (converted instanceof Interpolation) {
|
17998 | const placeholders = assembleBoundTextPlaceholders(message);
|
17999 | const params = placeholdersToParams(placeholders);
|
18000 | i18nAttrArgs.push(literal(attr.name), this.i18nTranslate(message, params));
|
18001 | converted.expressions.forEach(expression => {
|
18002 | hasBindings = true;
|
18003 | bindings.push({
|
18004 | sourceSpan,
|
18005 | value: () => this.convertPropertyBinding(expression),
|
18006 | });
|
18007 | });
|
18008 | }
|
18009 | });
|
18010 | if (bindings.length > 0) {
|
18011 | this.updateInstructionChainWithAdvance(nodeIndex, Identifiers$1.i18nExp, bindings);
|
18012 | }
|
18013 | if (i18nAttrArgs.length > 0) {
|
18014 | const index = literal(this.allocateDataSlot());
|
18015 | const constIndex = this.addToConsts(literalArr(i18nAttrArgs));
|
18016 | this.creationInstruction(sourceSpan, Identifiers$1.i18nAttributes, [index, constIndex]);
|
18017 | if (hasBindings) {
|
18018 | this.updateInstruction(sourceSpan, Identifiers$1.i18nApply, [index]);
|
18019 | }
|
18020 | }
|
18021 | }
|
18022 | getNamespaceInstruction(namespaceKey) {
|
18023 | switch (namespaceKey) {
|
18024 | case 'math':
|
18025 | return Identifiers$1.namespaceMathML;
|
18026 | case 'svg':
|
18027 | return Identifiers$1.namespaceSVG;
|
18028 | default:
|
18029 | return Identifiers$1.namespaceHTML;
|
18030 | }
|
18031 | }
|
18032 | addNamespaceInstruction(nsInstruction, element) {
|
18033 | this._namespace = nsInstruction;
|
18034 | this.creationInstruction(element.startSourceSpan, nsInstruction);
|
18035 | }
|
18036 | /**
|
18037 | * Adds an update instruction for an interpolated property or attribute, such as
|
18038 | * `prop="{{value}}"` or `attr.title="{{value}}"`
|
18039 | */
|
18040 | interpolatedUpdateInstruction(instruction, elementIndex, attrName, input, value, params) {
|
18041 | this.updateInstructionWithAdvance(elementIndex, input.sourceSpan, instruction, () => [literal(attrName), ...this.getUpdateInstructionArguments(value), ...params]);
|
18042 | }
|
18043 | visitContent(ngContent) {
|
18044 | const slot = this.allocateDataSlot();
|
18045 | const projectionSlotIdx = this._ngContentSelectorsOffset + this._ngContentReservedSlots.length;
|
18046 | const parameters = [literal(slot)];
|
18047 | this._ngContentReservedSlots.push(ngContent.selector);
|
18048 | const nonContentSelectAttributes = ngContent.attributes.filter(attr => attr.name.toLowerCase() !== NG_CONTENT_SELECT_ATTR$1);
|
18049 | const attributes = this.getAttributeExpressions(ngContent.name, nonContentSelectAttributes, [], []);
|
18050 | if (attributes.length > 0) {
|
18051 | parameters.push(literal(projectionSlotIdx), literalArr(attributes));
|
18052 | }
|
18053 | else if (projectionSlotIdx !== 0) {
|
18054 | parameters.push(literal(projectionSlotIdx));
|
18055 | }
|
18056 | this.creationInstruction(ngContent.sourceSpan, Identifiers$1.projection, parameters);
|
18057 | if (this.i18n) {
|
18058 | this.i18n.appendProjection(ngContent.i18n, slot);
|
18059 | }
|
18060 | }
|
18061 | visitElement(element) {
|
18062 | var _a, _b;
|
18063 | const elementIndex = this.allocateDataSlot();
|
18064 | const stylingBuilder = new StylingBuilder(null);
|
18065 | let isNonBindableMode = false;
|
18066 | const isI18nRootElement = isI18nRootNode(element.i18n) && !isSingleI18nIcu(element.i18n);
|
18067 | const outputAttrs = [];
|
18068 | const [namespaceKey, elementName] = splitNsName(element.name);
|
18069 | const isNgContainer$1 = isNgContainer(element.name);
|
18070 | // Handle styling, i18n, ngNonBindable attributes
|
18071 | for (const attr of element.attributes) {
|
18072 | const { name, value } = attr;
|
18073 | if (name === NON_BINDABLE_ATTR) {
|
18074 | isNonBindableMode = true;
|
18075 | }
|
18076 | else if (name === 'style') {
|
18077 | stylingBuilder.registerStyleAttr(value);
|
18078 | }
|
18079 | else if (name === 'class') {
|
18080 | stylingBuilder.registerClassAttr(value);
|
18081 | }
|
18082 | else {
|
18083 | outputAttrs.push(attr);
|
18084 | }
|
18085 | }
|
18086 | // Match directives on non i18n attributes
|
18087 | this.matchDirectives(element.name, element);
|
18088 | // Regular element or ng-container creation mode
|
18089 | const parameters = [literal(elementIndex)];
|
18090 | if (!isNgContainer$1) {
|
18091 | parameters.push(literal(elementName));
|
18092 | }
|
18093 | // Add the attributes
|
18094 | const allOtherInputs = [];
|
18095 | const boundI18nAttrs = [];
|
18096 | element.inputs.forEach(input => {
|
18097 | const stylingInputWasSet = stylingBuilder.registerBoundInput(input);
|
18098 | if (!stylingInputWasSet) {
|
18099 | if (input.type === 0 /* Property */ && input.i18n) {
|
18100 | boundI18nAttrs.push(input);
|
18101 | }
|
18102 | else {
|
18103 | allOtherInputs.push(input);
|
18104 | }
|
18105 | }
|
18106 | });
|
18107 | // add attributes for directive and projection matching purposes
|
18108 | const attributes = this.getAttributeExpressions(element.name, outputAttrs, allOtherInputs, element.outputs, stylingBuilder, [], boundI18nAttrs);
|
18109 | parameters.push(this.addAttrsToConsts(attributes));
|
18110 | // local refs (ex.: <div #foo #bar="baz">)
|
18111 | const refs = this.prepareRefsArray(element.references);
|
18112 | parameters.push(this.addToConsts(refs));
|
18113 | const wasInNamespace = this._namespace;
|
18114 | const currentNamespace = this.getNamespaceInstruction(namespaceKey);
|
18115 | // If the namespace is changing now, include an instruction to change it
|
18116 | // during element creation.
|
18117 | if (currentNamespace !== wasInNamespace) {
|
18118 | this.addNamespaceInstruction(currentNamespace, element);
|
18119 | }
|
18120 | if (this.i18n) {
|
18121 | this.i18n.appendElement(element.i18n, elementIndex);
|
18122 | }
|
18123 | // Note that we do not append text node instructions and ICUs inside i18n section,
|
18124 | // so we exclude them while calculating whether current element has children
|
18125 | const hasChildren = (!isI18nRootElement && this.i18n) ? !hasTextChildrenOnly(element.children) :
|
18126 | element.children.length > 0;
|
18127 | const createSelfClosingInstruction = !stylingBuilder.hasBindingsWithPipes &&
|
18128 | element.outputs.length === 0 && boundI18nAttrs.length === 0 && !hasChildren;
|
18129 | const createSelfClosingI18nInstruction = !createSelfClosingInstruction && hasTextChildrenOnly(element.children);
|
18130 | if (createSelfClosingInstruction) {
|
18131 | this.creationInstruction(element.sourceSpan, isNgContainer$1 ? Identifiers$1.elementContainer : Identifiers$1.element, trimTrailingNulls(parameters));
|
18132 | }
|
18133 | else {
|
18134 | this.creationInstruction(element.startSourceSpan, isNgContainer$1 ? Identifiers$1.elementContainerStart : Identifiers$1.elementStart, trimTrailingNulls(parameters));
|
18135 | if (isNonBindableMode) {
|
18136 | this.creationInstruction(element.startSourceSpan, Identifiers$1.disableBindings);
|
18137 | }
|
18138 | if (boundI18nAttrs.length > 0) {
|
18139 | this.i18nAttributesInstruction(elementIndex, boundI18nAttrs, (_a = element.startSourceSpan) !== null && _a !== void 0 ? _a : element.sourceSpan);
|
18140 | }
|
18141 | // Generate Listeners (outputs)
|
18142 | if (element.outputs.length > 0) {
|
18143 | const listeners = element.outputs.map((outputAst) => ({
|
18144 | sourceSpan: outputAst.sourceSpan,
|
18145 | params: this.prepareListenerParameter(element.name, outputAst, elementIndex)
|
18146 | }));
|
18147 | this.creationInstructionChain(Identifiers$1.listener, listeners);
|
18148 | }
|
18149 | // Note: it's important to keep i18n/i18nStart instructions after i18nAttributes and
|
18150 | // listeners, to make sure i18nAttributes instruction targets current element at runtime.
|
18151 | if (isI18nRootElement) {
|
18152 | this.i18nStart(element.startSourceSpan, element.i18n, createSelfClosingI18nInstruction);
|
18153 | }
|
18154 | }
|
18155 | // the code here will collect all update-level styling instructions and add them to the
|
18156 | // update block of the template function AOT code. Instructions like `styleProp`,
|
18157 | // `styleMap`, `classMap`, `classProp`
|
18158 | // are all generated and assigned in the code below.
|
18159 | const stylingInstructions = stylingBuilder.buildUpdateLevelInstructions(this._valueConverter);
|
18160 | const limit = stylingInstructions.length - 1;
|
18161 | for (let i = 0; i <= limit; i++) {
|
18162 | const instruction = stylingInstructions[i];
|
18163 | this._bindingSlots += this.processStylingUpdateInstruction(elementIndex, instruction);
|
18164 | }
|
18165 | // the reason why `undefined` is used is because the renderer understands this as a
|
18166 | // special value to symbolize that there is no RHS to this binding
|
18167 | // TODO (matsko): revisit this once FW-959 is approached
|
18168 | const emptyValueBindInstruction = literal(undefined);
|
18169 | const propertyBindings = [];
|
18170 | const attributeBindings = [];
|
18171 | // Generate element input bindings
|
18172 | allOtherInputs.forEach(input => {
|
18173 | const inputType = input.type;
|
18174 | if (inputType === 4 /* Animation */) {
|
18175 | const value = input.value.visit(this._valueConverter);
|
18176 | // animation bindings can be presented in the following formats:
|
18177 | // 1. [@binding]="fooExp"
|
18178 | // 2. [@binding]="{value:fooExp, params:{...}}"
|
18179 | // 3. [@binding]
|
18180 | // 4. @binding
|
18181 | // All formats will be valid for when a synthetic binding is created.
|
18182 | // The reasoning for this is because the renderer should get each
|
18183 | // synthetic binding value in the order of the array that they are
|
18184 | // defined in...
|
18185 | const hasValue = value instanceof LiteralPrimitive ? !!value.value : true;
|
18186 | this.allocateBindingSlots(value);
|
18187 | propertyBindings.push({
|
18188 | name: prepareSyntheticPropertyName(input.name),
|
18189 | sourceSpan: input.sourceSpan,
|
18190 | value: () => hasValue ? this.convertPropertyBinding(value) : emptyValueBindInstruction
|
18191 | });
|
18192 | }
|
18193 | else {
|
18194 | // we must skip attributes with associated i18n context, since these attributes are handled
|
18195 | // separately and corresponding `i18nExp` and `i18nApply` instructions will be generated
|
18196 | if (input.i18n)
|
18197 | return;
|
18198 | const value = input.value.visit(this._valueConverter);
|
18199 | if (value !== undefined) {
|
18200 | const params = [];
|
18201 | const [attrNamespace, attrName] = splitNsName(input.name);
|
18202 | const isAttributeBinding = inputType === 1 /* Attribute */;
|
18203 | const sanitizationRef = resolveSanitizationFn(input.securityContext, isAttributeBinding);
|
18204 | if (sanitizationRef)
|
18205 | params.push(sanitizationRef);
|
18206 | if (attrNamespace) {
|
18207 | const namespaceLiteral = literal(attrNamespace);
|
18208 | if (sanitizationRef) {
|
18209 | params.push(namespaceLiteral);
|
18210 | }
|
18211 | else {
|
18212 | // If there wasn't a sanitization ref, we need to add
|
18213 | // an extra param so that we can pass in the namespace.
|
18214 | params.push(literal(null), namespaceLiteral);
|
18215 | }
|
18216 | }
|
18217 | this.allocateBindingSlots(value);
|
18218 | if (inputType === 0 /* Property */) {
|
18219 | if (value instanceof Interpolation) {
|
18220 | // prop="{{value}}" and friends
|
18221 | this.interpolatedUpdateInstruction(getPropertyInterpolationExpression(value), elementIndex, attrName, input, value, params);
|
18222 | }
|
18223 | else {
|
18224 | // [prop]="value"
|
18225 | // Collect all the properties so that we can chain into a single function at the end.
|
18226 | propertyBindings.push({
|
18227 | name: attrName,
|
18228 | sourceSpan: input.sourceSpan,
|
18229 | value: () => this.convertPropertyBinding(value),
|
18230 | params
|
18231 | });
|
18232 | }
|
18233 | }
|
18234 | else if (inputType === 1 /* Attribute */) {
|
18235 | if (value instanceof Interpolation && getInterpolationArgsLength(value) > 1) {
|
18236 | // attr.name="text{{value}}" and friends
|
18237 | this.interpolatedUpdateInstruction(getAttributeInterpolationExpression(value), elementIndex, attrName, input, value, params);
|
18238 | }
|
18239 | else {
|
18240 | const boundValue = value instanceof Interpolation ? value.expressions[0] : value;
|
18241 | // [attr.name]="value" or attr.name="{{value}}"
|
18242 | // Collect the attribute bindings so that they can be chained at the end.
|
18243 | attributeBindings.push({
|
18244 | name: attrName,
|
18245 | sourceSpan: input.sourceSpan,
|
18246 | value: () => this.convertPropertyBinding(boundValue),
|
18247 | params
|
18248 | });
|
18249 | }
|
18250 | }
|
18251 | else {
|
18252 | // class prop
|
18253 | this.updateInstructionWithAdvance(elementIndex, input.sourceSpan, Identifiers$1.classProp, () => {
|
18254 | return [
|
18255 | literal(elementIndex), literal(attrName), this.convertPropertyBinding(value),
|
18256 | ...params
|
18257 | ];
|
18258 | });
|
18259 | }
|
18260 | }
|
18261 | }
|
18262 | });
|
18263 | if (propertyBindings.length > 0) {
|
18264 | this.updateInstructionChainWithAdvance(elementIndex, Identifiers$1.property, propertyBindings);
|
18265 | }
|
18266 | if (attributeBindings.length > 0) {
|
18267 | this.updateInstructionChainWithAdvance(elementIndex, Identifiers$1.attribute, attributeBindings);
|
18268 | }
|
18269 | // Traverse element child nodes
|
18270 | visitAll(this, element.children);
|
18271 | if (!isI18nRootElement && this.i18n) {
|
18272 | this.i18n.appendElement(element.i18n, elementIndex, true);
|
18273 | }
|
18274 | if (!createSelfClosingInstruction) {
|
18275 | // Finish element construction mode.
|
18276 | const span = (_b = element.endSourceSpan) !== null && _b !== void 0 ? _b : element.sourceSpan;
|
18277 | if (isI18nRootElement) {
|
18278 | this.i18nEnd(span, createSelfClosingI18nInstruction);
|
18279 | }
|
18280 | if (isNonBindableMode) {
|
18281 | this.creationInstruction(span, Identifiers$1.enableBindings);
|
18282 | }
|
18283 | this.creationInstruction(span, isNgContainer$1 ? Identifiers$1.elementContainerEnd : Identifiers$1.elementEnd);
|
18284 | }
|
18285 | }
|
18286 | visitTemplate(template) {
|
18287 | var _a;
|
18288 | const NG_TEMPLATE_TAG_NAME = 'ng-template';
|
18289 | const templateIndex = this.allocateDataSlot();
|
18290 | if (this.i18n) {
|
18291 | this.i18n.appendTemplate(template.i18n, templateIndex);
|
18292 | }
|
18293 | const tagName = sanitizeIdentifier(template.tagName || '');
|
18294 | const contextName = `${this.contextName}${tagName ? '_' + tagName : ''}_${templateIndex}`;
|
18295 | const templateName = `${contextName}_Template`;
|
18296 | const parameters = [
|
18297 | literal(templateIndex),
|
18298 | variable(templateName),
|
18299 | // We don't care about the tag's namespace here, because we infer
|
18300 | // it based on the parent nodes inside the template instruction.
|
18301 | literal(template.tagName ? splitNsName(template.tagName)[1] : template.tagName),
|
18302 | ];
|
18303 | // find directives matching on a given <ng-template> node
|
18304 | this.matchDirectives(NG_TEMPLATE_TAG_NAME, template);
|
18305 | // prepare attributes parameter (including attributes used for directive matching)
|
18306 | const attrsExprs = this.getAttributeExpressions(NG_TEMPLATE_TAG_NAME, template.attributes, template.inputs, template.outputs, undefined /* styles */, template.templateAttrs);
|
18307 | parameters.push(this.addAttrsToConsts(attrsExprs));
|
18308 | // local refs (ex.: <ng-template #foo>)
|
18309 | if (template.references && template.references.length) {
|
18310 | const refs = this.prepareRefsArray(template.references);
|
18311 | parameters.push(this.addToConsts(refs));
|
18312 | parameters.push(importExpr(Identifiers$1.templateRefExtractor));
|
18313 | }
|
18314 | // Create the template function
|
18315 | const templateVisitor = new TemplateDefinitionBuilder(this.constantPool, this._bindingScope, this.level + 1, contextName, this.i18n, templateIndex, templateName, this.directiveMatcher, this.directives, this.pipeTypeByName, this.pipes, this._namespace, this.fileBasedI18nSuffix, this.i18nUseExternalIds, this._constants);
|
18316 | // Nested templates must not be visited until after their parent templates have completed
|
18317 | // processing, so they are queued here until after the initial pass. Otherwise, we wouldn't
|
18318 | // be able to support bindings in nested templates to local refs that occur after the
|
18319 | // template definition. e.g. <div *ngIf="showing">{{ foo }}</div> <div #foo></div>
|
18320 | this._nestedTemplateFns.push(() => {
|
18321 | const templateFunctionExpr = templateVisitor.buildTemplateFunction(template.children, template.variables, this._ngContentReservedSlots.length + this._ngContentSelectorsOffset, template.i18n);
|
18322 | this.constantPool.statements.push(templateFunctionExpr.toDeclStmt(templateName));
|
18323 | if (templateVisitor._ngContentReservedSlots.length) {
|
18324 | this._ngContentReservedSlots.push(...templateVisitor._ngContentReservedSlots);
|
18325 | }
|
18326 | });
|
18327 | // e.g. template(1, MyComp_Template_1)
|
18328 | this.creationInstruction(template.sourceSpan, Identifiers$1.templateCreate, () => {
|
18329 | parameters.splice(2, 0, literal(templateVisitor.getConstCount()), literal(templateVisitor.getVarCount()));
|
18330 | return trimTrailingNulls(parameters);
|
18331 | });
|
18332 | // handle property bindings e.g. ɵɵproperty('ngForOf', ctx.items), et al;
|
18333 | this.templatePropertyBindings(templateIndex, template.templateAttrs);
|
18334 | // Only add normal input/output binding instructions on explicit <ng-template> elements.
|
18335 | if (template.tagName === NG_TEMPLATE_TAG_NAME) {
|
18336 | const [i18nInputs, inputs] = partitionArray(template.inputs, hasI18nMeta);
|
18337 | // Add i18n attributes that may act as inputs to directives. If such attributes are present,
|
18338 | // generate `i18nAttributes` instruction. Note: we generate it only for explicit <ng-template>
|
18339 | // elements, in case of inline templates, corresponding instructions will be generated in the
|
18340 | // nested template function.
|
18341 | if (i18nInputs.length > 0) {
|
18342 | this.i18nAttributesInstruction(templateIndex, i18nInputs, (_a = template.startSourceSpan) !== null && _a !== void 0 ? _a : template.sourceSpan);
|
18343 | }
|
18344 | // Add the input bindings
|
18345 | if (inputs.length > 0) {
|
18346 | this.templatePropertyBindings(templateIndex, inputs);
|
18347 | }
|
18348 | // Generate listeners for directive output
|
18349 | if (template.outputs.length > 0) {
|
18350 | const listeners = template.outputs.map((outputAst) => ({
|
18351 | sourceSpan: outputAst.sourceSpan,
|
18352 | params: this.prepareListenerParameter('ng_template', outputAst, templateIndex)
|
18353 | }));
|
18354 | this.creationInstructionChain(Identifiers$1.listener, listeners);
|
18355 | }
|
18356 | }
|
18357 | }
|
18358 | visitBoundText(text) {
|
18359 | if (this.i18n) {
|
18360 | const value = text.value.visit(this._valueConverter);
|
18361 | this.allocateBindingSlots(value);
|
18362 | if (value instanceof Interpolation) {
|
18363 | this.i18n.appendBoundText(text.i18n);
|
18364 | this.i18nAppendBindings(value.expressions);
|
18365 | }
|
18366 | return;
|
18367 | }
|
18368 | const nodeIndex = this.allocateDataSlot();
|
18369 | this.creationInstruction(text.sourceSpan, Identifiers$1.text, [literal(nodeIndex)]);
|
18370 | const value = text.value.visit(this._valueConverter);
|
18371 | this.allocateBindingSlots(value);
|
18372 | if (value instanceof Interpolation) {
|
18373 | this.updateInstructionWithAdvance(nodeIndex, text.sourceSpan, getTextInterpolationExpression(value), () => this.getUpdateInstructionArguments(value));
|
18374 | }
|
18375 | else {
|
18376 | error('Text nodes should be interpolated and never bound directly.');
|
18377 | }
|
18378 | }
|
18379 | visitText(text) {
|
18380 | // when a text element is located within a translatable
|
18381 | // block, we exclude this text element from instructions set,
|
18382 | // since it will be captured in i18n content and processed at runtime
|
18383 | if (!this.i18n) {
|
18384 | this.creationInstruction(text.sourceSpan, Identifiers$1.text, [literal(this.allocateDataSlot()), literal(text.value)]);
|
18385 | }
|
18386 | }
|
18387 | visitIcu(icu) {
|
18388 | let initWasInvoked = false;
|
18389 | // if an ICU was created outside of i18n block, we still treat
|
18390 | // it as a translatable entity and invoke i18nStart and i18nEnd
|
18391 | // to generate i18n context and the necessary instructions
|
18392 | if (!this.i18n) {
|
18393 | initWasInvoked = true;
|
18394 | this.i18nStart(null, icu.i18n, true);
|
18395 | }
|
18396 | const i18n = this.i18n;
|
18397 | const vars = this.i18nBindProps(icu.vars);
|
18398 | const placeholders = this.i18nBindProps(icu.placeholders);
|
18399 | // output ICU directly and keep ICU reference in context
|
18400 | const message = icu.i18n;
|
18401 | // we always need post-processing function for ICUs, to make sure that:
|
18402 | // - all placeholders in a form of {PLACEHOLDER} are replaced with actual values (note:
|
18403 | // `goog.getMsg` does not process ICUs and uses the `{PLACEHOLDER}` format for placeholders
|
18404 | // inside ICUs)
|
18405 | // - all ICU vars (such as `VAR_SELECT` or `VAR_PLURAL`) are replaced with correct values
|
18406 | const transformFn = (raw) => {
|
18407 | const params = Object.assign(Object.assign({}, vars), placeholders);
|
18408 | const formatted = i18nFormatPlaceholderNames(params, /* useCamelCase */ false);
|
18409 | return instruction(null, Identifiers$1.i18nPostprocess, [raw, mapLiteral(formatted, true)]);
|
18410 | };
|
18411 | // in case the whole i18n message is a single ICU - we do not need to
|
18412 | // create a separate top-level translation, we can use the root ref instead
|
18413 | // and make this ICU a top-level translation
|
18414 | // note: ICU placeholders are replaced with actual values in `i18nPostprocess` function
|
18415 | // separately, so we do not pass placeholders into `i18nTranslate` function.
|
18416 | if (isSingleI18nIcu(i18n.meta)) {
|
18417 | this.i18nTranslate(message, /* placeholders */ {}, i18n.ref, transformFn);
|
18418 | }
|
18419 | else {
|
18420 | // output ICU directly and keep ICU reference in context
|
18421 | const ref = this.i18nTranslate(message, /* placeholders */ {}, /* ref */ undefined, transformFn);
|
18422 | i18n.appendIcu(icuFromI18nMessage(message).name, ref);
|
18423 | }
|
18424 | if (initWasInvoked) {
|
18425 | this.i18nEnd(null, true);
|
18426 | }
|
18427 | return null;
|
18428 | }
|
18429 | allocateDataSlot() {
|
18430 | return this._dataIndex++;
|
18431 | }
|
18432 | getConstCount() {
|
18433 | return this._dataIndex;
|
18434 | }
|
18435 | getVarCount() {
|
18436 | return this._pureFunctionSlots;
|
18437 | }
|
18438 | getConsts() {
|
18439 | return this._constants;
|
18440 | }
|
18441 | getNgContentSelectors() {
|
18442 | return this._ngContentReservedSlots.length ?
|
18443 | this.constantPool.getConstLiteral(asLiteral(this._ngContentReservedSlots), true) :
|
18444 | null;
|
18445 | }
|
18446 | bindingContext() {
|
18447 | return `${this._bindingContext++}`;
|
18448 | }
|
18449 | templatePropertyBindings(templateIndex, attrs) {
|
18450 | const propertyBindings = [];
|
18451 | attrs.forEach(input => {
|
18452 | if (input instanceof BoundAttribute) {
|
18453 | const value = input.value.visit(this._valueConverter);
|
18454 | if (value !== undefined) {
|
18455 | this.allocateBindingSlots(value);
|
18456 | if (value instanceof Interpolation) {
|
18457 | // Params typically contain attribute namespace and value sanitizer, which is applicable
|
18458 | // for regular HTML elements, but not applicable for <ng-template> (since props act as
|
18459 | // inputs to directives), so keep params array empty.
|
18460 | const params = [];
|
18461 | // prop="{{value}}" case
|
18462 | this.interpolatedUpdateInstruction(getPropertyInterpolationExpression(value), templateIndex, input.name, input, value, params);
|
18463 | }
|
18464 | else {
|
18465 | // [prop]="value" case
|
18466 | propertyBindings.push({
|
18467 | name: input.name,
|
18468 | sourceSpan: input.sourceSpan,
|
18469 | value: () => this.convertPropertyBinding(value)
|
18470 | });
|
18471 | }
|
18472 | }
|
18473 | }
|
18474 | });
|
18475 | if (propertyBindings.length > 0) {
|
18476 | this.updateInstructionChainWithAdvance(templateIndex, Identifiers$1.property, propertyBindings);
|
18477 | }
|
18478 | }
|
18479 | // Bindings must only be resolved after all local refs have been visited, so all
|
18480 | // instructions are queued in callbacks that execute once the initial pass has completed.
|
18481 | // Otherwise, we wouldn't be able to support local refs that are defined after their
|
18482 | // bindings. e.g. {{ foo }} <div #foo></div>
|
18483 | instructionFn(fns, span, reference, paramsOrFn, prepend = false) {
|
18484 | fns[prepend ? 'unshift' : 'push'](() => {
|
18485 | const params = Array.isArray(paramsOrFn) ? paramsOrFn : paramsOrFn();
|
18486 | return instruction(span, reference, params).toStmt();
|
18487 | });
|
18488 | }
|
18489 | processStylingUpdateInstruction(elementIndex, instruction) {
|
18490 | let allocateBindingSlots = 0;
|
18491 | if (instruction) {
|
18492 | const calls = [];
|
18493 | instruction.calls.forEach(call => {
|
18494 | allocateBindingSlots += call.allocateBindingSlots;
|
18495 | calls.push({
|
18496 | sourceSpan: call.sourceSpan,
|
18497 | value: () => {
|
18498 | return call.params(value => (call.supportsInterpolation && value instanceof Interpolation) ?
|
18499 | this.getUpdateInstructionArguments(value) :
|
18500 | this.convertPropertyBinding(value));
|
18501 | }
|
18502 | });
|
18503 | });
|
18504 | this.updateInstructionChainWithAdvance(elementIndex, instruction.reference, calls);
|
18505 | }
|
18506 | return allocateBindingSlots;
|
18507 | }
|
18508 | creationInstruction(span, reference, paramsOrFn, prepend) {
|
18509 | this.instructionFn(this._creationCodeFns, span, reference, paramsOrFn || [], prepend);
|
18510 | }
|
18511 | creationInstructionChain(reference, calls) {
|
18512 | const span = calls.length ? calls[0].sourceSpan : null;
|
18513 | this._creationCodeFns.push(() => {
|
18514 | return chainedInstruction(reference, calls.map(call => call.params()), span).toStmt();
|
18515 | });
|
18516 | }
|
18517 | updateInstructionWithAdvance(nodeIndex, span, reference, paramsOrFn) {
|
18518 | this.addAdvanceInstructionIfNecessary(nodeIndex, span);
|
18519 | this.updateInstruction(span, reference, paramsOrFn);
|
18520 | }
|
18521 | updateInstruction(span, reference, paramsOrFn) {
|
18522 | this.instructionFn(this._updateCodeFns, span, reference, paramsOrFn || []);
|
18523 | }
|
18524 | updateInstructionChain(reference, bindings) {
|
18525 | const span = bindings.length ? bindings[0].sourceSpan : null;
|
18526 | this._updateCodeFns.push(() => {
|
18527 | const calls = bindings.map(property => {
|
18528 | const value = property.value();
|
18529 | const fnParams = Array.isArray(value) ? value : [value];
|
18530 | if (property.params) {
|
18531 | fnParams.push(...property.params);
|
18532 | }
|
18533 | if (property.name) {
|
18534 | // We want the property name to always be the first function parameter.
|
18535 | fnParams.unshift(literal(property.name));
|
18536 | }
|
18537 | return fnParams;
|
18538 | });
|
18539 | return chainedInstruction(reference, calls, span).toStmt();
|
18540 | });
|
18541 | }
|
18542 | updateInstructionChainWithAdvance(nodeIndex, reference, bindings) {
|
18543 | this.addAdvanceInstructionIfNecessary(nodeIndex, bindings.length ? bindings[0].sourceSpan : null);
|
18544 | this.updateInstructionChain(reference, bindings);
|
18545 | }
|
18546 | addAdvanceInstructionIfNecessary(nodeIndex, span) {
|
18547 | if (nodeIndex !== this._currentIndex) {
|
18548 | const delta = nodeIndex - this._currentIndex;
|
18549 | if (delta < 1) {
|
18550 | throw new Error('advance instruction can only go forwards');
|
18551 | }
|
18552 | this.instructionFn(this._updateCodeFns, span, Identifiers$1.advance, [literal(delta)]);
|
18553 | this._currentIndex = nodeIndex;
|
18554 | }
|
18555 | }
|
18556 | allocatePureFunctionSlots(numSlots) {
|
18557 | const originalSlots = this._pureFunctionSlots;
|
18558 | this._pureFunctionSlots += numSlots;
|
18559 | return originalSlots;
|
18560 | }
|
18561 | allocateBindingSlots(value) {
|
18562 | this._bindingSlots += value instanceof Interpolation ? value.expressions.length : 1;
|
18563 | }
|
18564 | /**
|
18565 | * Gets an expression that refers to the implicit receiver. The implicit
|
18566 | * receiver is always the root level context.
|
18567 | */
|
18568 | getImplicitReceiverExpr() {
|
18569 | if (this._implicitReceiverExpr) {
|
18570 | return this._implicitReceiverExpr;
|
18571 | }
|
18572 | return this._implicitReceiverExpr = this.level === 0 ?
|
18573 | variable(CONTEXT_NAME) :
|
18574 | this._bindingScope.getOrCreateSharedContextVar(0);
|
18575 | }
|
18576 | convertPropertyBinding(value) {
|
18577 | const convertedPropertyBinding = convertPropertyBinding(this, this.getImplicitReceiverExpr(), value, this.bindingContext(), BindingForm.Expression, () => error('Unexpected interpolation'));
|
18578 | const valExpr = convertedPropertyBinding.currValExpr;
|
18579 | this._tempVariables.push(...convertedPropertyBinding.stmts);
|
18580 | return valExpr;
|
18581 | }
|
18582 | /**
|
18583 | * Gets a list of argument expressions to pass to an update instruction expression. Also updates
|
18584 | * the temp variables state with temp variables that were identified as needing to be created
|
18585 | * while visiting the arguments.
|
18586 | * @param value The original expression we will be resolving an arguments list from.
|
18587 | */
|
18588 | getUpdateInstructionArguments(value) {
|
18589 | const { args, stmts } = convertUpdateArguments(this, this.getImplicitReceiverExpr(), value, this.bindingContext());
|
18590 | this._tempVariables.push(...stmts);
|
18591 | return args;
|
18592 | }
|
18593 | matchDirectives(elementName, elOrTpl) {
|
18594 | if (this.directiveMatcher) {
|
18595 | const selector = createCssSelector(elementName, getAttrsForDirectiveMatching(elOrTpl));
|
18596 | this.directiveMatcher.match(selector, (cssSelector, staticType) => {
|
18597 | this.directives.add(staticType);
|
18598 | });
|
18599 | }
|
18600 | }
|
18601 | /**
|
18602 | * Prepares all attribute expression values for the `TAttributes` array.
|
18603 | *
|
18604 | * The purpose of this function is to properly construct an attributes array that
|
18605 | * is passed into the `elementStart` (or just `element`) functions. Because there
|
18606 | * are many different types of attributes, the array needs to be constructed in a
|
18607 | * special way so that `elementStart` can properly evaluate them.
|
18608 | *
|
18609 | * The format looks like this:
|
18610 | *
|
18611 | * ```
|
18612 | * attrs = [prop, value, prop2, value2,
|
18613 | * PROJECT_AS, selector,
|
18614 | * CLASSES, class1, class2,
|
18615 | * STYLES, style1, value1, style2, value2,
|
18616 | * BINDINGS, name1, name2, name3,
|
18617 | * TEMPLATE, name4, name5, name6,
|
18618 | * I18N, name7, name8, ...]
|
18619 | * ```
|
18620 | *
|
18621 | * Note that this function will fully ignore all synthetic (@foo) attribute values
|
18622 | * because those values are intended to always be generated as property instructions.
|
18623 | */
|
18624 | getAttributeExpressions(elementName, renderAttributes, inputs, outputs, styles, templateAttrs = [], boundI18nAttrs = []) {
|
18625 | const alreadySeen = new Set();
|
18626 | const attrExprs = [];
|
18627 | let ngProjectAsAttr;
|
18628 | for (const attr of renderAttributes) {
|
18629 | if (attr.name === NG_PROJECT_AS_ATTR_NAME) {
|
18630 | ngProjectAsAttr = attr;
|
18631 | }
|
18632 | // Note that static i18n attributes aren't in the i18n array,
|
18633 | // because they're treated in the same way as regular attributes.
|
18634 | if (attr.i18n) {
|
18635 | // When i18n attributes are present on elements with structural directives
|
18636 | // (e.g. `<div *ngIf title="Hello" i18n-title>`), we want to avoid generating
|
18637 | // duplicate i18n translation blocks for `ɵɵtemplate` and `ɵɵelement` instruction
|
18638 | // attributes. So we do a cache lookup to see if suitable i18n translation block
|
18639 | // already exists.
|
18640 | const { i18nVarRefsCache } = this._constants;
|
18641 | let i18nVarRef;
|
18642 | if (i18nVarRefsCache.has(attr.i18n)) {
|
18643 | i18nVarRef = i18nVarRefsCache.get(attr.i18n);
|
18644 | }
|
18645 | else {
|
18646 | i18nVarRef = this.i18nTranslate(attr.i18n);
|
18647 | i18nVarRefsCache.set(attr.i18n, i18nVarRef);
|
18648 | }
|
18649 | attrExprs.push(literal(attr.name), i18nVarRef);
|
18650 | }
|
18651 | else {
|
18652 | attrExprs.push(...getAttributeNameLiterals(attr.name), trustedConstAttribute(elementName, attr));
|
18653 | }
|
18654 | }
|
18655 | // Keep ngProjectAs next to the other name, value pairs so we can verify that we match
|
18656 | // ngProjectAs marker in the attribute name slot.
|
18657 | if (ngProjectAsAttr) {
|
18658 | attrExprs.push(...getNgProjectAsLiteral(ngProjectAsAttr));
|
18659 | }
|
18660 | function addAttrExpr(key, value) {
|
18661 | if (typeof key === 'string') {
|
18662 | if (!alreadySeen.has(key)) {
|
18663 | attrExprs.push(...getAttributeNameLiterals(key));
|
18664 | value !== undefined && attrExprs.push(value);
|
18665 | alreadySeen.add(key);
|
18666 | }
|
18667 | }
|
18668 | else {
|
18669 | attrExprs.push(literal(key));
|
18670 | }
|
18671 | }
|
18672 | // it's important that this occurs before BINDINGS and TEMPLATE because once `elementStart`
|
18673 | // comes across the BINDINGS or TEMPLATE markers then it will continue reading each value as
|
18674 | // as single property value cell by cell.
|
18675 | if (styles) {
|
18676 | styles.populateInitialStylingAttrs(attrExprs);
|
18677 | }
|
18678 | if (inputs.length || outputs.length) {
|
18679 | const attrsLengthBeforeInputs = attrExprs.length;
|
18680 | for (let i = 0; i < inputs.length; i++) {
|
18681 | const input = inputs[i];
|
18682 | // We don't want the animation and attribute bindings in the
|
18683 | // attributes array since they aren't used for directive matching.
|
18684 | if (input.type !== 4 /* Animation */ && input.type !== 1 /* Attribute */) {
|
18685 | addAttrExpr(input.name);
|
18686 | }
|
18687 | }
|
18688 | for (let i = 0; i < outputs.length; i++) {
|
18689 | const output = outputs[i];
|
18690 | if (output.type !== 1 /* Animation */) {
|
18691 | addAttrExpr(output.name);
|
18692 | }
|
18693 | }
|
18694 | // this is a cheap way of adding the marker only after all the input/output
|
18695 | // values have been filtered (by not including the animation ones) and added
|
18696 | // to the expressions. The marker is important because it tells the runtime
|
18697 | // code that this is where attributes without values start...
|
18698 | if (attrExprs.length !== attrsLengthBeforeInputs) {
|
18699 | attrExprs.splice(attrsLengthBeforeInputs, 0, literal(3 /* Bindings */));
|
18700 | }
|
18701 | }
|
18702 | if (templateAttrs.length) {
|
18703 | attrExprs.push(literal(4 /* Template */));
|
18704 | templateAttrs.forEach(attr => addAttrExpr(attr.name));
|
18705 | }
|
18706 | if (boundI18nAttrs.length) {
|
18707 | attrExprs.push(literal(6 /* I18n */));
|
18708 | boundI18nAttrs.forEach(attr => addAttrExpr(attr.name));
|
18709 | }
|
18710 | return attrExprs;
|
18711 | }
|
18712 | addToConsts(expression) {
|
18713 | if (isNull(expression)) {
|
18714 | return TYPED_NULL_EXPR;
|
18715 | }
|
18716 | const consts = this._constants.constExpressions;
|
18717 | // Try to reuse a literal that's already in the array, if possible.
|
18718 | for (let i = 0; i < consts.length; i++) {
|
18719 | if (consts[i].isEquivalent(expression)) {
|
18720 | return literal(i);
|
18721 | }
|
18722 | }
|
18723 | return literal(consts.push(expression) - 1);
|
18724 | }
|
18725 | addAttrsToConsts(attrs) {
|
18726 | return attrs.length > 0 ? this.addToConsts(literalArr(attrs)) : TYPED_NULL_EXPR;
|
18727 | }
|
18728 | prepareRefsArray(references) {
|
18729 | if (!references || references.length === 0) {
|
18730 | return TYPED_NULL_EXPR;
|
18731 | }
|
18732 | const refsParam = flatten(references.map(reference => {
|
18733 | const slot = this.allocateDataSlot();
|
18734 | // Generate the update temporary.
|
18735 | const variableName = this._bindingScope.freshReferenceName();
|
18736 | const retrievalLevel = this.level;
|
18737 | const lhs = variable(variableName);
|
18738 | this._bindingScope.set(retrievalLevel, reference.name, lhs, 0 /* DEFAULT */, (scope, relativeLevel) => {
|
18739 | // e.g. nextContext(2);
|
18740 | const nextContextStmt = relativeLevel > 0 ? [generateNextContextExpr(relativeLevel).toStmt()] : [];
|
18741 | // e.g. const $foo$ = reference(1);
|
18742 | const refExpr = lhs.set(importExpr(Identifiers$1.reference).callFn([literal(slot)]));
|
18743 | return nextContextStmt.concat(refExpr.toConstDecl());
|
18744 | }, true);
|
18745 | return [reference.name, reference.value];
|
18746 | }));
|
18747 | return asLiteral(refsParam);
|
18748 | }
|
18749 | prepareListenerParameter(tagName, outputAst, index) {
|
18750 | return () => {
|
18751 | const eventName = outputAst.name;
|
18752 | const bindingFnName = outputAst.type === 1 /* Animation */ ?
|
18753 | // synthetic @listener.foo values are treated the exact same as are standard listeners
|
18754 | prepareSyntheticListenerFunctionName(eventName, outputAst.phase) :
|
18755 | sanitizeIdentifier(eventName);
|
18756 | const handlerName = `${this.templateName}_${tagName}_${bindingFnName}_${index}_listener`;
|
18757 | const scope = this._bindingScope.nestedScope(this._bindingScope.bindingLevel, EVENT_BINDING_SCOPE_GLOBALS);
|
18758 | return prepareEventListenerParameters(outputAst, handlerName, scope);
|
18759 | };
|
18760 | }
|
18761 | }
|
18762 | class ValueConverter extends AstMemoryEfficientTransformer {
|
18763 | constructor(constantPool, allocateSlot, allocatePureFunctionSlots, definePipe) {
|
18764 | super();
|
18765 | this.constantPool = constantPool;
|
18766 | this.allocateSlot = allocateSlot;
|
18767 | this.allocatePureFunctionSlots = allocatePureFunctionSlots;
|
18768 | this.definePipe = definePipe;
|
18769 | this._pipeBindExprs = [];
|
18770 | }
|
18771 | // AstMemoryEfficientTransformer
|
18772 | visitPipe(pipe, context) {
|
18773 | // Allocate a slot to create the pipe
|
18774 | const slot = this.allocateSlot();
|
18775 | const slotPseudoLocal = `PIPE:${slot}`;
|
18776 | // Allocate one slot for the result plus one slot per pipe argument
|
18777 | const pureFunctionSlot = this.allocatePureFunctionSlots(2 + pipe.args.length);
|
18778 | const target = new PropertyRead(pipe.span, pipe.sourceSpan, pipe.nameSpan, new ImplicitReceiver(pipe.span, pipe.sourceSpan), slotPseudoLocal);
|
18779 | const { identifier, isVarLength } = pipeBindingCallInfo(pipe.args);
|
18780 | this.definePipe(pipe.name, slotPseudoLocal, slot, importExpr(identifier));
|
18781 | const args = [pipe.exp, ...pipe.args];
|
18782 | const convertedArgs = isVarLength ?
|
18783 | this.visitAll([new LiteralArray(pipe.span, pipe.sourceSpan, args)]) :
|
18784 | this.visitAll(args);
|
18785 | const pipeBindExpr = new FunctionCall(pipe.span, pipe.sourceSpan, target, [
|
18786 | new LiteralPrimitive(pipe.span, pipe.sourceSpan, slot),
|
18787 | new LiteralPrimitive(pipe.span, pipe.sourceSpan, pureFunctionSlot),
|
18788 | ...convertedArgs,
|
18789 | ]);
|
18790 | this._pipeBindExprs.push(pipeBindExpr);
|
18791 | return pipeBindExpr;
|
18792 | }
|
18793 | updatePipeSlotOffsets(bindingSlots) {
|
18794 | this._pipeBindExprs.forEach((pipe) => {
|
18795 | // update the slot offset arg (index 1) to account for binding slots
|
18796 | const slotOffset = pipe.args[1];
|
18797 | slotOffset.value += bindingSlots;
|
18798 | });
|
18799 | }
|
18800 | visitLiteralArray(array, context) {
|
18801 | return new BuiltinFunctionCall(array.span, array.sourceSpan, this.visitAll(array.expressions), values => {
|
18802 | // If the literal has calculated (non-literal) elements transform it into
|
18803 | // calls to literal factories that compose the literal and will cache intermediate
|
18804 | // values.
|
18805 | const literal = literalArr(values);
|
18806 | return getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots);
|
18807 | });
|
18808 | }
|
18809 | visitLiteralMap(map, context) {
|
18810 | return new BuiltinFunctionCall(map.span, map.sourceSpan, this.visitAll(map.values), values => {
|
18811 | // If the literal has calculated (non-literal) elements transform it into
|
18812 | // calls to literal factories that compose the literal and will cache intermediate
|
18813 | // values.
|
18814 | const literal = literalMap(values.map((value, index) => ({ key: map.keys[index].key, value, quoted: map.keys[index].quoted })));
|
18815 | return getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots);
|
18816 | });
|
18817 | }
|
18818 | }
|
18819 | // Pipes always have at least one parameter, the value they operate on
|
18820 | const pipeBindingIdentifiers = [Identifiers$1.pipeBind1, Identifiers$1.pipeBind2, Identifiers$1.pipeBind3, Identifiers$1.pipeBind4];
|
18821 | function pipeBindingCallInfo(args) {
|
18822 | const identifier = pipeBindingIdentifiers[args.length];
|
18823 | return {
|
18824 | identifier: identifier || Identifiers$1.pipeBindV,
|
18825 | isVarLength: !identifier,
|
18826 | };
|
18827 | }
|
18828 | const pureFunctionIdentifiers = [
|
18829 | Identifiers$1.pureFunction0, Identifiers$1.pureFunction1, Identifiers$1.pureFunction2, Identifiers$1.pureFunction3, Identifiers$1.pureFunction4,
|
18830 | Identifiers$1.pureFunction5, Identifiers$1.pureFunction6, Identifiers$1.pureFunction7, Identifiers$1.pureFunction8
|
18831 | ];
|
18832 | function pureFunctionCallInfo(args) {
|
18833 | const identifier = pureFunctionIdentifiers[args.length];
|
18834 | return {
|
18835 | identifier: identifier || Identifiers$1.pureFunctionV,
|
18836 | isVarLength: !identifier,
|
18837 | };
|
18838 | }
|
18839 | function instruction(span, reference, params) {
|
18840 | return importExpr(reference, null, span).callFn(params, span);
|
18841 | }
|
18842 | // e.g. x(2);
|
18843 | function generateNextContextExpr(relativeLevelDiff) {
|
18844 | return importExpr(Identifiers$1.nextContext)
|
18845 | .callFn(relativeLevelDiff > 1 ? [literal(relativeLevelDiff)] : []);
|
18846 | }
|
18847 | function getLiteralFactory(constantPool, literal$1, allocateSlots) {
|
18848 | const { literalFactory, literalFactoryArguments } = constantPool.getLiteralFactory(literal$1);
|
18849 | // Allocate 1 slot for the result plus 1 per argument
|
18850 | const startSlot = allocateSlots(1 + literalFactoryArguments.length);
|
18851 | const { identifier, isVarLength } = pureFunctionCallInfo(literalFactoryArguments);
|
18852 | // Literal factories are pure functions that only need to be re-invoked when the parameters
|
18853 | // change.
|
18854 | const args = [literal(startSlot), literalFactory];
|
18855 | if (isVarLength) {
|
18856 | args.push(literalArr(literalFactoryArguments));
|
18857 | }
|
18858 | else {
|
18859 | args.push(...literalFactoryArguments);
|
18860 | }
|
18861 | return importExpr(identifier).callFn(args);
|
18862 | }
|
18863 | /**
|
18864 | * Gets an array of literals that can be added to an expression
|
18865 | * to represent the name and namespace of an attribute. E.g.
|
18866 | * `:xlink:href` turns into `[AttributeMarker.NamespaceURI, 'xlink', 'href']`.
|
18867 | *
|
18868 | * @param name Name of the attribute, including the namespace.
|
18869 | */
|
18870 | function getAttributeNameLiterals(name) {
|
18871 | const [attributeNamespace, attributeName] = splitNsName(name);
|
18872 | const nameLiteral = literal(attributeName);
|
18873 | if (attributeNamespace) {
|
18874 | return [
|
18875 | literal(0 /* NamespaceURI */), literal(attributeNamespace), nameLiteral
|
18876 | ];
|
18877 | }
|
18878 | return [nameLiteral];
|
18879 | }
|
18880 | /** The prefix used to get a shared context in BindingScope's map. */
|
18881 | const SHARED_CONTEXT_KEY = '$$shared_ctx$$';
|
18882 | class BindingScope {
|
18883 | constructor(bindingLevel = 0, parent = null, globals) {
|
18884 | this.bindingLevel = bindingLevel;
|
18885 | this.parent = parent;
|
18886 | this.globals = globals;
|
18887 | /** Keeps a map from local variables to their BindingData. */
|
18888 | this.map = new Map();
|
18889 | this.referenceNameIndex = 0;
|
18890 | this.restoreViewVariable = null;
|
18891 | if (globals !== undefined) {
|
18892 | for (const name of globals) {
|
18893 | this.set(0, name, variable(name));
|
18894 | }
|
18895 | }
|
18896 | }
|
18897 | static createRootScope() {
|
18898 | return new BindingScope();
|
18899 | }
|
18900 | get(name) {
|
18901 | let current = this;
|
18902 | while (current) {
|
18903 | let value = current.map.get(name);
|
18904 | if (value != null) {
|
18905 | if (current !== this) {
|
18906 | // make a local copy and reset the `declare` state
|
18907 | value = {
|
18908 | retrievalLevel: value.retrievalLevel,
|
18909 | lhs: value.lhs,
|
18910 | declareLocalCallback: value.declareLocalCallback,
|
18911 | declare: false,
|
18912 | priority: value.priority,
|
18913 | localRef: value.localRef
|
18914 | };
|
18915 | // Cache the value locally.
|
18916 | this.map.set(name, value);
|
18917 | // Possibly generate a shared context var
|
18918 | this.maybeGenerateSharedContextVar(value);
|
18919 | this.maybeRestoreView(value.retrievalLevel, value.localRef);
|
18920 | }
|
18921 | if (value.declareLocalCallback && !value.declare) {
|
18922 | value.declare = true;
|
18923 | }
|
18924 | return value.lhs;
|
18925 | }
|
18926 | current = current.parent;
|
18927 | }
|
18928 | // If we get to this point, we are looking for a property on the top level component
|
18929 | // - If level === 0, we are on the top and don't need to re-declare `ctx`.
|
18930 | // - If level > 0, we are in an embedded view. We need to retrieve the name of the
|
18931 | // local var we used to store the component context, e.g. const $comp$ = x();
|
18932 | return this.bindingLevel === 0 ? null : this.getComponentProperty(name);
|
18933 | }
|
18934 | /**
|
18935 | * Create a local variable for later reference.
|
18936 | *
|
18937 | * @param retrievalLevel The level from which this value can be retrieved
|
18938 | * @param name Name of the variable.
|
18939 | * @param lhs AST representing the left hand side of the `let lhs = rhs;`.
|
18940 | * @param priority The sorting priority of this var
|
18941 | * @param declareLocalCallback The callback to invoke when declaring this local var
|
18942 | * @param localRef Whether or not this is a local ref
|
18943 | */
|
18944 | set(retrievalLevel, name, lhs, priority = 0 /* DEFAULT */, declareLocalCallback, localRef) {
|
18945 | if (this.map.has(name)) {
|
18946 | if (localRef) {
|
18947 | // Do not throw an error if it's a local ref and do not update existing value,
|
18948 | // so the first defined ref is always returned.
|
18949 | return this;
|
18950 | }
|
18951 | error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`);
|
18952 | }
|
18953 | this.map.set(name, {
|
18954 | retrievalLevel: retrievalLevel,
|
18955 | lhs: lhs,
|
18956 | declare: false,
|
18957 | declareLocalCallback: declareLocalCallback,
|
18958 | priority: priority,
|
18959 | localRef: localRef || false
|
18960 | });
|
18961 | return this;
|
18962 | }
|
18963 | // Implemented as part of LocalResolver.
|
18964 | getLocal(name) {
|
18965 | return this.get(name);
|
18966 | }
|
18967 | // Implemented as part of LocalResolver.
|
18968 | notifyImplicitReceiverUse() {
|
18969 | if (this.bindingLevel !== 0) {
|
18970 | // Since the implicit receiver is accessed in an embedded view, we need to
|
18971 | // ensure that we declare a shared context variable for the current template
|
18972 | // in the update variables.
|
18973 | this.map.get(SHARED_CONTEXT_KEY + 0).declare = true;
|
18974 | }
|
18975 | }
|
18976 | nestedScope(level, globals) {
|
18977 | const newScope = new BindingScope(level, this, globals);
|
18978 | if (level > 0)
|
18979 | newScope.generateSharedContextVar(0);
|
18980 | return newScope;
|
18981 | }
|
18982 | /**
|
18983 | * Gets or creates a shared context variable and returns its expression. Note that
|
18984 | * this does not mean that the shared variable will be declared. Variables in the
|
18985 | * binding scope will be only declared if they are used.
|
18986 | */
|
18987 | getOrCreateSharedContextVar(retrievalLevel) {
|
18988 | const bindingKey = SHARED_CONTEXT_KEY + retrievalLevel;
|
18989 | if (!this.map.has(bindingKey)) {
|
18990 | this.generateSharedContextVar(retrievalLevel);
|
18991 | }
|
18992 | // Shared context variables are always generated as "ReadVarExpr".
|
18993 | return this.map.get(bindingKey).lhs;
|
18994 | }
|
18995 | getSharedContextName(retrievalLevel) {
|
18996 | const sharedCtxObj = this.map.get(SHARED_CONTEXT_KEY + retrievalLevel);
|
18997 | // Shared context variables are always generated as "ReadVarExpr".
|
18998 | return sharedCtxObj && sharedCtxObj.declare ? sharedCtxObj.lhs : null;
|
18999 | }
|
19000 | maybeGenerateSharedContextVar(value) {
|
19001 | if (value.priority === 1 /* CONTEXT */ &&
|
19002 | value.retrievalLevel < this.bindingLevel) {
|
19003 | const sharedCtxObj = this.map.get(SHARED_CONTEXT_KEY + value.retrievalLevel);
|
19004 | if (sharedCtxObj) {
|
19005 | sharedCtxObj.declare = true;
|
19006 | }
|
19007 | else {
|
19008 | this.generateSharedContextVar(value.retrievalLevel);
|
19009 | }
|
19010 | }
|
19011 | }
|
19012 | generateSharedContextVar(retrievalLevel) {
|
19013 | const lhs = variable(CONTEXT_NAME + this.freshReferenceName());
|
19014 | this.map.set(SHARED_CONTEXT_KEY + retrievalLevel, {
|
19015 | retrievalLevel: retrievalLevel,
|
19016 | lhs: lhs,
|
19017 | declareLocalCallback: (scope, relativeLevel) => {
|
19018 | // const ctx_r0 = nextContext(2);
|
19019 | return [lhs.set(generateNextContextExpr(relativeLevel)).toConstDecl()];
|
19020 | },
|
19021 | declare: false,
|
19022 | priority: 2 /* SHARED_CONTEXT */,
|
19023 | localRef: false
|
19024 | });
|
19025 | }
|
19026 | getComponentProperty(name) {
|
19027 | const componentValue = this.map.get(SHARED_CONTEXT_KEY + 0);
|
19028 | componentValue.declare = true;
|
19029 | this.maybeRestoreView(0, false);
|
19030 | return componentValue.lhs.prop(name);
|
19031 | }
|
19032 | maybeRestoreView(retrievalLevel, localRefLookup) {
|
19033 | // We want to restore the current view in listener fns if:
|
19034 | // 1 - we are accessing a value in a parent view, which requires walking the view tree rather
|
19035 | // than using the ctx arg. In this case, the retrieval and binding level will be different.
|
19036 | // 2 - we are looking up a local ref, which requires restoring the view where the local
|
19037 | // ref is stored
|
19038 | if (this.isListenerScope() && (retrievalLevel < this.bindingLevel || localRefLookup)) {
|
19039 | if (!this.parent.restoreViewVariable) {
|
19040 | // parent saves variable to generate a shared `const $s$ = getCurrentView();` instruction
|
19041 | this.parent.restoreViewVariable = variable(this.parent.freshReferenceName());
|
19042 | }
|
19043 | this.restoreViewVariable = this.parent.restoreViewVariable;
|
19044 | }
|
19045 | }
|
19046 | restoreViewStatement() {
|
19047 | // restoreView($state$);
|
19048 | return this.restoreViewVariable ?
|
19049 | [instruction(null, Identifiers$1.restoreView, [this.restoreViewVariable]).toStmt()] :
|
19050 | [];
|
19051 | }
|
19052 | viewSnapshotStatements() {
|
19053 | // const $state$ = getCurrentView();
|
19054 | const getCurrentViewInstruction = instruction(null, Identifiers$1.getCurrentView, []);
|
19055 | return this.restoreViewVariable ?
|
19056 | [this.restoreViewVariable.set(getCurrentViewInstruction).toConstDecl()] :
|
19057 | [];
|
19058 | }
|
19059 | isListenerScope() {
|
19060 | return this.parent && this.parent.bindingLevel === this.bindingLevel;
|
19061 | }
|
19062 | variableDeclarations() {
|
19063 | let currentContextLevel = 0;
|
19064 | return Array.from(this.map.values())
|
19065 | .filter(value => value.declare)
|
19066 | .sort((a, b) => b.retrievalLevel - a.retrievalLevel || b.priority - a.priority)
|
19067 | .reduce((stmts, value) => {
|
19068 | const levelDiff = this.bindingLevel - value.retrievalLevel;
|
19069 | const currStmts = value.declareLocalCallback(this, levelDiff - currentContextLevel);
|
19070 | currentContextLevel = levelDiff;
|
19071 | return stmts.concat(currStmts);
|
19072 | }, []);
|
19073 | }
|
19074 | freshReferenceName() {
|
19075 | let current = this;
|
19076 | // Find the top scope as it maintains the global reference count
|
19077 | while (current.parent)
|
19078 | current = current.parent;
|
19079 | const ref = `${REFERENCE_PREFIX}${current.referenceNameIndex++}`;
|
19080 | return ref;
|
19081 | }
|
19082 | }
|
19083 | /**
|
19084 | * Creates a `CssSelector` given a tag name and a map of attributes
|
19085 | */
|
19086 | function createCssSelector(elementName, attributes) {
|
19087 | const cssSelector = new CssSelector();
|
19088 | const elementNameNoNs = splitNsName(elementName)[1];
|
19089 | cssSelector.setElement(elementNameNoNs);
|
19090 | Object.getOwnPropertyNames(attributes).forEach((name) => {
|
19091 | const nameNoNs = splitNsName(name)[1];
|
19092 | const value = attributes[name];
|
19093 | cssSelector.addAttribute(nameNoNs, value);
|
19094 | if (name.toLowerCase() === 'class') {
|
19095 | const classes = value.trim().split(/\s+/);
|
19096 | classes.forEach(className => cssSelector.addClassName(className));
|
19097 | }
|
19098 | });
|
19099 | return cssSelector;
|
19100 | }
|
19101 | /**
|
19102 | * Creates an array of expressions out of an `ngProjectAs` attributes
|
19103 | * which can be added to the instruction parameters.
|
19104 | */
|
19105 | function getNgProjectAsLiteral(attribute) {
|
19106 | // Parse the attribute value into a CssSelectorList. Note that we only take the
|
19107 | // first selector, because we don't support multiple selectors in ngProjectAs.
|
19108 | const parsedR3Selector = parseSelectorToR3Selector(attribute.value)[0];
|
19109 | return [literal(5 /* ProjectAs */), asLiteral(parsedR3Selector)];
|
19110 | }
|
19111 | /**
|
19112 | * Gets the instruction to generate for an interpolated property
|
19113 | * @param interpolation An Interpolation AST
|
19114 | */
|
19115 | function getPropertyInterpolationExpression(interpolation) {
|
19116 | switch (getInterpolationArgsLength(interpolation)) {
|
19117 | case 1:
|
19118 | return Identifiers$1.propertyInterpolate;
|
19119 | case 3:
|
19120 | return Identifiers$1.propertyInterpolate1;
|
19121 | case 5:
|
19122 | return Identifiers$1.propertyInterpolate2;
|
19123 | case 7:
|
19124 | return Identifiers$1.propertyInterpolate3;
|
19125 | case 9:
|
19126 | return Identifiers$1.propertyInterpolate4;
|
19127 | case 11:
|
19128 | return Identifiers$1.propertyInterpolate5;
|
19129 | case 13:
|
19130 | return Identifiers$1.propertyInterpolate6;
|
19131 | case 15:
|
19132 | return Identifiers$1.propertyInterpolate7;
|
19133 | case 17:
|
19134 | return Identifiers$1.propertyInterpolate8;
|
19135 | default:
|
19136 | return Identifiers$1.propertyInterpolateV;
|
19137 | }
|
19138 | }
|
19139 | /**
|
19140 | * Gets the instruction to generate for an interpolated attribute
|
19141 | * @param interpolation An Interpolation AST
|
19142 | */
|
19143 | function getAttributeInterpolationExpression(interpolation) {
|
19144 | switch (getInterpolationArgsLength(interpolation)) {
|
19145 | case 3:
|
19146 | return Identifiers$1.attributeInterpolate1;
|
19147 | case 5:
|
19148 | return Identifiers$1.attributeInterpolate2;
|
19149 | case 7:
|
19150 | return Identifiers$1.attributeInterpolate3;
|
19151 | case 9:
|
19152 | return Identifiers$1.attributeInterpolate4;
|
19153 | case 11:
|
19154 | return Identifiers$1.attributeInterpolate5;
|
19155 | case 13:
|
19156 | return Identifiers$1.attributeInterpolate6;
|
19157 | case 15:
|
19158 | return Identifiers$1.attributeInterpolate7;
|
19159 | case 17:
|
19160 | return Identifiers$1.attributeInterpolate8;
|
19161 | default:
|
19162 | return Identifiers$1.attributeInterpolateV;
|
19163 | }
|
19164 | }
|
19165 | /**
|
19166 | * Gets the instruction to generate for interpolated text.
|
19167 | * @param interpolation An Interpolation AST
|
19168 | */
|
19169 | function getTextInterpolationExpression(interpolation) {
|
19170 | switch (getInterpolationArgsLength(interpolation)) {
|
19171 | case 1:
|
19172 | return Identifiers$1.textInterpolate;
|
19173 | case 3:
|
19174 | return Identifiers$1.textInterpolate1;
|
19175 | case 5:
|
19176 | return Identifiers$1.textInterpolate2;
|
19177 | case 7:
|
19178 | return Identifiers$1.textInterpolate3;
|
19179 | case 9:
|
19180 | return Identifiers$1.textInterpolate4;
|
19181 | case 11:
|
19182 | return Identifiers$1.textInterpolate5;
|
19183 | case 13:
|
19184 | return Identifiers$1.textInterpolate6;
|
19185 | case 15:
|
19186 | return Identifiers$1.textInterpolate7;
|
19187 | case 17:
|
19188 | return Identifiers$1.textInterpolate8;
|
19189 | default:
|
19190 | return Identifiers$1.textInterpolateV;
|
19191 | }
|
19192 | }
|
19193 | /**
|
19194 | * Parse a template into render3 `Node`s and additional metadata, with no other dependencies.
|
19195 | *
|
19196 | * @param template text of the template to parse
|
19197 | * @param templateUrl URL to use for source mapping of the parsed template
|
19198 | * @param options options to modify how the template is parsed
|
19199 | */
|
19200 | function parseTemplate(template, templateUrl, options = {}) {
|
19201 | var _a;
|
19202 | const { interpolationConfig, preserveWhitespaces, enableI18nLegacyMessageIdFormat } = options;
|
19203 | const isInline = (_a = options.isInline) !== null && _a !== void 0 ? _a : false;
|
19204 | const bindingParser = makeBindingParser(interpolationConfig);
|
19205 | const htmlParser = new HtmlParser();
|
19206 | const parseResult = htmlParser.parse(template, templateUrl, Object.assign(Object.assign({ leadingTriviaChars: LEADING_TRIVIA_CHARS }, options), { tokenizeExpansionForms: true }));
|
19207 | if (!options.alwaysAttemptHtmlToR3AstConversion && parseResult.errors &&
|
19208 | parseResult.errors.length > 0) {
|
19209 | return {
|
19210 | interpolationConfig,
|
19211 | preserveWhitespaces,
|
19212 | template,
|
19213 | templateUrl,
|
19214 | isInline,
|
19215 | errors: parseResult.errors,
|
19216 | nodes: [],
|
19217 | styleUrls: [],
|
19218 | styles: [],
|
19219 | ngContentSelectors: []
|
19220 | };
|
19221 | }
|
19222 | let rootNodes = parseResult.rootNodes;
|
19223 | // process i18n meta information (scan attributes, generate ids)
|
19224 | // before we run whitespace removal process, because existing i18n
|
19225 | // extraction process (ng extract-i18n) relies on a raw content to generate
|
19226 | // message ids
|
19227 | const i18nMetaVisitor = new I18nMetaVisitor(interpolationConfig, /* keepI18nAttrs */ !preserveWhitespaces, enableI18nLegacyMessageIdFormat);
|
19228 | const i18nMetaResult = i18nMetaVisitor.visitAllWithErrors(rootNodes);
|
19229 | if (!options.alwaysAttemptHtmlToR3AstConversion && i18nMetaResult.errors &&
|
19230 | i18nMetaResult.errors.length > 0) {
|
19231 | return {
|
19232 | interpolationConfig,
|
19233 | preserveWhitespaces,
|
19234 | template,
|
19235 | templateUrl,
|
19236 | isInline,
|
19237 | errors: i18nMetaResult.errors,
|
19238 | nodes: [],
|
19239 | styleUrls: [],
|
19240 | styles: [],
|
19241 | ngContentSelectors: []
|
19242 | };
|
19243 | }
|
19244 | rootNodes = i18nMetaResult.rootNodes;
|
19245 | if (!preserveWhitespaces) {
|
19246 | rootNodes = visitAll$1(new WhitespaceVisitor(), rootNodes);
|
19247 | // run i18n meta visitor again in case whitespaces are removed (because that might affect
|
19248 | // generated i18n message content) and first pass indicated that i18n content is present in a
|
19249 | // template. During this pass i18n IDs generated at the first pass will be preserved, so we can
|
19250 | // mimic existing extraction process (ng extract-i18n)
|
19251 | if (i18nMetaVisitor.hasI18nMeta) {
|
19252 | rootNodes = visitAll$1(new I18nMetaVisitor(interpolationConfig, /* keepI18nAttrs */ false), rootNodes);
|
19253 | }
|
19254 | }
|
19255 | const { nodes, errors, styleUrls, styles, ngContentSelectors } = htmlAstToRender3Ast(rootNodes, bindingParser);
|
19256 | errors.push(...parseResult.errors, ...i18nMetaResult.errors);
|
19257 | return {
|
19258 | interpolationConfig,
|
19259 | preserveWhitespaces,
|
19260 | errors: errors.length > 0 ? errors : null,
|
19261 | template,
|
19262 | templateUrl,
|
19263 | isInline,
|
19264 | nodes,
|
19265 | styleUrls,
|
19266 | styles,
|
19267 | ngContentSelectors
|
19268 | };
|
19269 | }
|
19270 | const elementRegistry = new DomElementSchemaRegistry();
|
19271 | /**
|
19272 | * Construct a `BindingParser` with a default configuration.
|
19273 | */
|
19274 | function makeBindingParser(interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
|
19275 | return new BindingParser(new IvyParser(new Lexer()), interpolationConfig, elementRegistry, null, []);
|
19276 | }
|
19277 | function resolveSanitizationFn(context, isAttribute) {
|
19278 | switch (context) {
|
19279 | case SecurityContext.HTML:
|
19280 | return importExpr(Identifiers$1.sanitizeHtml);
|
19281 | case SecurityContext.SCRIPT:
|
19282 | return importExpr(Identifiers$1.sanitizeScript);
|
19283 | case SecurityContext.STYLE:
|
19284 | // the compiler does not fill in an instruction for [style.prop?] binding
|
19285 | // values because the style algorithm knows internally what props are subject
|
19286 | // to sanitization (only [attr.style] values are explicitly sanitized)
|
19287 | return isAttribute ? importExpr(Identifiers$1.sanitizeStyle) : null;
|
19288 | case SecurityContext.URL:
|
19289 | return importExpr(Identifiers$1.sanitizeUrl);
|
19290 | case SecurityContext.RESOURCE_URL:
|
19291 | return importExpr(Identifiers$1.sanitizeResourceUrl);
|
19292 | default:
|
19293 | return null;
|
19294 | }
|
19295 | }
|
19296 | function trustedConstAttribute(tagName, attr) {
|
19297 | const value = asLiteral(attr.value);
|
19298 | if (isTrustedTypesSink(tagName, attr.name)) {
|
19299 | switch (elementRegistry.securityContext(tagName, attr.name, /* isAttribute */ true)) {
|
19300 | case SecurityContext.HTML:
|
19301 | return taggedTemplate(importExpr(Identifiers$1.trustConstantHtml), new TemplateLiteral([new TemplateLiteralElement(attr.value)], []), undefined, attr.valueSpan);
|
19302 | // NB: no SecurityContext.SCRIPT here, as the corresponding tags are stripped by the compiler.
|
19303 | case SecurityContext.RESOURCE_URL:
|
19304 | return taggedTemplate(importExpr(Identifiers$1.trustConstantResourceUrl), new TemplateLiteral([new TemplateLiteralElement(attr.value)], []), undefined, attr.valueSpan);
|
19305 | default:
|
19306 | return value;
|
19307 | }
|
19308 | }
|
19309 | else {
|
19310 | return value;
|
19311 | }
|
19312 | }
|
19313 | function isSingleElementTemplate(children) {
|
19314 | return children.length === 1 && children[0] instanceof Element;
|
19315 | }
|
19316 | function isTextNode(node) {
|
19317 | return node instanceof Text || node instanceof BoundText || node instanceof Icu;
|
19318 | }
|
19319 | function hasTextChildrenOnly(children) {
|
19320 | return children.every(isTextNode);
|
19321 | }
|
19322 | /** Name of the global variable that is used to determine if we use Closure translations or not */
|
19323 | const NG_I18N_CLOSURE_MODE = 'ngI18nClosureMode';
|
19324 | /**
|
19325 | * Generate statements that define a given translation message.
|
19326 | *
|
19327 | * ```
|
19328 | * var I18N_1;
|
19329 | * if (typeof ngI18nClosureMode !== undefined && ngI18nClosureMode) {
|
19330 | * var MSG_EXTERNAL_XXX = goog.getMsg(
|
19331 | * "Some message with {$interpolation}!",
|
19332 | * { "interpolation": "\uFFFD0\uFFFD" }
|
19333 | * );
|
19334 | * I18N_1 = MSG_EXTERNAL_XXX;
|
19335 | * }
|
19336 | * else {
|
19337 | * I18N_1 = $localize`Some message with ${'\uFFFD0\uFFFD'}!`;
|
19338 | * }
|
19339 | * ```
|
19340 | *
|
19341 | * @param message The original i18n AST message node
|
19342 | * @param variable The variable that will be assigned the translation, e.g. `I18N_1`.
|
19343 | * @param closureVar The variable for Closure `goog.getMsg` calls, e.g. `MSG_EXTERNAL_XXX`.
|
19344 | * @param params Object mapping placeholder names to their values (e.g.
|
19345 | * `{ "interpolation": "\uFFFD0\uFFFD" }`).
|
19346 | * @param transformFn Optional transformation function that will be applied to the translation (e.g.
|
19347 | * post-processing).
|
19348 | * @returns An array of statements that defined a given translation.
|
19349 | */
|
19350 | function getTranslationDeclStmts(message, variable, closureVar, params = {}, transformFn) {
|
19351 | const statements = [
|
19352 | declareI18nVariable(variable),
|
19353 | ifStmt(createClosureModeGuard(), createGoogleGetMsgStatements(variable, message, closureVar, i18nFormatPlaceholderNames(params, /* useCamelCase */ true)), createLocalizeStatements(variable, message, i18nFormatPlaceholderNames(params, /* useCamelCase */ false))),
|
19354 | ];
|
19355 | if (transformFn) {
|
19356 | statements.push(new ExpressionStatement(variable.set(transformFn(variable))));
|
19357 | }
|
19358 | return statements;
|
19359 | }
|
19360 | /**
|
19361 | * Create the expression that will be used to guard the closure mode block
|
19362 | * It is equivalent to:
|
19363 | *
|
19364 | * ```
|
19365 | * typeof ngI18nClosureMode !== undefined && ngI18nClosureMode
|
19366 | * ```
|
19367 | */
|
19368 | function createClosureModeGuard() {
|
19369 | return typeofExpr(variable(NG_I18N_CLOSURE_MODE))
|
19370 | .notIdentical(literal('undefined', STRING_TYPE))
|
19371 | .and(variable(NG_I18N_CLOSURE_MODE));
|
19372 | }
|
19373 |
|
19374 | /**
|
19375 | * @license
|
19376 | * Copyright Google LLC All Rights Reserved.
|
19377 | *
|
19378 | * Use of this source code is governed by an MIT-style license that can be
|
19379 | * found in the LICENSE file at https://angular.io/license
|
19380 | */
|
19381 | // This regex matches any binding names that contain the "attr." prefix, e.g. "attr.required"
|
19382 | // If there is a match, the first matching group will contain the attribute name to bind.
|
19383 | const ATTR_REGEX = /attr\.([^\]]+)/;
|
19384 | function baseDirectiveFields(meta, constantPool, bindingParser) {
|
19385 | const definitionMap = new DefinitionMap();
|
19386 | const selectors = parseSelectorToR3Selector(meta.selector);
|
19387 | // e.g. `type: MyDirective`
|
19388 | definitionMap.set('type', meta.internalType);
|
19389 | // e.g. `selectors: [['', 'someDir', '']]`
|
19390 | if (selectors.length > 0) {
|
19391 | definitionMap.set('selectors', asLiteral(selectors));
|
19392 | }
|
19393 | if (meta.queries.length > 0) {
|
19394 | // e.g. `contentQueries: (rf, ctx, dirIndex) => { ... }
|
19395 | definitionMap.set('contentQueries', createContentQueriesFunction(meta.queries, constantPool, meta.name));
|
19396 | }
|
19397 | if (meta.viewQueries.length) {
|
19398 | definitionMap.set('viewQuery', createViewQueriesFunction(meta.viewQueries, constantPool, meta.name));
|
19399 | }
|
19400 | // e.g. `hostBindings: (rf, ctx) => { ... }
|
19401 | definitionMap.set('hostBindings', createHostBindingsFunction(meta.host, meta.typeSourceSpan, bindingParser, constantPool, meta.selector || '', meta.name, definitionMap));
|
19402 | // e.g 'inputs: {a: 'a'}`
|
19403 | definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs, true));
|
19404 | // e.g 'outputs: {a: 'a'}`
|
19405 | definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs));
|
19406 | if (meta.exportAs !== null) {
|
19407 | definitionMap.set('exportAs', literalArr(meta.exportAs.map(e => literal(e))));
|
19408 | }
|
19409 | return definitionMap;
|
19410 | }
|
19411 | /**
|
19412 | * Add features to the definition map.
|
19413 | */
|
19414 | function addFeatures(definitionMap, meta) {
|
19415 | // e.g. `features: [NgOnChangesFeature]`
|
19416 | const features = [];
|
19417 | const providers = meta.providers;
|
19418 | const viewProviders = meta.viewProviders;
|
19419 | if (providers || viewProviders) {
|
19420 | const args = [providers || new LiteralArrayExpr([])];
|
19421 | if (viewProviders) {
|
19422 | args.push(viewProviders);
|
19423 | }
|
19424 | features.push(importExpr(Identifiers$1.ProvidersFeature).callFn(args));
|
19425 | }
|
19426 | if (meta.usesInheritance) {
|
19427 | features.push(importExpr(Identifiers$1.InheritDefinitionFeature));
|
19428 | }
|
19429 | if (meta.fullInheritance) {
|
19430 | features.push(importExpr(Identifiers$1.CopyDefinitionFeature));
|
19431 | }
|
19432 | if (meta.lifecycle.usesOnChanges) {
|
19433 | features.push(importExpr(Identifiers$1.NgOnChangesFeature));
|
19434 | }
|
19435 | if (features.length) {
|
19436 | definitionMap.set('features', literalArr(features));
|
19437 | }
|
19438 | }
|
19439 | /**
|
19440 | * Compile a directive for the render3 runtime as defined by the `R3DirectiveMetadata`.
|
19441 | */
|
19442 | function compileDirectiveFromMetadata(meta, constantPool, bindingParser) {
|
19443 | const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser);
|
19444 | addFeatures(definitionMap, meta);
|
19445 | const expression = importExpr(Identifiers$1.defineDirective).callFn([definitionMap.toLiteralMap()]);
|
19446 | const type = createDirectiveType(meta);
|
19447 | return { expression, type };
|
19448 | }
|
19449 | /**
|
19450 | * Compile a component for the render3 runtime as defined by the `R3ComponentMetadata`.
|
19451 | */
|
19452 | function compileComponentFromMetadata(meta, constantPool, bindingParser) {
|
19453 | const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser);
|
19454 | addFeatures(definitionMap, meta);
|
19455 | const selector = meta.selector && CssSelector.parse(meta.selector);
|
19456 | const firstSelector = selector && selector[0];
|
19457 | // e.g. `attr: ["class", ".my.app"]`
|
19458 | // This is optional an only included if the first selector of a component specifies attributes.
|
19459 | if (firstSelector) {
|
19460 | const selectorAttributes = firstSelector.getAttrs();
|
19461 | if (selectorAttributes.length) {
|
19462 | definitionMap.set('attrs', constantPool.getConstLiteral(literalArr(selectorAttributes.map(value => value != null ? literal(value) : literal(undefined))),
|
19463 | /* forceShared */ true));
|
19464 | }
|
19465 | }
|
19466 | // Generate the CSS matcher that recognize directive
|
19467 | let directiveMatcher = null;
|
19468 | if (meta.directives.length > 0) {
|
19469 | const matcher = new SelectorMatcher();
|
19470 | for (const { selector, type } of meta.directives) {
|
19471 | matcher.addSelectables(CssSelector.parse(selector), type);
|
19472 | }
|
19473 | directiveMatcher = matcher;
|
19474 | }
|
19475 | // e.g. `template: function MyComponent_Template(_ctx, _cm) {...}`
|
19476 | const templateTypeName = meta.name;
|
19477 | const templateName = templateTypeName ? `${templateTypeName}_Template` : null;
|
19478 | const directivesUsed = new Set();
|
19479 | const pipesUsed = new Set();
|
19480 | const changeDetection = meta.changeDetection;
|
19481 | const template = meta.template;
|
19482 | const templateBuilder = new TemplateDefinitionBuilder(constantPool, BindingScope.createRootScope(), 0, templateTypeName, null, null, templateName, directiveMatcher, directivesUsed, meta.pipes, pipesUsed, Identifiers$1.namespaceHTML, meta.relativeContextFilePath, meta.i18nUseExternalIds);
|
19483 | const templateFunctionExpression = templateBuilder.buildTemplateFunction(template.nodes, []);
|
19484 | // We need to provide this so that dynamically generated components know what
|
19485 | // projected content blocks to pass through to the component when it is instantiated.
|
19486 | const ngContentSelectors = templateBuilder.getNgContentSelectors();
|
19487 | if (ngContentSelectors) {
|
19488 | definitionMap.set('ngContentSelectors', ngContentSelectors);
|
19489 | }
|
19490 | // e.g. `decls: 2`
|
19491 | definitionMap.set('decls', literal(templateBuilder.getConstCount()));
|
19492 | // e.g. `vars: 2`
|
19493 | definitionMap.set('vars', literal(templateBuilder.getVarCount()));
|
19494 | // Generate `consts` section of ComponentDef:
|
19495 | // - either as an array:
|
19496 | // `consts: [['one', 'two'], ['three', 'four']]`
|
19497 | // - or as a factory function in case additional statements are present (to support i18n):
|
19498 | // `consts: function() { var i18n_0; if (ngI18nClosureMode) {...} else {...} return [i18n_0]; }`
|
19499 | const { constExpressions, prepareStatements } = templateBuilder.getConsts();
|
19500 | if (constExpressions.length > 0) {
|
19501 | let constsExpr = literalArr(constExpressions);
|
19502 | // Prepare statements are present - turn `consts` into a function.
|
19503 | if (prepareStatements.length > 0) {
|
19504 | constsExpr = fn([], [...prepareStatements, new ReturnStatement(constsExpr)]);
|
19505 | }
|
19506 | definitionMap.set('consts', constsExpr);
|
19507 | }
|
19508 | definitionMap.set('template', templateFunctionExpression);
|
19509 | // e.g. `directives: [MyDirective]`
|
19510 | if (directivesUsed.size) {
|
19511 | const directivesList = literalArr(Array.from(directivesUsed));
|
19512 | const directivesExpr = compileDeclarationList(directivesList, meta.declarationListEmitMode);
|
19513 | definitionMap.set('directives', directivesExpr);
|
19514 | }
|
19515 | // e.g. `pipes: [MyPipe]`
|
19516 | if (pipesUsed.size) {
|
19517 | const pipesList = literalArr(Array.from(pipesUsed));
|
19518 | const pipesExpr = compileDeclarationList(pipesList, meta.declarationListEmitMode);
|
19519 | definitionMap.set('pipes', pipesExpr);
|
19520 | }
|
19521 | if (meta.encapsulation === null) {
|
19522 | meta.encapsulation = ViewEncapsulation.Emulated;
|
19523 | }
|
19524 | // e.g. `styles: [str1, str2]`
|
19525 | if (meta.styles && meta.styles.length) {
|
19526 | const styleValues = meta.encapsulation == ViewEncapsulation.Emulated ?
|
19527 | compileStyles(meta.styles, CONTENT_ATTR, HOST_ATTR) :
|
19528 | meta.styles;
|
19529 | const strings = styleValues.map(str => constantPool.getConstLiteral(literal(str)));
|
19530 | definitionMap.set('styles', literalArr(strings));
|
19531 | }
|
19532 | else if (meta.encapsulation === ViewEncapsulation.Emulated) {
|
19533 | // If there is no style, don't generate css selectors on elements
|
19534 | meta.encapsulation = ViewEncapsulation.None;
|
19535 | }
|
19536 | // Only set view encapsulation if it's not the default value
|
19537 | if (meta.encapsulation !== ViewEncapsulation.Emulated) {
|
19538 | definitionMap.set('encapsulation', literal(meta.encapsulation));
|
19539 | }
|
19540 | // e.g. `animation: [trigger('123', [])]`
|
19541 | if (meta.animations !== null) {
|
19542 | definitionMap.set('data', literalMap([{ key: 'animation', value: meta.animations, quoted: false }]));
|
19543 | }
|
19544 | // Only set the change detection flag if it's defined and it's not the default.
|
19545 | if (changeDetection != null && changeDetection !== ChangeDetectionStrategy.Default) {
|
19546 | definitionMap.set('changeDetection', literal(changeDetection));
|
19547 | }
|
19548 | const expression = importExpr(Identifiers$1.defineComponent).callFn([definitionMap.toLiteralMap()]);
|
19549 | const type = createComponentType(meta);
|
19550 | return { expression, type };
|
19551 | }
|
19552 | /**
|
19553 | * Creates the type specification from the component meta. This type is inserted into .d.ts files
|
19554 | * to be consumed by upstream compilations.
|
19555 | */
|
19556 | function createComponentType(meta) {
|
19557 | const typeParams = createDirectiveTypeParams(meta);
|
19558 | typeParams.push(stringArrayAsType(meta.template.ngContentSelectors));
|
19559 | return expressionType(importExpr(Identifiers$1.ComponentDefWithMeta, typeParams));
|
19560 | }
|
19561 | /**
|
19562 | * Compiles the array literal of declarations into an expression according to the provided emit
|
19563 | * mode.
|
19564 | */
|
19565 | function compileDeclarationList(list, mode) {
|
19566 | switch (mode) {
|
19567 | case 0 /* Direct */:
|
19568 | // directives: [MyDir],
|
19569 | return list;
|
19570 | case 1 /* Closure */:
|
19571 | // directives: function () { return [MyDir]; }
|
19572 | return fn([], [new ReturnStatement(list)]);
|
19573 | case 2 /* ClosureResolved */:
|
19574 | // directives: function () { return [MyDir].map(ng.resolveForwardRef); }
|
19575 | const resolvedList = list.callMethod('map', [importExpr(Identifiers$1.resolveForwardRef)]);
|
19576 | return fn([], [new ReturnStatement(resolvedList)]);
|
19577 | }
|
19578 | }
|
19579 | function prepareQueryParams(query, constantPool) {
|
19580 | const parameters = [getQueryPredicate(query, constantPool), literal(toQueryFlags(query))];
|
19581 | if (query.read) {
|
19582 | parameters.push(query.read);
|
19583 | }
|
19584 | return parameters;
|
19585 | }
|
19586 | /**
|
19587 | * Translates query flags into `TQueryFlags` type in packages/core/src/render3/interfaces/query.ts
|
19588 | * @param query
|
19589 | */
|
19590 | function toQueryFlags(query) {
|
19591 | return (query.descendants ? 1 /* descendants */ : 0 /* none */) |
|
19592 | (query.static ? 2 /* isStatic */ : 0 /* none */) |
|
19593 | (query.emitDistinctChangesOnly ? 4 /* emitDistinctChangesOnly */ : 0 /* none */);
|
19594 | }
|
19595 | function convertAttributesToExpressions(attributes) {
|
19596 | const values = [];
|
19597 | for (let key of Object.getOwnPropertyNames(attributes)) {
|
19598 | const value = attributes[key];
|
19599 | values.push(literal(key), value);
|
19600 | }
|
19601 | return values;
|
19602 | }
|
19603 | // Define and update any content queries
|
19604 | function createContentQueriesFunction(queries, constantPool, name) {
|
19605 | const createStatements = [];
|
19606 | const updateStatements = [];
|
19607 | const tempAllocator = temporaryAllocator(updateStatements, TEMPORARY_NAME);
|
19608 | for (const query of queries) {
|
19609 | // creation, e.g. r3.contentQuery(dirIndex, somePredicate, true, null);
|
19610 | createStatements.push(importExpr(Identifiers$1.contentQuery)
|
19611 | .callFn([variable('dirIndex'), ...prepareQueryParams(query, constantPool)])
|
19612 | .toStmt());
|
19613 | // update, e.g. (r3.queryRefresh(tmp = r3.loadQuery()) && (ctx.someDir = tmp));
|
19614 | const temporary = tempAllocator();
|
19615 | const getQueryList = importExpr(Identifiers$1.loadQuery).callFn([]);
|
19616 | const refresh = importExpr(Identifiers$1.queryRefresh).callFn([temporary.set(getQueryList)]);
|
19617 | const updateDirective = variable(CONTEXT_NAME)
|
19618 | .prop(query.propertyName)
|
19619 | .set(query.first ? temporary.prop('first') : temporary);
|
19620 | updateStatements.push(refresh.and(updateDirective).toStmt());
|
19621 | }
|
19622 | const contentQueriesFnName = name ? `${name}_ContentQueries` : null;
|
19623 | return fn([
|
19624 | new FnParam(RENDER_FLAGS, NUMBER_TYPE), new FnParam(CONTEXT_NAME, null),
|
19625 | new FnParam('dirIndex', null)
|
19626 | ], [
|
19627 | renderFlagCheckIfStmt(1 /* Create */, createStatements),
|
19628 | renderFlagCheckIfStmt(2 /* Update */, updateStatements)
|
19629 | ], INFERRED_TYPE, null, contentQueriesFnName);
|
19630 | }
|
19631 | function stringAsType(str) {
|
19632 | return expressionType(literal(str));
|
19633 | }
|
19634 | function stringMapAsType(map) {
|
19635 | const mapValues = Object.keys(map).map(key => {
|
19636 | const value = Array.isArray(map[key]) ? map[key][0] : map[key];
|
19637 | return {
|
19638 | key,
|
19639 | value: literal(value),
|
19640 | quoted: true,
|
19641 | };
|
19642 | });
|
19643 | return expressionType(literalMap(mapValues));
|
19644 | }
|
19645 | function stringArrayAsType(arr) {
|
19646 | return arr.length > 0 ? expressionType(literalArr(arr.map(value => literal(value)))) :
|
19647 | NONE_TYPE;
|
19648 | }
|
19649 | function createDirectiveTypeParams(meta) {
|
19650 | // On the type side, remove newlines from the selector as it will need to fit into a TypeScript
|
19651 | // string literal, which must be on one line.
|
19652 | const selectorForType = meta.selector !== null ? meta.selector.replace(/\n/g, '') : null;
|
19653 | return [
|
19654 | typeWithParameters(meta.type.type, meta.typeArgumentCount),
|
19655 | selectorForType !== null ? stringAsType(selectorForType) : NONE_TYPE,
|
19656 | meta.exportAs !== null ? stringArrayAsType(meta.exportAs) : NONE_TYPE,
|
19657 | stringMapAsType(meta.inputs),
|
19658 | stringMapAsType(meta.outputs),
|
19659 | stringArrayAsType(meta.queries.map(q => q.propertyName)),
|
19660 | ];
|
19661 | }
|
19662 | /**
|
19663 | * Creates the type specification from the directive meta. This type is inserted into .d.ts files
|
19664 | * to be consumed by upstream compilations.
|
19665 | */
|
19666 | function createDirectiveType(meta) {
|
19667 | const typeParams = createDirectiveTypeParams(meta);
|
19668 | return expressionType(importExpr(Identifiers$1.DirectiveDefWithMeta, typeParams));
|
19669 | }
|
19670 | // Define and update any view queries
|
19671 | function createViewQueriesFunction(viewQueries, constantPool, name) {
|
19672 | const createStatements = [];
|
19673 | const updateStatements = [];
|
19674 | const tempAllocator = temporaryAllocator(updateStatements, TEMPORARY_NAME);
|
19675 | viewQueries.forEach((query) => {
|
19676 | // creation, e.g. r3.viewQuery(somePredicate, true);
|
19677 | const queryDefinition = importExpr(Identifiers$1.viewQuery).callFn(prepareQueryParams(query, constantPool));
|
19678 | createStatements.push(queryDefinition.toStmt());
|
19679 | // update, e.g. (r3.queryRefresh(tmp = r3.loadQuery()) && (ctx.someDir = tmp));
|
19680 | const temporary = tempAllocator();
|
19681 | const getQueryList = importExpr(Identifiers$1.loadQuery).callFn([]);
|
19682 | const refresh = importExpr(Identifiers$1.queryRefresh).callFn([temporary.set(getQueryList)]);
|
19683 | const updateDirective = variable(CONTEXT_NAME)
|
19684 | .prop(query.propertyName)
|
19685 | .set(query.first ? temporary.prop('first') : temporary);
|
19686 | updateStatements.push(refresh.and(updateDirective).toStmt());
|
19687 | });
|
19688 | const viewQueryFnName = name ? `${name}_Query` : null;
|
19689 | return fn([new FnParam(RENDER_FLAGS, NUMBER_TYPE), new FnParam(CONTEXT_NAME, null)], [
|
19690 | renderFlagCheckIfStmt(1 /* Create */, createStatements),
|
19691 | renderFlagCheckIfStmt(2 /* Update */, updateStatements)
|
19692 | ], INFERRED_TYPE, null, viewQueryFnName);
|
19693 | }
|
19694 | // Return a host binding function or null if one is not necessary.
|
19695 | function createHostBindingsFunction(hostBindingsMetadata, typeSourceSpan, bindingParser, constantPool, selector, name, definitionMap) {
|
19696 | const bindingContext = variable(CONTEXT_NAME);
|
19697 | const styleBuilder = new StylingBuilder(bindingContext);
|
19698 | const { styleAttr, classAttr } = hostBindingsMetadata.specialAttributes;
|
19699 | if (styleAttr !== undefined) {
|
19700 | styleBuilder.registerStyleAttr(styleAttr);
|
19701 | }
|
19702 | if (classAttr !== undefined) {
|
19703 | styleBuilder.registerClassAttr(classAttr);
|
19704 | }
|
19705 | const createStatements = [];
|
19706 | const updateStatements = [];
|
19707 | const hostBindingSourceSpan = typeSourceSpan;
|
19708 | const directiveSummary = metadataAsSummary(hostBindingsMetadata);
|
19709 | // Calculate host event bindings
|
19710 | const eventBindings = bindingParser.createDirectiveHostEventAsts(directiveSummary, hostBindingSourceSpan);
|
19711 | if (eventBindings && eventBindings.length) {
|
19712 | const listeners = createHostListeners(eventBindings, name);
|
19713 | createStatements.push(...listeners);
|
19714 | }
|
19715 | // Calculate the host property bindings
|
19716 | const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan);
|
19717 | const allOtherBindings = [];
|
19718 | // We need to calculate the total amount of binding slots required by
|
19719 | // all the instructions together before any value conversions happen.
|
19720 | // Value conversions may require additional slots for interpolation and
|
19721 | // bindings with pipes. These calculates happen after this block.
|
19722 | let totalHostVarsCount = 0;
|
19723 | bindings && bindings.forEach((binding) => {
|
19724 | const stylingInputWasSet = styleBuilder.registerInputBasedOnName(binding.name, binding.expression, hostBindingSourceSpan);
|
19725 | if (stylingInputWasSet) {
|
19726 | totalHostVarsCount += MIN_STYLING_BINDING_SLOTS_REQUIRED;
|
19727 | }
|
19728 | else {
|
19729 | allOtherBindings.push(binding);
|
19730 | totalHostVarsCount++;
|
19731 | }
|
19732 | });
|
19733 | let valueConverter;
|
19734 | const getValueConverter = () => {
|
19735 | if (!valueConverter) {
|
19736 | const hostVarsCountFn = (numSlots) => {
|
19737 | const originalVarsCount = totalHostVarsCount;
|
19738 | totalHostVarsCount += numSlots;
|
19739 | return originalVarsCount;
|
19740 | };
|
19741 | valueConverter = new ValueConverter(constantPool, () => error('Unexpected node'), // new nodes are illegal here
|
19742 | hostVarsCountFn, () => error('Unexpected pipe')); // pipes are illegal here
|
19743 | }
|
19744 | return valueConverter;
|
19745 | };
|
19746 | const propertyBindings = [];
|
19747 | const attributeBindings = [];
|
19748 | const syntheticHostBindings = [];
|
19749 | allOtherBindings.forEach((binding) => {
|
19750 | // resolve literal arrays and literal objects
|
19751 | const value = binding.expression.visit(getValueConverter());
|
19752 | const bindingExpr = bindingFn(bindingContext, value);
|
19753 | const { bindingName, instruction, isAttribute } = getBindingNameAndInstruction(binding);
|
19754 | const securityContexts = bindingParser.calcPossibleSecurityContexts(selector, bindingName, isAttribute)
|
19755 | .filter(context => context !== SecurityContext.NONE);
|
19756 | let sanitizerFn = null;
|
19757 | if (securityContexts.length) {
|
19758 | if (securityContexts.length === 2 &&
|
19759 | securityContexts.indexOf(SecurityContext.URL) > -1 &&
|
19760 | securityContexts.indexOf(SecurityContext.RESOURCE_URL) > -1) {
|
19761 | // Special case for some URL attributes (such as "src" and "href") that may be a part
|
19762 | // of different security contexts. In this case we use special santitization function and
|
19763 | // select the actual sanitizer at runtime based on a tag name that is provided while
|
19764 | // invoking sanitization function.
|
19765 | sanitizerFn = importExpr(Identifiers$1.sanitizeUrlOrResourceUrl);
|
19766 | }
|
19767 | else {
|
19768 | sanitizerFn = resolveSanitizationFn(securityContexts[0], isAttribute);
|
19769 | }
|
19770 | }
|
19771 | const instructionParams = [literal(bindingName), bindingExpr.currValExpr];
|
19772 | if (sanitizerFn) {
|
19773 | instructionParams.push(sanitizerFn);
|
19774 | }
|
19775 | updateStatements.push(...bindingExpr.stmts);
|
19776 | if (instruction === Identifiers$1.hostProperty) {
|
19777 | propertyBindings.push(instructionParams);
|
19778 | }
|
19779 | else if (instruction === Identifiers$1.attribute) {
|
19780 | attributeBindings.push(instructionParams);
|
19781 | }
|
19782 | else if (instruction === Identifiers$1.syntheticHostProperty) {
|
19783 | syntheticHostBindings.push(instructionParams);
|
19784 | }
|
19785 | else {
|
19786 | updateStatements.push(importExpr(instruction).callFn(instructionParams).toStmt());
|
19787 | }
|
19788 | });
|
19789 | if (propertyBindings.length > 0) {
|
19790 | updateStatements.push(chainedInstruction(Identifiers$1.hostProperty, propertyBindings).toStmt());
|
19791 | }
|
19792 | if (attributeBindings.length > 0) {
|
19793 | updateStatements.push(chainedInstruction(Identifiers$1.attribute, attributeBindings).toStmt());
|
19794 | }
|
19795 | if (syntheticHostBindings.length > 0) {
|
19796 | updateStatements.push(chainedInstruction(Identifiers$1.syntheticHostProperty, syntheticHostBindings).toStmt());
|
19797 | }
|
19798 | // since we're dealing with directives/components and both have hostBinding
|
19799 | // functions, we need to generate a special hostAttrs instruction that deals
|
19800 | // with both the assignment of styling as well as static attributes to the host
|
19801 | // element. The instruction below will instruct all initial styling (styling
|
19802 | // that is inside of a host binding within a directive/component) to be attached
|
19803 | // to the host element alongside any of the provided host attributes that were
|
19804 | // collected earlier.
|
19805 | const hostAttrs = convertAttributesToExpressions(hostBindingsMetadata.attributes);
|
19806 | styleBuilder.assignHostAttrs(hostAttrs, definitionMap);
|
19807 | if (styleBuilder.hasBindings) {
|
19808 | // finally each binding that was registered in the statement above will need to be added to
|
19809 | // the update block of a component/directive templateFn/hostBindingsFn so that the bindings
|
19810 | // are evaluated and updated for the element.
|
19811 | styleBuilder.buildUpdateLevelInstructions(getValueConverter()).forEach(instruction => {
|
19812 | if (instruction.calls.length > 0) {
|
19813 | const calls = [];
|
19814 | instruction.calls.forEach(call => {
|
19815 | // we subtract a value of `1` here because the binding slot was already allocated
|
19816 | // at the top of this method when all the input bindings were counted.
|
19817 | totalHostVarsCount +=
|
19818 | Math.max(call.allocateBindingSlots - MIN_STYLING_BINDING_SLOTS_REQUIRED, 0);
|
19819 | calls.push(convertStylingCall(call, bindingContext, bindingFn));
|
19820 | });
|
19821 | updateStatements.push(chainedInstruction(instruction.reference, calls).toStmt());
|
19822 | }
|
19823 | });
|
19824 | }
|
19825 | if (totalHostVarsCount) {
|
19826 | definitionMap.set('hostVars', literal(totalHostVarsCount));
|
19827 | }
|
19828 | if (createStatements.length > 0 || updateStatements.length > 0) {
|
19829 | const hostBindingsFnName = name ? `${name}_HostBindings` : null;
|
19830 | const statements = [];
|
19831 | if (createStatements.length > 0) {
|
19832 | statements.push(renderFlagCheckIfStmt(1 /* Create */, createStatements));
|
19833 | }
|
19834 | if (updateStatements.length > 0) {
|
19835 | statements.push(renderFlagCheckIfStmt(2 /* Update */, updateStatements));
|
19836 | }
|
19837 | return fn([new FnParam(RENDER_FLAGS, NUMBER_TYPE), new FnParam(CONTEXT_NAME, null)], statements, INFERRED_TYPE, null, hostBindingsFnName);
|
19838 | }
|
19839 | return null;
|
19840 | }
|
19841 | function bindingFn(implicit, value) {
|
19842 | return convertPropertyBinding(null, implicit, value, 'b', BindingForm.Expression, () => error('Unexpected interpolation'));
|
19843 | }
|
19844 | function convertStylingCall(call, bindingContext, bindingFn) {
|
19845 | return call.params(value => bindingFn(bindingContext, value).currValExpr);
|
19846 | }
|
19847 | function getBindingNameAndInstruction(binding) {
|
19848 | let bindingName = binding.name;
|
19849 | let instruction;
|
19850 | // Check to see if this is an attr binding or a property binding
|
19851 | const attrMatches = bindingName.match(ATTR_REGEX);
|
19852 | if (attrMatches) {
|
19853 | bindingName = attrMatches[1];
|
19854 | instruction = Identifiers$1.attribute;
|
19855 | }
|
19856 | else {
|
19857 | if (binding.isAnimation) {
|
19858 | bindingName = prepareSyntheticPropertyName(bindingName);
|
19859 | // host bindings that have a synthetic property (e.g. @foo) should always be rendered
|
19860 | // in the context of the component and not the parent. Therefore there is a special
|
19861 | // compatibility instruction available for this purpose.
|
19862 | instruction = Identifiers$1.syntheticHostProperty;
|
19863 | }
|
19864 | else {
|
19865 | instruction = Identifiers$1.hostProperty;
|
19866 | }
|
19867 | }
|
19868 | return { bindingName, instruction, isAttribute: !!attrMatches };
|
19869 | }
|
19870 | function createHostListeners(eventBindings, name) {
|
19871 | const listeners = [];
|
19872 | const syntheticListeners = [];
|
19873 | const instructions = [];
|
19874 | eventBindings.forEach(binding => {
|
19875 | let bindingName = binding.name && sanitizeIdentifier(binding.name);
|
19876 | const bindingFnName = binding.type === 1 /* Animation */ ?
|
19877 | prepareSyntheticListenerFunctionName(bindingName, binding.targetOrPhase) :
|
19878 | bindingName;
|
19879 | const handlerName = name && bindingName ? `${name}_${bindingFnName}_HostBindingHandler` : null;
|
19880 | const params = prepareEventListenerParameters(BoundEvent.fromParsedEvent(binding), handlerName);
|
19881 | if (binding.type == 1 /* Animation */) {
|
19882 | syntheticListeners.push(params);
|
19883 | }
|
19884 | else {
|
19885 | listeners.push(params);
|
19886 | }
|
19887 | });
|
19888 | if (syntheticListeners.length > 0) {
|
19889 | instructions.push(chainedInstruction(Identifiers$1.syntheticHostListener, syntheticListeners).toStmt());
|
19890 | }
|
19891 | if (listeners.length > 0) {
|
19892 | instructions.push(chainedInstruction(Identifiers$1.listener, listeners).toStmt());
|
19893 | }
|
19894 | return instructions;
|
19895 | }
|
19896 | function metadataAsSummary(meta) {
|
19897 | // clang-format off
|
19898 | return {
|
19899 | // This is used by the BindingParser, which only deals with listeners and properties. There's no
|
19900 | // need to pass attributes to it.
|
19901 | hostAttributes: {},
|
19902 | hostListeners: meta.listeners,
|
19903 | hostProperties: meta.properties,
|
19904 | };
|
19905 | // clang-format on
|
19906 | }
|
19907 | const HOST_REG_EXP = /^(?:\[([^\]]+)\])|(?:\(([^\)]+)\))$/;
|
19908 | function parseHostBindings(host) {
|
19909 | const attributes = {};
|
19910 | const listeners = {};
|
19911 | const properties = {};
|
19912 | const specialAttributes = {};
|
19913 | for (const key of Object.keys(host)) {
|
19914 | const value = host[key];
|
19915 | const matches = key.match(HOST_REG_EXP);
|
19916 | if (matches === null) {
|
19917 | switch (key) {
|
19918 | case 'class':
|
19919 | if (typeof value !== 'string') {
|
19920 | // TODO(alxhub): make this a diagnostic.
|
19921 | throw new Error(`Class binding must be string`);
|
19922 | }
|
19923 | specialAttributes.classAttr = value;
|
19924 | break;
|
19925 | case 'style':
|
19926 | if (typeof value !== 'string') {
|
19927 | // TODO(alxhub): make this a diagnostic.
|
19928 | throw new Error(`Style binding must be string`);
|
19929 | }
|
19930 | specialAttributes.styleAttr = value;
|
19931 | break;
|
19932 | default:
|
19933 | if (typeof value === 'string') {
|
19934 | attributes[key] = literal(value);
|
19935 | }
|
19936 | else {
|
19937 | attributes[key] = value;
|
19938 | }
|
19939 | }
|
19940 | }
|
19941 | else if (matches[1 /* Binding */] != null) {
|
19942 | if (typeof value !== 'string') {
|
19943 | // TODO(alxhub): make this a diagnostic.
|
19944 | throw new Error(`Property binding must be string`);
|
19945 | }
|
19946 | // synthetic properties (the ones that have a `@` as a prefix)
|
19947 | // are still treated the same as regular properties. Therefore
|
19948 | // there is no point in storing them in a separate map.
|
19949 | properties[matches[1 /* Binding */]] = value;
|
19950 | }
|
19951 | else if (matches[2 /* Event */] != null) {
|
19952 | if (typeof value !== 'string') {
|
19953 | // TODO(alxhub): make this a diagnostic.
|
19954 | throw new Error(`Event binding must be string`);
|
19955 | }
|
19956 | listeners[matches[2 /* Event */]] = value;
|
19957 | }
|
19958 | }
|
19959 | return { attributes, listeners, properties, specialAttributes };
|
19960 | }
|
19961 | /**
|
19962 | * Verifies host bindings and returns the list of errors (if any). Empty array indicates that a
|
19963 | * given set of host bindings has no errors.
|
19964 | *
|
19965 | * @param bindings set of host bindings to verify.
|
19966 | * @param sourceSpan source span where host bindings were defined.
|
19967 | * @returns array of errors associated with a given set of host bindings.
|
19968 | */
|
19969 | function verifyHostBindings(bindings, sourceSpan) {
|
19970 | const summary = metadataAsSummary(bindings);
|
19971 | // TODO: abstract out host bindings verification logic and use it instead of
|
19972 | // creating events and properties ASTs to detect errors (FW-996)
|
19973 | const bindingParser = makeBindingParser();
|
19974 | bindingParser.createDirectiveHostEventAsts(summary, sourceSpan);
|
19975 | bindingParser.createBoundHostProperties(summary, sourceSpan);
|
19976 | return bindingParser.errors;
|
19977 | }
|
19978 | function compileStyles(styles, selector, hostSelector) {
|
19979 | const shadowCss = new ShadowCss();
|
19980 | return styles.map(style => {
|
19981 | return shadowCss.shimCssText(style, selector, hostSelector);
|
19982 | });
|
19983 | }
|
19984 |
|
19985 | /**
|
19986 | * @license
|
19987 | * Copyright Google LLC All Rights Reserved.
|
19988 | *
|
19989 | * Use of this source code is governed by an MIT-style license that can be
|
19990 | * found in the LICENSE file at https://angular.io/license
|
19991 | */
|
19992 | /**
|
19993 | * An interface for retrieving documents by URL that the compiler uses
|
19994 | * to load templates.
|
19995 | */
|
19996 | class ResourceLoader {
|
19997 | get(url) {
|
19998 | return '';
|
19999 | }
|
20000 | }
|
20001 |
|
20002 | /**
|
20003 | * @license
|
20004 | * Copyright Google LLC All Rights Reserved.
|
20005 | *
|
20006 | * Use of this source code is governed by an MIT-style license that can be
|
20007 | * found in the LICENSE file at https://angular.io/license
|
20008 | */
|
20009 | class CompilerFacadeImpl {
|
20010 | constructor(jitEvaluator = new JitEvaluator()) {
|
20011 | this.jitEvaluator = jitEvaluator;
|
20012 | this.R3ResolvedDependencyType = R3ResolvedDependencyType;
|
20013 | this.R3FactoryTarget = R3FactoryTarget;
|
20014 | this.ResourceLoader = ResourceLoader;
|
20015 | this.elementSchemaRegistry = new DomElementSchemaRegistry();
|
20016 | }
|
20017 | compilePipe(angularCoreEnv, sourceMapUrl, facade) {
|
20018 | const metadata = {
|
20019 | name: facade.name,
|
20020 | type: wrapReference(facade.type),
|
20021 | internalType: new WrappedNodeExpr(facade.type),
|
20022 | typeArgumentCount: facade.typeArgumentCount,
|
20023 | deps: convertR3DependencyMetadataArray(facade.deps),
|
20024 | pipeName: facade.pipeName,
|
20025 | pure: facade.pure,
|
20026 | };
|
20027 | const res = compilePipeFromMetadata(metadata);
|
20028 | return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, []);
|
20029 | }
|
20030 | compilePipeDeclaration(angularCoreEnv, sourceMapUrl, declaration) {
|
20031 | const meta = convertDeclarePipeFacadeToMetadata(declaration);
|
20032 | const res = compilePipeFromMetadata(meta);
|
20033 | return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, []);
|
20034 | }
|
20035 | compileInjectable(angularCoreEnv, sourceMapUrl, facade) {
|
20036 | const { expression, statements } = compileInjectable({
|
20037 | name: facade.name,
|
20038 | type: wrapReference(facade.type),
|
20039 | internalType: new WrappedNodeExpr(facade.type),
|
20040 | typeArgumentCount: facade.typeArgumentCount,
|
20041 | providedIn: computeProvidedIn(facade.providedIn),
|
20042 | useClass: wrapExpression(facade, USE_CLASS),
|
20043 | useFactory: wrapExpression(facade, USE_FACTORY),
|
20044 | useValue: wrapExpression(facade, USE_VALUE),
|
20045 | useExisting: wrapExpression(facade, USE_EXISTING),
|
20046 | userDeps: convertR3DependencyMetadataArray(facade.userDeps) || undefined,
|
20047 | });
|
20048 | return this.jitExpression(expression, angularCoreEnv, sourceMapUrl, statements);
|
20049 | }
|
20050 | compileInjector(angularCoreEnv, sourceMapUrl, facade) {
|
20051 | const meta = {
|
20052 | name: facade.name,
|
20053 | type: wrapReference(facade.type),
|
20054 | internalType: new WrappedNodeExpr(facade.type),
|
20055 | deps: convertR3DependencyMetadataArray(facade.deps),
|
20056 | providers: new WrappedNodeExpr(facade.providers),
|
20057 | imports: facade.imports.map(i => new WrappedNodeExpr(i)),
|
20058 | };
|
20059 | const res = compileInjector(meta);
|
20060 | return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, res.statements);
|
20061 | }
|
20062 | compileNgModule(angularCoreEnv, sourceMapUrl, facade) {
|
20063 | const meta = {
|
20064 | type: wrapReference(facade.type),
|
20065 | internalType: new WrappedNodeExpr(facade.type),
|
20066 | adjacentType: new WrappedNodeExpr(facade.type),
|
20067 | bootstrap: facade.bootstrap.map(wrapReference),
|
20068 | declarations: facade.declarations.map(wrapReference),
|
20069 | imports: facade.imports.map(wrapReference),
|
20070 | exports: facade.exports.map(wrapReference),
|
20071 | emitInline: true,
|
20072 | containsForwardDecls: false,
|
20073 | schemas: facade.schemas ? facade.schemas.map(wrapReference) : null,
|
20074 | id: facade.id ? new WrappedNodeExpr(facade.id) : null,
|
20075 | };
|
20076 | const res = compileNgModule(meta);
|
20077 | return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, []);
|
20078 | }
|
20079 | compileDirective(angularCoreEnv, sourceMapUrl, facade) {
|
20080 | const meta = convertDirectiveFacadeToMetadata(facade);
|
20081 | return this.compileDirectiveFromMeta(angularCoreEnv, sourceMapUrl, meta);
|
20082 | }
|
20083 | compileDirectiveDeclaration(angularCoreEnv, sourceMapUrl, declaration) {
|
20084 | const typeSourceSpan = this.createParseSourceSpan('Directive', declaration.type.name, sourceMapUrl);
|
20085 | const meta = convertDeclareDirectiveFacadeToMetadata(declaration, typeSourceSpan);
|
20086 | return this.compileDirectiveFromMeta(angularCoreEnv, sourceMapUrl, meta);
|
20087 | }
|
20088 | compileDirectiveFromMeta(angularCoreEnv, sourceMapUrl, meta) {
|
20089 | const constantPool = new ConstantPool();
|
20090 | const bindingParser = makeBindingParser();
|
20091 | const res = compileDirectiveFromMetadata(meta, constantPool, bindingParser);
|
20092 | return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, constantPool.statements);
|
20093 | }
|
20094 | compileComponent(angularCoreEnv, sourceMapUrl, facade) {
|
20095 | // Parse the template and check for errors.
|
20096 | const { template, interpolation } = parseJitTemplate(facade.template, facade.name, sourceMapUrl, facade.preserveWhitespaces, facade.interpolation);
|
20097 | // Compile the component metadata, including template, into an expression.
|
20098 | const meta = Object.assign(Object.assign(Object.assign({}, facade), convertDirectiveFacadeToMetadata(facade)), { selector: facade.selector || this.elementSchemaRegistry.getDefaultComponentElementName(), template, declarationListEmitMode: 0 /* Direct */, styles: [...facade.styles, ...template.styles], encapsulation: facade.encapsulation, interpolation, changeDetection: facade.changeDetection, animations: facade.animations != null ? new WrappedNodeExpr(facade.animations) : null, viewProviders: facade.viewProviders != null ? new WrappedNodeExpr(facade.viewProviders) :
|
20099 | null, relativeContextFilePath: '', i18nUseExternalIds: true });
|
20100 | const jitExpressionSourceMap = `ng:///${facade.name}.js`;
|
20101 | return this.compileComponentFromMeta(angularCoreEnv, jitExpressionSourceMap, meta);
|
20102 | }
|
20103 | compileComponentDeclaration(angularCoreEnv, sourceMapUrl, declaration) {
|
20104 | const typeSourceSpan = this.createParseSourceSpan('Component', declaration.type.name, sourceMapUrl);
|
20105 | const meta = convertDeclareComponentFacadeToMetadata(declaration, typeSourceSpan, sourceMapUrl);
|
20106 | return this.compileComponentFromMeta(angularCoreEnv, sourceMapUrl, meta);
|
20107 | }
|
20108 | compileComponentFromMeta(angularCoreEnv, sourceMapUrl, meta) {
|
20109 | const constantPool = new ConstantPool();
|
20110 | const bindingParser = makeBindingParser(meta.interpolation);
|
20111 | const res = compileComponentFromMetadata(meta, constantPool, bindingParser);
|
20112 | return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, constantPool.statements);
|
20113 | }
|
20114 | compileFactory(angularCoreEnv, sourceMapUrl, meta) {
|
20115 | const factoryRes = compileFactoryFunction({
|
20116 | name: meta.name,
|
20117 | type: wrapReference(meta.type),
|
20118 | internalType: new WrappedNodeExpr(meta.type),
|
20119 | typeArgumentCount: meta.typeArgumentCount,
|
20120 | deps: convertR3DependencyMetadataArray(meta.deps),
|
20121 | injectFn: meta.injectFn === 'directiveInject' ? Identifiers.directiveInject :
|
20122 | Identifiers.inject,
|
20123 | target: meta.target,
|
20124 | });
|
20125 | return this.jitExpression(factoryRes.factory, angularCoreEnv, sourceMapUrl, factoryRes.statements);
|
20126 | }
|
20127 | createParseSourceSpan(kind, typeName, sourceUrl) {
|
20128 | return r3JitTypeSourceSpan(kind, typeName, sourceUrl);
|
20129 | }
|
20130 | /**
|
20131 | * JIT compiles an expression and returns the result of executing that expression.
|
20132 | *
|
20133 | * @param def the definition which will be compiled and executed to get the value to patch
|
20134 | * @param context an object map of @angular/core symbol names to symbols which will be available
|
20135 | * in the context of the compiled expression
|
20136 | * @param sourceUrl a URL to use for the source map of the compiled expression
|
20137 | * @param preStatements a collection of statements that should be evaluated before the expression.
|
20138 | */
|
20139 | jitExpression(def, context, sourceUrl, preStatements) {
|
20140 | // The ConstantPool may contain Statements which declare variables used in the final expression.
|
20141 | // Therefore, its statements need to precede the actual JIT operation. The final statement is a
|
20142 | // declaration of $def which is set to the expression being compiled.
|
20143 | const statements = [
|
20144 | ...preStatements,
|
20145 | new DeclareVarStmt('$def', def, undefined, [StmtModifier.Exported]),
|
20146 | ];
|
20147 | const res = this.jitEvaluator.evaluateStatements(sourceUrl, statements, new R3JitReflector(context), /* enableSourceMaps */ true);
|
20148 | return res['$def'];
|
20149 | }
|
20150 | }
|
20151 | const USE_CLASS = Object.keys({ useClass: null })[0];
|
20152 | const USE_FACTORY = Object.keys({ useFactory: null })[0];
|
20153 | const USE_VALUE = Object.keys({ useValue: null })[0];
|
20154 | const USE_EXISTING = Object.keys({ useExisting: null })[0];
|
20155 | const wrapReference = function (value) {
|
20156 | const wrapped = new WrappedNodeExpr(value);
|
20157 | return { value: wrapped, type: wrapped };
|
20158 | };
|
20159 | function convertToR3QueryMetadata(facade) {
|
20160 | return Object.assign(Object.assign({}, facade), { predicate: Array.isArray(facade.predicate) ? facade.predicate :
|
20161 | new WrappedNodeExpr(facade.predicate), read: facade.read ? new WrappedNodeExpr(facade.read) : null, static: facade.static, emitDistinctChangesOnly: facade.emitDistinctChangesOnly });
|
20162 | }
|
20163 | function convertQueryDeclarationToMetadata(declaration) {
|
20164 | var _a, _b, _c, _d;
|
20165 | return {
|
20166 | propertyName: declaration.propertyName,
|
20167 | first: (_a = declaration.first) !== null && _a !== void 0 ? _a : false,
|
20168 | predicate: Array.isArray(declaration.predicate) ? declaration.predicate :
|
20169 | new WrappedNodeExpr(declaration.predicate),
|
20170 | descendants: (_b = declaration.descendants) !== null && _b !== void 0 ? _b : false,
|
20171 | read: declaration.read ? new WrappedNodeExpr(declaration.read) : null,
|
20172 | static: (_c = declaration.static) !== null && _c !== void 0 ? _c : false,
|
20173 | emitDistinctChangesOnly: (_d = declaration.emitDistinctChangesOnly) !== null && _d !== void 0 ? _d : true,
|
20174 | };
|
20175 | }
|
20176 | function convertDirectiveFacadeToMetadata(facade) {
|
20177 | const inputsFromMetadata = parseInputOutputs(facade.inputs || []);
|
20178 | const outputsFromMetadata = parseInputOutputs(facade.outputs || []);
|
20179 | const propMetadata = facade.propMetadata;
|
20180 | const inputsFromType = {};
|
20181 | const outputsFromType = {};
|
20182 | for (const field in propMetadata) {
|
20183 | if (propMetadata.hasOwnProperty(field)) {
|
20184 | propMetadata[field].forEach(ann => {
|
20185 | if (isInput(ann)) {
|
20186 | inputsFromType[field] =
|
20187 | ann.bindingPropertyName ? [ann.bindingPropertyName, field] : field;
|
20188 | }
|
20189 | else if (isOutput(ann)) {
|
20190 | outputsFromType[field] = ann.bindingPropertyName || field;
|
20191 | }
|
20192 | });
|
20193 | }
|
20194 | }
|
20195 | return Object.assign(Object.assign({}, facade), { typeSourceSpan: facade.typeSourceSpan, type: wrapReference(facade.type), internalType: new WrappedNodeExpr(facade.type), deps: convertR3DependencyMetadataArray(facade.deps), host: extractHostBindings(facade.propMetadata, facade.typeSourceSpan, facade.host), inputs: Object.assign(Object.assign({}, inputsFromMetadata), inputsFromType), outputs: Object.assign(Object.assign({}, outputsFromMetadata), outputsFromType), queries: facade.queries.map(convertToR3QueryMetadata), providers: facade.providers != null ? new WrappedNodeExpr(facade.providers) : null, viewQueries: facade.viewQueries.map(convertToR3QueryMetadata), fullInheritance: false });
|
20196 | }
|
20197 | function convertDeclareDirectiveFacadeToMetadata(declaration, typeSourceSpan) {
|
20198 | var _a, _b, _c, _d, _e, _f, _g, _h;
|
20199 | return {
|
20200 | name: declaration.type.name,
|
20201 | type: wrapReference(declaration.type),
|
20202 | typeSourceSpan,
|
20203 | internalType: new WrappedNodeExpr(declaration.type),
|
20204 | selector: (_a = declaration.selector) !== null && _a !== void 0 ? _a : null,
|
20205 | inputs: (_b = declaration.inputs) !== null && _b !== void 0 ? _b : {},
|
20206 | outputs: (_c = declaration.outputs) !== null && _c !== void 0 ? _c : {},
|
20207 | host: convertHostDeclarationToMetadata(declaration.host),
|
20208 | queries: ((_d = declaration.queries) !== null && _d !== void 0 ? _d : []).map(convertQueryDeclarationToMetadata),
|
20209 | viewQueries: ((_e = declaration.viewQueries) !== null && _e !== void 0 ? _e : []).map(convertQueryDeclarationToMetadata),
|
20210 | providers: declaration.providers !== undefined ? new WrappedNodeExpr(declaration.providers) :
|
20211 | null,
|
20212 | exportAs: (_f = declaration.exportAs) !== null && _f !== void 0 ? _f : null,
|
20213 | usesInheritance: (_g = declaration.usesInheritance) !== null && _g !== void 0 ? _g : false,
|
20214 | lifecycle: { usesOnChanges: (_h = declaration.usesOnChanges) !== null && _h !== void 0 ? _h : false },
|
20215 | deps: null,
|
20216 | typeArgumentCount: 0,
|
20217 | fullInheritance: false,
|
20218 | };
|
20219 | }
|
20220 | function convertHostDeclarationToMetadata(host = {}) {
|
20221 | var _a, _b, _c;
|
20222 | return {
|
20223 | attributes: convertOpaqueValuesToExpressions((_a = host.attributes) !== null && _a !== void 0 ? _a : {}),
|
20224 | listeners: (_b = host.listeners) !== null && _b !== void 0 ? _b : {},
|
20225 | properties: (_c = host.properties) !== null && _c !== void 0 ? _c : {},
|
20226 | specialAttributes: {
|
20227 | classAttr: host.classAttribute,
|
20228 | styleAttr: host.styleAttribute,
|
20229 | },
|
20230 | };
|
20231 | }
|
20232 | function convertOpaqueValuesToExpressions(obj) {
|
20233 | const result = {};
|
20234 | for (const key of Object.keys(obj)) {
|
20235 | result[key] = new WrappedNodeExpr(obj[key]);
|
20236 | }
|
20237 | return result;
|
20238 | }
|
20239 | function convertDeclareComponentFacadeToMetadata(declaration, typeSourceSpan, sourceMapUrl) {
|
20240 | var _a, _b, _c, _d, _e;
|
20241 | const { template, interpolation } = parseJitTemplate(declaration.template, declaration.type.name, sourceMapUrl, (_a = declaration.preserveWhitespaces) !== null && _a !== void 0 ? _a : false, declaration.interpolation);
|
20242 | return Object.assign(Object.assign({}, convertDeclareDirectiveFacadeToMetadata(declaration, typeSourceSpan)), { template, styles: (_b = declaration.styles) !== null && _b !== void 0 ? _b : [], directives: ((_c = declaration.directives) !== null && _c !== void 0 ? _c : []).map(convertUsedDirectiveDeclarationToMetadata), pipes: convertUsedPipesToMetadata(declaration.pipes), viewProviders: declaration.viewProviders !== undefined ?
|
20243 | new WrappedNodeExpr(declaration.viewProviders) :
|
20244 | null, animations: declaration.animations !== undefined ? new WrappedNodeExpr(declaration.animations) :
|
20245 | null, changeDetection: (_d = declaration.changeDetection) !== null && _d !== void 0 ? _d : ChangeDetectionStrategy.Default, encapsulation: (_e = declaration.encapsulation) !== null && _e !== void 0 ? _e : ViewEncapsulation.Emulated, interpolation, declarationListEmitMode: 2 /* ClosureResolved */, relativeContextFilePath: '', i18nUseExternalIds: true });
|
20246 | }
|
20247 | function convertUsedDirectiveDeclarationToMetadata(declaration) {
|
20248 | var _a, _b, _c;
|
20249 | return {
|
20250 | selector: declaration.selector,
|
20251 | type: new WrappedNodeExpr(declaration.type),
|
20252 | inputs: (_a = declaration.inputs) !== null && _a !== void 0 ? _a : [],
|
20253 | outputs: (_b = declaration.outputs) !== null && _b !== void 0 ? _b : [],
|
20254 | exportAs: (_c = declaration.exportAs) !== null && _c !== void 0 ? _c : null,
|
20255 | };
|
20256 | }
|
20257 | function convertUsedPipesToMetadata(declaredPipes) {
|
20258 | const pipes = new Map();
|
20259 | if (declaredPipes === undefined) {
|
20260 | return pipes;
|
20261 | }
|
20262 | for (const pipeName of Object.keys(declaredPipes)) {
|
20263 | const pipeType = declaredPipes[pipeName];
|
20264 | pipes.set(pipeName, new WrappedNodeExpr(pipeType));
|
20265 | }
|
20266 | return pipes;
|
20267 | }
|
20268 | function parseJitTemplate(template, typeName, sourceMapUrl, preserveWhitespaces, interpolation) {
|
20269 | const interpolationConfig = interpolation ? InterpolationConfig.fromArray(interpolation) : DEFAULT_INTERPOLATION_CONFIG;
|
20270 | // Parse the template and check for errors.
|
20271 | const parsed = parseTemplate(template, sourceMapUrl, { preserveWhitespaces: preserveWhitespaces, interpolationConfig });
|
20272 | if (parsed.errors !== null) {
|
20273 | const errors = parsed.errors.map(err => err.toString()).join(', ');
|
20274 | throw new Error(`Errors during JIT compilation of template for ${typeName}: ${errors}`);
|
20275 | }
|
20276 | return { template: parsed, interpolation: interpolationConfig };
|
20277 | }
|
20278 | function wrapExpression(obj, property) {
|
20279 | if (obj.hasOwnProperty(property)) {
|
20280 | return new WrappedNodeExpr(obj[property]);
|
20281 | }
|
20282 | else {
|
20283 | return undefined;
|
20284 | }
|
20285 | }
|
20286 | function computeProvidedIn(providedIn) {
|
20287 | if (providedIn == null || typeof providedIn === 'string') {
|
20288 | return new LiteralExpr(providedIn);
|
20289 | }
|
20290 | else {
|
20291 | return new WrappedNodeExpr(providedIn);
|
20292 | }
|
20293 | }
|
20294 | function convertR3DependencyMetadata(facade) {
|
20295 | let tokenExpr;
|
20296 | if (facade.token === null) {
|
20297 | tokenExpr = new LiteralExpr(null);
|
20298 | }
|
20299 | else if (facade.resolved === R3ResolvedDependencyType.Attribute) {
|
20300 | tokenExpr = new LiteralExpr(facade.token);
|
20301 | }
|
20302 | else {
|
20303 | tokenExpr = new WrappedNodeExpr(facade.token);
|
20304 | }
|
20305 | return {
|
20306 | token: tokenExpr,
|
20307 | attribute: null,
|
20308 | resolved: facade.resolved,
|
20309 | host: facade.host,
|
20310 | optional: facade.optional,
|
20311 | self: facade.self,
|
20312 | skipSelf: facade.skipSelf,
|
20313 | };
|
20314 | }
|
20315 | function convertR3DependencyMetadataArray(facades) {
|
20316 | return facades == null ? null : facades.map(convertR3DependencyMetadata);
|
20317 | }
|
20318 | function extractHostBindings(propMetadata, sourceSpan, host) {
|
20319 | // First parse the declarations from the metadata.
|
20320 | const bindings = parseHostBindings(host || {});
|
20321 | // After that check host bindings for errors
|
20322 | const errors = verifyHostBindings(bindings, sourceSpan);
|
20323 | if (errors.length) {
|
20324 | throw new Error(errors.map((error) => error.msg).join('\n'));
|
20325 | }
|
20326 | // Next, loop over the properties of the object, looking for @HostBinding and @HostListener.
|
20327 | for (const field in propMetadata) {
|
20328 | if (propMetadata.hasOwnProperty(field)) {
|
20329 | propMetadata[field].forEach(ann => {
|
20330 | if (isHostBinding(ann)) {
|
20331 | // Since this is a decorator, we know that the value is a class member. Always access it
|
20332 | // through `this` so that further down the line it can't be confused for a literal value
|
20333 | // (e.g. if there's a property called `true`).
|
20334 | bindings.properties[ann.hostPropertyName || field] =
|
20335 | getSafePropertyAccessString('this', field);
|
20336 | }
|
20337 | else if (isHostListener(ann)) {
|
20338 | bindings.listeners[ann.eventName || field] = `${field}(${(ann.args || []).join(',')})`;
|
20339 | }
|
20340 | });
|
20341 | }
|
20342 | }
|
20343 | return bindings;
|
20344 | }
|
20345 | function isHostBinding(value) {
|
20346 | return value.ngMetadataName === 'HostBinding';
|
20347 | }
|
20348 | function isHostListener(value) {
|
20349 | return value.ngMetadataName === 'HostListener';
|
20350 | }
|
20351 | function isInput(value) {
|
20352 | return value.ngMetadataName === 'Input';
|
20353 | }
|
20354 | function isOutput(value) {
|
20355 | return value.ngMetadataName === 'Output';
|
20356 | }
|
20357 | function parseInputOutputs(values) {
|
20358 | return values.reduce((map, value) => {
|
20359 | const [field, property] = value.split(',').map(piece => piece.trim());
|
20360 | map[field] = property || field;
|
20361 | return map;
|
20362 | }, {});
|
20363 | }
|
20364 | function convertDeclarePipeFacadeToMetadata(declaration) {
|
20365 | var _a;
|
20366 | return {
|
20367 | name: declaration.type.name,
|
20368 | type: wrapReference(declaration.type),
|
20369 | internalType: new WrappedNodeExpr(declaration.type),
|
20370 | typeArgumentCount: 0,
|
20371 | pipeName: declaration.name,
|
20372 | deps: null,
|
20373 | pure: (_a = declaration.pure) !== null && _a !== void 0 ? _a : true,
|
20374 | };
|
20375 | }
|
20376 | function publishFacade(global) {
|
20377 | const ng = global.ng || (global.ng = {});
|
20378 | ng.ɵcompilerFacade = new CompilerFacadeImpl();
|
20379 | }
|
20380 |
|
20381 | /**
|
20382 | * @license
|
20383 | * Copyright Google LLC All Rights Reserved.
|
20384 | *
|
20385 | * Use of this source code is governed by an MIT-style license that can be
|
20386 | * found in the LICENSE file at https://angular.io/license
|
20387 | */
|
20388 | const VERSION$1 = new Version('11.2.4');
|
20389 |
|
20390 | /**
|
20391 | * @license
|
20392 | * Copyright Google LLC All Rights Reserved.
|
20393 | *
|
20394 | * Use of this source code is governed by an MIT-style license that can be
|
20395 | * found in the LICENSE file at https://angular.io/license
|
20396 | */
|
20397 | var _VisitorMode;
|
20398 | (function (_VisitorMode) {
|
20399 | _VisitorMode[_VisitorMode["Extract"] = 0] = "Extract";
|
20400 | _VisitorMode[_VisitorMode["Merge"] = 1] = "Merge";
|
20401 | })(_VisitorMode || (_VisitorMode = {}));
|
20402 |
|
20403 | /**
|
20404 | * @license
|
20405 | * Copyright Google LLC All Rights Reserved.
|
20406 | *
|
20407 | * Use of this source code is governed by an MIT-style license that can be
|
20408 | * found in the LICENSE file at https://angular.io/license
|
20409 | */
|
20410 | var LifecycleHooks;
|
20411 | (function (LifecycleHooks) {
|
20412 | LifecycleHooks[LifecycleHooks["OnInit"] = 0] = "OnInit";
|
20413 | LifecycleHooks[LifecycleHooks["OnDestroy"] = 1] = "OnDestroy";
|
20414 | LifecycleHooks[LifecycleHooks["DoCheck"] = 2] = "DoCheck";
|
20415 | LifecycleHooks[LifecycleHooks["OnChanges"] = 3] = "OnChanges";
|
20416 | LifecycleHooks[LifecycleHooks["AfterContentInit"] = 4] = "AfterContentInit";
|
20417 | LifecycleHooks[LifecycleHooks["AfterContentChecked"] = 5] = "AfterContentChecked";
|
20418 | LifecycleHooks[LifecycleHooks["AfterViewInit"] = 6] = "AfterViewInit";
|
20419 | LifecycleHooks[LifecycleHooks["AfterViewChecked"] = 7] = "AfterViewChecked";
|
20420 | })(LifecycleHooks || (LifecycleHooks = {}));
|
20421 | const LIFECYCLE_HOOKS_VALUES = [
|
20422 | LifecycleHooks.OnInit, LifecycleHooks.OnDestroy, LifecycleHooks.DoCheck, LifecycleHooks.OnChanges,
|
20423 | LifecycleHooks.AfterContentInit, LifecycleHooks.AfterContentChecked, LifecycleHooks.AfterViewInit,
|
20424 | LifecycleHooks.AfterViewChecked
|
20425 | ];
|
20426 |
|
20427 | /**
|
20428 | * @license
|
20429 | * Copyright Google LLC All Rights Reserved.
|
20430 | *
|
20431 | * Use of this source code is governed by an MIT-style license that can be
|
20432 | * found in the LICENSE file at https://angular.io/license
|
20433 | */
|
20434 | const LOG_VAR = variable('_l');
|
20435 |
|
20436 | /**
|
20437 | * @license
|
20438 | * Copyright Google LLC All Rights Reserved.
|
20439 | *
|
20440 | * Use of this source code is governed by an MIT-style license that can be
|
20441 | * found in the LICENSE file at https://angular.io/license
|
20442 | */
|
20443 | const LOG_VAR$1 = variable('_l');
|
20444 | const VIEW_VAR = variable('_v');
|
20445 | const CHECK_VAR = variable('_ck');
|
20446 | const COMP_VAR = variable('_co');
|
20447 | const EVENT_NAME_VAR = variable('en');
|
20448 | const ALLOW_DEFAULT_VAR = variable(`ad`);
|
20449 |
|
20450 | /**
|
20451 | * @license
|
20452 | * Copyright Google LLC All Rights Reserved.
|
20453 | *
|
20454 | * Use of this source code is governed by an MIT-style license that can be
|
20455 | * found in the LICENSE file at https://angular.io/license
|
20456 | */
|
20457 | /**
|
20458 | * The index of each URI component in the return value of goog.uri.utils.split.
|
20459 | * @enum {number}
|
20460 | */
|
20461 | var _ComponentIndex;
|
20462 | (function (_ComponentIndex) {
|
20463 | _ComponentIndex[_ComponentIndex["Scheme"] = 1] = "Scheme";
|
20464 | _ComponentIndex[_ComponentIndex["UserInfo"] = 2] = "UserInfo";
|
20465 | _ComponentIndex[_ComponentIndex["Domain"] = 3] = "Domain";
|
20466 | _ComponentIndex[_ComponentIndex["Port"] = 4] = "Port";
|
20467 | _ComponentIndex[_ComponentIndex["Path"] = 5] = "Path";
|
20468 | _ComponentIndex[_ComponentIndex["QueryData"] = 6] = "QueryData";
|
20469 | _ComponentIndex[_ComponentIndex["Fragment"] = 7] = "Fragment";
|
20470 | })(_ComponentIndex || (_ComponentIndex = {}));
|
20471 |
|
20472 | /**
|
20473 | * @license
|
20474 | * Copyright Google LLC All Rights Reserved.
|
20475 | *
|
20476 | * Use of this source code is governed by an MIT-style license that can be
|
20477 | * found in the LICENSE file at https://angular.io/license
|
20478 | */
|
20479 | /**
|
20480 | * Processes `Target`s with a given set of directives and performs a binding operation, which
|
20481 | * returns an object similar to TypeScript's `ts.TypeChecker` that contains knowledge about the
|
20482 | * target.
|
20483 | */
|
20484 | class R3TargetBinder {
|
20485 | constructor(directiveMatcher) {
|
20486 | this.directiveMatcher = directiveMatcher;
|
20487 | }
|
20488 | /**
|
20489 | * Perform a binding operation on the given `Target` and return a `BoundTarget` which contains
|
20490 | * metadata about the types referenced in the template.
|
20491 | */
|
20492 | bind(target) {
|
20493 | if (!target.template) {
|
20494 | // TODO(alxhub): handle targets which contain things like HostBindings, etc.
|
20495 | throw new Error('Binding without a template not yet supported');
|
20496 | }
|
20497 | // First, parse the template into a `Scope` structure. This operation captures the syntactic
|
20498 | // scopes in the template and makes them available for later use.
|
20499 | const scope = Scope.apply(target.template);
|
20500 | // Use the `Scope` to extract the entities present at every level of the template.
|
20501 | const templateEntities = extractTemplateEntities(scope);
|
20502 | // Next, perform directive matching on the template using the `DirectiveBinder`. This returns:
|
20503 | // - directives: Map of nodes (elements & ng-templates) to the directives on them.
|
20504 | // - bindings: Map of inputs, outputs, and attributes to the directive/element that claims
|
20505 | // them. TODO(alxhub): handle multiple directives claiming an input/output/etc.
|
20506 | // - references: Map of #references to their targets.
|
20507 | const { directives, bindings, references } = DirectiveBinder.apply(target.template, this.directiveMatcher);
|
20508 | // Finally, run the TemplateBinder to bind references, variables, and other entities within the
|
20509 | // template. This extracts all the metadata that doesn't depend on directive matching.
|
20510 | const { expressions, symbols, nestingLevel, usedPipes } = TemplateBinder.apply(target.template, scope);
|
20511 | return new R3BoundTarget(target, directives, bindings, references, expressions, symbols, nestingLevel, templateEntities, usedPipes);
|
20512 | }
|
20513 | }
|
20514 | /**
|
20515 | * Represents a binding scope within a template.
|
20516 | *
|
20517 | * Any variables, references, or other named entities declared within the template will
|
20518 | * be captured and available by name in `namedEntities`. Additionally, child templates will
|
20519 | * be analyzed and have their child `Scope`s available in `childScopes`.
|
20520 | */
|
20521 | class Scope {
|
20522 | constructor(parentScope, template) {
|
20523 | this.parentScope = parentScope;
|
20524 | this.template = template;
|
20525 | /**
|
20526 | * Named members of the `Scope`, such as `Reference`s or `Variable`s.
|
20527 | */
|
20528 | this.namedEntities = new Map();
|
20529 | /**
|
20530 | * Child `Scope`s for immediately nested `Template`s.
|
20531 | */
|
20532 | this.childScopes = new Map();
|
20533 | }
|
20534 | static newRootScope() {
|
20535 | return new Scope(null, null);
|
20536 | }
|
20537 | /**
|
20538 | * Process a template (either as a `Template` sub-template with variables, or a plain array of
|
20539 | * template `Node`s) and construct its `Scope`.
|
20540 | */
|
20541 | static apply(template) {
|
20542 | const scope = Scope.newRootScope();
|
20543 | scope.ingest(template);
|
20544 | return scope;
|
20545 | }
|
20546 | /**
|
20547 | * Internal method to process the template and populate the `Scope`.
|
20548 | */
|
20549 | ingest(template) {
|
20550 | if (template instanceof Template) {
|
20551 | // Variables on an <ng-template> are defined in the inner scope.
|
20552 | template.variables.forEach(node => this.visitVariable(node));
|
20553 | // Process the nodes of the template.
|
20554 | template.children.forEach(node => node.visit(this));
|
20555 | }
|
20556 | else {
|
20557 | // No overarching `Template` instance, so process the nodes directly.
|
20558 | template.forEach(node => node.visit(this));
|
20559 | }
|
20560 | }
|
20561 | visitElement(element) {
|
20562 | // `Element`s in the template may have `Reference`s which are captured in the scope.
|
20563 | element.references.forEach(node => this.visitReference(node));
|
20564 | // Recurse into the `Element`'s children.
|
20565 | element.children.forEach(node => node.visit(this));
|
20566 | }
|
20567 | visitTemplate(template) {
|
20568 | // References on a <ng-template> are defined in the outer scope, so capture them before
|
20569 | // processing the template's child scope.
|
20570 | template.references.forEach(node => this.visitReference(node));
|
20571 | // Next, create an inner scope and process the template within it.
|
20572 | const scope = new Scope(this, template);
|
20573 | scope.ingest(template);
|
20574 | this.childScopes.set(template, scope);
|
20575 | }
|
20576 | visitVariable(variable) {
|
20577 | // Declare the variable if it's not already.
|
20578 | this.maybeDeclare(variable);
|
20579 | }
|
20580 | visitReference(reference) {
|
20581 | // Declare the variable if it's not already.
|
20582 | this.maybeDeclare(reference);
|
20583 | }
|
20584 | // Unused visitors.
|
20585 | visitContent(content) { }
|
20586 | visitBoundAttribute(attr) { }
|
20587 | visitBoundEvent(event) { }
|
20588 | visitBoundText(text) { }
|
20589 | visitText(text) { }
|
20590 | visitTextAttribute(attr) { }
|
20591 | visitIcu(icu) { }
|
20592 | maybeDeclare(thing) {
|
20593 | // Declare something with a name, as long as that name isn't taken.
|
20594 | if (!this.namedEntities.has(thing.name)) {
|
20595 | this.namedEntities.set(thing.name, thing);
|
20596 | }
|
20597 | }
|
20598 | /**
|
20599 | * Look up a variable within this `Scope`.
|
20600 | *
|
20601 | * This can recurse into a parent `Scope` if it's available.
|
20602 | */
|
20603 | lookup(name) {
|
20604 | if (this.namedEntities.has(name)) {
|
20605 | // Found in the local scope.
|
20606 | return this.namedEntities.get(name);
|
20607 | }
|
20608 | else if (this.parentScope !== null) {
|
20609 | // Not in the local scope, but there's a parent scope so check there.
|
20610 | return this.parentScope.lookup(name);
|
20611 | }
|
20612 | else {
|
20613 | // At the top level and it wasn't found.
|
20614 | return null;
|
20615 | }
|
20616 | }
|
20617 | /**
|
20618 | * Get the child scope for a `Template`.
|
20619 | *
|
20620 | * This should always be defined.
|
20621 | */
|
20622 | getChildScope(template) {
|
20623 | const res = this.childScopes.get(template);
|
20624 | if (res === undefined) {
|
20625 | throw new Error(`Assertion error: child scope for ${template} not found`);
|
20626 | }
|
20627 | return res;
|
20628 | }
|
20629 | }
|
20630 | /**
|
20631 | * Processes a template and matches directives on nodes (elements and templates).
|
20632 | *
|
20633 | * Usually used via the static `apply()` method.
|
20634 | */
|
20635 | class DirectiveBinder {
|
20636 | constructor(matcher, directives, bindings, references) {
|
20637 | this.matcher = matcher;
|
20638 | this.directives = directives;
|
20639 | this.bindings = bindings;
|
20640 | this.references = references;
|
20641 | }
|
20642 | /**
|
20643 | * Process a template (list of `Node`s) and perform directive matching against each node.
|
20644 | *
|
20645 | * @param template the list of template `Node`s to match (recursively).
|
20646 | * @param selectorMatcher a `SelectorMatcher` containing the directives that are in scope for
|
20647 | * this template.
|
20648 | * @returns three maps which contain information about directives in the template: the
|
20649 | * `directives` map which lists directives matched on each node, the `bindings` map which
|
20650 | * indicates which directives claimed which bindings (inputs, outputs, etc), and the `references`
|
20651 | * map which resolves #references (`Reference`s) within the template to the named directive or
|
20652 | * template node.
|
20653 | */
|
20654 | static apply(template, selectorMatcher) {
|
20655 | const directives = new Map();
|
20656 | const bindings = new Map();
|
20657 | const references = new Map();
|
20658 | const matcher = new DirectiveBinder(selectorMatcher, directives, bindings, references);
|
20659 | matcher.ingest(template);
|
20660 | return { directives, bindings, references };
|
20661 | }
|
20662 | ingest(template) {
|
20663 | template.forEach(node => node.visit(this));
|
20664 | }
|
20665 | visitElement(element) {
|
20666 | this.visitElementOrTemplate(element.name, element);
|
20667 | }
|
20668 | visitTemplate(template) {
|
20669 | this.visitElementOrTemplate('ng-template', template);
|
20670 | }
|
20671 | visitElementOrTemplate(elementName, node) {
|
20672 | // First, determine the HTML shape of the node for the purpose of directive matching.
|
20673 | // Do this by building up a `CssSelector` for the node.
|
20674 | const cssSelector = createCssSelector(elementName, getAttrsForDirectiveMatching(node));
|
20675 | // Next, use the `SelectorMatcher` to get the list of directives on the node.
|
20676 | const directives = [];
|
20677 | this.matcher.match(cssSelector, (_, directive) => directives.push(directive));
|
20678 | if (directives.length > 0) {
|
20679 | this.directives.set(node, directives);
|
20680 | }
|
20681 | // Resolve any references that are created on this node.
|
20682 | node.references.forEach(ref => {
|
20683 | let dirTarget = null;
|
20684 | // If the reference expression is empty, then it matches the "primary" directive on the node
|
20685 | // (if there is one). Otherwise it matches the host node itself (either an element or
|
20686 | // <ng-template> node).
|
20687 | if (ref.value.trim() === '') {
|
20688 | // This could be a reference to a component if there is one.
|
20689 | dirTarget = directives.find(dir => dir.isComponent) || null;
|
20690 | }
|
20691 | else {
|
20692 | // This should be a reference to a directive exported via exportAs.
|
20693 | dirTarget =
|
20694 | directives.find(dir => dir.exportAs !== null && dir.exportAs.some(value => value === ref.value)) ||
|
20695 | null;
|
20696 | // Check if a matching directive was found.
|
20697 | if (dirTarget === null) {
|
20698 | // No matching directive was found - this reference points to an unknown target. Leave it
|
20699 | // unmapped.
|
20700 | return;
|
20701 | }
|
20702 | }
|
20703 | if (dirTarget !== null) {
|
20704 | // This reference points to a directive.
|
20705 | this.references.set(ref, { directive: dirTarget, node });
|
20706 | }
|
20707 | else {
|
20708 | // This reference points to the node itself.
|
20709 | this.references.set(ref, node);
|
20710 | }
|
20711 | });
|
20712 | const setAttributeBinding = (attribute, ioType) => {
|
20713 | const dir = directives.find(dir => dir[ioType].hasBindingPropertyName(attribute.name));
|
20714 | const binding = dir !== undefined ? dir : node;
|
20715 | this.bindings.set(attribute, binding);
|
20716 | };
|
20717 | // Node inputs (bound attributes) and text attributes can be bound to an
|
20718 | // input on a directive.
|
20719 | node.inputs.forEach(input => setAttributeBinding(input, 'inputs'));
|
20720 | node.attributes.forEach(attr => setAttributeBinding(attr, 'inputs'));
|
20721 | if (node instanceof Template) {
|
20722 | node.templateAttrs.forEach(attr => setAttributeBinding(attr, 'inputs'));
|
20723 | }
|
20724 | // Node outputs (bound events) can be bound to an output on a directive.
|
20725 | node.outputs.forEach(output => setAttributeBinding(output, 'outputs'));
|
20726 | // Recurse into the node's children.
|
20727 | node.children.forEach(child => child.visit(this));
|
20728 | }
|
20729 | // Unused visitors.
|
20730 | visitContent(content) { }
|
20731 | visitVariable(variable) { }
|
20732 | visitReference(reference) { }
|
20733 | visitTextAttribute(attribute) { }
|
20734 | visitBoundAttribute(attribute) { }
|
20735 | visitBoundEvent(attribute) { }
|
20736 | visitBoundAttributeOrEvent(node) { }
|
20737 | visitText(text) { }
|
20738 | visitBoundText(text) { }
|
20739 | visitIcu(icu) { }
|
20740 | }
|
20741 | /**
|
20742 | * Processes a template and extract metadata about expressions and symbols within.
|
20743 | *
|
20744 | * This is a companion to the `DirectiveBinder` that doesn't require knowledge of directives matched
|
20745 | * within the template in order to operate.
|
20746 | *
|
20747 | * Expressions are visited by the superclass `RecursiveAstVisitor`, with custom logic provided
|
20748 | * by overridden methods from that visitor.
|
20749 | */
|
20750 | class TemplateBinder extends RecursiveAstVisitor {
|
20751 | constructor(bindings, symbols, usedPipes, nestingLevel, scope, template, level) {
|
20752 | super();
|
20753 | this.bindings = bindings;
|
20754 | this.symbols = symbols;
|
20755 | this.usedPipes = usedPipes;
|
20756 | this.nestingLevel = nestingLevel;
|
20757 | this.scope = scope;
|
20758 | this.template = template;
|
20759 | this.level = level;
|
20760 | this.pipesUsed = [];
|
20761 | // Save a bit of processing time by constructing this closure in advance.
|
20762 | this.visitNode = (node) => node.visit(this);
|
20763 | }
|
20764 | // This method is defined to reconcile the type of TemplateBinder since both
|
20765 | // RecursiveAstVisitor and Visitor define the visit() method in their
|
20766 | // interfaces.
|
20767 | visit(node, context) {
|
20768 | if (node instanceof AST) {
|
20769 | node.visit(this, context);
|
20770 | }
|
20771 | else {
|
20772 | node.visit(this);
|
20773 | }
|
20774 | }
|
20775 | /**
|
20776 | * Process a template and extract metadata about expressions and symbols within.
|
20777 | *
|
20778 | * @param template the nodes of the template to process
|
20779 | * @param scope the `Scope` of the template being processed.
|
20780 | * @returns three maps which contain metadata about the template: `expressions` which interprets
|
20781 | * special `AST` nodes in expressions as pointing to references or variables declared within the
|
20782 | * template, `symbols` which maps those variables and references to the nested `Template` which
|
20783 | * declares them, if any, and `nestingLevel` which associates each `Template` with a integer
|
20784 | * nesting level (how many levels deep within the template structure the `Template` is), starting
|
20785 | * at 1.
|
20786 | */
|
20787 | static apply(template, scope) {
|
20788 | const expressions = new Map();
|
20789 | const symbols = new Map();
|
20790 | const nestingLevel = new Map();
|
20791 | const usedPipes = new Set();
|
20792 | // The top-level template has nesting level 0.
|
20793 | const binder = new TemplateBinder(expressions, symbols, usedPipes, nestingLevel, scope, template instanceof Template ? template : null, 0);
|
20794 | binder.ingest(template);
|
20795 | return { expressions, symbols, nestingLevel, usedPipes };
|
20796 | }
|
20797 | ingest(template) {
|
20798 | if (template instanceof Template) {
|
20799 | // For <ng-template>s, process only variables and child nodes. Inputs, outputs, templateAttrs,
|
20800 | // and references were all processed in the scope of the containing template.
|
20801 | template.variables.forEach(this.visitNode);
|
20802 | template.children.forEach(this.visitNode);
|
20803 | // Set the nesting level.
|
20804 | this.nestingLevel.set(template, this.level);
|
20805 | }
|
20806 | else {
|
20807 | // Visit each node from the top-level template.
|
20808 | template.forEach(this.visitNode);
|
20809 | }
|
20810 | }
|
20811 | visitElement(element) {
|
20812 | // Visit the inputs, outputs, and children of the element.
|
20813 | element.inputs.forEach(this.visitNode);
|
20814 | element.outputs.forEach(this.visitNode);
|
20815 | element.children.forEach(this.visitNode);
|
20816 | }
|
20817 | visitTemplate(template) {
|
20818 | // First, visit inputs, outputs and template attributes of the template node.
|
20819 | template.inputs.forEach(this.visitNode);
|
20820 | template.outputs.forEach(this.visitNode);
|
20821 | template.templateAttrs.forEach(this.visitNode);
|
20822 | // References are also evaluated in the outer context.
|
20823 | template.references.forEach(this.visitNode);
|
20824 | // Next, recurse into the template using its scope, and bumping the nesting level up by one.
|
20825 | const childScope = this.scope.getChildScope(template);
|
20826 | const binder = new TemplateBinder(this.bindings, this.symbols, this.usedPipes, this.nestingLevel, childScope, template, this.level + 1);
|
20827 | binder.ingest(template);
|
20828 | }
|
20829 | visitVariable(variable) {
|
20830 | // Register the `Variable` as a symbol in the current `Template`.
|
20831 | if (this.template !== null) {
|
20832 | this.symbols.set(variable, this.template);
|
20833 | }
|
20834 | }
|
20835 | visitReference(reference) {
|
20836 | // Register the `Reference` as a symbol in the current `Template`.
|
20837 | if (this.template !== null) {
|
20838 | this.symbols.set(reference, this.template);
|
20839 | }
|
20840 | }
|
20841 | // Unused template visitors
|
20842 | visitText(text) { }
|
20843 | visitContent(content) { }
|
20844 | visitTextAttribute(attribute) { }
|
20845 | visitIcu(icu) {
|
20846 | Object.keys(icu.vars).forEach(key => icu.vars[key].visit(this));
|
20847 | Object.keys(icu.placeholders).forEach(key => icu.placeholders[key].visit(this));
|
20848 | }
|
20849 | // The remaining visitors are concerned with processing AST expressions within template bindings
|
20850 | visitBoundAttribute(attribute) {
|
20851 | attribute.value.visit(this);
|
20852 | }
|
20853 | visitBoundEvent(event) {
|
20854 | event.handler.visit(this);
|
20855 | }
|
20856 | visitBoundText(text) {
|
20857 | text.value.visit(this);
|
20858 | }
|
20859 | visitPipe(ast, context) {
|
20860 | this.usedPipes.add(ast.name);
|
20861 | return super.visitPipe(ast, context);
|
20862 | }
|
20863 | // These five types of AST expressions can refer to expression roots, which could be variables
|
20864 | // or references in the current scope.
|
20865 | visitPropertyRead(ast, context) {
|
20866 | this.maybeMap(context, ast, ast.name);
|
20867 | return super.visitPropertyRead(ast, context);
|
20868 | }
|
20869 | visitSafePropertyRead(ast, context) {
|
20870 | this.maybeMap(context, ast, ast.name);
|
20871 | return super.visitSafePropertyRead(ast, context);
|
20872 | }
|
20873 | visitPropertyWrite(ast, context) {
|
20874 | this.maybeMap(context, ast, ast.name);
|
20875 | return super.visitPropertyWrite(ast, context);
|
20876 | }
|
20877 | visitMethodCall(ast, context) {
|
20878 | this.maybeMap(context, ast, ast.name);
|
20879 | return super.visitMethodCall(ast, context);
|
20880 | }
|
20881 | visitSafeMethodCall(ast, context) {
|
20882 | this.maybeMap(context, ast, ast.name);
|
20883 | return super.visitSafeMethodCall(ast, context);
|
20884 | }
|
20885 | maybeMap(scope, ast, name) {
|
20886 | // If the receiver of the expression isn't the `ImplicitReceiver`, this isn't the root of an
|
20887 | // `AST` expression that maps to a `Variable` or `Reference`.
|
20888 | if (!(ast.receiver instanceof ImplicitReceiver)) {
|
20889 | return;
|
20890 | }
|
20891 | // Check whether the name exists in the current scope. If so, map it. Otherwise, the name is
|
20892 | // probably a property on the top-level component context.
|
20893 | let target = this.scope.lookup(name);
|
20894 | if (target !== null) {
|
20895 | this.bindings.set(ast, target);
|
20896 | }
|
20897 | }
|
20898 | }
|
20899 | /**
|
20900 | * Metadata container for a `Target` that allows queries for specific bits of metadata.
|
20901 | *
|
20902 | * See `BoundTarget` for documentation on the individual methods.
|
20903 | */
|
20904 | class R3BoundTarget {
|
20905 | constructor(target, directives, bindings, references, exprTargets, symbols, nestingLevel, templateEntities, usedPipes) {
|
20906 | this.target = target;
|
20907 | this.directives = directives;
|
20908 | this.bindings = bindings;
|
20909 | this.references = references;
|
20910 | this.exprTargets = exprTargets;
|
20911 | this.symbols = symbols;
|
20912 | this.nestingLevel = nestingLevel;
|
20913 | this.templateEntities = templateEntities;
|
20914 | this.usedPipes = usedPipes;
|
20915 | }
|
20916 | getEntitiesInTemplateScope(template) {
|
20917 | var _a;
|
20918 | return (_a = this.templateEntities.get(template)) !== null && _a !== void 0 ? _a : new Set();
|
20919 | }
|
20920 | getDirectivesOfNode(node) {
|
20921 | return this.directives.get(node) || null;
|
20922 | }
|
20923 | getReferenceTarget(ref) {
|
20924 | return this.references.get(ref) || null;
|
20925 | }
|
20926 | getConsumerOfBinding(binding) {
|
20927 | return this.bindings.get(binding) || null;
|
20928 | }
|
20929 | getExpressionTarget(expr) {
|
20930 | return this.exprTargets.get(expr) || null;
|
20931 | }
|
20932 | getTemplateOfSymbol(symbol) {
|
20933 | return this.symbols.get(symbol) || null;
|
20934 | }
|
20935 | getNestingLevel(template) {
|
20936 | return this.nestingLevel.get(template) || 0;
|
20937 | }
|
20938 | getUsedDirectives() {
|
20939 | const set = new Set();
|
20940 | this.directives.forEach(dirs => dirs.forEach(dir => set.add(dir)));
|
20941 | return Array.from(set.values());
|
20942 | }
|
20943 | getUsedPipes() {
|
20944 | return Array.from(this.usedPipes);
|
20945 | }
|
20946 | }
|
20947 | function extractTemplateEntities(rootScope) {
|
20948 | const entityMap = new Map();
|
20949 | function extractScopeEntities(scope) {
|
20950 | if (entityMap.has(scope.template)) {
|
20951 | return entityMap.get(scope.template);
|
20952 | }
|
20953 | const currentEntities = scope.namedEntities;
|
20954 | let templateEntities;
|
20955 | if (scope.parentScope !== null) {
|
20956 | templateEntities = new Map([...extractScopeEntities(scope.parentScope), ...currentEntities]);
|
20957 | }
|
20958 | else {
|
20959 | templateEntities = new Map(currentEntities);
|
20960 | }
|
20961 | entityMap.set(scope.template, templateEntities);
|
20962 | return templateEntities;
|
20963 | }
|
20964 | const scopesToProcess = [rootScope];
|
20965 | while (scopesToProcess.length > 0) {
|
20966 | const scope = scopesToProcess.pop();
|
20967 | for (const childScope of scope.childScopes.values()) {
|
20968 | scopesToProcess.push(childScope);
|
20969 | }
|
20970 | extractScopeEntities(scope);
|
20971 | }
|
20972 | const templateEntities = new Map();
|
20973 | for (const [template, entities] of entityMap) {
|
20974 | templateEntities.set(template, new Set(entities.values()));
|
20975 | }
|
20976 | return templateEntities;
|
20977 | }
|
20978 |
|
20979 | /**
|
20980 | * @license
|
20981 | * Copyright Google LLC All Rights Reserved.
|
20982 | *
|
20983 | * Use of this source code is governed by an MIT-style license that can be
|
20984 | * found in the LICENSE file at https://angular.io/license
|
20985 | */
|
20986 | /**
|
20987 | * Creates an array literal expression from the given array, mapping all values to an expression
|
20988 | * using the provided mapping function. If the array is empty or null, then null is returned.
|
20989 | *
|
20990 | * @param values The array to transfer into literal array expression.
|
20991 | * @param mapper The logic to use for creating an expression for the array's values.
|
20992 | * @returns An array literal expression representing `values`, or null if `values` is empty or
|
20993 | * is itself null.
|
20994 | */
|
20995 | function toOptionalLiteralArray(values, mapper) {
|
20996 | if (values === null || values.length === 0) {
|
20997 | return null;
|
20998 | }
|
20999 | return literalArr(values.map(value => mapper(value)));
|
21000 | }
|
21001 | /**
|
21002 | * Creates an object literal expression from the given object, mapping all values to an expression
|
21003 | * using the provided mapping function. If the object has no keys, then null is returned.
|
21004 | *
|
21005 | * @param object The object to transfer into an object literal expression.
|
21006 | * @param mapper The logic to use for creating an expression for the object's values.
|
21007 | * @returns An object literal expression representing `object`, or null if `object` does not have
|
21008 | * any keys.
|
21009 | */
|
21010 | function toOptionalLiteralMap(object, mapper) {
|
21011 | const entries = Object.keys(object).map(key => {
|
21012 | const value = object[key];
|
21013 | return { key, value: mapper(value), quoted: true };
|
21014 | });
|
21015 | if (entries.length > 0) {
|
21016 | return literalMap(entries);
|
21017 | }
|
21018 | else {
|
21019 | return null;
|
21020 | }
|
21021 | }
|
21022 |
|
21023 | /**
|
21024 | * @license
|
21025 | * Copyright Google LLC All Rights Reserved.
|
21026 | *
|
21027 | * Use of this source code is governed by an MIT-style license that can be
|
21028 | * found in the LICENSE file at https://angular.io/license
|
21029 | */
|
21030 | /**
|
21031 | * Compile a directive declaration defined by the `R3DirectiveMetadata`.
|
21032 | */
|
21033 | function compileDeclareDirectiveFromMetadata(meta) {
|
21034 | const definitionMap = createDirectiveDefinitionMap(meta);
|
21035 | const expression = importExpr(Identifiers$1.declareDirective).callFn([definitionMap.toLiteralMap()]);
|
21036 | const type = createDirectiveType(meta);
|
21037 | return { expression, type };
|
21038 | }
|
21039 | /**
|
21040 | * Gathers the declaration fields for a directive into a `DefinitionMap`. This allows for reusing
|
21041 | * this logic for components, as they extend the directive metadata.
|
21042 | */
|
21043 | function createDirectiveDefinitionMap(meta) {
|
21044 | const definitionMap = new DefinitionMap();
|
21045 | definitionMap.set('version', literal('11.2.4'));
|
21046 | // e.g. `type: MyDirective`
|
21047 | definitionMap.set('type', meta.internalType);
|
21048 | // e.g. `selector: 'some-dir'`
|
21049 | if (meta.selector !== null) {
|
21050 | definitionMap.set('selector', literal(meta.selector));
|
21051 | }
|
21052 | definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs, true));
|
21053 | definitionMap.set('outputs', conditionallyCreateMapObjectLiteral(meta.outputs));
|
21054 | definitionMap.set('host', compileHostMetadata(meta.host));
|
21055 | definitionMap.set('providers', meta.providers);
|
21056 | if (meta.queries.length > 0) {
|
21057 | definitionMap.set('queries', literalArr(meta.queries.map(compileQuery)));
|
21058 | }
|
21059 | if (meta.viewQueries.length > 0) {
|
21060 | definitionMap.set('viewQueries', literalArr(meta.viewQueries.map(compileQuery)));
|
21061 | }
|
21062 | if (meta.exportAs !== null) {
|
21063 | definitionMap.set('exportAs', asLiteral(meta.exportAs));
|
21064 | }
|
21065 | if (meta.usesInheritance) {
|
21066 | definitionMap.set('usesInheritance', literal(true));
|
21067 | }
|
21068 | if (meta.lifecycle.usesOnChanges) {
|
21069 | definitionMap.set('usesOnChanges', literal(true));
|
21070 | }
|
21071 | definitionMap.set('ngImport', importExpr(Identifiers$1.core));
|
21072 | return definitionMap;
|
21073 | }
|
21074 | /**
|
21075 | * Compiles the metadata of a single query into its partial declaration form as declared
|
21076 | * by `R3DeclareQueryMetadata`.
|
21077 | */
|
21078 | function compileQuery(query) {
|
21079 | const meta = new DefinitionMap();
|
21080 | meta.set('propertyName', literal(query.propertyName));
|
21081 | if (query.first) {
|
21082 | meta.set('first', literal(true));
|
21083 | }
|
21084 | meta.set('predicate', Array.isArray(query.predicate) ? asLiteral(query.predicate) : query.predicate);
|
21085 | if (!query.emitDistinctChangesOnly) {
|
21086 | // `emitDistinctChangesOnly` is special because in future we expect it to be `true`. For this
|
21087 | // reason the absence should be interpreted as `true`.
|
21088 | meta.set('emitDistinctChangesOnly', literal(false));
|
21089 | }
|
21090 | if (query.descendants) {
|
21091 | meta.set('descendants', literal(true));
|
21092 | }
|
21093 | meta.set('read', query.read);
|
21094 | if (query.static) {
|
21095 | meta.set('static', literal(true));
|
21096 | }
|
21097 | return meta.toLiteralMap();
|
21098 | }
|
21099 | /**
|
21100 | * Compiles the host metadata into its partial declaration form as declared
|
21101 | * in `R3DeclareDirectiveMetadata['host']`
|
21102 | */
|
21103 | function compileHostMetadata(meta) {
|
21104 | const hostMetadata = new DefinitionMap();
|
21105 | hostMetadata.set('attributes', toOptionalLiteralMap(meta.attributes, expression => expression));
|
21106 | hostMetadata.set('listeners', toOptionalLiteralMap(meta.listeners, literal));
|
21107 | hostMetadata.set('properties', toOptionalLiteralMap(meta.properties, literal));
|
21108 | if (meta.specialAttributes.styleAttr) {
|
21109 | hostMetadata.set('styleAttribute', literal(meta.specialAttributes.styleAttr));
|
21110 | }
|
21111 | if (meta.specialAttributes.classAttr) {
|
21112 | hostMetadata.set('classAttribute', literal(meta.specialAttributes.classAttr));
|
21113 | }
|
21114 | if (hostMetadata.values.length > 0) {
|
21115 | return hostMetadata.toLiteralMap();
|
21116 | }
|
21117 | else {
|
21118 | return null;
|
21119 | }
|
21120 | }
|
21121 |
|
21122 | /**
|
21123 | * @license
|
21124 | * Copyright Google LLC All Rights Reserved.
|
21125 | *
|
21126 | * Use of this source code is governed by an MIT-style license that can be
|
21127 | * found in the LICENSE file at https://angular.io/license
|
21128 | */
|
21129 | /**
|
21130 | * Compile a component declaration defined by the `R3ComponentMetadata`.
|
21131 | */
|
21132 | function compileDeclareComponentFromMetadata(meta, template) {
|
21133 | const definitionMap = createComponentDefinitionMap(meta, template);
|
21134 | const expression = importExpr(Identifiers$1.declareComponent).callFn([definitionMap.toLiteralMap()]);
|
21135 | const type = createComponentType(meta);
|
21136 | return { expression, type };
|
21137 | }
|
21138 | /**
|
21139 | * Gathers the declaration fields for a component into a `DefinitionMap`.
|
21140 | */
|
21141 | function createComponentDefinitionMap(meta, template) {
|
21142 | const definitionMap = createDirectiveDefinitionMap(meta);
|
21143 | definitionMap.set('template', getTemplateExpression(template));
|
21144 | if (template.isInline) {
|
21145 | definitionMap.set('isInline', literal(true));
|
21146 | }
|
21147 | definitionMap.set('styles', toOptionalLiteralArray(meta.styles, literal));
|
21148 | definitionMap.set('directives', compileUsedDirectiveMetadata(meta));
|
21149 | definitionMap.set('pipes', compileUsedPipeMetadata(meta));
|
21150 | definitionMap.set('viewProviders', meta.viewProviders);
|
21151 | definitionMap.set('animations', meta.animations);
|
21152 | if (meta.changeDetection !== undefined) {
|
21153 | definitionMap.set('changeDetection', importExpr(Identifiers$1.ChangeDetectionStrategy)
|
21154 | .prop(ChangeDetectionStrategy[meta.changeDetection]));
|
21155 | }
|
21156 | if (meta.encapsulation !== ViewEncapsulation.Emulated) {
|
21157 | definitionMap.set('encapsulation', importExpr(Identifiers$1.ViewEncapsulation).prop(ViewEncapsulation[meta.encapsulation]));
|
21158 | }
|
21159 | if (meta.interpolation !== DEFAULT_INTERPOLATION_CONFIG) {
|
21160 | definitionMap.set('interpolation', literalArr([literal(meta.interpolation.start), literal(meta.interpolation.end)]));
|
21161 | }
|
21162 | if (template.preserveWhitespaces === true) {
|
21163 | definitionMap.set('preserveWhitespaces', literal(true));
|
21164 | }
|
21165 | return definitionMap;
|
21166 | }
|
21167 | function getTemplateExpression(template) {
|
21168 | if (typeof template.template === 'string') {
|
21169 | if (template.isInline) {
|
21170 | // The template is inline but not a simple literal string, so give up with trying to
|
21171 | // source-map it and just return a simple literal here.
|
21172 | return literal(template.template);
|
21173 | }
|
21174 | else {
|
21175 | // The template is external so we must synthesize an expression node with the appropriate
|
21176 | // source-span.
|
21177 | const contents = template.template;
|
21178 | const file = new ParseSourceFile(contents, template.templateUrl);
|
21179 | const start = new ParseLocation(file, 0, 0, 0);
|
21180 | const end = computeEndLocation(file, contents);
|
21181 | const span = new ParseSourceSpan(start, end);
|
21182 | return literal(contents, null, span);
|
21183 | }
|
21184 | }
|
21185 | else {
|
21186 | // The template is inline so we can just reuse the current expression node.
|
21187 | return template.template;
|
21188 | }
|
21189 | }
|
21190 | function computeEndLocation(file, contents) {
|
21191 | const length = contents.length;
|
21192 | let lineStart = 0;
|
21193 | let lastLineStart = 0;
|
21194 | let line = 0;
|
21195 | do {
|
21196 | lineStart = contents.indexOf('\n', lastLineStart);
|
21197 | if (lineStart !== -1) {
|
21198 | lastLineStart = lineStart + 1;
|
21199 | line++;
|
21200 | }
|
21201 | } while (lineStart !== -1);
|
21202 | return new ParseLocation(file, length, line, length - lastLineStart);
|
21203 | }
|
21204 | /**
|
21205 | * Compiles the directives as registered in the component metadata into an array literal of the
|
21206 | * individual directives. If the component does not use any directives, then null is returned.
|
21207 | */
|
21208 | function compileUsedDirectiveMetadata(meta) {
|
21209 | const wrapType = meta.declarationListEmitMode !== 0 /* Direct */ ?
|
21210 | generateForwardRef :
|
21211 | (expr) => expr;
|
21212 | return toOptionalLiteralArray(meta.directives, directive => {
|
21213 | const dirMeta = new DefinitionMap();
|
21214 | dirMeta.set('type', wrapType(directive.type));
|
21215 | dirMeta.set('selector', literal(directive.selector));
|
21216 | dirMeta.set('inputs', toOptionalLiteralArray(directive.inputs, literal));
|
21217 | dirMeta.set('outputs', toOptionalLiteralArray(directive.outputs, literal));
|
21218 | dirMeta.set('exportAs', toOptionalLiteralArray(directive.exportAs, literal));
|
21219 | return dirMeta.toLiteralMap();
|
21220 | });
|
21221 | }
|
21222 | /**
|
21223 | * Compiles the pipes as registered in the component metadata into an object literal, where the
|
21224 | * pipe's name is used as key and a reference to its type as value. If the component does not use
|
21225 | * any pipes, then null is returned.
|
21226 | */
|
21227 | function compileUsedPipeMetadata(meta) {
|
21228 | if (meta.pipes.size === 0) {
|
21229 | return null;
|
21230 | }
|
21231 | const wrapType = meta.declarationListEmitMode !== 0 /* Direct */ ?
|
21232 | generateForwardRef :
|
21233 | (expr) => expr;
|
21234 | const entries = [];
|
21235 | for (const [name, pipe] of meta.pipes) {
|
21236 | entries.push({ key: name, value: wrapType(pipe), quoted: true });
|
21237 | }
|
21238 | return literalMap(entries);
|
21239 | }
|
21240 | function generateForwardRef(expr) {
|
21241 | return importExpr(Identifiers$1.forwardRef).callFn([fn([], [new ReturnStatement(expr)])]);
|
21242 | }
|
21243 |
|
21244 | /**
|
21245 | * @license
|
21246 | * Copyright Google LLC All Rights Reserved.
|
21247 | *
|
21248 | * Use of this source code is governed by an MIT-style license that can be
|
21249 | * found in the LICENSE file at https://angular.io/license
|
21250 | */
|
21251 | /**
|
21252 | * Compile a Pipe declaration defined by the `R3PipeMetadata`.
|
21253 | */
|
21254 | function compileDeclarePipeFromMetadata(meta) {
|
21255 | const definitionMap = createPipeDefinitionMap(meta);
|
21256 | const expression = importExpr(Identifiers$1.declarePipe).callFn([definitionMap.toLiteralMap()]);
|
21257 | const type = createPipeType(meta);
|
21258 | return { expression, type };
|
21259 | }
|
21260 | /**
|
21261 | * Gathers the declaration fields for a Pipe into a `DefinitionMap`. This allows for reusing
|
21262 | * this logic for components, as they extend the Pipe metadata.
|
21263 | */
|
21264 | function createPipeDefinitionMap(meta) {
|
21265 | const definitionMap = new DefinitionMap();
|
21266 | definitionMap.set('version', literal('11.2.4'));
|
21267 | definitionMap.set('ngImport', importExpr(Identifiers$1.core));
|
21268 | // e.g. `type: MyPipe`
|
21269 | definitionMap.set('type', meta.internalType);
|
21270 | // e.g. `name: "myPipe"`
|
21271 | definitionMap.set('name', literal(meta.pipeName));
|
21272 | if (meta.pure === false) {
|
21273 | // e.g. `pure: false`
|
21274 | definitionMap.set('pure', literal(meta.pure));
|
21275 | }
|
21276 | return definitionMap;
|
21277 | }
|
21278 |
|
21279 | /**
|
21280 | * @license
|
21281 | * Copyright Google LLC All Rights Reserved.
|
21282 | *
|
21283 | * Use of this source code is governed by an MIT-style license that can be
|
21284 | * found in the LICENSE file at https://angular.io/license
|
21285 | */
|
21286 | // This file only reexports content of the `src` folder. Keep it that way.
|
21287 | // This function call has a global side effects and publishes the compiler into global namespace for
|
21288 | // the late binding of the Compiler to the @angular/core for jit compilation.
|
21289 | publishFacade(_global);
|
21290 |
|
21291 | /**
|
21292 | * @license
|
21293 | * Copyright Google LLC All Rights Reserved.
|
21294 | *
|
21295 | * Use of this source code is governed by an MIT-style license that can be
|
21296 | * found in the LICENSE file at https://angular.io/license
|
21297 | */
|
21298 | const VERSION$2 = new Version('11.2.4');
|
21299 |
|
21300 | /**
|
21301 | * @license
|
21302 | * Copyright Google LLC All Rights Reserved.
|
21303 | *
|
21304 | * Use of this source code is governed by an MIT-style license that can be
|
21305 | * found in the LICENSE file at https://angular.io/license
|
21306 | */
|
21307 | // In TypeScript 2.1 the spread element kind was renamed.
|
21308 | const spreadElementSyntaxKind = ts$1.SyntaxKind.SpreadElement || ts$1.SyntaxKind.SpreadElementExpression;
|
21309 | const empty$1 = ts$1.createNodeArray();
|
21310 |
|
21311 | /**
|
21312 | * @license
|
21313 | * Copyright Google LLC All Rights Reserved.
|
21314 | *
|
21315 | * Use of this source code is governed by an MIT-style license that can be
|
21316 | * found in the LICENSE file at https://angular.io/license
|
21317 | */
|
21318 | const UNKNOWN_ERROR_CODE = 500;
|
21319 | var EmitFlags;
|
21320 | (function (EmitFlags) {
|
21321 | EmitFlags[EmitFlags["DTS"] = 1] = "DTS";
|
21322 | EmitFlags[EmitFlags["JS"] = 2] = "JS";
|
21323 | EmitFlags[EmitFlags["Metadata"] = 4] = "Metadata";
|
21324 | EmitFlags[EmitFlags["I18nBundle"] = 8] = "I18nBundle";
|
21325 | EmitFlags[EmitFlags["Codegen"] = 16] = "Codegen";
|
21326 | EmitFlags[EmitFlags["Default"] = 19] = "Default";
|
21327 | EmitFlags[EmitFlags["All"] = 31] = "All";
|
21328 | })(EmitFlags || (EmitFlags = {}));
|
21329 |
|
21330 | /*! *****************************************************************************
|
21331 | Copyright (c) Microsoft Corporation.
|
21332 |
|
21333 | Permission to use, copy, modify, and/or distribute this software for any
|
21334 | purpose with or without fee is hereby granted.
|
21335 |
|
21336 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
21337 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
21338 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
21339 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
21340 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
21341 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
21342 | PERFORMANCE OF THIS SOFTWARE.
|
21343 | ***************************************************************************** */
|
21344 |
|
21345 | function __awaiter(thisArg, _arguments, P, generator) {
|
21346 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
21347 | return new (P || (P = Promise))(function (resolve, reject) {
|
21348 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
21349 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
21350 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
21351 | step((generator = generator.apply(thisArg, _arguments || [])).next());
|
21352 | });
|
21353 | }
|
21354 |
|
21355 | /**
|
21356 | * @license
|
21357 | * Copyright Google LLC All Rights Reserved.
|
21358 | *
|
21359 | * Use of this source code is governed by an MIT-style license that can be
|
21360 | * found in the LICENSE file at https://angular.io/license
|
21361 | */
|
21362 | /**
|
21363 | * @publicApi
|
21364 | */
|
21365 | var ErrorCode;
|
21366 | (function (ErrorCode) {
|
21367 | ErrorCode[ErrorCode["DECORATOR_ARG_NOT_LITERAL"] = 1001] = "DECORATOR_ARG_NOT_LITERAL";
|
21368 | ErrorCode[ErrorCode["DECORATOR_ARITY_WRONG"] = 1002] = "DECORATOR_ARITY_WRONG";
|
21369 | ErrorCode[ErrorCode["DECORATOR_NOT_CALLED"] = 1003] = "DECORATOR_NOT_CALLED";
|
21370 | ErrorCode[ErrorCode["DECORATOR_ON_ANONYMOUS_CLASS"] = 1004] = "DECORATOR_ON_ANONYMOUS_CLASS";
|
21371 | ErrorCode[ErrorCode["DECORATOR_UNEXPECTED"] = 1005] = "DECORATOR_UNEXPECTED";
|
21372 | /**
|
21373 | * This error code indicates that there are incompatible decorators on a type or a class field.
|
21374 | */
|
21375 | ErrorCode[ErrorCode["DECORATOR_COLLISION"] = 1006] = "DECORATOR_COLLISION";
|
21376 | ErrorCode[ErrorCode["VALUE_HAS_WRONG_TYPE"] = 1010] = "VALUE_HAS_WRONG_TYPE";
|
21377 | ErrorCode[ErrorCode["VALUE_NOT_LITERAL"] = 1011] = "VALUE_NOT_LITERAL";
|
21378 | ErrorCode[ErrorCode["COMPONENT_MISSING_TEMPLATE"] = 2001] = "COMPONENT_MISSING_TEMPLATE";
|
21379 | ErrorCode[ErrorCode["PIPE_MISSING_NAME"] = 2002] = "PIPE_MISSING_NAME";
|
21380 | ErrorCode[ErrorCode["PARAM_MISSING_TOKEN"] = 2003] = "PARAM_MISSING_TOKEN";
|
21381 | ErrorCode[ErrorCode["DIRECTIVE_MISSING_SELECTOR"] = 2004] = "DIRECTIVE_MISSING_SELECTOR";
|
21382 | /** Raised when an undecorated class is passed in as a provider to a module or a directive. */
|
21383 | ErrorCode[ErrorCode["UNDECORATED_PROVIDER"] = 2005] = "UNDECORATED_PROVIDER";
|
21384 | /**
|
21385 | * Raised when a Directive inherits its constructor from a base class without an Angular
|
21386 | * decorator.
|
21387 | */
|
21388 | ErrorCode[ErrorCode["DIRECTIVE_INHERITS_UNDECORATED_CTOR"] = 2006] = "DIRECTIVE_INHERITS_UNDECORATED_CTOR";
|
21389 | /**
|
21390 | * Raised when an undecorated class that is using Angular features
|
21391 | * has been discovered.
|
21392 | */
|
21393 | ErrorCode[ErrorCode["UNDECORATED_CLASS_USING_ANGULAR_FEATURES"] = 2007] = "UNDECORATED_CLASS_USING_ANGULAR_FEATURES";
|
21394 | /**
|
21395 | * Raised when an component cannot resolve an external resource, such as a template or a style
|
21396 | * sheet.
|
21397 | */
|
21398 | ErrorCode[ErrorCode["COMPONENT_RESOURCE_NOT_FOUND"] = 2008] = "COMPONENT_RESOURCE_NOT_FOUND";
|
21399 | ErrorCode[ErrorCode["SYMBOL_NOT_EXPORTED"] = 3001] = "SYMBOL_NOT_EXPORTED";
|
21400 | ErrorCode[ErrorCode["SYMBOL_EXPORTED_UNDER_DIFFERENT_NAME"] = 3002] = "SYMBOL_EXPORTED_UNDER_DIFFERENT_NAME";
|
21401 | /**
|
21402 | * Raised when a relationship between directives and/or pipes would cause a cyclic import to be
|
21403 | * created that cannot be handled, such as in partial compilation mode.
|
21404 | */
|
21405 | ErrorCode[ErrorCode["IMPORT_CYCLE_DETECTED"] = 3003] = "IMPORT_CYCLE_DETECTED";
|
21406 | ErrorCode[ErrorCode["CONFIG_FLAT_MODULE_NO_INDEX"] = 4001] = "CONFIG_FLAT_MODULE_NO_INDEX";
|
21407 | ErrorCode[ErrorCode["CONFIG_STRICT_TEMPLATES_IMPLIES_FULL_TEMPLATE_TYPECHECK"] = 4002] = "CONFIG_STRICT_TEMPLATES_IMPLIES_FULL_TEMPLATE_TYPECHECK";
|
21408 | /**
|
21409 | * Raised when a host expression has a parse error, such as a host listener or host binding
|
21410 | * expression containing a pipe.
|
21411 | */
|
21412 | ErrorCode[ErrorCode["HOST_BINDING_PARSE_ERROR"] = 5001] = "HOST_BINDING_PARSE_ERROR";
|
21413 | /**
|
21414 | * Raised when the compiler cannot parse a component's template.
|
21415 | */
|
21416 | ErrorCode[ErrorCode["TEMPLATE_PARSE_ERROR"] = 5002] = "TEMPLATE_PARSE_ERROR";
|
21417 | /**
|
21418 | * Raised when an NgModule contains an invalid reference in `declarations`.
|
21419 | */
|
21420 | ErrorCode[ErrorCode["NGMODULE_INVALID_DECLARATION"] = 6001] = "NGMODULE_INVALID_DECLARATION";
|
21421 | /**
|
21422 | * Raised when an NgModule contains an invalid type in `imports`.
|
21423 | */
|
21424 | ErrorCode[ErrorCode["NGMODULE_INVALID_IMPORT"] = 6002] = "NGMODULE_INVALID_IMPORT";
|
21425 | /**
|
21426 | * Raised when an NgModule contains an invalid type in `exports`.
|
21427 | */
|
21428 | ErrorCode[ErrorCode["NGMODULE_INVALID_EXPORT"] = 6003] = "NGMODULE_INVALID_EXPORT";
|
21429 | /**
|
21430 | * Raised when an NgModule contains a type in `exports` which is neither in `declarations` nor
|
21431 | * otherwise imported.
|
21432 | */
|
21433 | ErrorCode[ErrorCode["NGMODULE_INVALID_REEXPORT"] = 6004] = "NGMODULE_INVALID_REEXPORT";
|
21434 | /**
|
21435 | * Raised when a `ModuleWithProviders` with a missing
|
21436 | * generic type argument is passed into an `NgModule`.
|
21437 | */
|
21438 | ErrorCode[ErrorCode["NGMODULE_MODULE_WITH_PROVIDERS_MISSING_GENERIC"] = 6005] = "NGMODULE_MODULE_WITH_PROVIDERS_MISSING_GENERIC";
|
21439 | /**
|
21440 | * Raised when an NgModule exports multiple directives/pipes of the same name and the compiler
|
21441 | * attempts to generate private re-exports within the NgModule file.
|
21442 | */
|
21443 | ErrorCode[ErrorCode["NGMODULE_REEXPORT_NAME_COLLISION"] = 6006] = "NGMODULE_REEXPORT_NAME_COLLISION";
|
21444 | /**
|
21445 | * Raised when a directive/pipe is part of the declarations of two or more NgModules.
|
21446 | */
|
21447 | ErrorCode[ErrorCode["NGMODULE_DECLARATION_NOT_UNIQUE"] = 6007] = "NGMODULE_DECLARATION_NOT_UNIQUE";
|
21448 | /**
|
21449 | * An element name failed validation against the DOM schema.
|
21450 | */
|
21451 | ErrorCode[ErrorCode["SCHEMA_INVALID_ELEMENT"] = 8001] = "SCHEMA_INVALID_ELEMENT";
|
21452 | /**
|
21453 | * An element's attribute name failed validation against the DOM schema.
|
21454 | */
|
21455 | ErrorCode[ErrorCode["SCHEMA_INVALID_ATTRIBUTE"] = 8002] = "SCHEMA_INVALID_ATTRIBUTE";
|
21456 | /**
|
21457 | * No matching directive was found for a `#ref="target"` expression.
|
21458 | */
|
21459 | ErrorCode[ErrorCode["MISSING_REFERENCE_TARGET"] = 8003] = "MISSING_REFERENCE_TARGET";
|
21460 | /**
|
21461 | * No matching pipe was found for a
|
21462 | */
|
21463 | ErrorCode[ErrorCode["MISSING_PIPE"] = 8004] = "MISSING_PIPE";
|
21464 | /**
|
21465 | * The left-hand side of an assignment expression was a template variable. Effectively, the
|
21466 | * template looked like:
|
21467 | *
|
21468 | * ```
|
21469 | * <ng-template let-something>
|
21470 | * <button (click)="something = ...">...</button>
|
21471 | * </ng-template>
|
21472 | * ```
|
21473 | *
|
21474 | * Template variables are read-only.
|
21475 | */
|
21476 | ErrorCode[ErrorCode["WRITE_TO_READ_ONLY_VARIABLE"] = 8005] = "WRITE_TO_READ_ONLY_VARIABLE";
|
21477 | /**
|
21478 | * A template variable was declared twice. For example:
|
21479 | *
|
21480 | * ```html
|
21481 | * <div *ngFor="let i of items; let i = index">
|
21482 | * </div>
|
21483 | * ```
|
21484 | */
|
21485 | ErrorCode[ErrorCode["DUPLICATE_VARIABLE_DECLARATION"] = 8006] = "DUPLICATE_VARIABLE_DECLARATION";
|
21486 | /**
|
21487 | * The template type-checking engine would need to generate an inline type check block for a
|
21488 | * component, but the current type-checking environment doesn't support it.
|
21489 | */
|
21490 | ErrorCode[ErrorCode["INLINE_TCB_REQUIRED"] = 8900] = "INLINE_TCB_REQUIRED";
|
21491 | /**
|
21492 | * The template type-checking engine would need to generate an inline type constructor for a
|
21493 | * directive or component, but the current type-checking environment doesn't support it.
|
21494 | */
|
21495 | ErrorCode[ErrorCode["INLINE_TYPE_CTOR_REQUIRED"] = 8901] = "INLINE_TYPE_CTOR_REQUIRED";
|
21496 | /**
|
21497 | * An injectable already has a `ɵprov` property.
|
21498 | */
|
21499 | ErrorCode[ErrorCode["INJECTABLE_DUPLICATE_PROV"] = 9001] = "INJECTABLE_DUPLICATE_PROV";
|
21500 | // 10XXX error codes are reserved for diagnostics with category
|
21501 | // `ts.DiagnosticCategory.Suggestion`. These diagnostics are generated by
|
21502 | // language service.
|
21503 | /**
|
21504 | * Suggest users to enable `strictTemplates` to make use of full capabilities
|
21505 | * provided by Angular language service.
|
21506 | */
|
21507 | ErrorCode[ErrorCode["SUGGEST_STRICT_TEMPLATES"] = 10001] = "SUGGEST_STRICT_TEMPLATES";
|
21508 | })(ErrorCode || (ErrorCode = {}));
|
21509 | /**
|
21510 | * @internal
|
21511 | * Base URL for the error details page.
|
21512 | * Keep this value in sync with a similar const in
|
21513 | * `packages/core/src/render3/error_code.ts`.
|
21514 | */
|
21515 | const ERROR_DETAILS_PAGE_BASE_URL = 'https://angular.io/errors';
|
21516 | /**
|
21517 | * @internal
|
21518 | * Contains a set of error messages that have detailed guides at angular.io.
|
21519 | * Full list of available error guides can be found at https://angular.io/errors
|
21520 | */
|
21521 | const COMPILER_ERRORS_WITH_GUIDES = new Set([
|
21522 | ErrorCode.DECORATOR_ARG_NOT_LITERAL,
|
21523 | ErrorCode.IMPORT_CYCLE_DETECTED,
|
21524 | ErrorCode.PARAM_MISSING_TOKEN,
|
21525 | ErrorCode.SCHEMA_INVALID_ELEMENT,
|
21526 | ErrorCode.SCHEMA_INVALID_ATTRIBUTE,
|
21527 | ErrorCode.MISSING_REFERENCE_TARGET,
|
21528 | ]);
|
21529 | /**
|
21530 | * @internal
|
21531 | */
|
21532 | function ngErrorCode(code) {
|
21533 | return parseInt('-99' + code);
|
21534 | }
|
21535 |
|
21536 | /**
|
21537 | * @license
|
21538 | * Copyright Google LLC All Rights Reserved.
|
21539 | *
|
21540 | * Use of this source code is governed by an MIT-style license that can be
|
21541 | * found in the LICENSE file at https://angular.io/license
|
21542 | */
|
21543 | class FatalDiagnosticError {
|
21544 | constructor(code, node, message, relatedInformation) {
|
21545 | this.code = code;
|
21546 | this.node = node;
|
21547 | this.message = message;
|
21548 | this.relatedInformation = relatedInformation;
|
21549 | /**
|
21550 | * @internal
|
21551 | */
|
21552 | this._isFatalDiagnosticError = true;
|
21553 | }
|
21554 | toDiagnostic() {
|
21555 | return makeDiagnostic(this.code, this.node, this.message, this.relatedInformation);
|
21556 | }
|
21557 | }
|
21558 | function makeDiagnostic(code, node, messageText, relatedInformation) {
|
21559 | node = ts$1.getOriginalNode(node);
|
21560 | return {
|
21561 | category: ts$1.DiagnosticCategory.Error,
|
21562 | code: ngErrorCode(code),
|
21563 | file: ts$1.getOriginalNode(node).getSourceFile(),
|
21564 | start: node.getStart(undefined, false),
|
21565 | length: node.getWidth(),
|
21566 | messageText,
|
21567 | relatedInformation,
|
21568 | };
|
21569 | }
|
21570 | function makeRelatedInformation(node, messageText) {
|
21571 | node = ts$1.getOriginalNode(node);
|
21572 | return {
|
21573 | category: ts$1.DiagnosticCategory.Message,
|
21574 | code: 0,
|
21575 | file: node.getSourceFile(),
|
21576 | start: node.getStart(),
|
21577 | length: node.getWidth(),
|
21578 | messageText,
|
21579 | };
|
21580 | }
|
21581 |
|
21582 | /**
|
21583 | * @license
|
21584 | * Copyright Google LLC All Rights Reserved.
|
21585 | *
|
21586 | * Use of this source code is governed by an MIT-style license that can be
|
21587 | * found in the LICENSE file at https://angular.io/license
|
21588 | */
|
21589 | const D_TS = /\.d\.ts$/i;
|
21590 | function isDtsPath(filePath) {
|
21591 | return D_TS.test(filePath);
|
21592 | }
|
21593 | function nodeNameForError(node) {
|
21594 | if (node.name !== undefined && ts$1.isIdentifier(node.name)) {
|
21595 | return node.name.text;
|
21596 | }
|
21597 | else {
|
21598 | const kind = ts$1.SyntaxKind[node.kind];
|
21599 | const { line, character } = ts$1.getLineAndCharacterOfPosition(node.getSourceFile(), node.getStart());
|
21600 | return `${kind}@${line}:${character}`;
|
21601 | }
|
21602 | }
|
21603 | function getSourceFile(node) {
|
21604 | // In certain transformation contexts, `ts.Node.getSourceFile()` can actually return `undefined`,
|
21605 | // despite the type signature not allowing it. In that event, get the `ts.SourceFile` via the
|
21606 | // original node instead (which works).
|
21607 | const directSf = node.getSourceFile();
|
21608 | return directSf !== undefined ? directSf : ts$1.getOriginalNode(node).getSourceFile();
|
21609 | }
|
21610 | function getSourceFileOrNull(program, fileName) {
|
21611 | return program.getSourceFile(fileName) || null;
|
21612 | }
|
21613 | function getTokenAtPosition(sf, pos) {
|
21614 | // getTokenAtPosition is part of TypeScript's private API.
|
21615 | return ts$1.getTokenAtPosition(sf, pos);
|
21616 | }
|
21617 | function identifierOfNode(decl) {
|
21618 | if (decl.name !== undefined && ts$1.isIdentifier(decl.name)) {
|
21619 | return decl.name;
|
21620 | }
|
21621 | else {
|
21622 | return null;
|
21623 | }
|
21624 | }
|
21625 | function isDeclaration(node) {
|
21626 | return isValueDeclaration(node) || isTypeDeclaration(node);
|
21627 | }
|
21628 | function isValueDeclaration(node) {
|
21629 | return ts$1.isClassDeclaration(node) || ts$1.isFunctionDeclaration(node) ||
|
21630 | ts$1.isVariableDeclaration(node);
|
21631 | }
|
21632 | function isTypeDeclaration(node) {
|
21633 | return ts$1.isEnumDeclaration(node) || ts$1.isTypeAliasDeclaration(node) ||
|
21634 | ts$1.isInterfaceDeclaration(node);
|
21635 | }
|
21636 | function isExported(node) {
|
21637 | let topLevel = node;
|
21638 | if (ts$1.isVariableDeclaration(node) && ts$1.isVariableDeclarationList(node.parent)) {
|
21639 | topLevel = node.parent.parent;
|
21640 | }
|
21641 | return topLevel.modifiers !== undefined &&
|
21642 | topLevel.modifiers.some(modifier => modifier.kind === ts$1.SyntaxKind.ExportKeyword);
|
21643 | }
|
21644 | function getRootDirs(host, options) {
|
21645 | const rootDirs = [];
|
21646 | if (options.rootDirs !== undefined) {
|
21647 | rootDirs.push(...options.rootDirs);
|
21648 | }
|
21649 | else if (options.rootDir !== undefined) {
|
21650 | rootDirs.push(options.rootDir);
|
21651 | }
|
21652 | else {
|
21653 | rootDirs.push(host.getCurrentDirectory());
|
21654 | }
|
21655 | // In Windows the above might not always return posix separated paths
|
21656 | // See:
|
21657 | // https://github.com/Microsoft/TypeScript/blob/3f7357d37f66c842d70d835bc925ec2a873ecfec/src/compiler/sys.ts#L650
|
21658 | // Also compiler options might be set via an API which doesn't normalize paths
|
21659 | return rootDirs.map(rootDir => absoluteFrom(host.getCanonicalFileName(rootDir)));
|
21660 | }
|
21661 | function nodeDebugInfo(node) {
|
21662 | const sf = getSourceFile(node);
|
21663 | const { line, character } = ts$1.getLineAndCharacterOfPosition(sf, node.pos);
|
21664 | return `[${sf.fileName}: ${ts$1.SyntaxKind[node.kind]} @ ${line}:${character}]`;
|
21665 | }
|
21666 | /**
|
21667 | * Resolve the specified `moduleName` using the given `compilerOptions` and `compilerHost`.
|
21668 | *
|
21669 | * This helper will attempt to use the `CompilerHost.resolveModuleNames()` method if available.
|
21670 | * Otherwise it will fallback on the `ts.ResolveModuleName()` function.
|
21671 | */
|
21672 | function resolveModuleName(moduleName, containingFile, compilerOptions, compilerHost, moduleResolutionCache) {
|
21673 | if (compilerHost.resolveModuleNames) {
|
21674 | return compilerHost.resolveModuleNames([moduleName], containingFile, undefined, // reusedNames
|
21675 | undefined, // redirectedReference
|
21676 | compilerOptions)[0];
|
21677 | }
|
21678 | else {
|
21679 | return ts$1.resolveModuleName(moduleName, containingFile, compilerOptions, compilerHost, moduleResolutionCache !== null ? moduleResolutionCache : undefined)
|
21680 | .resolvedModule;
|
21681 | }
|
21682 | }
|
21683 | /** Returns true if the node is an assignment expression. */
|
21684 | function isAssignment(node) {
|
21685 | return ts$1.isBinaryExpression(node) && node.operatorToken.kind === ts$1.SyntaxKind.EqualsToken;
|
21686 | }
|
21687 |
|
21688 | /**
|
21689 | * @license
|
21690 | * Copyright Google LLC All Rights Reserved.
|
21691 | *
|
21692 | * Use of this source code is governed by an MIT-style license that can be
|
21693 | * found in the LICENSE file at https://angular.io/license
|
21694 | */
|
21695 | /**
|
21696 | * Find the name, if any, by which a node is exported from a given file.
|
21697 | */
|
21698 | function findExportedNameOfNode(target, file, reflector) {
|
21699 | const exports = reflector.getExportsOfModule(file);
|
21700 | if (exports === null) {
|
21701 | return null;
|
21702 | }
|
21703 | // Look for the export which declares the node.
|
21704 | const keys = Array.from(exports.keys());
|
21705 | const name = keys.find(key => {
|
21706 | const decl = exports.get(key);
|
21707 | return decl !== undefined && decl.node === target;
|
21708 | });
|
21709 | if (name === undefined) {
|
21710 | throw new Error(`Failed to find exported name of node (${target.getText()}) in '${file.fileName}'.`);
|
21711 | }
|
21712 | return name;
|
21713 | }
|
21714 |
|
21715 | /**
|
21716 | * @license
|
21717 | * Copyright Google LLC All Rights Reserved.
|
21718 | *
|
21719 | * Use of this source code is governed by an MIT-style license that can be
|
21720 | * found in the LICENSE file at https://angular.io/license
|
21721 | */
|
21722 | /**
|
21723 | * Flags which alter the imports generated by the `ReferenceEmitter`.
|
21724 | */
|
21725 | var ImportFlags;
|
21726 | (function (ImportFlags) {
|
21727 | ImportFlags[ImportFlags["None"] = 0] = "None";
|
21728 | /**
|
21729 | * Force the generation of a new import when generating a reference, even if an identifier already
|
21730 | * exists in the target file which could be used instead.
|
21731 | *
|
21732 | * This is sometimes required if there's a risk TypeScript might remove imports during emit.
|
21733 | */
|
21734 | ImportFlags[ImportFlags["ForceNewImport"] = 1] = "ForceNewImport";
|
21735 | /**
|
21736 | * Don't make use of any aliasing information when emitting a reference.
|
21737 | *
|
21738 | * This is sometimes required if emitting into a context where generated references will be fed
|
21739 | * into TypeScript and type-checked (such as in template type-checking).
|
21740 | */
|
21741 | ImportFlags[ImportFlags["NoAliasing"] = 2] = "NoAliasing";
|
21742 | /**
|
21743 | * Indicates that an import to a type-only declaration is allowed.
|
21744 | *
|
21745 | * For references that occur in type-positions, the referred declaration may be a type-only
|
21746 | * declaration that is not retained during emit. Including this flag allows to emit references to
|
21747 | * type-only declarations as used in e.g. template type-checking.
|
21748 | */
|
21749 | ImportFlags[ImportFlags["AllowTypeImports"] = 4] = "AllowTypeImports";
|
21750 | })(ImportFlags || (ImportFlags = {}));
|
21751 | /**
|
21752 | * Generates `Expression`s which refer to `Reference`s in a given context.
|
21753 | *
|
21754 | * A `ReferenceEmitter` uses one or more `ReferenceEmitStrategy` implementations to produce an
|
21755 | * `Expression` which refers to a `Reference` in the context of a particular file.
|
21756 | */
|
21757 | class ReferenceEmitter {
|
21758 | constructor(strategies) {
|
21759 | this.strategies = strategies;
|
21760 | }
|
21761 | emit(ref, context, importFlags = ImportFlags.None) {
|
21762 | for (const strategy of this.strategies) {
|
21763 | const emitted = strategy.emit(ref, context, importFlags);
|
21764 | if (emitted !== null) {
|
21765 | return emitted;
|
21766 | }
|
21767 | }
|
21768 | throw new Error(`Unable to write a reference to ${nodeNameForError(ref.node)} in ${ref.node.getSourceFile().fileName} from ${context.fileName}`);
|
21769 | }
|
21770 | }
|
21771 | /**
|
21772 | * A `ReferenceEmitStrategy` which will refer to declarations by any local `ts.Identifier`s, if
|
21773 | * such identifiers are available.
|
21774 | */
|
21775 | class LocalIdentifierStrategy {
|
21776 | emit(ref, context, importFlags) {
|
21777 | const refSf = getSourceFile(ref.node);
|
21778 | // If the emitter has specified ForceNewImport, then LocalIdentifierStrategy should not use a
|
21779 | // local identifier at all, *except* in the source file where the node is actually declared.
|
21780 | if (importFlags & ImportFlags.ForceNewImport && refSf !== context) {
|
21781 | return null;
|
21782 | }
|
21783 | // If referenced node is not an actual TS declaration (e.g. `class Foo` or `function foo() {}`,
|
21784 | // etc) and it is in the current file then just use it directly.
|
21785 | // This is important because the reference could be a property access (e.g. `exports.foo`). In
|
21786 | // such a case, the reference's `identities` property would be `[foo]`, which would result in an
|
21787 | // invalid emission of a free-standing `foo` identifier, rather than `exports.foo`.
|
21788 | if (!isDeclaration(ref.node) && refSf === context) {
|
21789 | return new WrappedNodeExpr(ref.node);
|
21790 | }
|
21791 | // A Reference can have multiple identities in different files, so it may already have an
|
21792 | // Identifier in the requested context file.
|
21793 | const identifier = ref.getIdentityIn(context);
|
21794 | if (identifier !== null) {
|
21795 | return new WrappedNodeExpr(identifier);
|
21796 | }
|
21797 | else {
|
21798 | return null;
|
21799 | }
|
21800 | }
|
21801 | }
|
21802 | /**
|
21803 | * A `ReferenceEmitStrategy` which will refer to declarations that come from `node_modules` using
|
21804 | * an absolute import.
|
21805 | *
|
21806 | * Part of this strategy involves looking at the target entry point and identifying the exported
|
21807 | * name of the targeted declaration, as it might be different from the declared name (e.g. a
|
21808 | * directive might be declared as FooDirImpl, but exported as FooDir). If no export can be found
|
21809 | * which maps back to the original directive, an error is thrown.
|
21810 | */
|
21811 | class AbsoluteModuleStrategy {
|
21812 | constructor(program, checker, moduleResolver, reflectionHost) {
|
21813 | this.program = program;
|
21814 | this.checker = checker;
|
21815 | this.moduleResolver = moduleResolver;
|
21816 | this.reflectionHost = reflectionHost;
|
21817 | /**
|
21818 | * A cache of the exports of specific modules, because resolving a module to its exports is a
|
21819 | * costly operation.
|
21820 | */
|
21821 | this.moduleExportsCache = new Map();
|
21822 | }
|
21823 | emit(ref, context, importFlags) {
|
21824 | if (ref.bestGuessOwningModule === null) {
|
21825 | // There is no module name available for this Reference, meaning it was arrived at via a
|
21826 | // relative path.
|
21827 | return null;
|
21828 | }
|
21829 | else if (!isDeclaration(ref.node)) {
|
21830 | // It's not possible to import something which isn't a declaration.
|
21831 | throw new Error(`Debug assert: unable to import a Reference to non-declaration of type ${ts$1.SyntaxKind[ref.node.kind]}.`);
|
21832 | }
|
21833 | else if ((importFlags & ImportFlags.AllowTypeImports) === 0 && isTypeDeclaration(ref.node)) {
|
21834 | throw new Error(`Importing a type-only declaration of type ${ts$1.SyntaxKind[ref.node.kind]} in a value position is not allowed.`);
|
21835 | }
|
21836 | // Try to find the exported name of the declaration, if one is available.
|
21837 | const { specifier, resolutionContext } = ref.bestGuessOwningModule;
|
21838 | const symbolName = this.resolveImportName(specifier, ref.node, resolutionContext);
|
21839 | if (symbolName === null) {
|
21840 | // TODO(alxhub): make this error a ts.Diagnostic pointing at whatever caused this import to be
|
21841 | // triggered.
|
21842 | throw new Error(`Symbol ${ref.debugName} declared in ${getSourceFile(ref.node).fileName} is not exported from ${specifier} (import into ${context.fileName})`);
|
21843 | }
|
21844 | return new ExternalExpr(new ExternalReference(specifier, symbolName));
|
21845 | }
|
21846 | resolveImportName(moduleName, target, fromFile) {
|
21847 | const exports = this.getExportsOfModule(moduleName, fromFile);
|
21848 | if (exports !== null && exports.has(target)) {
|
21849 | return exports.get(target);
|
21850 | }
|
21851 | else {
|
21852 | return null;
|
21853 | }
|
21854 | }
|
21855 | getExportsOfModule(moduleName, fromFile) {
|
21856 | if (!this.moduleExportsCache.has(moduleName)) {
|
21857 | this.moduleExportsCache.set(moduleName, this.enumerateExportsOfModule(moduleName, fromFile));
|
21858 | }
|
21859 | return this.moduleExportsCache.get(moduleName);
|
21860 | }
|
21861 | enumerateExportsOfModule(specifier, fromFile) {
|
21862 | // First, resolve the module specifier to its entry point, and get the ts.Symbol for it.
|
21863 | const entryPointFile = this.moduleResolver.resolveModule(specifier, fromFile);
|
21864 | if (entryPointFile === null) {
|
21865 | return null;
|
21866 | }
|
21867 | const exports = this.reflectionHost.getExportsOfModule(entryPointFile);
|
21868 | if (exports === null) {
|
21869 | return null;
|
21870 | }
|
21871 | const exportMap = new Map();
|
21872 | exports.forEach((declaration, name) => {
|
21873 | exportMap.set(declaration.node, name);
|
21874 | });
|
21875 | return exportMap;
|
21876 | }
|
21877 | }
|
21878 | /**
|
21879 | * A `ReferenceEmitStrategy` which will refer to declarations via relative paths, provided they're
|
21880 | * both in the logical project "space" of paths.
|
21881 | *
|
21882 | * This is trickier than it sounds, as the two files may be in different root directories in the
|
21883 | * project. Simply calculating a file system relative path between the two is not sufficient.
|
21884 | * Instead, `LogicalProjectPath`s are used.
|
21885 | */
|
21886 | class LogicalProjectStrategy {
|
21887 | constructor(reflector, logicalFs) {
|
21888 | this.reflector = reflector;
|
21889 | this.logicalFs = logicalFs;
|
21890 | }
|
21891 | emit(ref, context) {
|
21892 | const destSf = getSourceFile(ref.node);
|
21893 | // Compute the relative path from the importing file to the file being imported. This is done
|
21894 | // as a logical path computation, because the two files might be in different rootDirs.
|
21895 | const destPath = this.logicalFs.logicalPathOfSf(destSf);
|
21896 | if (destPath === null) {
|
21897 | // The imported file is not within the logical project filesystem.
|
21898 | return null;
|
21899 | }
|
21900 | const originPath = this.logicalFs.logicalPathOfSf(context);
|
21901 | if (originPath === null) {
|
21902 | throw new Error(`Debug assert: attempt to import from ${context.fileName} but it's outside the program?`);
|
21903 | }
|
21904 | // There's no way to emit a relative reference from a file to itself.
|
21905 | if (destPath === originPath) {
|
21906 | return null;
|
21907 | }
|
21908 | const name = findExportedNameOfNode(ref.node, destSf, this.reflector);
|
21909 | if (name === null) {
|
21910 | // The target declaration isn't exported from the file it's declared in. This is an issue!
|
21911 | return null;
|
21912 | }
|
21913 | // With both files expressed as LogicalProjectPaths, getting the module specifier as a relative
|
21914 | // path is now straightforward.
|
21915 | const moduleName = LogicalProjectPath.relativePathBetween(originPath, destPath);
|
21916 | return new ExternalExpr({ moduleName, name });
|
21917 | }
|
21918 | }
|
21919 | /**
|
21920 | * A `ReferenceEmitStrategy` which constructs relatives paths between `ts.SourceFile`s.
|
21921 | *
|
21922 | * This strategy can be used if there is no `rootDir`/`rootDirs` structure for the project which
|
21923 | * necessitates the stronger logic of `LogicalProjectStrategy`.
|
21924 | */
|
21925 | class RelativePathStrategy {
|
21926 | constructor(reflector) {
|
21927 | this.reflector = reflector;
|
21928 | }
|
21929 | emit(ref, context) {
|
21930 | const destSf = getSourceFile(ref.node);
|
21931 | const relativePath = relative(dirname(absoluteFromSourceFile(context)), absoluteFromSourceFile(destSf));
|
21932 | const moduleName = toRelativeImport(stripExtension(relativePath));
|
21933 | const name = findExportedNameOfNode(ref.node, destSf, this.reflector);
|
21934 | return new ExternalExpr({ moduleName, name });
|
21935 | }
|
21936 | }
|
21937 | /**
|
21938 | * A `ReferenceEmitStrategy` which uses a `UnifiedModulesHost` to generate absolute import
|
21939 | * references.
|
21940 | */
|
21941 | class UnifiedModulesStrategy {
|
21942 | constructor(reflector, unifiedModulesHost) {
|
21943 | this.reflector = reflector;
|
21944 | this.unifiedModulesHost = unifiedModulesHost;
|
21945 | }
|
21946 | emit(ref, context) {
|
21947 | const destSf = getSourceFile(ref.node);
|
21948 | const name = findExportedNameOfNode(ref.node, destSf, this.reflector);
|
21949 | if (name === null) {
|
21950 | return null;
|
21951 | }
|
21952 | const moduleName = this.unifiedModulesHost.fileNameToModuleName(destSf.fileName, context.fileName);
|
21953 | return new ExternalExpr({ moduleName, name });
|
21954 | }
|
21955 | }
|
21956 |
|
21957 | /**
|
21958 | * @license
|
21959 | * Copyright Google LLC All Rights Reserved.
|
21960 | *
|
21961 | * Use of this source code is governed by an MIT-style license that can be
|
21962 | * found in the LICENSE file at https://angular.io/license
|
21963 | */
|
21964 | // Escape anything that isn't alphanumeric, '/' or '_'.
|
21965 | const CHARS_TO_ESCAPE = /[^a-zA-Z0-9/_]/g;
|
21966 | /**
|
21967 | * An `AliasingHost` which generates and consumes alias re-exports when module names for each file
|
21968 | * are determined by a `UnifiedModulesHost`.
|
21969 | *
|
21970 | * When using a `UnifiedModulesHost`, aliasing prevents issues with transitive dependencies. See the
|
21971 | * README.md for more details.
|
21972 | */
|
21973 | class UnifiedModulesAliasingHost {
|
21974 | constructor(unifiedModulesHost) {
|
21975 | this.unifiedModulesHost = unifiedModulesHost;
|
21976 | /**
|
21977 | * With a `UnifiedModulesHost`, aliases are chosen automatically without the need to look through
|
21978 | * the exports present in a .d.ts file, so we can avoid cluttering the .d.ts files.
|
21979 | */
|
21980 | this.aliasExportsInDts = false;
|
21981 | }
|
21982 | maybeAliasSymbolAs(ref, context, ngModuleName, isReExport) {
|
21983 | if (!isReExport) {
|
21984 | // Aliasing is used with a UnifiedModulesHost to prevent transitive dependencies. Thus,
|
21985 | // aliases
|
21986 | // only need to be created for directives/pipes which are not direct declarations of an
|
21987 | // NgModule which exports them.
|
21988 | return null;
|
21989 | }
|
21990 | return this.aliasName(ref.node, context);
|
21991 | }
|
21992 | /**
|
21993 | * Generates an `Expression` to import `decl` from `via`, assuming an export was added when `via`
|
21994 | * was compiled per `maybeAliasSymbolAs` above.
|
21995 | */
|
21996 | getAliasIn(decl, via, isReExport) {
|
21997 | if (!isReExport) {
|
21998 | // Directly exported directives/pipes don't require an alias, per the logic in
|
21999 | // `maybeAliasSymbolAs`.
|
22000 | return null;
|
22001 | }
|
22002 | // viaModule is the module it'll actually be imported from.
|
22003 | const moduleName = this.unifiedModulesHost.fileNameToModuleName(via.fileName, via.fileName);
|
22004 | return new ExternalExpr({ moduleName, name: this.aliasName(decl, via) });
|
22005 | }
|
22006 | /**
|
22007 | * Generates an alias name based on the full module name of the file which declares the aliased
|
22008 | * directive/pipe.
|
22009 | */
|
22010 | aliasName(decl, context) {
|
22011 | // The declared module is used to get the name of the alias.
|
22012 | const declModule = this.unifiedModulesHost.fileNameToModuleName(decl.getSourceFile().fileName, context.fileName);
|
22013 | const replaced = declModule.replace(CHARS_TO_ESCAPE, '_').replace(/\//g, '$');
|
22014 | return 'ɵng$' + replaced + '$$' + decl.name.text;
|
22015 | }
|
22016 | }
|
22017 | /**
|
22018 | * An `AliasingHost` which exports directives from any file containing an NgModule in which they're
|
22019 | * declared/exported, under a private symbol name.
|
22020 | *
|
22021 | * These exports support cases where an NgModule is imported deeply from an absolute module path
|
22022 | * (that is, it's not part of an Angular Package Format entrypoint), and the compiler needs to
|
22023 | * import any matched directives/pipes from the same path (to the NgModule file). See README.md for
|
22024 | * more details.
|
22025 | */
|
22026 | class PrivateExportAliasingHost {
|
22027 | constructor(host) {
|
22028 | this.host = host;
|
22029 | /**
|
22030 | * Under private export aliasing, the `AbsoluteModuleStrategy` used for emitting references will
|
22031 | * will select aliased exports that it finds in the .d.ts file for an NgModule's file. Thus,
|
22032 | * emitting these exports in .d.ts is a requirement for the `PrivateExportAliasingHost` to
|
22033 | * function correctly.
|
22034 | */
|
22035 | this.aliasExportsInDts = true;
|
22036 | }
|
22037 | maybeAliasSymbolAs(ref, context, ngModuleName) {
|
22038 | if (ref.hasOwningModuleGuess) {
|
22039 | // Skip nodes that already have an associated absolute module specifier, since they can be
|
22040 | // safely imported from that specifier.
|
22041 | return null;
|
22042 | }
|
22043 | // Look for a user-provided export of `decl` in `context`. If one exists, then an alias export
|
22044 | // is not needed.
|
22045 | // TODO(alxhub): maybe add a host method to check for the existence of an export without going
|
22046 | // through the entire list of exports.
|
22047 | const exports = this.host.getExportsOfModule(context);
|
22048 | if (exports === null) {
|
22049 | // Something went wrong, and no exports were available at all. Bail rather than risk creating
|
22050 | // re-exports when they're not needed.
|
22051 | throw new Error(`Could not determine the exports of: ${context.fileName}`);
|
22052 | }
|
22053 | let found = false;
|
22054 | exports.forEach(value => {
|
22055 | if (value.node === ref.node) {
|
22056 | found = true;
|
22057 | }
|
22058 | });
|
22059 | if (found) {
|
22060 | // The module exports the declared class directly, no alias is necessary.
|
22061 | return null;
|
22062 | }
|
22063 | return `ɵngExportɵ${ngModuleName}ɵ${ref.node.name.text}`;
|
22064 | }
|
22065 | /**
|
22066 | * A `PrivateExportAliasingHost` only generates re-exports and does not direct the compiler to
|
22067 | * directly consume the aliases it creates.
|
22068 | *
|
22069 | * Instead, they're consumed indirectly: `AbsoluteModuleStrategy` `ReferenceEmitterStrategy` will
|
22070 | * select these alias exports automatically when looking for an export of the directive/pipe from
|
22071 | * the same path as the NgModule was imported.
|
22072 | *
|
22073 | * Thus, `getAliasIn` always returns `null`.
|
22074 | */
|
22075 | getAliasIn() {
|
22076 | return null;
|
22077 | }
|
22078 | }
|
22079 | /**
|
22080 | * A `ReferenceEmitStrategy` which will consume the alias attached to a particular `Reference` to a
|
22081 | * directive or pipe, if it exists.
|
22082 | */
|
22083 | class AliasStrategy {
|
22084 | emit(ref, context, importMode) {
|
22085 | if (importMode & ImportFlags.NoAliasing) {
|
22086 | return null;
|
22087 | }
|
22088 | return ref.alias;
|
22089 | }
|
22090 | }
|
22091 |
|
22092 | /**
|
22093 | * @license
|
22094 | * Copyright Google LLC All Rights Reserved.
|
22095 | *
|
22096 | * Use of this source code is governed by an MIT-style license that can be
|
22097 | * found in the LICENSE file at https://angular.io/license
|
22098 | */
|
22099 | function relativePathBetween(from, to) {
|
22100 | const relativePath = stripExtension(relative(dirname(resolve(from)), resolve(to)));
|
22101 | return relativePath !== '' ? toRelativeImport(relativePath) : null;
|
22102 | }
|
22103 |
|
22104 | /**
|
22105 | * @license
|
22106 | * Copyright Google LLC All Rights Reserved.
|
22107 | *
|
22108 | * Use of this source code is governed by an MIT-style license that can be
|
22109 | * found in the LICENSE file at https://angular.io/license
|
22110 | */
|
22111 | /**
|
22112 | * `ImportRewriter` that does no rewriting.
|
22113 | */
|
22114 | class NoopImportRewriter {
|
22115 | shouldImportSymbol(symbol, specifier) {
|
22116 | return true;
|
22117 | }
|
22118 | rewriteSymbol(symbol, specifier) {
|
22119 | return symbol;
|
22120 | }
|
22121 | rewriteSpecifier(specifier, inContextOfFile) {
|
22122 | return specifier;
|
22123 | }
|
22124 | }
|
22125 | /**
|
22126 | * A mapping of supported symbols that can be imported from within @angular/core, and the names by
|
22127 | * which they're exported from r3_symbols.
|
22128 | */
|
22129 | const CORE_SUPPORTED_SYMBOLS = new Map([
|
22130 | ['ɵɵdefineInjectable', 'ɵɵdefineInjectable'],
|
22131 | ['ɵɵdefineInjector', 'ɵɵdefineInjector'],
|
22132 | ['ɵɵdefineNgModule', 'ɵɵdefineNgModule'],
|
22133 | ['ɵɵsetNgModuleScope', 'ɵɵsetNgModuleScope'],
|
22134 | ['ɵɵinject', 'ɵɵinject'],
|
22135 | ['ɵɵFactoryDef', 'ɵɵFactoryDef'],
|
22136 | ['ɵsetClassMetadata', 'setClassMetadata'],
|
22137 | ['ɵɵInjectableDef', 'ɵɵInjectableDef'],
|
22138 | ['ɵɵInjectorDef', 'ɵɵInjectorDef'],
|
22139 | ['ɵɵNgModuleDefWithMeta', 'ɵɵNgModuleDefWithMeta'],
|
22140 | ['ɵNgModuleFactory', 'NgModuleFactory'],
|
22141 | ['ɵnoSideEffects', 'ɵnoSideEffects'],
|
22142 | ]);
|
22143 | const CORE_MODULE = '@angular/core';
|
22144 | /**
|
22145 | * `ImportRewriter` that rewrites imports from '@angular/core' to be imported from the r3_symbols.ts
|
22146 | * file instead.
|
22147 | */
|
22148 | class R3SymbolsImportRewriter {
|
22149 | constructor(r3SymbolsPath) {
|
22150 | this.r3SymbolsPath = r3SymbolsPath;
|
22151 | }
|
22152 | shouldImportSymbol(symbol, specifier) {
|
22153 | return true;
|
22154 | }
|
22155 | rewriteSymbol(symbol, specifier) {
|
22156 | if (specifier !== CORE_MODULE) {
|
22157 | // This import isn't from core, so ignore it.
|
22158 | return symbol;
|
22159 | }
|
22160 | return validateAndRewriteCoreSymbol(symbol);
|
22161 | }
|
22162 | rewriteSpecifier(specifier, inContextOfFile) {
|
22163 | if (specifier !== CORE_MODULE) {
|
22164 | // This module isn't core, so ignore it.
|
22165 | return specifier;
|
22166 | }
|
22167 | const relativePathToR3Symbols = relativePathBetween(inContextOfFile, this.r3SymbolsPath);
|
22168 | if (relativePathToR3Symbols === null) {
|
22169 | throw new Error(`Failed to rewrite import inside ${CORE_MODULE}: ${inContextOfFile} -> ${this.r3SymbolsPath}`);
|
22170 | }
|
22171 | return relativePathToR3Symbols;
|
22172 | }
|
22173 | }
|
22174 | function validateAndRewriteCoreSymbol(name) {
|
22175 | if (!CORE_SUPPORTED_SYMBOLS.has(name)) {
|
22176 | throw new Error(`Importing unexpected symbol ${name} while compiling ${CORE_MODULE}`);
|
22177 | }
|
22178 | return CORE_SUPPORTED_SYMBOLS.get(name);
|
22179 | }
|
22180 |
|
22181 | /**
|
22182 | * @license
|
22183 | * Copyright Google LLC All Rights Reserved.
|
22184 | *
|
22185 | * Use of this source code is governed by an MIT-style license that can be
|
22186 | * found in the LICENSE file at https://angular.io/license
|
22187 | */
|
22188 | /**
|
22189 | * TypeScript has trouble with generating default imports inside of transformers for some module
|
22190 | * formats. The issue is that for the statement:
|
22191 | *
|
22192 | * import X from 'some/module';
|
22193 | * console.log(X);
|
22194 | *
|
22195 | * TypeScript will not use the "X" name in generated code. For normal user code, this is fine
|
22196 | * because references to X will also be renamed. However, if both the import and any references are
|
22197 | * added in a transformer, TypeScript does not associate the two, and will leave the "X" references
|
22198 | * dangling while renaming the import variable. The generated code looks something like:
|
22199 | *
|
22200 | * const module_1 = require('some/module');
|
22201 | * console.log(X); // now X is a dangling reference.
|
22202 | *
|
22203 | * Therefore, we cannot synthetically add default imports, and must reuse the imports that users
|
22204 | * include. Doing this poses a challenge for imports that are only consumed in the type position in
|
22205 | * the user's code. If Angular reuses the imported symbol in a value position (for example, we
|
22206 | * see a constructor parameter of type Foo and try to write "inject(Foo)") we will also end up with
|
22207 | * a dangling reference, as TS will elide the import because it was only used in the type position
|
22208 | * originally.
|
22209 | *
|
22210 | * To avoid this, the compiler must "touch" the imports with `ts.getMutableClone`, and should
|
22211 | * only do this for imports which are actually consumed. The `DefaultImportTracker` keeps track of
|
22212 | * these imports as they're encountered and emitted, and implements a transform which can correctly
|
22213 | * flag the imports as required.
|
22214 | *
|
22215 | * This problem does not exist for non-default imports as the compiler can easily insert
|
22216 | * "import * as X" style imports for those, and the "X" identifier survives transformation.
|
22217 | */
|
22218 | class DefaultImportTracker {
|
22219 | constructor() {
|
22220 | /**
|
22221 | * A `Map` which tracks the `Map` of default import `ts.Identifier`s to their
|
22222 | * `ts.ImportDeclaration`s. These declarations are not guaranteed to be used.
|
22223 | */
|
22224 | this.sourceFileToImportMap = new Map();
|
22225 | /**
|
22226 | * A `Map` which tracks the `Set` of `ts.ImportDeclaration`s for default imports that were used in
|
22227 | * a given `ts.SourceFile` and need to be preserved.
|
22228 | */
|
22229 | this.sourceFileToUsedImports = new Map();
|
22230 | }
|
22231 | recordImportedIdentifier(id, decl) {
|
22232 | const sf = getSourceFile(id);
|
22233 | if (!this.sourceFileToImportMap.has(sf)) {
|
22234 | this.sourceFileToImportMap.set(sf, new Map());
|
22235 | }
|
22236 | this.sourceFileToImportMap.get(sf).set(id, decl);
|
22237 | }
|
22238 | recordUsedIdentifier(id) {
|
22239 | const sf = getSourceFile(id);
|
22240 | if (!this.sourceFileToImportMap.has(sf)) {
|
22241 | // The identifier's source file has no registered default imports at all.
|
22242 | return;
|
22243 | }
|
22244 | const identiferToDeclaration = this.sourceFileToImportMap.get(sf);
|
22245 | if (!identiferToDeclaration.has(id)) {
|
22246 | // The identifier isn't from a registered default import.
|
22247 | return;
|
22248 | }
|
22249 | const decl = identiferToDeclaration.get(id);
|
22250 | // Add the default import declaration to the set of used import declarations for the file.
|
22251 | if (!this.sourceFileToUsedImports.has(sf)) {
|
22252 | this.sourceFileToUsedImports.set(sf, new Set());
|
22253 | }
|
22254 | this.sourceFileToUsedImports.get(sf).add(decl);
|
22255 | }
|
22256 | /**
|
22257 | * Get a `ts.TransformerFactory` which will preserve default imports that were previously marked
|
22258 | * as used.
|
22259 | *
|
22260 | * This transformer must run after any other transformers which call `recordUsedIdentifier`.
|
22261 | */
|
22262 | importPreservingTransformer() {
|
22263 | return (context) => {
|
22264 | return (sf) => {
|
22265 | return this.transformSourceFile(sf);
|
22266 | };
|
22267 | };
|
22268 | }
|
22269 | /**
|
22270 | * Process a `ts.SourceFile` and replace any `ts.ImportDeclaration`s.
|
22271 | */
|
22272 | transformSourceFile(sf) {
|
22273 | const originalSf = ts$1.getOriginalNode(sf);
|
22274 | // Take a fast path if no import declarations need to be preserved in the file.
|
22275 | if (!this.sourceFileToUsedImports.has(originalSf)) {
|
22276 | return sf;
|
22277 | }
|
22278 | // There are declarations that need to be preserved.
|
22279 | const importsToPreserve = this.sourceFileToUsedImports.get(originalSf);
|
22280 | // Generate a new statement list which preserves any imports present in `importsToPreserve`.
|
22281 | const statements = sf.statements.map(stmt => {
|
22282 | if (ts$1.isImportDeclaration(stmt) && importsToPreserve.has(stmt)) {
|
22283 | // Preserving an import that's marked as unreferenced (type-only) is tricky in TypeScript.
|
22284 | //
|
22285 | // Various approaches have been tried, with mixed success:
|
22286 | //
|
22287 | // 1. Using `ts.updateImportDeclaration` does not cause the import to be retained.
|
22288 | //
|
22289 | // 2. Using `ts.createImportDeclaration` with the same `ts.ImportClause` causes the import
|
22290 | // to correctly be retained, but when emitting CommonJS module format code, references
|
22291 | // to the imported value will not match the import variable.
|
22292 | //
|
22293 | // 3. Emitting "import * as" imports instead generates the correct import variable, but
|
22294 | // references are missing the ".default" access. This happens to work for tsickle code
|
22295 | // with goog.module transformations as tsickle strips the ".default" anyway.
|
22296 | //
|
22297 | // 4. It's possible to trick TypeScript by setting `ts.NodeFlag.Synthesized` on the import
|
22298 | // declaration. This causes the import to be correctly retained and generated, but can
|
22299 | // violate invariants elsewhere in the compiler and cause crashes.
|
22300 | //
|
22301 | // 5. Using `ts.getMutableClone` seems to correctly preserve the import and correctly
|
22302 | // generate references to the import variable across all module types.
|
22303 | //
|
22304 | // Therefore, option 5 is the one used here. It seems to be implemented as the correct way
|
22305 | // to perform option 4, which preserves all the compiler's invariants.
|
22306 | //
|
22307 | // TODO(alxhub): discuss with the TypeScript team and determine if there's a better way to
|
22308 | // deal with this issue.
|
22309 | stmt = ts$1.getMutableClone(stmt);
|
22310 | }
|
22311 | return stmt;
|
22312 | });
|
22313 | // Save memory - there's no need to keep these around once the transform has run for the given
|
22314 | // file.
|
22315 | this.sourceFileToImportMap.delete(originalSf);
|
22316 | this.sourceFileToUsedImports.delete(originalSf);
|
22317 | return ts$1.updateSourceFileNode(sf, statements);
|
22318 | }
|
22319 | }
|
22320 |
|
22321 | /**
|
22322 | * @license
|
22323 | * Copyright Google LLC All Rights Reserved.
|
22324 | *
|
22325 | * Use of this source code is governed by an MIT-style license that can be
|
22326 | * found in the LICENSE file at https://angular.io/license
|
22327 | */
|
22328 | /**
|
22329 | * A `ts.Node` plus the context in which it was discovered.
|
22330 | *
|
22331 | * A `Reference` is a pointer to a `ts.Node` that was extracted from the program somehow. It
|
22332 | * contains not only the node itself, but the information regarding how the node was located. In
|
22333 | * particular, it might track different identifiers by which the node is exposed, as well as
|
22334 | * potentially a module specifier which might expose the node.
|
22335 | *
|
22336 | * The Angular compiler uses `Reference`s instead of `ts.Node`s when tracking classes or generating
|
22337 | * imports.
|
22338 | */
|
22339 | class Reference$1 {
|
22340 | constructor(node, bestGuessOwningModule = null) {
|
22341 | this.node = node;
|
22342 | this.identifiers = [];
|
22343 | /**
|
22344 | * Indicates that the Reference was created synthetically, not as a result of natural value
|
22345 | * resolution.
|
22346 | *
|
22347 | * This is used to avoid misinterpreting the Reference in certain contexts.
|
22348 | */
|
22349 | this.synthetic = false;
|
22350 | this._alias = null;
|
22351 | this.bestGuessOwningModule = bestGuessOwningModule;
|
22352 | const id = identifierOfNode(node);
|
22353 | if (id !== null) {
|
22354 | this.identifiers.push(id);
|
22355 | }
|
22356 | }
|
22357 | /**
|
22358 | * The best guess at which module specifier owns this particular reference, or `null` if there
|
22359 | * isn't one.
|
22360 | */
|
22361 | get ownedByModuleGuess() {
|
22362 | if (this.bestGuessOwningModule !== null) {
|
22363 | return this.bestGuessOwningModule.specifier;
|
22364 | }
|
22365 | else {
|
22366 | return null;
|
22367 | }
|
22368 | }
|
22369 | /**
|
22370 | * Whether this reference has a potential owning module or not.
|
22371 | *
|
22372 | * See `bestGuessOwningModule`.
|
22373 | */
|
22374 | get hasOwningModuleGuess() {
|
22375 | return this.bestGuessOwningModule !== null;
|
22376 | }
|
22377 | /**
|
22378 | * A name for the node, if one is available.
|
22379 | *
|
22380 | * This is only suited for debugging. Any actual references to this node should be made with
|
22381 | * `ts.Identifier`s (see `getIdentityIn`).
|
22382 | */
|
22383 | get debugName() {
|
22384 | const id = identifierOfNode(this.node);
|
22385 | return id !== null ? id.text : null;
|
22386 | }
|
22387 | get alias() {
|
22388 | return this._alias;
|
22389 | }
|
22390 | /**
|
22391 | * Record a `ts.Identifier` by which it's valid to refer to this node, within the context of this
|
22392 | * `Reference`.
|
22393 | */
|
22394 | addIdentifier(identifier) {
|
22395 | this.identifiers.push(identifier);
|
22396 | }
|
22397 | /**
|
22398 | * Get a `ts.Identifier` within this `Reference` that can be used to refer within the context of a
|
22399 | * given `ts.SourceFile`, if any.
|
22400 | */
|
22401 | getIdentityIn(context) {
|
22402 | return this.identifiers.find(id => id.getSourceFile() === context) || null;
|
22403 | }
|
22404 | /**
|
22405 | * Get a `ts.Identifier` for this `Reference` that exists within the given expression.
|
22406 | *
|
22407 | * This is very useful for producing `ts.Diagnostic`s that reference `Reference`s that were
|
22408 | * extracted from some larger expression, as it can be used to pinpoint the `ts.Identifier` within
|
22409 | * the expression from which the `Reference` originated.
|
22410 | */
|
22411 | getIdentityInExpression(expr) {
|
22412 | const sf = expr.getSourceFile();
|
22413 | return this.identifiers.find(id => {
|
22414 | if (id.getSourceFile() !== sf) {
|
22415 | return false;
|
22416 | }
|
22417 | // This identifier is a match if its position lies within the given expression.
|
22418 | return id.pos >= expr.pos && id.end <= expr.end;
|
22419 | }) ||
|
22420 | null;
|
22421 | }
|
22422 | /**
|
22423 | * Given the 'container' expression from which this `Reference` was extracted, produce a
|
22424 | * `ts.Expression` to use in a diagnostic which best indicates the position within the container
|
22425 | * expression that generated the `Reference`.
|
22426 | *
|
22427 | * For example, given a `Reference` to the class 'Bar' and the containing expression:
|
22428 | * `[Foo, Bar, Baz]`, this function would attempt to return the `ts.Identifier` for `Bar` within
|
22429 | * the array. This could be used to produce a nice diagnostic context:
|
22430 | *
|
22431 | * ```text
|
22432 | * [Foo, Bar, Baz]
|
22433 | * ~~~
|
22434 | * ```
|
22435 | *
|
22436 | * If no specific node can be found, then the `fallback` expression is used, which defaults to the
|
22437 | * entire containing expression.
|
22438 | */
|
22439 | getOriginForDiagnostics(container, fallback = container) {
|
22440 | const id = this.getIdentityInExpression(container);
|
22441 | return id !== null ? id : fallback;
|
22442 | }
|
22443 | cloneWithAlias(alias) {
|
22444 | const ref = new Reference$1(this.node, this.bestGuessOwningModule);
|
22445 | ref.identifiers = [...this.identifiers];
|
22446 | ref._alias = alias;
|
22447 | return ref;
|
22448 | }
|
22449 | cloneWithNoIdentifiers() {
|
22450 | const ref = new Reference$1(this.node, this.bestGuessOwningModule);
|
22451 | ref._alias = this._alias;
|
22452 | ref.identifiers = [];
|
22453 | return ref;
|
22454 | }
|
22455 | }
|
22456 |
|
22457 | /**
|
22458 | * Used by `RouterEntryPointManager` and `NgModuleRouteAnalyzer` (which is in turn is used by
|
22459 | * `NgModuleDecoratorHandler`) for resolving the module source-files references in lazy-loaded
|
22460 | * routes (relative to the source-file containing the `NgModule` that provides the route
|
22461 | * definitions).
|
22462 | */
|
22463 | class ModuleResolver {
|
22464 | constructor(program, compilerOptions, host, moduleResolutionCache) {
|
22465 | this.program = program;
|
22466 | this.compilerOptions = compilerOptions;
|
22467 | this.host = host;
|
22468 | this.moduleResolutionCache = moduleResolutionCache;
|
22469 | }
|
22470 | resolveModule(moduleName, containingFile) {
|
22471 | const resolved = resolveModuleName(moduleName, containingFile, this.compilerOptions, this.host, this.moduleResolutionCache);
|
22472 | if (resolved === undefined) {
|
22473 | return null;
|
22474 | }
|
22475 | return getSourceFileOrNull(this.program, absoluteFrom(resolved.resolvedFileName));
|
22476 | }
|
22477 | }
|
22478 |
|
22479 | /**
|
22480 | * @license
|
22481 | * Copyright Google LLC All Rights Reserved.
|
22482 | *
|
22483 | * Use of this source code is governed by an MIT-style license that can be
|
22484 | * found in the LICENSE file at https://angular.io/license
|
22485 | */
|
22486 | const Decorator = {
|
22487 | nodeForError: (decorator) => {
|
22488 | if (decorator.node !== null) {
|
22489 | return decorator.node;
|
22490 | }
|
22491 | else {
|
22492 | // TODO(alxhub): we can't rely on narrowing until TS 3.6 is in g3.
|
22493 | return decorator.synthesizedFor;
|
22494 | }
|
22495 | },
|
22496 | };
|
22497 | function isDecoratorIdentifier(exp) {
|
22498 | return ts$1.isIdentifier(exp) ||
|
22499 | ts$1.isPropertyAccessExpression(exp) && ts$1.isIdentifier(exp.expression) &&
|
22500 | ts$1.isIdentifier(exp.name);
|
22501 | }
|
22502 | /**
|
22503 | * An enumeration of possible kinds of class members.
|
22504 | */
|
22505 | var ClassMemberKind;
|
22506 | (function (ClassMemberKind) {
|
22507 | ClassMemberKind[ClassMemberKind["Constructor"] = 0] = "Constructor";
|
22508 | ClassMemberKind[ClassMemberKind["Getter"] = 1] = "Getter";
|
22509 | ClassMemberKind[ClassMemberKind["Setter"] = 2] = "Setter";
|
22510 | ClassMemberKind[ClassMemberKind["Property"] = 3] = "Property";
|
22511 | ClassMemberKind[ClassMemberKind["Method"] = 4] = "Method";
|
22512 | })(ClassMemberKind || (ClassMemberKind = {}));
|
22513 | /**
|
22514 | * Possible declarations of known values, such as built-in objects/functions or TypeScript helpers.
|
22515 | */
|
22516 | var KnownDeclaration;
|
22517 | (function (KnownDeclaration) {
|
22518 | /**
|
22519 | * Indicates the JavaScript global `Object` class.
|
22520 | */
|
22521 | KnownDeclaration[KnownDeclaration["JsGlobalObject"] = 0] = "JsGlobalObject";
|
22522 | /**
|
22523 | * Indicates the `__assign` TypeScript helper function.
|
22524 | */
|
22525 | KnownDeclaration[KnownDeclaration["TsHelperAssign"] = 1] = "TsHelperAssign";
|
22526 | /**
|
22527 | * Indicates the `__spread` TypeScript helper function.
|
22528 | */
|
22529 | KnownDeclaration[KnownDeclaration["TsHelperSpread"] = 2] = "TsHelperSpread";
|
22530 | /**
|
22531 | * Indicates the `__spreadArrays` TypeScript helper function.
|
22532 | */
|
22533 | KnownDeclaration[KnownDeclaration["TsHelperSpreadArrays"] = 3] = "TsHelperSpreadArrays";
|
22534 | })(KnownDeclaration || (KnownDeclaration = {}));
|
22535 | /**
|
22536 | * Returns true if the `decl` is a `ConcreteDeclaration` (ie. that its `node` property is a
|
22537 | * `ts.Declaration`).
|
22538 | */
|
22539 | function isConcreteDeclaration(decl) {
|
22540 | return decl.kind === 0 /* Concrete */;
|
22541 | }
|
22542 |
|
22543 | /**
|
22544 | * @license
|
22545 | * Copyright Google LLC All Rights Reserved.
|
22546 | *
|
22547 | * Use of this source code is governed by an MIT-style license that can be
|
22548 | * found in the LICENSE file at https://angular.io/license
|
22549 | */
|
22550 | /**
|
22551 | * Potentially convert a `ts.TypeNode` to a `TypeValueReference`, which indicates how to use the
|
22552 | * type given in the `ts.TypeNode` in a value position.
|
22553 | *
|
22554 | * This can return `null` if the `typeNode` is `null`, if it does not refer to a symbol with a value
|
22555 | * declaration, or if it is not possible to statically understand.
|
22556 | */
|
22557 | function typeToValue(typeNode, checker) {
|
22558 | // It's not possible to get a value expression if the parameter doesn't even have a type.
|
22559 | if (typeNode === null) {
|
22560 | return missingType();
|
22561 | }
|
22562 | if (!ts$1.isTypeReferenceNode(typeNode)) {
|
22563 | return unsupportedType(typeNode);
|
22564 | }
|
22565 | const symbols = resolveTypeSymbols(typeNode, checker);
|
22566 | if (symbols === null) {
|
22567 | return unknownReference(typeNode);
|
22568 | }
|
22569 | const { local, decl } = symbols;
|
22570 | // It's only valid to convert a type reference to a value reference if the type actually
|
22571 | // has a value declaration associated with it. Note that const enums are an exception,
|
22572 | // because while they do have a value declaration, they don't exist at runtime.
|
22573 | if (decl.valueDeclaration === undefined || decl.flags & ts$1.SymbolFlags.ConstEnum) {
|
22574 | let typeOnlyDecl = null;
|
22575 | if (decl.declarations !== undefined && decl.declarations.length > 0) {
|
22576 | typeOnlyDecl = decl.declarations[0];
|
22577 | }
|
22578 | return noValueDeclaration(typeNode, typeOnlyDecl);
|
22579 | }
|
22580 | // The type points to a valid value declaration. Rewrite the TypeReference into an
|
22581 | // Expression which references the value pointed to by the TypeReference, if possible.
|
22582 | // Look at the local `ts.Symbol`'s declarations and see if it comes from an import
|
22583 | // statement. If so, extract the module specifier and the name of the imported type.
|
22584 | const firstDecl = local.declarations && local.declarations[0];
|
22585 | if (firstDecl !== undefined) {
|
22586 | if (ts$1.isImportClause(firstDecl) && firstDecl.name !== undefined) {
|
22587 | // This is a default import.
|
22588 | // import Foo from 'foo';
|
22589 | if (firstDecl.isTypeOnly) {
|
22590 | // Type-only imports cannot be represented as value.
|
22591 | return typeOnlyImport(typeNode, firstDecl);
|
22592 | }
|
22593 | return {
|
22594 | kind: 0 /* LOCAL */,
|
22595 | expression: firstDecl.name,
|
22596 | defaultImportStatement: firstDecl.parent,
|
22597 | };
|
22598 | }
|
22599 | else if (ts$1.isImportSpecifier(firstDecl)) {
|
22600 | // The symbol was imported by name
|
22601 | // import {Foo} from 'foo';
|
22602 | // or
|
22603 | // import {Foo as Bar} from 'foo';
|
22604 | if (firstDecl.parent.parent.isTypeOnly) {
|
22605 | // Type-only imports cannot be represented as value.
|
22606 | return typeOnlyImport(typeNode, firstDecl.parent.parent);
|
22607 | }
|
22608 | // Determine the name to import (`Foo`) from the import specifier, as the symbol names of
|
22609 | // the imported type could refer to a local alias (like `Bar` in the example above).
|
22610 | const importedName = (firstDecl.propertyName || firstDecl.name).text;
|
22611 | // The first symbol name refers to the local name, which is replaced by `importedName` above.
|
22612 | // Any remaining symbol names make up the complete path to the value.
|
22613 | const [_localName, ...nestedPath] = symbols.symbolNames;
|
22614 | const moduleName = extractModuleName(firstDecl.parent.parent.parent);
|
22615 | return {
|
22616 | kind: 1 /* IMPORTED */,
|
22617 | valueDeclaration: decl.valueDeclaration,
|
22618 | moduleName,
|
22619 | importedName,
|
22620 | nestedPath
|
22621 | };
|
22622 | }
|
22623 | else if (ts$1.isNamespaceImport(firstDecl)) {
|
22624 | // The import is a namespace import
|
22625 | // import * as Foo from 'foo';
|
22626 | if (firstDecl.parent.isTypeOnly) {
|
22627 | // Type-only imports cannot be represented as value.
|
22628 | return typeOnlyImport(typeNode, firstDecl.parent);
|
22629 | }
|
22630 | if (symbols.symbolNames.length === 1) {
|
22631 | // The type refers to the namespace itself, which cannot be represented as a value.
|
22632 | return namespaceImport(typeNode, firstDecl.parent);
|
22633 | }
|
22634 | // The first symbol name refers to the local name of the namespace, which is is discarded
|
22635 | // as a new namespace import will be generated. This is followed by the symbol name that needs
|
22636 | // to be imported and any remaining names that constitute the complete path to the value.
|
22637 | const [_ns, importedName, ...nestedPath] = symbols.symbolNames;
|
22638 | const moduleName = extractModuleName(firstDecl.parent.parent);
|
22639 | return {
|
22640 | kind: 1 /* IMPORTED */,
|
22641 | valueDeclaration: decl.valueDeclaration,
|
22642 | moduleName,
|
22643 | importedName,
|
22644 | nestedPath
|
22645 | };
|
22646 | }
|
22647 | }
|
22648 | // If the type is not imported, the type reference can be converted into an expression as is.
|
22649 | const expression = typeNodeToValueExpr(typeNode);
|
22650 | if (expression !== null) {
|
22651 | return {
|
22652 | kind: 0 /* LOCAL */,
|
22653 | expression,
|
22654 | defaultImportStatement: null,
|
22655 | };
|
22656 | }
|
22657 | else {
|
22658 | return unsupportedType(typeNode);
|
22659 | }
|
22660 | }
|
22661 | function unsupportedType(typeNode) {
|
22662 | return {
|
22663 | kind: 2 /* UNAVAILABLE */,
|
22664 | reason: { kind: 5 /* UNSUPPORTED */, typeNode },
|
22665 | };
|
22666 | }
|
22667 | function noValueDeclaration(typeNode, decl) {
|
22668 | return {
|
22669 | kind: 2 /* UNAVAILABLE */,
|
22670 | reason: { kind: 1 /* NO_VALUE_DECLARATION */, typeNode, decl },
|
22671 | };
|
22672 | }
|
22673 | function typeOnlyImport(typeNode, importClause) {
|
22674 | return {
|
22675 | kind: 2 /* UNAVAILABLE */,
|
22676 | reason: { kind: 2 /* TYPE_ONLY_IMPORT */, typeNode, importClause },
|
22677 | };
|
22678 | }
|
22679 | function unknownReference(typeNode) {
|
22680 | return {
|
22681 | kind: 2 /* UNAVAILABLE */,
|
22682 | reason: { kind: 3 /* UNKNOWN_REFERENCE */, typeNode },
|
22683 | };
|
22684 | }
|
22685 | function namespaceImport(typeNode, importClause) {
|
22686 | return {
|
22687 | kind: 2 /* UNAVAILABLE */,
|
22688 | reason: { kind: 4 /* NAMESPACE */, typeNode, importClause },
|
22689 | };
|
22690 | }
|
22691 | function missingType() {
|
22692 | return {
|
22693 | kind: 2 /* UNAVAILABLE */,
|
22694 | reason: { kind: 0 /* MISSING_TYPE */ },
|
22695 | };
|
22696 | }
|
22697 | /**
|
22698 | * Attempt to extract a `ts.Expression` that's equivalent to a `ts.TypeNode`, as the two have
|
22699 | * different AST shapes but can reference the same symbols.
|
22700 | *
|
22701 | * This will return `null` if an equivalent expression cannot be constructed.
|
22702 | */
|
22703 | function typeNodeToValueExpr(node) {
|
22704 | if (ts$1.isTypeReferenceNode(node)) {
|
22705 | return entityNameToValue(node.typeName);
|
22706 | }
|
22707 | else {
|
22708 | return null;
|
22709 | }
|
22710 | }
|
22711 | /**
|
22712 | * Resolve a `TypeReference` node to the `ts.Symbol`s for both its declaration and its local source.
|
22713 | *
|
22714 | * In the event that the `TypeReference` refers to a locally declared symbol, these will be the
|
22715 | * same. If the `TypeReference` refers to an imported symbol, then `decl` will be the fully resolved
|
22716 | * `ts.Symbol` of the referenced symbol. `local` will be the `ts.Symbol` of the `ts.Identifier`
|
22717 | * which points to the import statement by which the symbol was imported.
|
22718 | *
|
22719 | * All symbol names that make up the type reference are returned left-to-right into the
|
22720 | * `symbolNames` array, which is guaranteed to include at least one entry.
|
22721 | */
|
22722 | function resolveTypeSymbols(typeRef, checker) {
|
22723 | const typeName = typeRef.typeName;
|
22724 | // typeRefSymbol is the ts.Symbol of the entire type reference.
|
22725 | const typeRefSymbol = checker.getSymbolAtLocation(typeName);
|
22726 | if (typeRefSymbol === undefined) {
|
22727 | return null;
|
22728 | }
|
22729 | // `local` is the `ts.Symbol` for the local `ts.Identifier` for the type.
|
22730 | // If the type is actually locally declared or is imported by name, for example:
|
22731 | // import {Foo} from './foo';
|
22732 | // then it'll be the same as `typeRefSymbol`.
|
22733 | //
|
22734 | // If the type is imported via a namespace import, for example:
|
22735 | // import * as foo from './foo';
|
22736 | // and then referenced as:
|
22737 | // constructor(f: foo.Foo)
|
22738 | // then `local` will be the `ts.Symbol` of `foo`, whereas `typeRefSymbol` will be the `ts.Symbol`
|
22739 | // of `foo.Foo`. This allows tracking of the import behind whatever type reference exists.
|
22740 | let local = typeRefSymbol;
|
22741 | // Destructure a name like `foo.X.Y.Z` as follows:
|
22742 | // - in `leftMost`, the `ts.Identifier` of the left-most name (`foo`) in the qualified name.
|
22743 | // This identifier is used to resolve the `ts.Symbol` for `local`.
|
22744 | // - in `symbolNames`, all names involved in the qualified path, or a single symbol name if the
|
22745 | // type is not qualified.
|
22746 | let leftMost = typeName;
|
22747 | const symbolNames = [];
|
22748 | while (ts$1.isQualifiedName(leftMost)) {
|
22749 | symbolNames.unshift(leftMost.right.text);
|
22750 | leftMost = leftMost.left;
|
22751 | }
|
22752 | symbolNames.unshift(leftMost.text);
|
22753 | if (leftMost !== typeName) {
|
22754 | const localTmp = checker.getSymbolAtLocation(leftMost);
|
22755 | if (localTmp !== undefined) {
|
22756 | local = localTmp;
|
22757 | }
|
22758 | }
|
22759 | // De-alias the top-level type reference symbol to get the symbol of the actual declaration.
|
22760 | let decl = typeRefSymbol;
|
22761 | if (typeRefSymbol.flags & ts$1.SymbolFlags.Alias) {
|
22762 | decl = checker.getAliasedSymbol(typeRefSymbol);
|
22763 | }
|
22764 | return { local, decl, symbolNames };
|
22765 | }
|
22766 | function entityNameToValue(node) {
|
22767 | if (ts$1.isQualifiedName(node)) {
|
22768 | const left = entityNameToValue(node.left);
|
22769 | return left !== null ? ts$1.createPropertyAccess(left, node.right) : null;
|
22770 | }
|
22771 | else if (ts$1.isIdentifier(node)) {
|
22772 | return ts$1.getMutableClone(node);
|
22773 | }
|
22774 | else {
|
22775 | return null;
|
22776 | }
|
22777 | }
|
22778 | function extractModuleName(node) {
|
22779 | if (!ts$1.isStringLiteral(node.moduleSpecifier)) {
|
22780 | throw new Error('not a module specifier');
|
22781 | }
|
22782 | return node.moduleSpecifier.text;
|
22783 | }
|
22784 |
|
22785 | /**
|
22786 | * @license
|
22787 | * Copyright Google LLC All Rights Reserved.
|
22788 | *
|
22789 | * Use of this source code is governed by an MIT-style license that can be
|
22790 | * found in the LICENSE file at https://angular.io/license
|
22791 | */
|
22792 | function isNamedClassDeclaration(node) {
|
22793 | return ts$1.isClassDeclaration(node) && isIdentifier$1(node.name);
|
22794 | }
|
22795 | function isIdentifier$1(node) {
|
22796 | return node !== undefined && ts$1.isIdentifier(node);
|
22797 | }
|
22798 |
|
22799 | /**
|
22800 | * @license
|
22801 | * Copyright Google LLC All Rights Reserved.
|
22802 | *
|
22803 | * Use of this source code is governed by an MIT-style license that can be
|
22804 | * found in the LICENSE file at https://angular.io/license
|
22805 | */
|
22806 | /**
|
22807 | * reflector.ts implements static reflection of declarations using the TypeScript `ts.TypeChecker`.
|
22808 | */
|
22809 | class TypeScriptReflectionHost {
|
22810 | constructor(checker) {
|
22811 | this.checker = checker;
|
22812 | }
|
22813 | getDecoratorsOfDeclaration(declaration) {
|
22814 | if (declaration.decorators === undefined || declaration.decorators.length === 0) {
|
22815 | return null;
|
22816 | }
|
22817 | return declaration.decorators.map(decorator => this._reflectDecorator(decorator))
|
22818 | .filter((dec) => dec !== null);
|
22819 | }
|
22820 | getMembersOfClass(clazz) {
|
22821 | const tsClazz = castDeclarationToClassOrDie(clazz);
|
22822 | return tsClazz.members.map(member => this._reflectMember(member))
|
22823 | .filter((member) => member !== null);
|
22824 | }
|
22825 | getConstructorParameters(clazz) {
|
22826 | const tsClazz = castDeclarationToClassOrDie(clazz);
|
22827 | const isDeclaration = tsClazz.getSourceFile().isDeclarationFile;
|
22828 | // For non-declaration files, we want to find the constructor with a `body`. The constructors
|
22829 | // without a `body` are overloads whereas we want the implementation since it's the one that'll
|
22830 | // be executed and which can have decorators. For declaration files, we take the first one that
|
22831 | // we get.
|
22832 | const ctor = tsClazz.members.find((member) => ts$1.isConstructorDeclaration(member) && (isDeclaration || member.body !== undefined));
|
22833 | if (ctor === undefined) {
|
22834 | return null;
|
22835 | }
|
22836 | return ctor.parameters.map(node => {
|
22837 | // The name of the parameter is easy.
|
22838 | const name = parameterName(node.name);
|
22839 | const decorators = this.getDecoratorsOfDeclaration(node);
|
22840 | // It may or may not be possible to write an expression that refers to the value side of the
|
22841 | // type named for the parameter.
|
22842 | let originalTypeNode = node.type || null;
|
22843 | let typeNode = originalTypeNode;
|
22844 | // Check if we are dealing with a simple nullable union type e.g. `foo: Foo|null`
|
22845 | // and extract the type. More complex union types e.g. `foo: Foo|Bar` are not supported.
|
22846 | // We also don't need to support `foo: Foo|undefined` because Angular's DI injects `null` for
|
22847 | // optional tokes that don't have providers.
|
22848 | if (typeNode && ts$1.isUnionTypeNode(typeNode)) {
|
22849 | let childTypeNodes = typeNode.types.filter(childTypeNode => !(ts$1.isLiteralTypeNode(childTypeNode) &&
|
22850 | childTypeNode.literal.kind === ts$1.SyntaxKind.NullKeyword));
|
22851 | if (childTypeNodes.length === 1) {
|
22852 | typeNode = childTypeNodes[0];
|
22853 | }
|
22854 | }
|
22855 | const typeValueReference = typeToValue(typeNode, this.checker);
|
22856 | return {
|
22857 | name,
|
22858 | nameNode: node.name,
|
22859 | typeValueReference,
|
22860 | typeNode: originalTypeNode,
|
22861 | decorators,
|
22862 | };
|
22863 | });
|
22864 | }
|
22865 | getImportOfIdentifier(id) {
|
22866 | const directImport = this.getDirectImportOfIdentifier(id);
|
22867 | if (directImport !== null) {
|
22868 | return directImport;
|
22869 | }
|
22870 | else if (ts$1.isQualifiedName(id.parent) && id.parent.right === id) {
|
22871 | return this.getImportOfNamespacedIdentifier(id, getQualifiedNameRoot(id.parent));
|
22872 | }
|
22873 | else if (ts$1.isPropertyAccessExpression(id.parent) && id.parent.name === id) {
|
22874 | return this.getImportOfNamespacedIdentifier(id, getFarLeftIdentifier(id.parent));
|
22875 | }
|
22876 | else {
|
22877 | return null;
|
22878 | }
|
22879 | }
|
22880 | getExportsOfModule(node) {
|
22881 | // In TypeScript code, modules are only ts.SourceFiles. Throw if the node isn't a module.
|
22882 | if (!ts$1.isSourceFile(node)) {
|
22883 | throw new Error(`getExportsOfModule() called on non-SourceFile in TS code`);
|
22884 | }
|
22885 | // Reflect the module to a Symbol, and use getExportsOfModule() to get a list of exported
|
22886 | // Symbols.
|
22887 | const symbol = this.checker.getSymbolAtLocation(node);
|
22888 | if (symbol === undefined) {
|
22889 | return null;
|
22890 | }
|
22891 | const map = new Map();
|
22892 | this.checker.getExportsOfModule(symbol).forEach(exportSymbol => {
|
22893 | // Map each exported Symbol to a Declaration and add it to the map.
|
22894 | const decl = this.getDeclarationOfSymbol(exportSymbol, null);
|
22895 | if (decl !== null) {
|
22896 | map.set(exportSymbol.name, decl);
|
22897 | }
|
22898 | });
|
22899 | return map;
|
22900 | }
|
22901 | isClass(node) {
|
22902 | // For our purposes, classes are "named" ts.ClassDeclarations;
|
22903 | // (`node.name` can be undefined in unnamed default exports: `default export class { ... }`).
|
22904 | return isNamedClassDeclaration(node);
|
22905 | }
|
22906 | hasBaseClass(clazz) {
|
22907 | return this.getBaseClassExpression(clazz) !== null;
|
22908 | }
|
22909 | getBaseClassExpression(clazz) {
|
22910 | if (!(ts$1.isClassDeclaration(clazz) || ts$1.isClassExpression(clazz)) ||
|
22911 | clazz.heritageClauses === undefined) {
|
22912 | return null;
|
22913 | }
|
22914 | const extendsClause = clazz.heritageClauses.find(clause => clause.token === ts$1.SyntaxKind.ExtendsKeyword);
|
22915 | if (extendsClause === undefined) {
|
22916 | return null;
|
22917 | }
|
22918 | const extendsType = extendsClause.types[0];
|
22919 | if (extendsType === undefined) {
|
22920 | return null;
|
22921 | }
|
22922 | return extendsType.expression;
|
22923 | }
|
22924 | getDeclarationOfIdentifier(id) {
|
22925 | // Resolve the identifier to a Symbol, and return the declaration of that.
|
22926 | let symbol = this.checker.getSymbolAtLocation(id);
|
22927 | if (symbol === undefined) {
|
22928 | return null;
|
22929 | }
|
22930 | return this.getDeclarationOfSymbol(symbol, id);
|
22931 | }
|
22932 | getDefinitionOfFunction(node) {
|
22933 | if (!ts$1.isFunctionDeclaration(node) && !ts$1.isMethodDeclaration(node) &&
|
22934 | !ts$1.isFunctionExpression(node)) {
|
22935 | return null;
|
22936 | }
|
22937 | return {
|
22938 | node,
|
22939 | body: node.body !== undefined ? Array.from(node.body.statements) : null,
|
22940 | parameters: node.parameters.map(param => {
|
22941 | const name = parameterName(param.name);
|
22942 | const initializer = param.initializer || null;
|
22943 | return { name, node: param, initializer };
|
22944 | }),
|
22945 | };
|
22946 | }
|
22947 | getGenericArityOfClass(clazz) {
|
22948 | if (!ts$1.isClassDeclaration(clazz)) {
|
22949 | return null;
|
22950 | }
|
22951 | return clazz.typeParameters !== undefined ? clazz.typeParameters.length : 0;
|
22952 | }
|
22953 | getVariableValue(declaration) {
|
22954 | return declaration.initializer || null;
|
22955 | }
|
22956 | getDtsDeclaration(_) {
|
22957 | return null;
|
22958 | }
|
22959 | getInternalNameOfClass(clazz) {
|
22960 | return clazz.name;
|
22961 | }
|
22962 | getAdjacentNameOfClass(clazz) {
|
22963 | return clazz.name;
|
22964 | }
|
22965 | getDirectImportOfIdentifier(id) {
|
22966 | const symbol = this.checker.getSymbolAtLocation(id);
|
22967 | if (symbol === undefined || symbol.declarations === undefined ||
|
22968 | symbol.declarations.length !== 1) {
|
22969 | return null;
|
22970 | }
|
22971 | const decl = symbol.declarations[0];
|
22972 | const importDecl = getContainingImportDeclaration(decl);
|
22973 | // Ignore declarations that are defined locally (not imported).
|
22974 | if (importDecl === null) {
|
22975 | return null;
|
22976 | }
|
22977 | // The module specifier is guaranteed to be a string literal, so this should always pass.
|
22978 | if (!ts$1.isStringLiteral(importDecl.moduleSpecifier)) {
|
22979 | // Not allowed to happen in TypeScript ASTs.
|
22980 | return null;
|
22981 | }
|
22982 | return { from: importDecl.moduleSpecifier.text, name: getExportedName(decl, id) };
|
22983 | }
|
22984 | /**
|
22985 | * Try to get the import info for this identifier as though it is a namespaced import.
|
22986 | *
|
22987 | * For example, if the identifier is the `Directive` part of a qualified type chain like:
|
22988 | *
|
22989 | * ```
|
22990 | * core.Directive
|
22991 | * ```
|
22992 | *
|
22993 | * then it might be that `core` is a namespace import such as:
|
22994 | *
|
22995 | * ```
|
22996 | * import * as core from 'tslib';
|
22997 | * ```
|
22998 | *
|
22999 | * @param id the TypeScript identifier to find the import info for.
|
23000 | * @returns The import info if this is a namespaced import or `null`.
|
23001 | */
|
23002 | getImportOfNamespacedIdentifier(id, namespaceIdentifier) {
|
23003 | if (namespaceIdentifier === null) {
|
23004 | return null;
|
23005 | }
|
23006 | const namespaceSymbol = this.checker.getSymbolAtLocation(namespaceIdentifier);
|
23007 | if (!namespaceSymbol) {
|
23008 | return null;
|
23009 | }
|
23010 | const declaration = namespaceSymbol.declarations.length === 1 ? namespaceSymbol.declarations[0] : null;
|
23011 | if (!declaration) {
|
23012 | return null;
|
23013 | }
|
23014 | const namespaceDeclaration = ts$1.isNamespaceImport(declaration) ? declaration : null;
|
23015 | if (!namespaceDeclaration) {
|
23016 | return null;
|
23017 | }
|
23018 | const importDeclaration = namespaceDeclaration.parent.parent;
|
23019 | if (!ts$1.isStringLiteral(importDeclaration.moduleSpecifier)) {
|
23020 | // Should not happen as this would be invalid TypesScript
|
23021 | return null;
|
23022 | }
|
23023 | return {
|
23024 | from: importDeclaration.moduleSpecifier.text,
|
23025 | name: id.text,
|
23026 | };
|
23027 | }
|
23028 | /**
|
23029 | * Resolve a `ts.Symbol` to its declaration, keeping track of the `viaModule` along the way.
|
23030 | */
|
23031 | getDeclarationOfSymbol(symbol, originalId) {
|
23032 | // If the symbol points to a ShorthandPropertyAssignment, resolve it.
|
23033 | let valueDeclaration = undefined;
|
23034 | if (symbol.valueDeclaration !== undefined) {
|
23035 | valueDeclaration = symbol.valueDeclaration;
|
23036 | }
|
23037 | else if (symbol.declarations !== undefined && symbol.declarations.length > 0) {
|
23038 | valueDeclaration = symbol.declarations[0];
|
23039 | }
|
23040 | if (valueDeclaration !== undefined && ts$1.isShorthandPropertyAssignment(valueDeclaration)) {
|
23041 | const shorthandSymbol = this.checker.getShorthandAssignmentValueSymbol(valueDeclaration);
|
23042 | if (shorthandSymbol === undefined) {
|
23043 | return null;
|
23044 | }
|
23045 | return this.getDeclarationOfSymbol(shorthandSymbol, originalId);
|
23046 | }
|
23047 | else if (valueDeclaration !== undefined && ts$1.isExportSpecifier(valueDeclaration)) {
|
23048 | const targetSymbol = this.checker.getExportSpecifierLocalTargetSymbol(valueDeclaration);
|
23049 | if (targetSymbol === undefined) {
|
23050 | return null;
|
23051 | }
|
23052 | return this.getDeclarationOfSymbol(targetSymbol, originalId);
|
23053 | }
|
23054 | const importInfo = originalId && this.getImportOfIdentifier(originalId);
|
23055 | const viaModule = importInfo !== null && importInfo.from !== null && !importInfo.from.startsWith('.') ?
|
23056 | importInfo.from :
|
23057 | null;
|
23058 | // Now, resolve the Symbol to its declaration by following any and all aliases.
|
23059 | while (symbol.flags & ts$1.SymbolFlags.Alias) {
|
23060 | symbol = this.checker.getAliasedSymbol(symbol);
|
23061 | }
|
23062 | // Look at the resolved Symbol's declarations and pick one of them to return. Value declarations
|
23063 | // are given precedence over type declarations.
|
23064 | if (symbol.valueDeclaration !== undefined) {
|
23065 | return {
|
23066 | node: symbol.valueDeclaration,
|
23067 | known: null,
|
23068 | viaModule,
|
23069 | identity: null,
|
23070 | kind: 0 /* Concrete */,
|
23071 | };
|
23072 | }
|
23073 | else if (symbol.declarations !== undefined && symbol.declarations.length > 0) {
|
23074 | return {
|
23075 | node: symbol.declarations[0],
|
23076 | known: null,
|
23077 | viaModule,
|
23078 | identity: null,
|
23079 | kind: 0 /* Concrete */,
|
23080 | };
|
23081 | }
|
23082 | else {
|
23083 | return null;
|
23084 | }
|
23085 | }
|
23086 | _reflectDecorator(node) {
|
23087 | // Attempt to resolve the decorator expression into a reference to a concrete Identifier. The
|
23088 | // expression may contain a call to a function which returns the decorator function, in which
|
23089 | // case we want to return the arguments.
|
23090 | let decoratorExpr = node.expression;
|
23091 | let args = null;
|
23092 | // Check for call expressions.
|
23093 | if (ts$1.isCallExpression(decoratorExpr)) {
|
23094 | args = Array.from(decoratorExpr.arguments);
|
23095 | decoratorExpr = decoratorExpr.expression;
|
23096 | }
|
23097 | // The final resolved decorator should be a `ts.Identifier` - if it's not, then something is
|
23098 | // wrong and the decorator can't be resolved statically.
|
23099 | if (!isDecoratorIdentifier(decoratorExpr)) {
|
23100 | return null;
|
23101 | }
|
23102 | const decoratorIdentifier = ts$1.isIdentifier(decoratorExpr) ? decoratorExpr : decoratorExpr.name;
|
23103 | const importDecl = this.getImportOfIdentifier(decoratorIdentifier);
|
23104 | return {
|
23105 | name: decoratorIdentifier.text,
|
23106 | identifier: decoratorExpr,
|
23107 | import: importDecl,
|
23108 | node,
|
23109 | args,
|
23110 | };
|
23111 | }
|
23112 | _reflectMember(node) {
|
23113 | let kind = null;
|
23114 | let value = null;
|
23115 | let name = null;
|
23116 | let nameNode = null;
|
23117 | if (ts$1.isPropertyDeclaration(node)) {
|
23118 | kind = ClassMemberKind.Property;
|
23119 | value = node.initializer || null;
|
23120 | }
|
23121 | else if (ts$1.isGetAccessorDeclaration(node)) {
|
23122 | kind = ClassMemberKind.Getter;
|
23123 | }
|
23124 | else if (ts$1.isSetAccessorDeclaration(node)) {
|
23125 | kind = ClassMemberKind.Setter;
|
23126 | }
|
23127 | else if (ts$1.isMethodDeclaration(node)) {
|
23128 | kind = ClassMemberKind.Method;
|
23129 | }
|
23130 | else if (ts$1.isConstructorDeclaration(node)) {
|
23131 | kind = ClassMemberKind.Constructor;
|
23132 | }
|
23133 | else {
|
23134 | return null;
|
23135 | }
|
23136 | if (ts$1.isConstructorDeclaration(node)) {
|
23137 | name = 'constructor';
|
23138 | }
|
23139 | else if (ts$1.isIdentifier(node.name)) {
|
23140 | name = node.name.text;
|
23141 | nameNode = node.name;
|
23142 | }
|
23143 | else if (ts$1.isStringLiteral(node.name)) {
|
23144 | name = node.name.text;
|
23145 | nameNode = node.name;
|
23146 | }
|
23147 | else {
|
23148 | return null;
|
23149 | }
|
23150 | const decorators = this.getDecoratorsOfDeclaration(node);
|
23151 | const isStatic = node.modifiers !== undefined &&
|
23152 | node.modifiers.some(mod => mod.kind === ts$1.SyntaxKind.StaticKeyword);
|
23153 | return {
|
23154 | node,
|
23155 | implementation: node,
|
23156 | kind,
|
23157 | type: node.type || null,
|
23158 | name,
|
23159 | nameNode,
|
23160 | decorators,
|
23161 | value,
|
23162 | isStatic,
|
23163 | };
|
23164 | }
|
23165 | }
|
23166 | function reflectTypeEntityToDeclaration(type, checker) {
|
23167 | let realSymbol = checker.getSymbolAtLocation(type);
|
23168 | if (realSymbol === undefined) {
|
23169 | throw new Error(`Cannot resolve type entity ${type.getText()} to symbol`);
|
23170 | }
|
23171 | while (realSymbol.flags & ts$1.SymbolFlags.Alias) {
|
23172 | realSymbol = checker.getAliasedSymbol(realSymbol);
|
23173 | }
|
23174 | let node = null;
|
23175 | if (realSymbol.valueDeclaration !== undefined) {
|
23176 | node = realSymbol.valueDeclaration;
|
23177 | }
|
23178 | else if (realSymbol.declarations !== undefined && realSymbol.declarations.length === 1) {
|
23179 | node = realSymbol.declarations[0];
|
23180 | }
|
23181 | else {
|
23182 | throw new Error(`Cannot resolve type entity symbol to declaration`);
|
23183 | }
|
23184 | if (ts$1.isQualifiedName(type)) {
|
23185 | if (!ts$1.isIdentifier(type.left)) {
|
23186 | throw new Error(`Cannot handle qualified name with non-identifier lhs`);
|
23187 | }
|
23188 | const symbol = checker.getSymbolAtLocation(type.left);
|
23189 | if (symbol === undefined || symbol.declarations === undefined ||
|
23190 | symbol.declarations.length !== 1) {
|
23191 | throw new Error(`Cannot resolve qualified type entity lhs to symbol`);
|
23192 | }
|
23193 | const decl = symbol.declarations[0];
|
23194 | if (ts$1.isNamespaceImport(decl)) {
|
23195 | const clause = decl.parent;
|
23196 | const importDecl = clause.parent;
|
23197 | if (!ts$1.isStringLiteral(importDecl.moduleSpecifier)) {
|
23198 | throw new Error(`Module specifier is not a string`);
|
23199 | }
|
23200 | return { node, from: importDecl.moduleSpecifier.text };
|
23201 | }
|
23202 | else {
|
23203 | throw new Error(`Unknown import type?`);
|
23204 | }
|
23205 | }
|
23206 | else {
|
23207 | return { node, from: null };
|
23208 | }
|
23209 | }
|
23210 | function filterToMembersWithDecorator(members, name, module) {
|
23211 | return members.filter(member => !member.isStatic)
|
23212 | .map(member => {
|
23213 | if (member.decorators === null) {
|
23214 | return null;
|
23215 | }
|
23216 | const decorators = member.decorators.filter(dec => {
|
23217 | if (dec.import !== null) {
|
23218 | return dec.import.name === name && (module === undefined || dec.import.from === module);
|
23219 | }
|
23220 | else {
|
23221 | return dec.name === name && module === undefined;
|
23222 | }
|
23223 | });
|
23224 | if (decorators.length === 0) {
|
23225 | return null;
|
23226 | }
|
23227 | return { member, decorators };
|
23228 | })
|
23229 | .filter((value) => value !== null);
|
23230 | }
|
23231 | function reflectObjectLiteral(node) {
|
23232 | const map = new Map();
|
23233 | node.properties.forEach(prop => {
|
23234 | if (ts$1.isPropertyAssignment(prop)) {
|
23235 | const name = propertyNameToString(prop.name);
|
23236 | if (name === null) {
|
23237 | return;
|
23238 | }
|
23239 | map.set(name, prop.initializer);
|
23240 | }
|
23241 | else if (ts$1.isShorthandPropertyAssignment(prop)) {
|
23242 | map.set(prop.name.text, prop.name);
|
23243 | }
|
23244 | else {
|
23245 | return;
|
23246 | }
|
23247 | });
|
23248 | return map;
|
23249 | }
|
23250 | function castDeclarationToClassOrDie(declaration) {
|
23251 | if (!ts$1.isClassDeclaration(declaration)) {
|
23252 | throw new Error(`Reflecting on a ${ts$1.SyntaxKind[declaration.kind]} instead of a ClassDeclaration.`);
|
23253 | }
|
23254 | return declaration;
|
23255 | }
|
23256 | function parameterName(name) {
|
23257 | if (ts$1.isIdentifier(name)) {
|
23258 | return name.text;
|
23259 | }
|
23260 | else {
|
23261 | return null;
|
23262 | }
|
23263 | }
|
23264 | function propertyNameToString(node) {
|
23265 | if (ts$1.isIdentifier(node) || ts$1.isStringLiteral(node) || ts$1.isNumericLiteral(node)) {
|
23266 | return node.text;
|
23267 | }
|
23268 | else {
|
23269 | return null;
|
23270 | }
|
23271 | }
|
23272 | /**
|
23273 | * Compute the left most identifier in a qualified type chain. E.g. the `a` of `a.b.c.SomeType`.
|
23274 | * @param qualifiedName The starting property access expression from which we want to compute
|
23275 | * the left most identifier.
|
23276 | * @returns the left most identifier in the chain or `null` if it is not an identifier.
|
23277 | */
|
23278 | function getQualifiedNameRoot(qualifiedName) {
|
23279 | while (ts$1.isQualifiedName(qualifiedName.left)) {
|
23280 | qualifiedName = qualifiedName.left;
|
23281 | }
|
23282 | return ts$1.isIdentifier(qualifiedName.left) ? qualifiedName.left : null;
|
23283 | }
|
23284 | /**
|
23285 | * Compute the left most identifier in a property access chain. E.g. the `a` of `a.b.c.d`.
|
23286 | * @param propertyAccess The starting property access expression from which we want to compute
|
23287 | * the left most identifier.
|
23288 | * @returns the left most identifier in the chain or `null` if it is not an identifier.
|
23289 | */
|
23290 | function getFarLeftIdentifier(propertyAccess) {
|
23291 | while (ts$1.isPropertyAccessExpression(propertyAccess.expression)) {
|
23292 | propertyAccess = propertyAccess.expression;
|
23293 | }
|
23294 | return ts$1.isIdentifier(propertyAccess.expression) ? propertyAccess.expression : null;
|
23295 | }
|
23296 | /**
|
23297 | * Return the ImportDeclaration for the given `node` if it is either an `ImportSpecifier` or a
|
23298 | * `NamespaceImport`. If not return `null`.
|
23299 | */
|
23300 | function getContainingImportDeclaration(node) {
|
23301 | return ts$1.isImportSpecifier(node) ? node.parent.parent.parent :
|
23302 | ts$1.isNamespaceImport(node) ? node.parent.parent : null;
|
23303 | }
|
23304 | /**
|
23305 | * Compute the name by which the `decl` was exported, not imported.
|
23306 | * If no such declaration can be found (e.g. it is a namespace import)
|
23307 | * then fallback to the `originalId`.
|
23308 | */
|
23309 | function getExportedName(decl, originalId) {
|
23310 | return ts$1.isImportSpecifier(decl) ?
|
23311 | (decl.propertyName !== undefined ? decl.propertyName : decl.name).text :
|
23312 | originalId.text;
|
23313 | }
|
23314 |
|
23315 | /**
|
23316 | * @license
|
23317 | * Copyright Google LLC All Rights Reserved.
|
23318 | *
|
23319 | * Use of this source code is governed by an MIT-style license that can be
|
23320 | * found in the LICENSE file at https://angular.io/license
|
23321 | */
|
23322 | /**
|
23323 | * A mapping of component property and template binding property names, for example containing the
|
23324 | * inputs of a particular directive or component.
|
23325 | *
|
23326 | * A single component property has exactly one input/output annotation (and therefore one binding
|
23327 | * property name) associated with it, but the same binding property name may be shared across many
|
23328 | * component property names.
|
23329 | *
|
23330 | * Allows bidirectional querying of the mapping - looking up all inputs/outputs with a given
|
23331 | * property name, or mapping from a specific class property to its binding property name.
|
23332 | */
|
23333 | class ClassPropertyMapping {
|
23334 | constructor(forwardMap) {
|
23335 | this.forwardMap = forwardMap;
|
23336 | this.reverseMap = reverseMapFromForwardMap(forwardMap);
|
23337 | }
|
23338 | /**
|
23339 | * Construct a `ClassPropertyMapping` with no entries.
|
23340 | */
|
23341 | static empty() {
|
23342 | return new ClassPropertyMapping(new Map());
|
23343 | }
|
23344 | /**
|
23345 | * Construct a `ClassPropertyMapping` from a primitive JS object which maps class property names
|
23346 | * to either binding property names or an array that contains both names, which is used in on-disk
|
23347 | * metadata formats (e.g. in .d.ts files).
|
23348 | */
|
23349 | static fromMappedObject(obj) {
|
23350 | const forwardMap = new Map();
|
23351 | for (const classPropertyName of Object.keys(obj)) {
|
23352 | const value = obj[classPropertyName];
|
23353 | const bindingPropertyName = Array.isArray(value) ? value[0] : value;
|
23354 | const inputOrOutput = { classPropertyName, bindingPropertyName };
|
23355 | forwardMap.set(classPropertyName, inputOrOutput);
|
23356 | }
|
23357 | return new ClassPropertyMapping(forwardMap);
|
23358 | }
|
23359 | /**
|
23360 | * Merge two mappings into one, with class properties from `b` taking precedence over class
|
23361 | * properties from `a`.
|
23362 | */
|
23363 | static merge(a, b) {
|
23364 | const forwardMap = new Map(a.forwardMap.entries());
|
23365 | for (const [classPropertyName, inputOrOutput] of b.forwardMap) {
|
23366 | forwardMap.set(classPropertyName, inputOrOutput);
|
23367 | }
|
23368 | return new ClassPropertyMapping(forwardMap);
|
23369 | }
|
23370 | /**
|
23371 | * All class property names mapped in this mapping.
|
23372 | */
|
23373 | get classPropertyNames() {
|
23374 | return Array.from(this.forwardMap.keys());
|
23375 | }
|
23376 | /**
|
23377 | * All binding property names mapped in this mapping.
|
23378 | */
|
23379 | get propertyNames() {
|
23380 | return Array.from(this.reverseMap.keys());
|
23381 | }
|
23382 | /**
|
23383 | * Check whether a mapping for the given property name exists.
|
23384 | */
|
23385 | hasBindingPropertyName(propertyName) {
|
23386 | return this.reverseMap.has(propertyName);
|
23387 | }
|
23388 | /**
|
23389 | * Lookup all `InputOrOutput`s that use this `propertyName`.
|
23390 | */
|
23391 | getByBindingPropertyName(propertyName) {
|
23392 | return this.reverseMap.has(propertyName) ? this.reverseMap.get(propertyName) : null;
|
23393 | }
|
23394 | /**
|
23395 | * Lookup the `InputOrOutput` associated with a `classPropertyName`.
|
23396 | */
|
23397 | getByClassPropertyName(classPropertyName) {
|
23398 | return this.forwardMap.has(classPropertyName) ? this.forwardMap.get(classPropertyName) : null;
|
23399 | }
|
23400 | /**
|
23401 | * Convert this mapping to a primitive JS object which maps each class property directly to the
|
23402 | * binding property name associated with it.
|
23403 | */
|
23404 | toDirectMappedObject() {
|
23405 | const obj = {};
|
23406 | for (const [classPropertyName, inputOrOutput] of this.forwardMap) {
|
23407 | obj[classPropertyName] = inputOrOutput.bindingPropertyName;
|
23408 | }
|
23409 | return obj;
|
23410 | }
|
23411 | /**
|
23412 | * Convert this mapping to a primitive JS object which maps each class property either to itself
|
23413 | * (for cases where the binding property name is the same) or to an array which contains both
|
23414 | * names if they differ.
|
23415 | *
|
23416 | * This object format is used when mappings are serialized (for example into .d.ts files).
|
23417 | */
|
23418 | toJointMappedObject() {
|
23419 | const obj = {};
|
23420 | for (const [classPropertyName, inputOrOutput] of this.forwardMap) {
|
23421 | if (inputOrOutput.bindingPropertyName === classPropertyName) {
|
23422 | obj[classPropertyName] = inputOrOutput.bindingPropertyName;
|
23423 | }
|
23424 | else {
|
23425 | obj[classPropertyName] = [inputOrOutput.bindingPropertyName, classPropertyName];
|
23426 | }
|
23427 | }
|
23428 | return obj;
|
23429 | }
|
23430 | /**
|
23431 | * Implement the iterator protocol and return entry objects which contain the class and binding
|
23432 | * property names (and are useful for destructuring).
|
23433 | */
|
23434 | *[Symbol.iterator]() {
|
23435 | for (const [classPropertyName, inputOrOutput] of this.forwardMap.entries()) {
|
23436 | yield [classPropertyName, inputOrOutput.bindingPropertyName];
|
23437 | }
|
23438 | }
|
23439 | }
|
23440 | function reverseMapFromForwardMap(forwardMap) {
|
23441 | const reverseMap = new Map();
|
23442 | for (const [_, inputOrOutput] of forwardMap) {
|
23443 | if (!reverseMap.has(inputOrOutput.bindingPropertyName)) {
|
23444 | reverseMap.set(inputOrOutput.bindingPropertyName, []);
|
23445 | }
|
23446 | reverseMap.get(inputOrOutput.bindingPropertyName).push(inputOrOutput);
|
23447 | }
|
23448 | return reverseMap;
|
23449 | }
|
23450 |
|
23451 | /**
|
23452 | * @license
|
23453 | * Copyright Google LLC All Rights Reserved.
|
23454 | *
|
23455 | * Use of this source code is governed by an MIT-style license that can be
|
23456 | * found in the LICENSE file at https://angular.io/license
|
23457 | */
|
23458 | function extractReferencesFromType(checker, def, ngModuleImportedFrom, resolutionContext) {
|
23459 | if (!ts$1.isTupleTypeNode(def)) {
|
23460 | return [];
|
23461 | }
|
23462 | return def.elements.map(element => {
|
23463 | if (!ts$1.isTypeQueryNode(element)) {
|
23464 | throw new Error(`Expected TypeQueryNode: ${nodeDebugInfo(element)}`);
|
23465 | }
|
23466 | const type = element.exprName;
|
23467 | const { node, from } = reflectTypeEntityToDeclaration(type, checker);
|
23468 | if (!isNamedClassDeclaration(node)) {
|
23469 | throw new Error(`Expected named ClassDeclaration: ${nodeDebugInfo(node)}`);
|
23470 | }
|
23471 | const specifier = (from !== null && !from.startsWith('.') ? from : ngModuleImportedFrom);
|
23472 | if (specifier !== null) {
|
23473 | return new Reference$1(node, { specifier, resolutionContext });
|
23474 | }
|
23475 | else {
|
23476 | return new Reference$1(node);
|
23477 | }
|
23478 | });
|
23479 | }
|
23480 | function readStringType(type) {
|
23481 | if (!ts$1.isLiteralTypeNode(type) || !ts$1.isStringLiteral(type.literal)) {
|
23482 | return null;
|
23483 | }
|
23484 | return type.literal.text;
|
23485 | }
|
23486 | function readStringMapType(type) {
|
23487 | if (!ts$1.isTypeLiteralNode(type)) {
|
23488 | return {};
|
23489 | }
|
23490 | const obj = {};
|
23491 | type.members.forEach(member => {
|
23492 | if (!ts$1.isPropertySignature(member) || member.type === undefined || member.name === undefined ||
|
23493 | !ts$1.isStringLiteral(member.name)) {
|
23494 | return;
|
23495 | }
|
23496 | const value = readStringType(member.type);
|
23497 | if (value === null) {
|
23498 | return null;
|
23499 | }
|
23500 | obj[member.name.text] = value;
|
23501 | });
|
23502 | return obj;
|
23503 | }
|
23504 | function readStringArrayType(type) {
|
23505 | if (!ts$1.isTupleTypeNode(type)) {
|
23506 | return [];
|
23507 | }
|
23508 | const res = [];
|
23509 | type.elements.forEach(el => {
|
23510 | if (!ts$1.isLiteralTypeNode(el) || !ts$1.isStringLiteral(el.literal)) {
|
23511 | return;
|
23512 | }
|
23513 | res.push(el.literal.text);
|
23514 | });
|
23515 | return res;
|
23516 | }
|
23517 | /**
|
23518 | * Inspects the class' members and extracts the metadata that is used when type-checking templates
|
23519 | * that use the directive. This metadata does not contain information from a base class, if any,
|
23520 | * making this metadata invariant to changes of inherited classes.
|
23521 | */
|
23522 | function extractDirectiveTypeCheckMeta(node, inputs, reflector) {
|
23523 | const members = reflector.getMembersOfClass(node);
|
23524 | const staticMembers = members.filter(member => member.isStatic);
|
23525 | const ngTemplateGuards = staticMembers.map(extractTemplateGuard)
|
23526 | .filter((guard) => guard !== null);
|
23527 | const hasNgTemplateContextGuard = staticMembers.some(member => member.kind === ClassMemberKind.Method && member.name === 'ngTemplateContextGuard');
|
23528 | const coercedInputFields = new Set(staticMembers.map(extractCoercedInput)
|
23529 | .filter((inputName) => inputName !== null));
|
23530 | const restrictedInputFields = new Set();
|
23531 | const stringLiteralInputFields = new Set();
|
23532 | const undeclaredInputFields = new Set();
|
23533 | for (const classPropertyName of inputs.classPropertyNames) {
|
23534 | const field = members.find(member => member.name === classPropertyName);
|
23535 | if (field === undefined || field.node === null) {
|
23536 | undeclaredInputFields.add(classPropertyName);
|
23537 | continue;
|
23538 | }
|
23539 | if (isRestricted(field.node)) {
|
23540 | restrictedInputFields.add(classPropertyName);
|
23541 | }
|
23542 | if (field.nameNode !== null && ts$1.isStringLiteral(field.nameNode)) {
|
23543 | stringLiteralInputFields.add(classPropertyName);
|
23544 | }
|
23545 | }
|
23546 | const arity = reflector.getGenericArityOfClass(node);
|
23547 | return {
|
23548 | hasNgTemplateContextGuard,
|
23549 | ngTemplateGuards,
|
23550 | coercedInputFields,
|
23551 | restrictedInputFields,
|
23552 | stringLiteralInputFields,
|
23553 | undeclaredInputFields,
|
23554 | isGeneric: arity !== null && arity > 0,
|
23555 | };
|
23556 | }
|
23557 | function isRestricted(node) {
|
23558 | if (node.modifiers === undefined) {
|
23559 | return false;
|
23560 | }
|
23561 | return node.modifiers.some(modifier => modifier.kind === ts$1.SyntaxKind.PrivateKeyword ||
|
23562 | modifier.kind === ts$1.SyntaxKind.ProtectedKeyword ||
|
23563 | modifier.kind === ts$1.SyntaxKind.ReadonlyKeyword);
|
23564 | }
|
23565 | function extractTemplateGuard(member) {
|
23566 | if (!member.name.startsWith('ngTemplateGuard_')) {
|
23567 | return null;
|
23568 | }
|
23569 | const inputName = afterUnderscore(member.name);
|
23570 | if (member.kind === ClassMemberKind.Property) {
|
23571 | let type = null;
|
23572 | if (member.type !== null && ts$1.isLiteralTypeNode(member.type) &&
|
23573 | ts$1.isStringLiteral(member.type.literal)) {
|
23574 | type = member.type.literal.text;
|
23575 | }
|
23576 | // Only property members with string literal type 'binding' are considered as template guard.
|
23577 | if (type !== 'binding') {
|
23578 | return null;
|
23579 | }
|
23580 | return { inputName, type };
|
23581 | }
|
23582 | else if (member.kind === ClassMemberKind.Method) {
|
23583 | return { inputName, type: 'invocation' };
|
23584 | }
|
23585 | else {
|
23586 | return null;
|
23587 | }
|
23588 | }
|
23589 | function extractCoercedInput(member) {
|
23590 | if (member.kind !== ClassMemberKind.Property || !member.name.startsWith('ngAcceptInputType_')) {
|
23591 | return null;
|
23592 | }
|
23593 | return afterUnderscore(member.name);
|
23594 | }
|
23595 | /**
|
23596 | * A `MetadataReader` that reads from an ordered set of child readers until it obtains the requested
|
23597 | * metadata.
|
23598 | *
|
23599 | * This is used to combine `MetadataReader`s that read from different sources (e.g. from a registry
|
23600 | * and from .d.ts files).
|
23601 | */
|
23602 | class CompoundMetadataReader {
|
23603 | constructor(readers) {
|
23604 | this.readers = readers;
|
23605 | }
|
23606 | getDirectiveMetadata(node) {
|
23607 | for (const reader of this.readers) {
|
23608 | const meta = reader.getDirectiveMetadata(node);
|
23609 | if (meta !== null) {
|
23610 | return meta;
|
23611 | }
|
23612 | }
|
23613 | return null;
|
23614 | }
|
23615 | getNgModuleMetadata(node) {
|
23616 | for (const reader of this.readers) {
|
23617 | const meta = reader.getNgModuleMetadata(node);
|
23618 | if (meta !== null) {
|
23619 | return meta;
|
23620 | }
|
23621 | }
|
23622 | return null;
|
23623 | }
|
23624 | getPipeMetadata(node) {
|
23625 | for (const reader of this.readers) {
|
23626 | const meta = reader.getPipeMetadata(node);
|
23627 | if (meta !== null) {
|
23628 | return meta;
|
23629 | }
|
23630 | }
|
23631 | return null;
|
23632 | }
|
23633 | }
|
23634 | function afterUnderscore(str) {
|
23635 | const pos = str.indexOf('_');
|
23636 | if (pos === -1) {
|
23637 | throw new Error(`Expected '${str}' to contain '_'`);
|
23638 | }
|
23639 | return str.substr(pos + 1);
|
23640 | }
|
23641 | /** Returns whether a class declaration has the necessary class fields to make it injectable. */
|
23642 | function hasInjectableFields(clazz, host) {
|
23643 | const members = host.getMembersOfClass(clazz);
|
23644 | return members.some(({ isStatic, name }) => isStatic && (name === 'ɵprov' || name === 'ɵfac' || name === 'ɵinj'));
|
23645 | }
|
23646 |
|
23647 | /**
|
23648 | * @license
|
23649 | * Copyright Google LLC All Rights Reserved.
|
23650 | *
|
23651 | * Use of this source code is governed by an MIT-style license that can be
|
23652 | * found in the LICENSE file at https://angular.io/license
|
23653 | */
|
23654 | /**
|
23655 | * A `MetadataReader` that can read metadata from `.d.ts` files, which have static Ivy properties
|
23656 | * from an upstream compilation already.
|
23657 | */
|
23658 | class DtsMetadataReader {
|
23659 | constructor(checker, reflector) {
|
23660 | this.checker = checker;
|
23661 | this.reflector = reflector;
|
23662 | }
|
23663 | /**
|
23664 | * Read the metadata from a class that has already been compiled somehow (either it's in a .d.ts
|
23665 | * file, or in a .ts file with a handwritten definition).
|
23666 | *
|
23667 | * @param ref `Reference` to the class of interest, with the context of how it was obtained.
|
23668 | */
|
23669 | getNgModuleMetadata(ref) {
|
23670 | const clazz = ref.node;
|
23671 | const resolutionContext = clazz.getSourceFile().fileName;
|
23672 | // This operation is explicitly not memoized, as it depends on `ref.ownedByModuleGuess`.
|
23673 | // TODO(alxhub): investigate caching of .d.ts module metadata.
|
23674 | const ngModuleDef = this.reflector.getMembersOfClass(clazz).find(member => member.name === 'ɵmod' && member.isStatic);
|
23675 | if (ngModuleDef === undefined) {
|
23676 | return null;
|
23677 | }
|
23678 | else if (
|
23679 | // Validate that the shape of the ngModuleDef type is correct.
|
23680 | ngModuleDef.type === null || !ts$1.isTypeReferenceNode(ngModuleDef.type) ||
|
23681 | ngModuleDef.type.typeArguments === undefined ||
|
23682 | ngModuleDef.type.typeArguments.length !== 4) {
|
23683 | return null;
|
23684 | }
|
23685 | // Read the ModuleData out of the type arguments.
|
23686 | const [_, declarationMetadata, importMetadata, exportMetadata] = ngModuleDef.type.typeArguments;
|
23687 | return {
|
23688 | ref,
|
23689 | declarations: extractReferencesFromType(this.checker, declarationMetadata, ref.ownedByModuleGuess, resolutionContext),
|
23690 | exports: extractReferencesFromType(this.checker, exportMetadata, ref.ownedByModuleGuess, resolutionContext),
|
23691 | imports: extractReferencesFromType(this.checker, importMetadata, ref.ownedByModuleGuess, resolutionContext),
|
23692 | schemas: [],
|
23693 | rawDeclarations: null,
|
23694 | };
|
23695 | }
|
23696 | /**
|
23697 | * Read directive (or component) metadata from a referenced class in a .d.ts file.
|
23698 | */
|
23699 | getDirectiveMetadata(ref) {
|
23700 | const clazz = ref.node;
|
23701 | const def = this.reflector.getMembersOfClass(clazz).find(field => field.isStatic && (field.name === 'ɵcmp' || field.name === 'ɵdir'));
|
23702 | if (def === undefined) {
|
23703 | // No definition could be found.
|
23704 | return null;
|
23705 | }
|
23706 | else if (def.type === null || !ts$1.isTypeReferenceNode(def.type) ||
|
23707 | def.type.typeArguments === undefined || def.type.typeArguments.length < 2) {
|
23708 | // The type metadata was the wrong shape.
|
23709 | return null;
|
23710 | }
|
23711 | const isComponent = def.name === 'ɵcmp';
|
23712 | const ctorParams = this.reflector.getConstructorParameters(clazz);
|
23713 | // A directive is considered to be structural if:
|
23714 | // 1) it's a directive, not a component, and
|
23715 | // 2) it injects `TemplateRef`
|
23716 | const isStructural = !isComponent && ctorParams !== null && ctorParams.some(param => {
|
23717 | return param.typeValueReference.kind === 1 /* IMPORTED */ &&
|
23718 | param.typeValueReference.moduleName === '@angular/core' &&
|
23719 | param.typeValueReference.importedName === 'TemplateRef';
|
23720 | });
|
23721 | const inputs = ClassPropertyMapping.fromMappedObject(readStringMapType(def.type.typeArguments[3]));
|
23722 | const outputs = ClassPropertyMapping.fromMappedObject(readStringMapType(def.type.typeArguments[4]));
|
23723 | return Object.assign(Object.assign({ ref, name: clazz.name.text, isComponent, selector: readStringType(def.type.typeArguments[1]), exportAs: readStringArrayType(def.type.typeArguments[2]), inputs,
|
23724 | outputs, queries: readStringArrayType(def.type.typeArguments[5]) }, extractDirectiveTypeCheckMeta(clazz, inputs, this.reflector)), { baseClass: readBaseClass(clazz, this.checker, this.reflector), isPoisoned: false, isStructural });
|
23725 | }
|
23726 | /**
|
23727 | * Read pipe metadata from a referenced class in a .d.ts file.
|
23728 | */
|
23729 | getPipeMetadata(ref) {
|
23730 | const def = this.reflector.getMembersOfClass(ref.node).find(field => field.isStatic && field.name === 'ɵpipe');
|
23731 | if (def === undefined) {
|
23732 | // No definition could be found.
|
23733 | return null;
|
23734 | }
|
23735 | else if (def.type === null || !ts$1.isTypeReferenceNode(def.type) ||
|
23736 | def.type.typeArguments === undefined || def.type.typeArguments.length < 2) {
|
23737 | // The type metadata was the wrong shape.
|
23738 | return null;
|
23739 | }
|
23740 | const type = def.type.typeArguments[1];
|
23741 | if (!ts$1.isLiteralTypeNode(type) || !ts$1.isStringLiteral(type.literal)) {
|
23742 | // The type metadata was the wrong type.
|
23743 | return null;
|
23744 | }
|
23745 | const name = type.literal.text;
|
23746 | return { ref, name };
|
23747 | }
|
23748 | }
|
23749 | function readBaseClass(clazz, checker, reflector) {
|
23750 | if (!isNamedClassDeclaration(clazz)) {
|
23751 | // Technically this is an error in a .d.ts file, but for the purposes of finding the base class
|
23752 | // it's ignored.
|
23753 | return reflector.hasBaseClass(clazz) ? 'dynamic' : null;
|
23754 | }
|
23755 | if (clazz.heritageClauses !== undefined) {
|
23756 | for (const clause of clazz.heritageClauses) {
|
23757 | if (clause.token === ts$1.SyntaxKind.ExtendsKeyword) {
|
23758 | const baseExpr = clause.types[0].expression;
|
23759 | let symbol = checker.getSymbolAtLocation(baseExpr);
|
23760 | if (symbol === undefined) {
|
23761 | return 'dynamic';
|
23762 | }
|
23763 | else if (symbol.flags & ts$1.SymbolFlags.Alias) {
|
23764 | symbol = checker.getAliasedSymbol(symbol);
|
23765 | }
|
23766 | if (symbol.valueDeclaration !== undefined &&
|
23767 | isNamedClassDeclaration(symbol.valueDeclaration)) {
|
23768 | return new Reference$1(symbol.valueDeclaration);
|
23769 | }
|
23770 | else {
|
23771 | return 'dynamic';
|
23772 | }
|
23773 | }
|
23774 | }
|
23775 | }
|
23776 | return null;
|
23777 | }
|
23778 |
|
23779 | /**
|
23780 | * @license
|
23781 | * Copyright Google LLC All Rights Reserved.
|
23782 | *
|
23783 | * Use of this source code is governed by an MIT-style license that can be
|
23784 | * found in the LICENSE file at https://angular.io/license
|
23785 | */
|
23786 | /**
|
23787 | * Given a reference to a directive, return a flattened version of its `DirectiveMeta` metadata
|
23788 | * which includes metadata from its entire inheritance chain.
|
23789 | *
|
23790 | * The returned `DirectiveMeta` will either have `baseClass: null` if the inheritance chain could be
|
23791 | * fully resolved, or `baseClass: 'dynamic'` if the inheritance chain could not be completely
|
23792 | * followed.
|
23793 | */
|
23794 | function flattenInheritedDirectiveMetadata(reader, dir) {
|
23795 | const topMeta = reader.getDirectiveMetadata(dir);
|
23796 | if (topMeta === null) {
|
23797 | throw new Error(`Metadata not found for directive: ${dir.debugName}`);
|
23798 | }
|
23799 | if (topMeta.baseClass === null) {
|
23800 | return topMeta;
|
23801 | }
|
23802 | const coercedInputFields = new Set();
|
23803 | const undeclaredInputFields = new Set();
|
23804 | const restrictedInputFields = new Set();
|
23805 | const stringLiteralInputFields = new Set();
|
23806 | let isDynamic = false;
|
23807 | let inputs = ClassPropertyMapping.empty();
|
23808 | let outputs = ClassPropertyMapping.empty();
|
23809 | let isStructural = false;
|
23810 | const addMetadata = (meta) => {
|
23811 | if (meta.baseClass === 'dynamic') {
|
23812 | isDynamic = true;
|
23813 | }
|
23814 | else if (meta.baseClass !== null) {
|
23815 | const baseMeta = reader.getDirectiveMetadata(meta.baseClass);
|
23816 | if (baseMeta !== null) {
|
23817 | addMetadata(baseMeta);
|
23818 | }
|
23819 | else {
|
23820 | // Missing metadata for the base class means it's effectively dynamic.
|
23821 | isDynamic = true;
|
23822 | }
|
23823 | }
|
23824 | isStructural = isStructural || meta.isStructural;
|
23825 | inputs = ClassPropertyMapping.merge(inputs, meta.inputs);
|
23826 | outputs = ClassPropertyMapping.merge(outputs, meta.outputs);
|
23827 | for (const coercedInputField of meta.coercedInputFields) {
|
23828 | coercedInputFields.add(coercedInputField);
|
23829 | }
|
23830 | for (const undeclaredInputField of meta.undeclaredInputFields) {
|
23831 | undeclaredInputFields.add(undeclaredInputField);
|
23832 | }
|
23833 | for (const restrictedInputField of meta.restrictedInputFields) {
|
23834 | restrictedInputFields.add(restrictedInputField);
|
23835 | }
|
23836 | for (const field of meta.stringLiteralInputFields) {
|
23837 | stringLiteralInputFields.add(field);
|
23838 | }
|
23839 | };
|
23840 | addMetadata(topMeta);
|
23841 | return Object.assign(Object.assign({}, topMeta), { inputs,
|
23842 | outputs,
|
23843 | coercedInputFields,
|
23844 | undeclaredInputFields,
|
23845 | restrictedInputFields,
|
23846 | stringLiteralInputFields, baseClass: isDynamic ? 'dynamic' : null, isStructural });
|
23847 | }
|
23848 |
|
23849 | /**
|
23850 | * @license
|
23851 | * Copyright Google LLC All Rights Reserved.
|
23852 | *
|
23853 | * Use of this source code is governed by an MIT-style license that can be
|
23854 | * found in the LICENSE file at https://angular.io/license
|
23855 | */
|
23856 | /**
|
23857 | * A registry of directive, pipe, and module metadata for types defined in the current compilation
|
23858 | * unit, which supports both reading and registering.
|
23859 | */
|
23860 | class LocalMetadataRegistry {
|
23861 | constructor() {
|
23862 | this.directives = new Map();
|
23863 | this.ngModules = new Map();
|
23864 | this.pipes = new Map();
|
23865 | }
|
23866 | getDirectiveMetadata(ref) {
|
23867 | return this.directives.has(ref.node) ? this.directives.get(ref.node) : null;
|
23868 | }
|
23869 | getNgModuleMetadata(ref) {
|
23870 | return this.ngModules.has(ref.node) ? this.ngModules.get(ref.node) : null;
|
23871 | }
|
23872 | getPipeMetadata(ref) {
|
23873 | return this.pipes.has(ref.node) ? this.pipes.get(ref.node) : null;
|
23874 | }
|
23875 | registerDirectiveMetadata(meta) {
|
23876 | this.directives.set(meta.ref.node, meta);
|
23877 | }
|
23878 | registerNgModuleMetadata(meta) {
|
23879 | this.ngModules.set(meta.ref.node, meta);
|
23880 | }
|
23881 | registerPipeMetadata(meta) {
|
23882 | this.pipes.set(meta.ref.node, meta);
|
23883 | }
|
23884 | }
|
23885 | /**
|
23886 | * A `MetadataRegistry` which registers metdata with multiple delegate `MetadataRegistry` instances.
|
23887 | */
|
23888 | class CompoundMetadataRegistry {
|
23889 | constructor(registries) {
|
23890 | this.registries = registries;
|
23891 | }
|
23892 | registerDirectiveMetadata(meta) {
|
23893 | for (const registry of this.registries) {
|
23894 | registry.registerDirectiveMetadata(meta);
|
23895 | }
|
23896 | }
|
23897 | registerNgModuleMetadata(meta) {
|
23898 | for (const registry of this.registries) {
|
23899 | registry.registerNgModuleMetadata(meta);
|
23900 | }
|
23901 | }
|
23902 | registerPipeMetadata(meta) {
|
23903 | for (const registry of this.registries) {
|
23904 | registry.registerPipeMetadata(meta);
|
23905 | }
|
23906 | }
|
23907 | }
|
23908 | /**
|
23909 | * Registry that keeps track of classes that can be constructed via dependency injection (e.g.
|
23910 | * injectables, directives, pipes).
|
23911 | */
|
23912 | class InjectableClassRegistry {
|
23913 | constructor(host) {
|
23914 | this.host = host;
|
23915 | this.classes = new Set();
|
23916 | }
|
23917 | registerInjectable(declaration) {
|
23918 | this.classes.add(declaration);
|
23919 | }
|
23920 | isInjectable(declaration) {
|
23921 | // Figure out whether the class is injectable based on the registered classes, otherwise
|
23922 | // fall back to looking at its members since we might not have been able register the class
|
23923 | // if it was compiled already.
|
23924 | return this.classes.has(declaration) || hasInjectableFields(declaration, this.host);
|
23925 | }
|
23926 | }
|
23927 |
|
23928 | /**
|
23929 | * @license
|
23930 | * Copyright Google LLC All Rights Reserved.
|
23931 | *
|
23932 | * Use of this source code is governed by an MIT-style license that can be
|
23933 | * found in the LICENSE file at https://angular.io/license
|
23934 | */
|
23935 | function isExternalResource(resource) {
|
23936 | return resource.path !== null;
|
23937 | }
|
23938 | /**
|
23939 | * Tracks the mapping between external template/style files and the component(s) which use them.
|
23940 | *
|
23941 | * This information is produced during analysis of the program and is used mainly to support
|
23942 | * external tooling, for which such a mapping is challenging to determine without compiler
|
23943 | * assistance.
|
23944 | */
|
23945 | class ResourceRegistry {
|
23946 | constructor() {
|
23947 | this.externalTemplateToComponentsMap = new Map();
|
23948 | this.componentToTemplateMap = new Map();
|
23949 | this.componentToStylesMap = new Map();
|
23950 | this.externalStyleToComponentsMap = new Map();
|
23951 | }
|
23952 | getComponentsWithTemplate(template) {
|
23953 | if (!this.externalTemplateToComponentsMap.has(template)) {
|
23954 | return new Set();
|
23955 | }
|
23956 | return this.externalTemplateToComponentsMap.get(template);
|
23957 | }
|
23958 | registerResources(resources, component) {
|
23959 | if (resources.template !== null) {
|
23960 | this.registerTemplate(resources.template, component);
|
23961 | }
|
23962 | for (const style of resources.styles) {
|
23963 | this.registerStyle(style, component);
|
23964 | }
|
23965 | }
|
23966 | registerTemplate(templateResource, component) {
|
23967 | const { path } = templateResource;
|
23968 | if (path !== null) {
|
23969 | if (!this.externalTemplateToComponentsMap.has(path)) {
|
23970 | this.externalTemplateToComponentsMap.set(path, new Set());
|
23971 | }
|
23972 | this.externalTemplateToComponentsMap.get(path).add(component);
|
23973 | }
|
23974 | this.componentToTemplateMap.set(component, templateResource);
|
23975 | }
|
23976 | getTemplate(component) {
|
23977 | if (!this.componentToTemplateMap.has(component)) {
|
23978 | return null;
|
23979 | }
|
23980 | return this.componentToTemplateMap.get(component);
|
23981 | }
|
23982 | registerStyle(styleResource, component) {
|
23983 | const { path } = styleResource;
|
23984 | if (!this.componentToStylesMap.has(component)) {
|
23985 | this.componentToStylesMap.set(component, new Set());
|
23986 | }
|
23987 | if (path !== null) {
|
23988 | if (!this.externalStyleToComponentsMap.has(path)) {
|
23989 | this.externalStyleToComponentsMap.set(path, new Set());
|
23990 | }
|
23991 | this.externalStyleToComponentsMap.get(path).add(component);
|
23992 | }
|
23993 | this.componentToStylesMap.get(component).add(styleResource);
|
23994 | }
|
23995 | getStyles(component) {
|
23996 | if (!this.componentToStylesMap.has(component)) {
|
23997 | return new Set();
|
23998 | }
|
23999 | return this.componentToStylesMap.get(component);
|
24000 | }
|
24001 | getComponentsWithStyle(styleUrl) {
|
24002 | if (!this.externalStyleToComponentsMap.has(styleUrl)) {
|
24003 | return new Set();
|
24004 | }
|
24005 | return this.externalStyleToComponentsMap.get(styleUrl);
|
24006 | }
|
24007 | }
|
24008 |
|
24009 | /**
|
24010 | * @license
|
24011 | * Copyright Google LLC All Rights Reserved.
|
24012 | *
|
24013 | * Use of this source code is governed by an MIT-style license that can be
|
24014 | * found in the LICENSE file at https://angular.io/license
|
24015 | */
|
24016 | /**
|
24017 | * Represents a value which cannot be determined statically.
|
24018 | */
|
24019 | class DynamicValue {
|
24020 | constructor(node, reason, code) {
|
24021 | this.node = node;
|
24022 | this.reason = reason;
|
24023 | this.code = code;
|
24024 | }
|
24025 | static fromDynamicInput(node, input) {
|
24026 | return new DynamicValue(node, input, 0 /* DYNAMIC_INPUT */);
|
24027 | }
|
24028 | static fromDynamicString(node) {
|
24029 | return new DynamicValue(node, undefined, 1 /* DYNAMIC_STRING */);
|
24030 | }
|
24031 | static fromExternalReference(node, ref) {
|
24032 | return new DynamicValue(node, ref, 2 /* EXTERNAL_REFERENCE */);
|
24033 | }
|
24034 | static fromUnsupportedSyntax(node) {
|
24035 | return new DynamicValue(node, undefined, 3 /* UNSUPPORTED_SYNTAX */);
|
24036 | }
|
24037 | static fromUnknownIdentifier(node) {
|
24038 | return new DynamicValue(node, undefined, 4 /* UNKNOWN_IDENTIFIER */);
|
24039 | }
|
24040 | static fromInvalidExpressionType(node, value) {
|
24041 | return new DynamicValue(node, value, 5 /* INVALID_EXPRESSION_TYPE */);
|
24042 | }
|
24043 | static fromComplexFunctionCall(node, fn) {
|
24044 | return new DynamicValue(node, fn, 6 /* COMPLEX_FUNCTION_CALL */);
|
24045 | }
|
24046 | static fromUnknown(node) {
|
24047 | return new DynamicValue(node, undefined, 7 /* UNKNOWN */);
|
24048 | }
|
24049 | isFromDynamicInput() {
|
24050 | return this.code === 0 /* DYNAMIC_INPUT */;
|
24051 | }
|
24052 | isFromDynamicString() {
|
24053 | return this.code === 1 /* DYNAMIC_STRING */;
|
24054 | }
|
24055 | isFromExternalReference() {
|
24056 | return this.code === 2 /* EXTERNAL_REFERENCE */;
|
24057 | }
|
24058 | isFromUnsupportedSyntax() {
|
24059 | return this.code === 3 /* UNSUPPORTED_SYNTAX */;
|
24060 | }
|
24061 | isFromUnknownIdentifier() {
|
24062 | return this.code === 4 /* UNKNOWN_IDENTIFIER */;
|
24063 | }
|
24064 | isFromInvalidExpressionType() {
|
24065 | return this.code === 5 /* INVALID_EXPRESSION_TYPE */;
|
24066 | }
|
24067 | isFromComplexFunctionCall() {
|
24068 | return this.code === 6 /* COMPLEX_FUNCTION_CALL */;
|
24069 | }
|
24070 | isFromUnknown() {
|
24071 | return this.code === 7 /* UNKNOWN */;
|
24072 | }
|
24073 | accept(visitor) {
|
24074 | switch (this.code) {
|
24075 | case 0 /* DYNAMIC_INPUT */:
|
24076 | return visitor.visitDynamicInput(this);
|
24077 | case 1 /* DYNAMIC_STRING */:
|
24078 | return visitor.visitDynamicString(this);
|
24079 | case 2 /* EXTERNAL_REFERENCE */:
|
24080 | return visitor.visitExternalReference(this);
|
24081 | case 3 /* UNSUPPORTED_SYNTAX */:
|
24082 | return visitor.visitUnsupportedSyntax(this);
|
24083 | case 4 /* UNKNOWN_IDENTIFIER */:
|
24084 | return visitor.visitUnknownIdentifier(this);
|
24085 | case 5 /* INVALID_EXPRESSION_TYPE */:
|
24086 | return visitor.visitInvalidExpressionType(this);
|
24087 | case 6 /* COMPLEX_FUNCTION_CALL */:
|
24088 | return visitor.visitComplexFunctionCall(this);
|
24089 | case 7 /* UNKNOWN */:
|
24090 | return visitor.visitUnknown(this);
|
24091 | }
|
24092 | }
|
24093 | }
|
24094 |
|
24095 | /**
|
24096 | * @license
|
24097 | * Copyright Google LLC All Rights Reserved.
|
24098 | *
|
24099 | * Use of this source code is governed by an MIT-style license that can be
|
24100 | * found in the LICENSE file at https://angular.io/license
|
24101 | */
|
24102 | /**
|
24103 | * A collection of publicly exported declarations from a module. Each declaration is evaluated
|
24104 | * lazily upon request.
|
24105 | */
|
24106 | class ResolvedModule {
|
24107 | constructor(exports, evaluate) {
|
24108 | this.exports = exports;
|
24109 | this.evaluate = evaluate;
|
24110 | }
|
24111 | getExport(name) {
|
24112 | if (!this.exports.has(name)) {
|
24113 | return undefined;
|
24114 | }
|
24115 | return this.evaluate(this.exports.get(name));
|
24116 | }
|
24117 | getExports() {
|
24118 | const map = new Map();
|
24119 | this.exports.forEach((decl, name) => {
|
24120 | map.set(name, this.evaluate(decl));
|
24121 | });
|
24122 | return map;
|
24123 | }
|
24124 | }
|
24125 | /**
|
24126 | * A value member of an enumeration.
|
24127 | *
|
24128 | * Contains a `Reference` to the enumeration itself, and the name of the referenced member.
|
24129 | */
|
24130 | class EnumValue {
|
24131 | constructor(enumRef, name, resolved) {
|
24132 | this.enumRef = enumRef;
|
24133 | this.name = name;
|
24134 | this.resolved = resolved;
|
24135 | }
|
24136 | }
|
24137 | /**
|
24138 | * An implementation of a known function that can be statically evaluated.
|
24139 | * It could be a built-in function or method (such as `Array.prototype.slice`) or a TypeScript
|
24140 | * helper (such as `__spread`).
|
24141 | */
|
24142 | class KnownFn {
|
24143 | }
|
24144 |
|
24145 | /**
|
24146 | * @license
|
24147 | * Copyright Google LLC All Rights Reserved.
|
24148 | *
|
24149 | * Use of this source code is governed by an MIT-style license that can be
|
24150 | * found in the LICENSE file at https://angular.io/license
|
24151 | */
|
24152 | /**
|
24153 | * Derives a type representation from a resolved value to be reported in a diagnostic.
|
24154 | *
|
24155 | * @param value The resolved value for which a type representation should be derived.
|
24156 | * @param maxDepth The maximum nesting depth of objects and arrays, defaults to 1 level.
|
24157 | */
|
24158 | function describeResolvedType(value, maxDepth = 1) {
|
24159 | var _a, _b;
|
24160 | if (value === null) {
|
24161 | return 'null';
|
24162 | }
|
24163 | else if (value === undefined) {
|
24164 | return 'undefined';
|
24165 | }
|
24166 | else if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'string') {
|
24167 | return typeof value;
|
24168 | }
|
24169 | else if (value instanceof Map) {
|
24170 | if (maxDepth === 0) {
|
24171 | return 'object';
|
24172 | }
|
24173 | const entries = Array.from(value.entries()).map(([key, v]) => {
|
24174 | return `${quoteKey(key)}: ${describeResolvedType(v, maxDepth - 1)}`;
|
24175 | });
|
24176 | return entries.length > 0 ? `{ ${entries.join('; ')} }` : '{}';
|
24177 | }
|
24178 | else if (value instanceof ResolvedModule) {
|
24179 | return '(module)';
|
24180 | }
|
24181 | else if (value instanceof EnumValue) {
|
24182 | return (_a = value.enumRef.debugName) !== null && _a !== void 0 ? _a : '(anonymous)';
|
24183 | }
|
24184 | else if (value instanceof Reference$1) {
|
24185 | return (_b = value.debugName) !== null && _b !== void 0 ? _b : '(anonymous)';
|
24186 | }
|
24187 | else if (Array.isArray(value)) {
|
24188 | if (maxDepth === 0) {
|
24189 | return 'Array';
|
24190 | }
|
24191 | return `[${value.map(v => describeResolvedType(v, maxDepth - 1)).join(', ')}]`;
|
24192 | }
|
24193 | else if (value instanceof DynamicValue) {
|
24194 | return '(not statically analyzable)';
|
24195 | }
|
24196 | else if (value instanceof KnownFn) {
|
24197 | return 'Function';
|
24198 | }
|
24199 | else {
|
24200 | return 'unknown';
|
24201 | }
|
24202 | }
|
24203 | function quoteKey(key) {
|
24204 | if (/^[a-z0-9_]+$/i.test(key)) {
|
24205 | return key;
|
24206 | }
|
24207 | else {
|
24208 | return `'${key.replace(/'/g, '\\\'')}'`;
|
24209 | }
|
24210 | }
|
24211 | /**
|
24212 | * Creates an array of related information diagnostics for a `DynamicValue` that describe the trace
|
24213 | * of why an expression was evaluated as dynamic.
|
24214 | *
|
24215 | * @param node The node for which a `ts.Diagnostic` is to be created with the trace.
|
24216 | * @param value The dynamic value for which a trace should be created.
|
24217 | */
|
24218 | function traceDynamicValue(node, value) {
|
24219 | return value.accept(new TraceDynamicValueVisitor(node));
|
24220 | }
|
24221 | class TraceDynamicValueVisitor {
|
24222 | constructor(node) {
|
24223 | this.node = node;
|
24224 | this.currentContainerNode = null;
|
24225 | }
|
24226 | visitDynamicInput(value) {
|
24227 | const trace = value.reason.accept(this);
|
24228 | if (this.shouldTrace(value.node)) {
|
24229 | const info = makeRelatedInformation(value.node, 'Unable to evaluate this expression statically.');
|
24230 | trace.unshift(info);
|
24231 | }
|
24232 | return trace;
|
24233 | }
|
24234 | visitDynamicString(value) {
|
24235 | return [makeRelatedInformation(value.node, 'A string value could not be determined statically.')];
|
24236 | }
|
24237 | visitExternalReference(value) {
|
24238 | const name = value.reason.debugName;
|
24239 | const description = name !== null ? `'${name}'` : 'an anonymous declaration';
|
24240 | return [makeRelatedInformation(value.node, `A value for ${description} cannot be determined statically, as it is an external declaration.`)];
|
24241 | }
|
24242 | visitComplexFunctionCall(value) {
|
24243 | return [
|
24244 | makeRelatedInformation(value.node, 'Unable to evaluate function call of complex function. A function must have exactly one return statement.'),
|
24245 | makeRelatedInformation(value.reason.node, 'Function is declared here.')
|
24246 | ];
|
24247 | }
|
24248 | visitInvalidExpressionType(value) {
|
24249 | return [makeRelatedInformation(value.node, 'Unable to evaluate an invalid expression.')];
|
24250 | }
|
24251 | visitUnknown(value) {
|
24252 | return [makeRelatedInformation(value.node, 'Unable to evaluate statically.')];
|
24253 | }
|
24254 | visitUnknownIdentifier(value) {
|
24255 | return [makeRelatedInformation(value.node, 'Unknown reference.')];
|
24256 | }
|
24257 | visitUnsupportedSyntax(value) {
|
24258 | return [makeRelatedInformation(value.node, 'This syntax is not supported.')];
|
24259 | }
|
24260 | /**
|
24261 | * Determines whether the dynamic value reported for the node should be traced, i.e. if it is not
|
24262 | * part of the container for which the most recent trace was created.
|
24263 | */
|
24264 | shouldTrace(node) {
|
24265 | if (node === this.node) {
|
24266 | // Do not include a dynamic value for the origin node, as the main diagnostic is already
|
24267 | // reported on that node.
|
24268 | return false;
|
24269 | }
|
24270 | const container = getContainerNode(node);
|
24271 | if (container === this.currentContainerNode) {
|
24272 | // The node is part of the same container as the previous trace entry, so this dynamic value
|
24273 | // should not become part of the trace.
|
24274 | return false;
|
24275 | }
|
24276 | this.currentContainerNode = container;
|
24277 | return true;
|
24278 | }
|
24279 | }
|
24280 | /**
|
24281 | * Determines the closest parent node that is to be considered as container, which is used to reduce
|
24282 | * the granularity of tracing the dynamic values to a single entry per container. Currently, full
|
24283 | * statements and destructuring patterns are considered as container.
|
24284 | */
|
24285 | function getContainerNode(node) {
|
24286 | let currentNode = node;
|
24287 | while (currentNode !== undefined) {
|
24288 | switch (currentNode.kind) {
|
24289 | case ts$1.SyntaxKind.ExpressionStatement:
|
24290 | case ts$1.SyntaxKind.VariableStatement:
|
24291 | case ts$1.SyntaxKind.ReturnStatement:
|
24292 | case ts$1.SyntaxKind.IfStatement:
|
24293 | case ts$1.SyntaxKind.SwitchStatement:
|
24294 | case ts$1.SyntaxKind.DoStatement:
|
24295 | case ts$1.SyntaxKind.WhileStatement:
|
24296 | case ts$1.SyntaxKind.ForStatement:
|
24297 | case ts$1.SyntaxKind.ForInStatement:
|
24298 | case ts$1.SyntaxKind.ForOfStatement:
|
24299 | case ts$1.SyntaxKind.ContinueStatement:
|
24300 | case ts$1.SyntaxKind.BreakStatement:
|
24301 | case ts$1.SyntaxKind.ThrowStatement:
|
24302 | case ts$1.SyntaxKind.ObjectBindingPattern:
|
24303 | case ts$1.SyntaxKind.ArrayBindingPattern:
|
24304 | return currentNode;
|
24305 | }
|
24306 | currentNode = currentNode.parent;
|
24307 | }
|
24308 | return node.getSourceFile();
|
24309 | }
|
24310 |
|
24311 | /**
|
24312 | * @license
|
24313 | * Copyright Google LLC All Rights Reserved.
|
24314 | *
|
24315 | * Use of this source code is governed by an MIT-style license that can be
|
24316 | * found in the LICENSE file at https://angular.io/license
|
24317 | */
|
24318 | class ArraySliceBuiltinFn extends KnownFn {
|
24319 | constructor(lhs) {
|
24320 | super();
|
24321 | this.lhs = lhs;
|
24322 | }
|
24323 | evaluate(node, args) {
|
24324 | if (args.length === 0) {
|
24325 | return this.lhs;
|
24326 | }
|
24327 | else {
|
24328 | return DynamicValue.fromUnknown(node);
|
24329 | }
|
24330 | }
|
24331 | }
|
24332 | class ArrayConcatBuiltinFn extends KnownFn {
|
24333 | constructor(lhs) {
|
24334 | super();
|
24335 | this.lhs = lhs;
|
24336 | }
|
24337 | evaluate(node, args) {
|
24338 | const result = [...this.lhs];
|
24339 | for (const arg of args) {
|
24340 | if (arg instanceof DynamicValue) {
|
24341 | result.push(DynamicValue.fromDynamicInput(node, arg));
|
24342 | }
|
24343 | else if (Array.isArray(arg)) {
|
24344 | result.push(...arg);
|
24345 | }
|
24346 | else {
|
24347 | result.push(arg);
|
24348 | }
|
24349 | }
|
24350 | return result;
|
24351 | }
|
24352 | }
|
24353 | class ObjectAssignBuiltinFn extends KnownFn {
|
24354 | evaluate(node, args) {
|
24355 | if (args.length === 0) {
|
24356 | return DynamicValue.fromUnsupportedSyntax(node);
|
24357 | }
|
24358 | for (const arg of args) {
|
24359 | if (arg instanceof DynamicValue) {
|
24360 | return DynamicValue.fromDynamicInput(node, arg);
|
24361 | }
|
24362 | else if (!(arg instanceof Map)) {
|
24363 | return DynamicValue.fromUnsupportedSyntax(node);
|
24364 | }
|
24365 | }
|
24366 | const [target, ...sources] = args;
|
24367 | for (const source of sources) {
|
24368 | source.forEach((value, key) => target.set(key, value));
|
24369 | }
|
24370 | return target;
|
24371 | }
|
24372 | }
|
24373 |
|
24374 | /**
|
24375 | * @license
|
24376 | * Copyright Google LLC All Rights Reserved.
|
24377 | *
|
24378 | * Use of this source code is governed by an MIT-style license that can be
|
24379 | * found in the LICENSE file at https://angular.io/license
|
24380 | */
|
24381 | // Use the same implementation we use for `Object.assign()`. Semantically these functions are the
|
24382 | // same, so they can also share the same evaluation code.
|
24383 | class AssignHelperFn extends ObjectAssignBuiltinFn {
|
24384 | }
|
24385 | // Used for both `__spread()` and `__spreadArrays()` TypeScript helper functions.
|
24386 | class SpreadHelperFn extends KnownFn {
|
24387 | evaluate(node, args) {
|
24388 | const result = [];
|
24389 | for (const arg of args) {
|
24390 | if (arg instanceof DynamicValue) {
|
24391 | result.push(DynamicValue.fromDynamicInput(node, arg));
|
24392 | }
|
24393 | else if (Array.isArray(arg)) {
|
24394 | result.push(...arg);
|
24395 | }
|
24396 | else {
|
24397 | result.push(arg);
|
24398 | }
|
24399 | }
|
24400 | return result;
|
24401 | }
|
24402 | }
|
24403 |
|
24404 | /**
|
24405 | * @license
|
24406 | * Copyright Google LLC All Rights Reserved.
|
24407 | *
|
24408 | * Use of this source code is governed by an MIT-style license that can be
|
24409 | * found in the LICENSE file at https://angular.io/license
|
24410 | */
|
24411 | /** Resolved value for the JavaScript global `Object` declaration. */
|
24412 | const jsGlobalObjectValue = new Map([['assign', new ObjectAssignBuiltinFn()]]);
|
24413 | /** Resolved value for the `__assign()` TypeScript helper declaration. */
|
24414 | const assignTsHelperFn = new AssignHelperFn();
|
24415 | /** Resolved value for the `__spread()` and `__spreadArrays()` TypeScript helper declarations. */
|
24416 | const spreadTsHelperFn = new SpreadHelperFn();
|
24417 | /**
|
24418 | * Resolves the specified known declaration to a resolved value. For example,
|
24419 | * the known JavaScript global `Object` will resolve to a `Map` that provides the
|
24420 | * `assign` method with a built-in function. This enables evaluation of `Object.assign`.
|
24421 | */
|
24422 | function resolveKnownDeclaration(decl) {
|
24423 | switch (decl) {
|
24424 | case KnownDeclaration.JsGlobalObject:
|
24425 | return jsGlobalObjectValue;
|
24426 | case KnownDeclaration.TsHelperAssign:
|
24427 | return assignTsHelperFn;
|
24428 | case KnownDeclaration.TsHelperSpread:
|
24429 | case KnownDeclaration.TsHelperSpreadArrays:
|
24430 | return spreadTsHelperFn;
|
24431 | default:
|
24432 | throw new Error(`Cannot resolve known declaration. Received: ${KnownDeclaration[decl]}.`);
|
24433 | }
|
24434 | }
|
24435 |
|
24436 | /**
|
24437 | * @license
|
24438 | * Copyright Google LLC All Rights Reserved.
|
24439 | *
|
24440 | * Use of this source code is governed by an MIT-style license that can be
|
24441 | * found in the LICENSE file at https://angular.io/license
|
24442 | */
|
24443 | function literalBinaryOp(op) {
|
24444 | return { op, literal: true };
|
24445 | }
|
24446 | function referenceBinaryOp(op) {
|
24447 | return { op, literal: false };
|
24448 | }
|
24449 | const BINARY_OPERATORS = new Map([
|
24450 | [ts$1.SyntaxKind.PlusToken, literalBinaryOp((a, b) => a + b)],
|
24451 | [ts$1.SyntaxKind.MinusToken, literalBinaryOp((a, b) => a - b)],
|
24452 | [ts$1.SyntaxKind.AsteriskToken, literalBinaryOp((a, b) => a * b)],
|
24453 | [ts$1.SyntaxKind.SlashToken, literalBinaryOp((a, b) => a / b)],
|
24454 | [ts$1.SyntaxKind.PercentToken, literalBinaryOp((a, b) => a % b)],
|
24455 | [ts$1.SyntaxKind.AmpersandToken, literalBinaryOp((a, b) => a & b)],
|
24456 | [ts$1.SyntaxKind.BarToken, literalBinaryOp((a, b) => a | b)],
|
24457 | [ts$1.SyntaxKind.CaretToken, literalBinaryOp((a, b) => a ^ b)],
|
24458 | [ts$1.SyntaxKind.LessThanToken, literalBinaryOp((a, b) => a < b)],
|
24459 | [ts$1.SyntaxKind.LessThanEqualsToken, literalBinaryOp((a, b) => a <= b)],
|
24460 | [ts$1.SyntaxKind.GreaterThanToken, literalBinaryOp((a, b) => a > b)],
|
24461 | [ts$1.SyntaxKind.GreaterThanEqualsToken, literalBinaryOp((a, b) => a >= b)],
|
24462 | [ts$1.SyntaxKind.EqualsEqualsToken, literalBinaryOp((a, b) => a == b)],
|
24463 | [ts$1.SyntaxKind.EqualsEqualsEqualsToken, literalBinaryOp((a, b) => a === b)],
|
24464 | [ts$1.SyntaxKind.ExclamationEqualsToken, literalBinaryOp((a, b) => a != b)],
|
24465 | [ts$1.SyntaxKind.ExclamationEqualsEqualsToken, literalBinaryOp((a, b) => a !== b)],
|
24466 | [ts$1.SyntaxKind.LessThanLessThanToken, literalBinaryOp((a, b) => a << b)],
|
24467 | [ts$1.SyntaxKind.GreaterThanGreaterThanToken, literalBinaryOp((a, b) => a >> b)],
|
24468 | [ts$1.SyntaxKind.GreaterThanGreaterThanGreaterThanToken, literalBinaryOp((a, b) => a >>> b)],
|
24469 | [ts$1.SyntaxKind.AsteriskAsteriskToken, literalBinaryOp((a, b) => Math.pow(a, b))],
|
24470 | [ts$1.SyntaxKind.AmpersandAmpersandToken, referenceBinaryOp((a, b) => a && b)],
|
24471 | [ts$1.SyntaxKind.BarBarToken, referenceBinaryOp((a, b) => a || b)]
|
24472 | ]);
|
24473 | const UNARY_OPERATORS = new Map([
|
24474 | [ts$1.SyntaxKind.TildeToken, a => ~a], [ts$1.SyntaxKind.MinusToken, a => -a],
|
24475 | [ts$1.SyntaxKind.PlusToken, a => +a], [ts$1.SyntaxKind.ExclamationToken, a => !a]
|
24476 | ]);
|
24477 | class StaticInterpreter {
|
24478 | constructor(host, checker, dependencyTracker) {
|
24479 | this.host = host;
|
24480 | this.checker = checker;
|
24481 | this.dependencyTracker = dependencyTracker;
|
24482 | }
|
24483 | visit(node, context) {
|
24484 | return this.visitExpression(node, context);
|
24485 | }
|
24486 | visitExpression(node, context) {
|
24487 | let result;
|
24488 | if (node.kind === ts$1.SyntaxKind.TrueKeyword) {
|
24489 | return true;
|
24490 | }
|
24491 | else if (node.kind === ts$1.SyntaxKind.FalseKeyword) {
|
24492 | return false;
|
24493 | }
|
24494 | else if (node.kind === ts$1.SyntaxKind.NullKeyword) {
|
24495 | return null;
|
24496 | }
|
24497 | else if (ts$1.isStringLiteral(node)) {
|
24498 | return node.text;
|
24499 | }
|
24500 | else if (ts$1.isNoSubstitutionTemplateLiteral(node)) {
|
24501 | return node.text;
|
24502 | }
|
24503 | else if (ts$1.isTemplateExpression(node)) {
|
24504 | result = this.visitTemplateExpression(node, context);
|
24505 | }
|
24506 | else if (ts$1.isNumericLiteral(node)) {
|
24507 | return parseFloat(node.text);
|
24508 | }
|
24509 | else if (ts$1.isObjectLiteralExpression(node)) {
|
24510 | result = this.visitObjectLiteralExpression(node, context);
|
24511 | }
|
24512 | else if (ts$1.isIdentifier(node)) {
|
24513 | result = this.visitIdentifier(node, context);
|
24514 | }
|
24515 | else if (ts$1.isPropertyAccessExpression(node)) {
|
24516 | result = this.visitPropertyAccessExpression(node, context);
|
24517 | }
|
24518 | else if (ts$1.isCallExpression(node)) {
|
24519 | result = this.visitCallExpression(node, context);
|
24520 | }
|
24521 | else if (ts$1.isConditionalExpression(node)) {
|
24522 | result = this.visitConditionalExpression(node, context);
|
24523 | }
|
24524 | else if (ts$1.isPrefixUnaryExpression(node)) {
|
24525 | result = this.visitPrefixUnaryExpression(node, context);
|
24526 | }
|
24527 | else if (ts$1.isBinaryExpression(node)) {
|
24528 | result = this.visitBinaryExpression(node, context);
|
24529 | }
|
24530 | else if (ts$1.isArrayLiteralExpression(node)) {
|
24531 | result = this.visitArrayLiteralExpression(node, context);
|
24532 | }
|
24533 | else if (ts$1.isParenthesizedExpression(node)) {
|
24534 | result = this.visitParenthesizedExpression(node, context);
|
24535 | }
|
24536 | else if (ts$1.isElementAccessExpression(node)) {
|
24537 | result = this.visitElementAccessExpression(node, context);
|
24538 | }
|
24539 | else if (ts$1.isAsExpression(node)) {
|
24540 | result = this.visitExpression(node.expression, context);
|
24541 | }
|
24542 | else if (ts$1.isNonNullExpression(node)) {
|
24543 | result = this.visitExpression(node.expression, context);
|
24544 | }
|
24545 | else if (this.host.isClass(node)) {
|
24546 | result = this.visitDeclaration(node, context);
|
24547 | }
|
24548 | else {
|
24549 | return DynamicValue.fromUnsupportedSyntax(node);
|
24550 | }
|
24551 | if (result instanceof DynamicValue && result.node !== node) {
|
24552 | return DynamicValue.fromDynamicInput(node, result);
|
24553 | }
|
24554 | return result;
|
24555 | }
|
24556 | visitArrayLiteralExpression(node, context) {
|
24557 | const array = [];
|
24558 | for (let i = 0; i < node.elements.length; i++) {
|
24559 | const element = node.elements[i];
|
24560 | if (ts$1.isSpreadElement(element)) {
|
24561 | array.push(...this.visitSpreadElement(element, context));
|
24562 | }
|
24563 | else {
|
24564 | array.push(this.visitExpression(element, context));
|
24565 | }
|
24566 | }
|
24567 | return array;
|
24568 | }
|
24569 | visitObjectLiteralExpression(node, context) {
|
24570 | const map = new Map();
|
24571 | for (let i = 0; i < node.properties.length; i++) {
|
24572 | const property = node.properties[i];
|
24573 | if (ts$1.isPropertyAssignment(property)) {
|
24574 | const name = this.stringNameFromPropertyName(property.name, context);
|
24575 | // Check whether the name can be determined statically.
|
24576 | if (name === undefined) {
|
24577 | return DynamicValue.fromDynamicInput(node, DynamicValue.fromDynamicString(property.name));
|
24578 | }
|
24579 | map.set(name, this.visitExpression(property.initializer, context));
|
24580 | }
|
24581 | else if (ts$1.isShorthandPropertyAssignment(property)) {
|
24582 | const symbol = this.checker.getShorthandAssignmentValueSymbol(property);
|
24583 | if (symbol === undefined || symbol.valueDeclaration === undefined) {
|
24584 | map.set(property.name.text, DynamicValue.fromUnknown(property));
|
24585 | }
|
24586 | else {
|
24587 | map.set(property.name.text, this.visitDeclaration(symbol.valueDeclaration, context));
|
24588 | }
|
24589 | }
|
24590 | else if (ts$1.isSpreadAssignment(property)) {
|
24591 | const spread = this.visitExpression(property.expression, context);
|
24592 | if (spread instanceof DynamicValue) {
|
24593 | return DynamicValue.fromDynamicInput(node, spread);
|
24594 | }
|
24595 | else if (spread instanceof Map) {
|
24596 | spread.forEach((value, key) => map.set(key, value));
|
24597 | }
|
24598 | else if (spread instanceof ResolvedModule) {
|
24599 | spread.getExports().forEach((value, key) => map.set(key, value));
|
24600 | }
|
24601 | else {
|
24602 | return DynamicValue.fromDynamicInput(node, DynamicValue.fromInvalidExpressionType(property, spread));
|
24603 | }
|
24604 | }
|
24605 | else {
|
24606 | return DynamicValue.fromUnknown(node);
|
24607 | }
|
24608 | }
|
24609 | return map;
|
24610 | }
|
24611 | visitTemplateExpression(node, context) {
|
24612 | const pieces = [node.head.text];
|
24613 | for (let i = 0; i < node.templateSpans.length; i++) {
|
24614 | const span = node.templateSpans[i];
|
24615 | const value = literal$1(this.visit(span.expression, context), () => DynamicValue.fromDynamicString(span.expression));
|
24616 | if (value instanceof DynamicValue) {
|
24617 | return DynamicValue.fromDynamicInput(node, value);
|
24618 | }
|
24619 | pieces.push(`${value}`, span.literal.text);
|
24620 | }
|
24621 | return pieces.join('');
|
24622 | }
|
24623 | visitIdentifier(node, context) {
|
24624 | const decl = this.host.getDeclarationOfIdentifier(node);
|
24625 | if (decl === null) {
|
24626 | if (node.originalKeywordKind === ts$1.SyntaxKind.UndefinedKeyword) {
|
24627 | return undefined;
|
24628 | }
|
24629 | else {
|
24630 | // Check if the symbol here is imported.
|
24631 | if (this.dependencyTracker !== null && this.host.getImportOfIdentifier(node) !== null) {
|
24632 | // It was, but no declaration for the node could be found. This means that the dependency
|
24633 | // graph for the current file cannot be properly updated to account for this (broken)
|
24634 | // import. Instead, the originating file is reported as failing dependency analysis,
|
24635 | // ensuring that future compilations will always attempt to re-resolve the previously
|
24636 | // broken identifier.
|
24637 | this.dependencyTracker.recordDependencyAnalysisFailure(context.originatingFile);
|
24638 | }
|
24639 | return DynamicValue.fromUnknownIdentifier(node);
|
24640 | }
|
24641 | }
|
24642 | if (decl.known !== null) {
|
24643 | return resolveKnownDeclaration(decl.known);
|
24644 | }
|
24645 | else if (isConcreteDeclaration(decl) && decl.identity !== null &&
|
24646 | decl.identity.kind === 0 /* DownleveledEnum */) {
|
24647 | return this.getResolvedEnum(decl.node, decl.identity.enumMembers, context);
|
24648 | }
|
24649 | const declContext = Object.assign(Object.assign({}, context), joinModuleContext(context, node, decl));
|
24650 | const result = this.visitAmbiguousDeclaration(decl, declContext);
|
24651 | if (result instanceof Reference$1) {
|
24652 | // Only record identifiers to non-synthetic references. Synthetic references may not have the
|
24653 | // same value at runtime as they do at compile time, so it's not legal to refer to them by the
|
24654 | // identifier here.
|
24655 | if (!result.synthetic) {
|
24656 | result.addIdentifier(node);
|
24657 | }
|
24658 | }
|
24659 | else if (result instanceof DynamicValue) {
|
24660 | return DynamicValue.fromDynamicInput(node, result);
|
24661 | }
|
24662 | return result;
|
24663 | }
|
24664 | visitDeclaration(node, context) {
|
24665 | if (this.dependencyTracker !== null) {
|
24666 | this.dependencyTracker.addDependency(context.originatingFile, node.getSourceFile());
|
24667 | }
|
24668 | if (this.host.isClass(node)) {
|
24669 | return this.getReference(node, context);
|
24670 | }
|
24671 | else if (ts$1.isVariableDeclaration(node)) {
|
24672 | return this.visitVariableDeclaration(node, context);
|
24673 | }
|
24674 | else if (ts$1.isParameter(node) && context.scope.has(node)) {
|
24675 | return context.scope.get(node);
|
24676 | }
|
24677 | else if (ts$1.isExportAssignment(node)) {
|
24678 | return this.visitExpression(node.expression, context);
|
24679 | }
|
24680 | else if (ts$1.isEnumDeclaration(node)) {
|
24681 | return this.visitEnumDeclaration(node, context);
|
24682 | }
|
24683 | else if (ts$1.isSourceFile(node)) {
|
24684 | return this.visitSourceFile(node, context);
|
24685 | }
|
24686 | else if (ts$1.isBindingElement(node)) {
|
24687 | return this.visitBindingElement(node, context);
|
24688 | }
|
24689 | else {
|
24690 | return this.getReference(node, context);
|
24691 | }
|
24692 | }
|
24693 | visitVariableDeclaration(node, context) {
|
24694 | const value = this.host.getVariableValue(node);
|
24695 | if (value !== null) {
|
24696 | return this.visitExpression(value, context);
|
24697 | }
|
24698 | else if (isVariableDeclarationDeclared(node)) {
|
24699 | return this.getReference(node, context);
|
24700 | }
|
24701 | else {
|
24702 | return undefined;
|
24703 | }
|
24704 | }
|
24705 | visitEnumDeclaration(node, context) {
|
24706 | const enumRef = this.getReference(node, context);
|
24707 | const map = new Map();
|
24708 | node.members.forEach(member => {
|
24709 | const name = this.stringNameFromPropertyName(member.name, context);
|
24710 | if (name !== undefined) {
|
24711 | const resolved = member.initializer && this.visit(member.initializer, context);
|
24712 | map.set(name, new EnumValue(enumRef, name, resolved));
|
24713 | }
|
24714 | });
|
24715 | return map;
|
24716 | }
|
24717 | visitElementAccessExpression(node, context) {
|
24718 | const lhs = this.visitExpression(node.expression, context);
|
24719 | if (lhs instanceof DynamicValue) {
|
24720 | return DynamicValue.fromDynamicInput(node, lhs);
|
24721 | }
|
24722 | const rhs = this.visitExpression(node.argumentExpression, context);
|
24723 | if (rhs instanceof DynamicValue) {
|
24724 | return DynamicValue.fromDynamicInput(node, rhs);
|
24725 | }
|
24726 | if (typeof rhs !== 'string' && typeof rhs !== 'number') {
|
24727 | return DynamicValue.fromInvalidExpressionType(node, rhs);
|
24728 | }
|
24729 | return this.accessHelper(node, lhs, rhs, context);
|
24730 | }
|
24731 | visitPropertyAccessExpression(node, context) {
|
24732 | const lhs = this.visitExpression(node.expression, context);
|
24733 | const rhs = node.name.text;
|
24734 | // TODO: handle reference to class declaration.
|
24735 | if (lhs instanceof DynamicValue) {
|
24736 | return DynamicValue.fromDynamicInput(node, lhs);
|
24737 | }
|
24738 | return this.accessHelper(node, lhs, rhs, context);
|
24739 | }
|
24740 | visitSourceFile(node, context) {
|
24741 | const declarations = this.host.getExportsOfModule(node);
|
24742 | if (declarations === null) {
|
24743 | return DynamicValue.fromUnknown(node);
|
24744 | }
|
24745 | return new ResolvedModule(declarations, decl => {
|
24746 | if (decl.known !== null) {
|
24747 | return resolveKnownDeclaration(decl.known);
|
24748 | }
|
24749 | const declContext = Object.assign(Object.assign({}, context), joinModuleContext(context, node, decl));
|
24750 | // Visit both concrete and inline declarations.
|
24751 | return this.visitAmbiguousDeclaration(decl, declContext);
|
24752 | });
|
24753 | }
|
24754 | visitAmbiguousDeclaration(decl, declContext) {
|
24755 | return decl.kind === 1 /* Inline */ && decl.implementation !== undefined &&
|
24756 | !isDeclaration(decl.implementation) ?
|
24757 | // Inline declarations whose `implementation` is a `ts.Expression` should be visited as
|
24758 | // an expression.
|
24759 | this.visitExpression(decl.implementation, declContext) :
|
24760 | // Otherwise just visit the `node` as a declaration.
|
24761 | this.visitDeclaration(decl.node, declContext);
|
24762 | }
|
24763 | accessHelper(node, lhs, rhs, context) {
|
24764 | const strIndex = `${rhs}`;
|
24765 | if (lhs instanceof Map) {
|
24766 | if (lhs.has(strIndex)) {
|
24767 | return lhs.get(strIndex);
|
24768 | }
|
24769 | else {
|
24770 | return undefined;
|
24771 | }
|
24772 | }
|
24773 | else if (lhs instanceof ResolvedModule) {
|
24774 | return lhs.getExport(strIndex);
|
24775 | }
|
24776 | else if (Array.isArray(lhs)) {
|
24777 | if (rhs === 'length') {
|
24778 | return lhs.length;
|
24779 | }
|
24780 | else if (rhs === 'slice') {
|
24781 | return new ArraySliceBuiltinFn(lhs);
|
24782 | }
|
24783 | else if (rhs === 'concat') {
|
24784 | return new ArrayConcatBuiltinFn(lhs);
|
24785 | }
|
24786 | if (typeof rhs !== 'number' || !Number.isInteger(rhs)) {
|
24787 | return DynamicValue.fromInvalidExpressionType(node, rhs);
|
24788 | }
|
24789 | return lhs[rhs];
|
24790 | }
|
24791 | else if (lhs instanceof Reference$1) {
|
24792 | const ref = lhs.node;
|
24793 | if (this.host.isClass(ref)) {
|
24794 | const module = owningModule(context, lhs.bestGuessOwningModule);
|
24795 | let value = undefined;
|
24796 | const member = this.host.getMembersOfClass(ref).find(member => member.isStatic && member.name === strIndex);
|
24797 | if (member !== undefined) {
|
24798 | if (member.value !== null) {
|
24799 | value = this.visitExpression(member.value, context);
|
24800 | }
|
24801 | else if (member.implementation !== null) {
|
24802 | value = new Reference$1(member.implementation, module);
|
24803 | }
|
24804 | else if (member.node) {
|
24805 | value = new Reference$1(member.node, module);
|
24806 | }
|
24807 | }
|
24808 | return value;
|
24809 | }
|
24810 | else if (isDeclaration(ref)) {
|
24811 | return DynamicValue.fromDynamicInput(node, DynamicValue.fromExternalReference(ref, lhs));
|
24812 | }
|
24813 | }
|
24814 | else if (lhs instanceof DynamicValue) {
|
24815 | return DynamicValue.fromDynamicInput(node, lhs);
|
24816 | }
|
24817 | return DynamicValue.fromUnknown(node);
|
24818 | }
|
24819 | visitCallExpression(node, context) {
|
24820 | const lhs = this.visitExpression(node.expression, context);
|
24821 | if (lhs instanceof DynamicValue) {
|
24822 | return DynamicValue.fromDynamicInput(node, lhs);
|
24823 | }
|
24824 | // If the call refers to a builtin function, attempt to evaluate the function.
|
24825 | if (lhs instanceof KnownFn) {
|
24826 | return lhs.evaluate(node, this.evaluateFunctionArguments(node, context));
|
24827 | }
|
24828 | if (!(lhs instanceof Reference$1)) {
|
24829 | return DynamicValue.fromInvalidExpressionType(node.expression, lhs);
|
24830 | }
|
24831 | const fn = this.host.getDefinitionOfFunction(lhs.node);
|
24832 | if (fn === null) {
|
24833 | return DynamicValue.fromInvalidExpressionType(node.expression, lhs);
|
24834 | }
|
24835 | if (!isFunctionOrMethodReference(lhs)) {
|
24836 | return DynamicValue.fromInvalidExpressionType(node.expression, lhs);
|
24837 | }
|
24838 | // If the function is foreign (declared through a d.ts file), attempt to resolve it with the
|
24839 | // foreignFunctionResolver, if one is specified.
|
24840 | if (fn.body === null) {
|
24841 | let expr = null;
|
24842 | if (context.foreignFunctionResolver) {
|
24843 | expr = context.foreignFunctionResolver(lhs, node.arguments);
|
24844 | }
|
24845 | if (expr === null) {
|
24846 | return DynamicValue.fromDynamicInput(node, DynamicValue.fromExternalReference(node.expression, lhs));
|
24847 | }
|
24848 | // If the function is declared in a different file, resolve the foreign function expression
|
24849 | // using the absolute module name of that file (if any).
|
24850 | if (lhs.bestGuessOwningModule !== null) {
|
24851 | context = Object.assign(Object.assign({}, context), { absoluteModuleName: lhs.bestGuessOwningModule.specifier, resolutionContext: node.getSourceFile().fileName });
|
24852 | }
|
24853 | return this.visitFfrExpression(expr, context);
|
24854 | }
|
24855 | let res = this.visitFunctionBody(node, fn, context);
|
24856 | // If the result of attempting to resolve the function body was a DynamicValue, attempt to use
|
24857 | // the foreignFunctionResolver if one is present. This could still potentially yield a usable
|
24858 | // value.
|
24859 | if (res instanceof DynamicValue && context.foreignFunctionResolver !== undefined) {
|
24860 | const ffrExpr = context.foreignFunctionResolver(lhs, node.arguments);
|
24861 | if (ffrExpr !== null) {
|
24862 | // The foreign function resolver was able to extract an expression from this function. See
|
24863 | // if that expression leads to a non-dynamic result.
|
24864 | const ffrRes = this.visitFfrExpression(ffrExpr, context);
|
24865 | if (!(ffrRes instanceof DynamicValue)) {
|
24866 | // FFR yielded an actual result that's not dynamic, so use that instead of the original
|
24867 | // resolution.
|
24868 | res = ffrRes;
|
24869 | }
|
24870 | }
|
24871 | }
|
24872 | return res;
|
24873 | }
|
24874 | /**
|
24875 | * Visit an expression which was extracted from a foreign-function resolver.
|
24876 | *
|
24877 | * This will process the result and ensure it's correct for FFR-resolved values, including marking
|
24878 | * `Reference`s as synthetic.
|
24879 | */
|
24880 | visitFfrExpression(expr, context) {
|
24881 | const res = this.visitExpression(expr, context);
|
24882 | if (res instanceof Reference$1) {
|
24883 | // This Reference was created synthetically, via a foreign function resolver. The real
|
24884 | // runtime value of the function expression may be different than the foreign function
|
24885 | // resolved value, so mark the Reference as synthetic to avoid it being misinterpreted.
|
24886 | res.synthetic = true;
|
24887 | }
|
24888 | return res;
|
24889 | }
|
24890 | visitFunctionBody(node, fn, context) {
|
24891 | if (fn.body === null) {
|
24892 | return DynamicValue.fromUnknown(node);
|
24893 | }
|
24894 | else if (fn.body.length !== 1 || !ts$1.isReturnStatement(fn.body[0])) {
|
24895 | return DynamicValue.fromComplexFunctionCall(node, fn);
|
24896 | }
|
24897 | const ret = fn.body[0];
|
24898 | const args = this.evaluateFunctionArguments(node, context);
|
24899 | const newScope = new Map();
|
24900 | const calleeContext = Object.assign(Object.assign({}, context), { scope: newScope });
|
24901 | fn.parameters.forEach((param, index) => {
|
24902 | let arg = args[index];
|
24903 | if (param.node.dotDotDotToken !== undefined) {
|
24904 | arg = args.slice(index);
|
24905 | }
|
24906 | if (arg === undefined && param.initializer !== null) {
|
24907 | arg = this.visitExpression(param.initializer, calleeContext);
|
24908 | }
|
24909 | newScope.set(param.node, arg);
|
24910 | });
|
24911 | return ret.expression !== undefined ? this.visitExpression(ret.expression, calleeContext) :
|
24912 | undefined;
|
24913 | }
|
24914 | visitConditionalExpression(node, context) {
|
24915 | const condition = this.visitExpression(node.condition, context);
|
24916 | if (condition instanceof DynamicValue) {
|
24917 | return DynamicValue.fromDynamicInput(node, condition);
|
24918 | }
|
24919 | if (condition) {
|
24920 | return this.visitExpression(node.whenTrue, context);
|
24921 | }
|
24922 | else {
|
24923 | return this.visitExpression(node.whenFalse, context);
|
24924 | }
|
24925 | }
|
24926 | visitPrefixUnaryExpression(node, context) {
|
24927 | const operatorKind = node.operator;
|
24928 | if (!UNARY_OPERATORS.has(operatorKind)) {
|
24929 | return DynamicValue.fromUnsupportedSyntax(node);
|
24930 | }
|
24931 | const op = UNARY_OPERATORS.get(operatorKind);
|
24932 | const value = this.visitExpression(node.operand, context);
|
24933 | if (value instanceof DynamicValue) {
|
24934 | return DynamicValue.fromDynamicInput(node, value);
|
24935 | }
|
24936 | else {
|
24937 | return op(value);
|
24938 | }
|
24939 | }
|
24940 | visitBinaryExpression(node, context) {
|
24941 | const tokenKind = node.operatorToken.kind;
|
24942 | if (!BINARY_OPERATORS.has(tokenKind)) {
|
24943 | return DynamicValue.fromUnsupportedSyntax(node);
|
24944 | }
|
24945 | const opRecord = BINARY_OPERATORS.get(tokenKind);
|
24946 | let lhs, rhs;
|
24947 | if (opRecord.literal) {
|
24948 | lhs = literal$1(this.visitExpression(node.left, context), value => DynamicValue.fromInvalidExpressionType(node.left, value));
|
24949 | rhs = literal$1(this.visitExpression(node.right, context), value => DynamicValue.fromInvalidExpressionType(node.right, value));
|
24950 | }
|
24951 | else {
|
24952 | lhs = this.visitExpression(node.left, context);
|
24953 | rhs = this.visitExpression(node.right, context);
|
24954 | }
|
24955 | if (lhs instanceof DynamicValue) {
|
24956 | return DynamicValue.fromDynamicInput(node, lhs);
|
24957 | }
|
24958 | else if (rhs instanceof DynamicValue) {
|
24959 | return DynamicValue.fromDynamicInput(node, rhs);
|
24960 | }
|
24961 | else {
|
24962 | return opRecord.op(lhs, rhs);
|
24963 | }
|
24964 | }
|
24965 | visitParenthesizedExpression(node, context) {
|
24966 | return this.visitExpression(node.expression, context);
|
24967 | }
|
24968 | evaluateFunctionArguments(node, context) {
|
24969 | const args = [];
|
24970 | for (const arg of node.arguments) {
|
24971 | if (ts$1.isSpreadElement(arg)) {
|
24972 | args.push(...this.visitSpreadElement(arg, context));
|
24973 | }
|
24974 | else {
|
24975 | args.push(this.visitExpression(arg, context));
|
24976 | }
|
24977 | }
|
24978 | return args;
|
24979 | }
|
24980 | visitSpreadElement(node, context) {
|
24981 | const spread = this.visitExpression(node.expression, context);
|
24982 | if (spread instanceof DynamicValue) {
|
24983 | return [DynamicValue.fromDynamicInput(node, spread)];
|
24984 | }
|
24985 | else if (!Array.isArray(spread)) {
|
24986 | return [DynamicValue.fromInvalidExpressionType(node, spread)];
|
24987 | }
|
24988 | else {
|
24989 | return spread;
|
24990 | }
|
24991 | }
|
24992 | visitBindingElement(node, context) {
|
24993 | const path = [];
|
24994 | let closestDeclaration = node;
|
24995 | while (ts$1.isBindingElement(closestDeclaration) ||
|
24996 | ts$1.isArrayBindingPattern(closestDeclaration) ||
|
24997 | ts$1.isObjectBindingPattern(closestDeclaration)) {
|
24998 | if (ts$1.isBindingElement(closestDeclaration)) {
|
24999 | path.unshift(closestDeclaration);
|
25000 | }
|
25001 | closestDeclaration = closestDeclaration.parent;
|
25002 | }
|
25003 | if (!ts$1.isVariableDeclaration(closestDeclaration) ||
|
25004 | closestDeclaration.initializer === undefined) {
|
25005 | return DynamicValue.fromUnknown(node);
|
25006 | }
|
25007 | let value = this.visit(closestDeclaration.initializer, context);
|
25008 | for (const element of path) {
|
25009 | let key;
|
25010 | if (ts$1.isArrayBindingPattern(element.parent)) {
|
25011 | key = element.parent.elements.indexOf(element);
|
25012 | }
|
25013 | else {
|
25014 | const name = element.propertyName || element.name;
|
25015 | if (ts$1.isIdentifier(name)) {
|
25016 | key = name.text;
|
25017 | }
|
25018 | else {
|
25019 | return DynamicValue.fromUnknown(element);
|
25020 | }
|
25021 | }
|
25022 | value = this.accessHelper(element, value, key, context);
|
25023 | if (value instanceof DynamicValue) {
|
25024 | return value;
|
25025 | }
|
25026 | }
|
25027 | return value;
|
25028 | }
|
25029 | stringNameFromPropertyName(node, context) {
|
25030 | if (ts$1.isIdentifier(node) || ts$1.isStringLiteral(node) || ts$1.isNumericLiteral(node)) {
|
25031 | return node.text;
|
25032 | }
|
25033 | else if (ts$1.isComputedPropertyName(node)) {
|
25034 | const literal = this.visitExpression(node.expression, context);
|
25035 | return typeof literal === 'string' ? literal : undefined;
|
25036 | }
|
25037 | else {
|
25038 | return undefined;
|
25039 | }
|
25040 | }
|
25041 | getResolvedEnum(node, enumMembers, context) {
|
25042 | const enumRef = this.getReference(node, context);
|
25043 | const map = new Map();
|
25044 | enumMembers.forEach(member => {
|
25045 | const name = this.stringNameFromPropertyName(member.name, context);
|
25046 | if (name !== undefined) {
|
25047 | const resolved = this.visit(member.initializer, context);
|
25048 | map.set(name, new EnumValue(enumRef, name, resolved));
|
25049 | }
|
25050 | });
|
25051 | return map;
|
25052 | }
|
25053 | getReference(node, context) {
|
25054 | return new Reference$1(node, owningModule(context));
|
25055 | }
|
25056 | }
|
25057 | function isFunctionOrMethodReference(ref) {
|
25058 | return ts$1.isFunctionDeclaration(ref.node) || ts$1.isMethodDeclaration(ref.node) ||
|
25059 | ts$1.isFunctionExpression(ref.node);
|
25060 | }
|
25061 | function literal$1(value, reject) {
|
25062 | if (value instanceof EnumValue) {
|
25063 | value = value.resolved;
|
25064 | }
|
25065 | if (value instanceof DynamicValue || value === null || value === undefined ||
|
25066 | typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
25067 | return value;
|
25068 | }
|
25069 | return reject(value);
|
25070 | }
|
25071 | function isVariableDeclarationDeclared(node) {
|
25072 | if (node.parent === undefined || !ts$1.isVariableDeclarationList(node.parent)) {
|
25073 | return false;
|
25074 | }
|
25075 | const declList = node.parent;
|
25076 | if (declList.parent === undefined || !ts$1.isVariableStatement(declList.parent)) {
|
25077 | return false;
|
25078 | }
|
25079 | const varStmt = declList.parent;
|
25080 | return varStmt.modifiers !== undefined &&
|
25081 | varStmt.modifiers.some(mod => mod.kind === ts$1.SyntaxKind.DeclareKeyword);
|
25082 | }
|
25083 | const EMPTY = {};
|
25084 | function joinModuleContext(existing, node, decl) {
|
25085 | if (decl.viaModule !== null && decl.viaModule !== existing.absoluteModuleName) {
|
25086 | return {
|
25087 | absoluteModuleName: decl.viaModule,
|
25088 | resolutionContext: node.getSourceFile().fileName,
|
25089 | };
|
25090 | }
|
25091 | else {
|
25092 | return EMPTY;
|
25093 | }
|
25094 | }
|
25095 | function owningModule(context, override = null) {
|
25096 | let specifier = context.absoluteModuleName;
|
25097 | if (override !== null) {
|
25098 | specifier = override.specifier;
|
25099 | }
|
25100 | if (specifier !== null) {
|
25101 | return {
|
25102 | specifier,
|
25103 | resolutionContext: context.resolutionContext,
|
25104 | };
|
25105 | }
|
25106 | else {
|
25107 | return null;
|
25108 | }
|
25109 | }
|
25110 |
|
25111 | /**
|
25112 | * @license
|
25113 | * Copyright Google LLC All Rights Reserved.
|
25114 | *
|
25115 | * Use of this source code is governed by an MIT-style license that can be
|
25116 | * found in the LICENSE file at https://angular.io/license
|
25117 | */
|
25118 | class PartialEvaluator {
|
25119 | constructor(host, checker, dependencyTracker) {
|
25120 | this.host = host;
|
25121 | this.checker = checker;
|
25122 | this.dependencyTracker = dependencyTracker;
|
25123 | }
|
25124 | evaluate(expr, foreignFunctionResolver) {
|
25125 | const interpreter = new StaticInterpreter(this.host, this.checker, this.dependencyTracker);
|
25126 | const sourceFile = expr.getSourceFile();
|
25127 | return interpreter.visit(expr, {
|
25128 | originatingFile: sourceFile,
|
25129 | absoluteModuleName: null,
|
25130 | resolutionContext: sourceFile.fileName,
|
25131 | scope: new Map(),
|
25132 | foreignFunctionResolver,
|
25133 | });
|
25134 | }
|
25135 | }
|
25136 |
|
25137 | /**
|
25138 | * @license
|
25139 | * Copyright Google LLC All Rights Reserved.
|
25140 | *
|
25141 | * Use of this source code is governed by an MIT-style license that can be
|
25142 | * found in the LICENSE file at https://angular.io/license
|
25143 | */
|
25144 | /**
|
25145 | * Specifies the compilation mode that is used for the compilation.
|
25146 | */
|
25147 | var CompilationMode;
|
25148 | (function (CompilationMode) {
|
25149 | /**
|
25150 | * Generates fully AOT compiled code using Ivy instructions.
|
25151 | */
|
25152 | CompilationMode[CompilationMode["FULL"] = 0] = "FULL";
|
25153 | /**
|
25154 | * Generates code using a stable, but intermediate format suitable to be published to NPM.
|
25155 | */
|
25156 | CompilationMode[CompilationMode["PARTIAL"] = 1] = "PARTIAL";
|
25157 | })(CompilationMode || (CompilationMode = {}));
|
25158 | var HandlerPrecedence;
|
25159 | (function (HandlerPrecedence) {
|
25160 | /**
|
25161 | * Handler with PRIMARY precedence cannot overlap - there can only be one on a given class.
|
25162 | *
|
25163 | * If more than one PRIMARY handler matches a class, an error is produced.
|
25164 | */
|
25165 | HandlerPrecedence[HandlerPrecedence["PRIMARY"] = 0] = "PRIMARY";
|
25166 | /**
|
25167 | * Handlers with SHARED precedence can match any class, possibly in addition to a single PRIMARY
|
25168 | * handler.
|
25169 | *
|
25170 | * It is not an error for a class to have any number of SHARED handlers.
|
25171 | */
|
25172 | HandlerPrecedence[HandlerPrecedence["SHARED"] = 1] = "SHARED";
|
25173 | /**
|
25174 | * Handlers with WEAK precedence that match a class are ignored if any handlers with stronger
|
25175 | * precedence match a class.
|
25176 | */
|
25177 | HandlerPrecedence[HandlerPrecedence["WEAK"] = 2] = "WEAK";
|
25178 | })(HandlerPrecedence || (HandlerPrecedence = {}));
|
25179 | /**
|
25180 | * A set of options which can be passed to a `DecoratorHandler` by a consumer, to tailor the output
|
25181 | * of compilation beyond the decorators themselves.
|
25182 | */
|
25183 | var HandlerFlags;
|
25184 | (function (HandlerFlags) {
|
25185 | /**
|
25186 | * No flags set.
|
25187 | */
|
25188 | HandlerFlags[HandlerFlags["NONE"] = 0] = "NONE";
|
25189 | /**
|
25190 | * Indicates that this decorator is fully inherited from its parent at runtime. In addition to
|
25191 | * normally inherited aspects such as inputs and queries, full inheritance applies to every aspect
|
25192 | * of the component or directive, such as the template function itself.
|
25193 | *
|
25194 | * Its primary effect is to cause the `CopyDefinitionFeature` to be applied to the definition
|
25195 | * being compiled. See that class for more information.
|
25196 | */
|
25197 | HandlerFlags[HandlerFlags["FULL_INHERITANCE"] = 1] = "FULL_INHERITANCE";
|
25198 | })(HandlerFlags || (HandlerFlags = {}));
|
25199 |
|
25200 | /**
|
25201 | * @license
|
25202 | * Copyright Google LLC All Rights Reserved.
|
25203 | *
|
25204 | * Use of this source code is governed by an MIT-style license that can be
|
25205 | * found in the LICENSE file at https://angular.io/license
|
25206 | */
|
25207 | function aliasTransformFactory(exportStatements) {
|
25208 | return (context) => {
|
25209 | return (file) => {
|
25210 | if (ts$1.isBundle(file) || !exportStatements.has(file.fileName)) {
|
25211 | return file;
|
25212 | }
|
25213 | const statements = [...file.statements];
|
25214 | exportStatements.get(file.fileName).forEach(([moduleName, symbolName], aliasName) => {
|
25215 | const stmt = ts$1.createExportDeclaration(
|
25216 | /* decorators */ undefined,
|
25217 | /* modifiers */ undefined,
|
25218 | /* exportClause */ ts$1.createNamedExports([ts$1.createExportSpecifier(
|
25219 | /* propertyName */ symbolName,
|
25220 | /* name */ aliasName)]),
|
25221 | /* moduleSpecifier */ ts$1.createStringLiteral(moduleName));
|
25222 | statements.push(stmt);
|
25223 | });
|
25224 | return ts$1.updateSourceFileNode(file, statements);
|
25225 | };
|
25226 | };
|
25227 | }
|
25228 |
|
25229 | /**
|
25230 | * @license
|
25231 | * Copyright Google LLC All Rights Reserved.
|
25232 | *
|
25233 | * Use of this source code is governed by an MIT-style license that can be
|
25234 | * found in the LICENSE file at https://angular.io/license
|
25235 | */
|
25236 | var TraitState;
|
25237 | (function (TraitState) {
|
25238 | /**
|
25239 | * Pending traits are freshly created and have never been analyzed.
|
25240 | */
|
25241 | TraitState[TraitState["Pending"] = 0] = "Pending";
|
25242 | /**
|
25243 | * Analyzed traits have successfully been analyzed, but are pending resolution.
|
25244 | */
|
25245 | TraitState[TraitState["Analyzed"] = 1] = "Analyzed";
|
25246 | /**
|
25247 | * Resolved traits have successfully been analyzed and resolved and are ready for compilation.
|
25248 | */
|
25249 | TraitState[TraitState["Resolved"] = 2] = "Resolved";
|
25250 | /**
|
25251 | * Skipped traits are no longer considered for compilation.
|
25252 | */
|
25253 | TraitState[TraitState["Skipped"] = 3] = "Skipped";
|
25254 | })(TraitState || (TraitState = {}));
|
25255 | /**
|
25256 | * The value side of `Trait` exposes a helper to create a `Trait` in a pending state (by delegating
|
25257 | * to `TraitImpl`).
|
25258 | */
|
25259 | const Trait = {
|
25260 | pending: (handler, detected) => TraitImpl.pending(handler, detected),
|
25261 | };
|
25262 | /**
|
25263 | * An implementation of the `Trait` type which transitions safely between the various
|
25264 | * `TraitState`s.
|
25265 | */
|
25266 | class TraitImpl {
|
25267 | constructor(handler, detected) {
|
25268 | this.state = TraitState.Pending;
|
25269 | this.analysis = null;
|
25270 | this.resolution = null;
|
25271 | this.analysisDiagnostics = null;
|
25272 | this.resolveDiagnostics = null;
|
25273 | this.handler = handler;
|
25274 | this.detected = detected;
|
25275 | }
|
25276 | toAnalyzed(analysis, diagnostics) {
|
25277 | // Only pending traits can be analyzed.
|
25278 | this.assertTransitionLegal(TraitState.Pending, TraitState.Analyzed);
|
25279 | this.analysis = analysis;
|
25280 | this.analysisDiagnostics = diagnostics;
|
25281 | this.state = TraitState.Analyzed;
|
25282 | return this;
|
25283 | }
|
25284 | toResolved(resolution, diagnostics) {
|
25285 | // Only analyzed traits can be resolved.
|
25286 | this.assertTransitionLegal(TraitState.Analyzed, TraitState.Resolved);
|
25287 | if (this.analysis === null) {
|
25288 | throw new Error(`Cannot transition an Analyzed trait with a null analysis to Resolved`);
|
25289 | }
|
25290 | this.resolution = resolution;
|
25291 | this.state = TraitState.Resolved;
|
25292 | this.resolveDiagnostics = diagnostics;
|
25293 | return this;
|
25294 | }
|
25295 | toSkipped() {
|
25296 | // Only pending traits can be skipped.
|
25297 | this.assertTransitionLegal(TraitState.Pending, TraitState.Skipped);
|
25298 | this.state = TraitState.Skipped;
|
25299 | return this;
|
25300 | }
|
25301 | /**
|
25302 | * Verifies that the trait is currently in one of the `allowedState`s.
|
25303 | *
|
25304 | * If correctly used, the `Trait` type and transition methods prevent illegal transitions from
|
25305 | * occurring. However, if a reference to the `TraitImpl` instance typed with the previous
|
25306 | * interface is retained after calling one of its transition methods, it will allow for illegal
|
25307 | * transitions to take place. Hence, this assertion provides a little extra runtime protection.
|
25308 | */
|
25309 | assertTransitionLegal(allowedState, transitionTo) {
|
25310 | if (!(this.state === allowedState)) {
|
25311 | throw new Error(`Assertion failure: cannot transition from ${TraitState[this.state]} to ${TraitState[transitionTo]}.`);
|
25312 | }
|
25313 | }
|
25314 | /**
|
25315 | * Construct a new `TraitImpl` in the pending state.
|
25316 | */
|
25317 | static pending(handler, detected) {
|
25318 | return new TraitImpl(handler, detected);
|
25319 | }
|
25320 | }
|
25321 |
|
25322 | /**
|
25323 | * @license
|
25324 | * Copyright Google LLC All Rights Reserved.
|
25325 | *
|
25326 | * Use of this source code is governed by an MIT-style license that can be
|
25327 | * found in the LICENSE file at https://angular.io/license
|
25328 | */
|
25329 | /**
|
25330 | * The heart of Angular compilation.
|
25331 | *
|
25332 | * The `TraitCompiler` is responsible for processing all classes in the program. Any time a
|
25333 | * `DecoratorHandler` matches a class, a "trait" is created to represent that Angular aspect of the
|
25334 | * class (such as the class having a component definition).
|
25335 | *
|
25336 | * The `TraitCompiler` transitions each trait through the various phases of compilation, culminating
|
25337 | * in the production of `CompileResult`s instructing the compiler to apply various mutations to the
|
25338 | * class (like adding fields or type declarations).
|
25339 | */
|
25340 | class TraitCompiler {
|
25341 | constructor(handlers, reflector, perf, incrementalBuild, compileNonExportedClasses, compilationMode, dtsTransforms) {
|
25342 | this.handlers = handlers;
|
25343 | this.reflector = reflector;
|
25344 | this.perf = perf;
|
25345 | this.incrementalBuild = incrementalBuild;
|
25346 | this.compileNonExportedClasses = compileNonExportedClasses;
|
25347 | this.compilationMode = compilationMode;
|
25348 | this.dtsTransforms = dtsTransforms;
|
25349 | /**
|
25350 | * Maps class declarations to their `ClassRecord`, which tracks the Ivy traits being applied to
|
25351 | * those classes.
|
25352 | */
|
25353 | this.classes = new Map();
|
25354 | /**
|
25355 | * Maps source files to any class declaration(s) within them which have been discovered to contain
|
25356 | * Ivy traits.
|
25357 | */
|
25358 | this.fileToClasses = new Map();
|
25359 | this.reexportMap = new Map();
|
25360 | this.handlersByName = new Map();
|
25361 | for (const handler of handlers) {
|
25362 | this.handlersByName.set(handler.name, handler);
|
25363 | }
|
25364 | }
|
25365 | analyzeSync(sf) {
|
25366 | this.analyze(sf, false);
|
25367 | }
|
25368 | analyzeAsync(sf) {
|
25369 | return this.analyze(sf, true);
|
25370 | }
|
25371 | analyze(sf, preanalyze) {
|
25372 | // We shouldn't analyze declaration files.
|
25373 | if (sf.isDeclarationFile) {
|
25374 | return undefined;
|
25375 | }
|
25376 | // analyze() really wants to return `Promise<void>|void`, but TypeScript cannot narrow a return
|
25377 | // type of 'void', so `undefined` is used instead.
|
25378 | const promises = [];
|
25379 | const priorWork = this.incrementalBuild.priorWorkFor(sf);
|
25380 | if (priorWork !== null) {
|
25381 | for (const priorRecord of priorWork) {
|
25382 | this.adopt(priorRecord);
|
25383 | }
|
25384 | // Skip the rest of analysis, as this file's prior traits are being reused.
|
25385 | return;
|
25386 | }
|
25387 | const visit = (node) => {
|
25388 | if (this.reflector.isClass(node)) {
|
25389 | this.analyzeClass(node, preanalyze ? promises : null);
|
25390 | }
|
25391 | ts$1.forEachChild(node, visit);
|
25392 | };
|
25393 | visit(sf);
|
25394 | if (preanalyze && promises.length > 0) {
|
25395 | return Promise.all(promises).then(() => undefined);
|
25396 | }
|
25397 | else {
|
25398 | return undefined;
|
25399 | }
|
25400 | }
|
25401 | recordFor(clazz) {
|
25402 | if (this.classes.has(clazz)) {
|
25403 | return this.classes.get(clazz);
|
25404 | }
|
25405 | else {
|
25406 | return null;
|
25407 | }
|
25408 | }
|
25409 | recordsFor(sf) {
|
25410 | if (!this.fileToClasses.has(sf)) {
|
25411 | return null;
|
25412 | }
|
25413 | const records = [];
|
25414 | for (const clazz of this.fileToClasses.get(sf)) {
|
25415 | records.push(this.classes.get(clazz));
|
25416 | }
|
25417 | return records;
|
25418 | }
|
25419 | /**
|
25420 | * Import a `ClassRecord` from a previous compilation.
|
25421 | *
|
25422 | * Traits from the `ClassRecord` have accurate metadata, but the `handler` is from the old program
|
25423 | * and needs to be updated (matching is done by name). A new pending trait is created and then
|
25424 | * transitioned to analyzed using the previous analysis. If the trait is in the errored state,
|
25425 | * instead the errors are copied over.
|
25426 | */
|
25427 | adopt(priorRecord) {
|
25428 | const record = {
|
25429 | hasPrimaryHandler: priorRecord.hasPrimaryHandler,
|
25430 | hasWeakHandlers: priorRecord.hasWeakHandlers,
|
25431 | metaDiagnostics: priorRecord.metaDiagnostics,
|
25432 | node: priorRecord.node,
|
25433 | traits: [],
|
25434 | };
|
25435 | for (const priorTrait of priorRecord.traits) {
|
25436 | const handler = this.handlersByName.get(priorTrait.handler.name);
|
25437 | let trait = Trait.pending(handler, priorTrait.detected);
|
25438 | if (priorTrait.state === TraitState.Analyzed || priorTrait.state === TraitState.Resolved) {
|
25439 | trait = trait.toAnalyzed(priorTrait.analysis, priorTrait.analysisDiagnostics);
|
25440 | if (trait.analysis !== null && trait.handler.register !== undefined) {
|
25441 | trait.handler.register(record.node, trait.analysis);
|
25442 | }
|
25443 | }
|
25444 | else if (priorTrait.state === TraitState.Skipped) {
|
25445 | trait = trait.toSkipped();
|
25446 | }
|
25447 | record.traits.push(trait);
|
25448 | }
|
25449 | this.classes.set(record.node, record);
|
25450 | const sf = record.node.getSourceFile();
|
25451 | if (!this.fileToClasses.has(sf)) {
|
25452 | this.fileToClasses.set(sf, new Set());
|
25453 | }
|
25454 | this.fileToClasses.get(sf).add(record.node);
|
25455 | }
|
25456 | scanClassForTraits(clazz) {
|
25457 | if (!this.compileNonExportedClasses && !isExported(clazz)) {
|
25458 | return null;
|
25459 | }
|
25460 | const decorators = this.reflector.getDecoratorsOfDeclaration(clazz);
|
25461 | return this.detectTraits(clazz, decorators);
|
25462 | }
|
25463 | detectTraits(clazz, decorators) {
|
25464 | let record = this.recordFor(clazz);
|
25465 | let foundTraits = [];
|
25466 | for (const handler of this.handlers) {
|
25467 | const result = handler.detect(clazz, decorators);
|
25468 | if (result === undefined) {
|
25469 | continue;
|
25470 | }
|
25471 | const isPrimaryHandler = handler.precedence === HandlerPrecedence.PRIMARY;
|
25472 | const isWeakHandler = handler.precedence === HandlerPrecedence.WEAK;
|
25473 | const trait = Trait.pending(handler, result);
|
25474 | foundTraits.push(trait);
|
25475 | if (record === null) {
|
25476 | // This is the first handler to match this class. This path is a fast path through which
|
25477 | // most classes will flow.
|
25478 | record = {
|
25479 | node: clazz,
|
25480 | traits: [trait],
|
25481 | metaDiagnostics: null,
|
25482 | hasPrimaryHandler: isPrimaryHandler,
|
25483 | hasWeakHandlers: isWeakHandler,
|
25484 | };
|
25485 | this.classes.set(clazz, record);
|
25486 | const sf = clazz.getSourceFile();
|
25487 | if (!this.fileToClasses.has(sf)) {
|
25488 | this.fileToClasses.set(sf, new Set());
|
25489 | }
|
25490 | this.fileToClasses.get(sf).add(clazz);
|
25491 | }
|
25492 | else {
|
25493 | // This is at least the second handler to match this class. This is a slower path that some
|
25494 | // classes will go through, which validates that the set of decorators applied to the class
|
25495 | // is valid.
|
25496 | // Validate according to rules as follows:
|
25497 | //
|
25498 | // * WEAK handlers are removed if a non-WEAK handler matches.
|
25499 | // * Only one PRIMARY handler can match at a time. Any other PRIMARY handler matching a
|
25500 | // class with an existing PRIMARY handler is an error.
|
25501 | if (!isWeakHandler && record.hasWeakHandlers) {
|
25502 | // The current handler is not a WEAK handler, but the class has other WEAK handlers.
|
25503 | // Remove them.
|
25504 | record.traits =
|
25505 | record.traits.filter(field => field.handler.precedence !== HandlerPrecedence.WEAK);
|
25506 | record.hasWeakHandlers = false;
|
25507 | }
|
25508 | else if (isWeakHandler && !record.hasWeakHandlers) {
|
25509 | // The current handler is a WEAK handler, but the class has non-WEAK handlers already.
|
25510 | // Drop the current one.
|
25511 | continue;
|
25512 | }
|
25513 | if (isPrimaryHandler && record.hasPrimaryHandler) {
|
25514 | // The class already has a PRIMARY handler, and another one just matched.
|
25515 | record.metaDiagnostics = [{
|
25516 | category: ts$1.DiagnosticCategory.Error,
|
25517 | code: Number('-99' + ErrorCode.DECORATOR_COLLISION),
|
25518 | file: getSourceFile(clazz),
|
25519 | start: clazz.getStart(undefined, false),
|
25520 | length: clazz.getWidth(),
|
25521 | messageText: 'Two incompatible decorators on class',
|
25522 | }];
|
25523 | record.traits = foundTraits = [];
|
25524 | break;
|
25525 | }
|
25526 | // Otherwise, it's safe to accept the multiple decorators here. Update some of the metadata
|
25527 | // regarding this class.
|
25528 | record.traits.push(trait);
|
25529 | record.hasPrimaryHandler = record.hasPrimaryHandler || isPrimaryHandler;
|
25530 | }
|
25531 | }
|
25532 | return foundTraits.length > 0 ? foundTraits : null;
|
25533 | }
|
25534 | analyzeClass(clazz, preanalyzeQueue) {
|
25535 | const traits = this.scanClassForTraits(clazz);
|
25536 | if (traits === null) {
|
25537 | // There are no Ivy traits on the class, so it can safely be skipped.
|
25538 | return;
|
25539 | }
|
25540 | for (const trait of traits) {
|
25541 | const analyze = () => this.analyzeTrait(clazz, trait);
|
25542 | let preanalysis = null;
|
25543 | if (preanalyzeQueue !== null && trait.handler.preanalyze !== undefined) {
|
25544 | // Attempt to run preanalysis. This could fail with a `FatalDiagnosticError`; catch it if it
|
25545 | // does.
|
25546 | try {
|
25547 | preanalysis = trait.handler.preanalyze(clazz, trait.detected.metadata) || null;
|
25548 | }
|
25549 | catch (err) {
|
25550 | if (err instanceof FatalDiagnosticError) {
|
25551 | trait.toAnalyzed(null, [err.toDiagnostic()]);
|
25552 | return;
|
25553 | }
|
25554 | else {
|
25555 | throw err;
|
25556 | }
|
25557 | }
|
25558 | }
|
25559 | if (preanalysis !== null) {
|
25560 | preanalyzeQueue.push(preanalysis.then(analyze));
|
25561 | }
|
25562 | else {
|
25563 | analyze();
|
25564 | }
|
25565 | }
|
25566 | }
|
25567 | analyzeTrait(clazz, trait, flags) {
|
25568 | var _a, _b;
|
25569 | if (trait.state !== TraitState.Pending) {
|
25570 | throw new Error(`Attempt to analyze trait of ${clazz.name.text} in state ${TraitState[trait.state]} (expected DETECTED)`);
|
25571 | }
|
25572 | // Attempt analysis. This could fail with a `FatalDiagnosticError`; catch it if it does.
|
25573 | let result;
|
25574 | try {
|
25575 | result = trait.handler.analyze(clazz, trait.detected.metadata, flags);
|
25576 | }
|
25577 | catch (err) {
|
25578 | if (err instanceof FatalDiagnosticError) {
|
25579 | trait.toAnalyzed(null, [err.toDiagnostic()]);
|
25580 | return;
|
25581 | }
|
25582 | else {
|
25583 | throw err;
|
25584 | }
|
25585 | }
|
25586 | if (result.analysis !== undefined && trait.handler.register !== undefined) {
|
25587 | trait.handler.register(clazz, result.analysis);
|
25588 | }
|
25589 | trait = trait.toAnalyzed((_a = result.analysis) !== null && _a !== void 0 ? _a : null, (_b = result.diagnostics) !== null && _b !== void 0 ? _b : null);
|
25590 | }
|
25591 | resolve() {
|
25592 | var _a, _b;
|
25593 | const classes = Array.from(this.classes.keys());
|
25594 | for (const clazz of classes) {
|
25595 | const record = this.classes.get(clazz);
|
25596 | for (let trait of record.traits) {
|
25597 | const handler = trait.handler;
|
25598 | switch (trait.state) {
|
25599 | case TraitState.Skipped:
|
25600 | continue;
|
25601 | case TraitState.Pending:
|
25602 | throw new Error(`Resolving a trait that hasn't been analyzed: ${clazz.name.text} / ${Object.getPrototypeOf(trait.handler).constructor.name}`);
|
25603 | case TraitState.Resolved:
|
25604 | throw new Error(`Resolving an already resolved trait`);
|
25605 | }
|
25606 | if (trait.analysis === null) {
|
25607 | // No analysis results, cannot further process this trait.
|
25608 | continue;
|
25609 | }
|
25610 | if (handler.resolve === undefined) {
|
25611 | // No resolution of this trait needed - it's considered successful by default.
|
25612 | trait = trait.toResolved(null, null);
|
25613 | continue;
|
25614 | }
|
25615 | let result;
|
25616 | try {
|
25617 | result = handler.resolve(clazz, trait.analysis);
|
25618 | }
|
25619 | catch (err) {
|
25620 | if (err instanceof FatalDiagnosticError) {
|
25621 | trait = trait.toResolved(null, [err.toDiagnostic()]);
|
25622 | continue;
|
25623 | }
|
25624 | else {
|
25625 | throw err;
|
25626 | }
|
25627 | }
|
25628 | trait = trait.toResolved((_a = result.data) !== null && _a !== void 0 ? _a : null, (_b = result.diagnostics) !== null && _b !== void 0 ? _b : null);
|
25629 | if (result.reexports !== undefined) {
|
25630 | const fileName = clazz.getSourceFile().fileName;
|
25631 | if (!this.reexportMap.has(fileName)) {
|
25632 | this.reexportMap.set(fileName, new Map());
|
25633 | }
|
25634 | const fileReexports = this.reexportMap.get(fileName);
|
25635 | for (const reexport of result.reexports) {
|
25636 | fileReexports.set(reexport.asAlias, [reexport.fromModule, reexport.symbolName]);
|
25637 | }
|
25638 | }
|
25639 | }
|
25640 | }
|
25641 | }
|
25642 | /**
|
25643 | * Generate type-checking code into the `TypeCheckContext` for any components within the given
|
25644 | * `ts.SourceFile`.
|
25645 | */
|
25646 | typeCheck(sf, ctx) {
|
25647 | if (!this.fileToClasses.has(sf)) {
|
25648 | return;
|
25649 | }
|
25650 | for (const clazz of this.fileToClasses.get(sf)) {
|
25651 | const record = this.classes.get(clazz);
|
25652 | for (const trait of record.traits) {
|
25653 | if (trait.state !== TraitState.Resolved) {
|
25654 | continue;
|
25655 | }
|
25656 | else if (trait.handler.typeCheck === undefined) {
|
25657 | continue;
|
25658 | }
|
25659 | if (trait.resolution !== null) {
|
25660 | trait.handler.typeCheck(ctx, clazz, trait.analysis, trait.resolution);
|
25661 | }
|
25662 | }
|
25663 | }
|
25664 | }
|
25665 | index(ctx) {
|
25666 | for (const clazz of this.classes.keys()) {
|
25667 | const record = this.classes.get(clazz);
|
25668 | for (const trait of record.traits) {
|
25669 | if (trait.state !== TraitState.Resolved) {
|
25670 | // Skip traits that haven't been resolved successfully.
|
25671 | continue;
|
25672 | }
|
25673 | else if (trait.handler.index === undefined) {
|
25674 | // Skip traits that don't affect indexing.
|
25675 | continue;
|
25676 | }
|
25677 | if (trait.resolution !== null) {
|
25678 | trait.handler.index(ctx, clazz, trait.analysis, trait.resolution);
|
25679 | }
|
25680 | }
|
25681 | }
|
25682 | }
|
25683 | updateResources(clazz) {
|
25684 | if (!this.reflector.isClass(clazz) || !this.classes.has(clazz)) {
|
25685 | return;
|
25686 | }
|
25687 | const record = this.classes.get(clazz);
|
25688 | for (const trait of record.traits) {
|
25689 | if (trait.state !== TraitState.Resolved || trait.handler.updateResources === undefined) {
|
25690 | continue;
|
25691 | }
|
25692 | trait.handler.updateResources(clazz, trait.analysis, trait.resolution);
|
25693 | }
|
25694 | }
|
25695 | compile(clazz, constantPool) {
|
25696 | const original = ts$1.getOriginalNode(clazz);
|
25697 | if (!this.reflector.isClass(clazz) || !this.reflector.isClass(original) ||
|
25698 | !this.classes.has(original)) {
|
25699 | return null;
|
25700 | }
|
25701 | const record = this.classes.get(original);
|
25702 | let res = [];
|
25703 | for (const trait of record.traits) {
|
25704 | if (trait.state !== TraitState.Resolved || trait.analysisDiagnostics !== null ||
|
25705 | trait.resolveDiagnostics !== null) {
|
25706 | // Cannot compile a trait that is not resolved, or had any errors in its declaration.
|
25707 | continue;
|
25708 | }
|
25709 | const compileSpan = this.perf.start('compileClass', original);
|
25710 | // `trait.resolution` is non-null asserted here because TypeScript does not recognize that
|
25711 | // `Readonly<unknown>` is nullable (as `unknown` itself is nullable) due to the way that
|
25712 | // `Readonly` works.
|
25713 | let compileRes;
|
25714 | if (this.compilationMode === CompilationMode.PARTIAL &&
|
25715 | trait.handler.compilePartial !== undefined) {
|
25716 | compileRes = trait.handler.compilePartial(clazz, trait.analysis, trait.resolution);
|
25717 | }
|
25718 | else {
|
25719 | compileRes =
|
25720 | trait.handler.compileFull(clazz, trait.analysis, trait.resolution, constantPool);
|
25721 | }
|
25722 | const compileMatchRes = compileRes;
|
25723 | this.perf.stop(compileSpan);
|
25724 | if (Array.isArray(compileMatchRes)) {
|
25725 | for (const result of compileMatchRes) {
|
25726 | if (!res.some(r => r.name === result.name)) {
|
25727 | res.push(result);
|
25728 | }
|
25729 | }
|
25730 | }
|
25731 | else if (!res.some(result => result.name === compileMatchRes.name)) {
|
25732 | res.push(compileMatchRes);
|
25733 | }
|
25734 | }
|
25735 | // Look up the .d.ts transformer for the input file and record that at least one field was
|
25736 | // generated, which will allow the .d.ts to be transformed later.
|
25737 | this.dtsTransforms.getIvyDeclarationTransform(original.getSourceFile())
|
25738 | .addFields(original, res);
|
25739 | // Return the instruction to the transformer so the fields will be added.
|
25740 | return res.length > 0 ? res : null;
|
25741 | }
|
25742 | decoratorsFor(node) {
|
25743 | const original = ts$1.getOriginalNode(node);
|
25744 | if (!this.reflector.isClass(original) || !this.classes.has(original)) {
|
25745 | return [];
|
25746 | }
|
25747 | const record = this.classes.get(original);
|
25748 | const decorators = [];
|
25749 | for (const trait of record.traits) {
|
25750 | if (trait.state !== TraitState.Resolved) {
|
25751 | continue;
|
25752 | }
|
25753 | if (trait.detected.trigger !== null && ts$1.isDecorator(trait.detected.trigger)) {
|
25754 | decorators.push(trait.detected.trigger);
|
25755 | }
|
25756 | }
|
25757 | return decorators;
|
25758 | }
|
25759 | get diagnostics() {
|
25760 | const diagnostics = [];
|
25761 | for (const clazz of this.classes.keys()) {
|
25762 | const record = this.classes.get(clazz);
|
25763 | if (record.metaDiagnostics !== null) {
|
25764 | diagnostics.push(...record.metaDiagnostics);
|
25765 | }
|
25766 | for (const trait of record.traits) {
|
25767 | if ((trait.state === TraitState.Analyzed || trait.state === TraitState.Resolved) &&
|
25768 | trait.analysisDiagnostics !== null) {
|
25769 | diagnostics.push(...trait.analysisDiagnostics);
|
25770 | }
|
25771 | if (trait.state === TraitState.Resolved && trait.resolveDiagnostics !== null) {
|
25772 | diagnostics.push(...trait.resolveDiagnostics);
|
25773 | }
|
25774 | }
|
25775 | }
|
25776 | return diagnostics;
|
25777 | }
|
25778 | get exportStatements() {
|
25779 | return this.reexportMap;
|
25780 | }
|
25781 | }
|
25782 |
|
25783 | /**
|
25784 | * @license
|
25785 | * Copyright Google LLC All Rights Reserved.
|
25786 | *
|
25787 | * Use of this source code is governed by an MIT-style license that can be
|
25788 | * found in the LICENSE file at https://angular.io/license
|
25789 | */
|
25790 | /**
|
25791 | * The current context of a translator visitor as it traverses the AST tree.
|
25792 | *
|
25793 | * It tracks whether we are in the process of outputting a statement or an expression.
|
25794 | */
|
25795 | class Context {
|
25796 | constructor(isStatement) {
|
25797 | this.isStatement = isStatement;
|
25798 | }
|
25799 | get withExpressionMode() {
|
25800 | return this.isStatement ? new Context(false) : this;
|
25801 | }
|
25802 | get withStatementMode() {
|
25803 | return !this.isStatement ? new Context(true) : this;
|
25804 | }
|
25805 | }
|
25806 |
|
25807 | /**
|
25808 | * @license
|
25809 | * Copyright Google LLC All Rights Reserved.
|
25810 | *
|
25811 | * Use of this source code is governed by an MIT-style license that can be
|
25812 | * found in the LICENSE file at https://angular.io/license
|
25813 | */
|
25814 | class ImportManager {
|
25815 | constructor(rewriter = new NoopImportRewriter(), prefix = 'i') {
|
25816 | this.rewriter = rewriter;
|
25817 | this.prefix = prefix;
|
25818 | this.specifierToIdentifier = new Map();
|
25819 | this.nextIndex = 0;
|
25820 | }
|
25821 | generateNamespaceImport(moduleName) {
|
25822 | if (!this.specifierToIdentifier.has(moduleName)) {
|
25823 | this.specifierToIdentifier.set(moduleName, ts$1.createIdentifier(`${this.prefix}${this.nextIndex++}`));
|
25824 | }
|
25825 | return this.specifierToIdentifier.get(moduleName);
|
25826 | }
|
25827 | generateNamedImport(moduleName, originalSymbol) {
|
25828 | // First, rewrite the symbol name.
|
25829 | const symbol = this.rewriter.rewriteSymbol(originalSymbol, moduleName);
|
25830 | // Ask the rewriter if this symbol should be imported at all. If not, it can be referenced
|
25831 | // directly (moduleImport: null).
|
25832 | if (!this.rewriter.shouldImportSymbol(symbol, moduleName)) {
|
25833 | // The symbol should be referenced directly.
|
25834 | return { moduleImport: null, symbol };
|
25835 | }
|
25836 | // If not, this symbol will be imported using a generated namespace import.
|
25837 | const moduleImport = this.generateNamespaceImport(moduleName);
|
25838 | return { moduleImport, symbol };
|
25839 | }
|
25840 | getAllImports(contextPath) {
|
25841 | const imports = [];
|
25842 | for (const [originalSpecifier, qualifier] of this.specifierToIdentifier) {
|
25843 | const specifier = this.rewriter.rewriteSpecifier(originalSpecifier, contextPath);
|
25844 | imports.push({
|
25845 | specifier,
|
25846 | qualifier,
|
25847 | });
|
25848 | }
|
25849 | return imports;
|
25850 | }
|
25851 | }
|
25852 |
|
25853 | /**
|
25854 | * @license
|
25855 | * Copyright Google LLC All Rights Reserved.
|
25856 | *
|
25857 | * Use of this source code is governed by an MIT-style license that can be
|
25858 | * found in the LICENSE file at https://angular.io/license
|
25859 | */
|
25860 | const UNARY_OPERATORS$1 = new Map([
|
25861 | [UnaryOperator.Minus, '-'],
|
25862 | [UnaryOperator.Plus, '+'],
|
25863 | ]);
|
25864 | const BINARY_OPERATORS$1 = new Map([
|
25865 | [BinaryOperator.And, '&&'],
|
25866 | [BinaryOperator.Bigger, '>'],
|
25867 | [BinaryOperator.BiggerEquals, '>='],
|
25868 | [BinaryOperator.BitwiseAnd, '&'],
|
25869 | [BinaryOperator.Divide, '/'],
|
25870 | [BinaryOperator.Equals, '=='],
|
25871 | [BinaryOperator.Identical, '==='],
|
25872 | [BinaryOperator.Lower, '<'],
|
25873 | [BinaryOperator.LowerEquals, '<='],
|
25874 | [BinaryOperator.Minus, '-'],
|
25875 | [BinaryOperator.Modulo, '%'],
|
25876 | [BinaryOperator.Multiply, '*'],
|
25877 | [BinaryOperator.NotEquals, '!='],
|
25878 | [BinaryOperator.NotIdentical, '!=='],
|
25879 | [BinaryOperator.Or, '||'],
|
25880 | [BinaryOperator.Plus, '+'],
|
25881 | ]);
|
25882 | class ExpressionTranslatorVisitor {
|
25883 | constructor(factory, imports, options) {
|
25884 | this.factory = factory;
|
25885 | this.imports = imports;
|
25886 | this.downlevelTaggedTemplates = options.downlevelTaggedTemplates === true;
|
25887 | this.downlevelVariableDeclarations = options.downlevelVariableDeclarations === true;
|
25888 | this.recordWrappedNodeExpr = options.recordWrappedNodeExpr || (() => { });
|
25889 | }
|
25890 | visitDeclareVarStmt(stmt, context) {
|
25891 | var _a;
|
25892 | const varType = this.downlevelVariableDeclarations ?
|
25893 | 'var' :
|
25894 | stmt.hasModifier(StmtModifier.Final) ? 'const' : 'let';
|
25895 | return this.attachComments(this.factory.createVariableDeclaration(stmt.name, (_a = stmt.value) === null || _a === void 0 ? void 0 : _a.visitExpression(this, context.withExpressionMode), varType), stmt.leadingComments);
|
25896 | }
|
25897 | visitDeclareFunctionStmt(stmt, context) {
|
25898 | return this.attachComments(this.factory.createFunctionDeclaration(stmt.name, stmt.params.map(param => param.name), this.factory.createBlock(this.visitStatements(stmt.statements, context.withStatementMode))), stmt.leadingComments);
|
25899 | }
|
25900 | visitExpressionStmt(stmt, context) {
|
25901 | return this.attachComments(this.factory.createExpressionStatement(stmt.expr.visitExpression(this, context.withStatementMode)), stmt.leadingComments);
|
25902 | }
|
25903 | visitReturnStmt(stmt, context) {
|
25904 | return this.attachComments(this.factory.createReturnStatement(stmt.value.visitExpression(this, context.withExpressionMode)), stmt.leadingComments);
|
25905 | }
|
25906 | visitDeclareClassStmt(_stmt, _context) {
|
25907 | throw new Error('Method not implemented.');
|
25908 | }
|
25909 | visitIfStmt(stmt, context) {
|
25910 | return this.attachComments(this.factory.createIfStatement(stmt.condition.visitExpression(this, context), this.factory.createBlock(this.visitStatements(stmt.trueCase, context.withStatementMode)), stmt.falseCase.length > 0 ? this.factory.createBlock(this.visitStatements(stmt.falseCase, context.withStatementMode)) :
|
25911 | null), stmt.leadingComments);
|
25912 | }
|
25913 | visitTryCatchStmt(_stmt, _context) {
|
25914 | throw new Error('Method not implemented.');
|
25915 | }
|
25916 | visitThrowStmt(stmt, context) {
|
25917 | return this.attachComments(this.factory.createThrowStatement(stmt.error.visitExpression(this, context.withExpressionMode)), stmt.leadingComments);
|
25918 | }
|
25919 | visitReadVarExpr(ast, _context) {
|
25920 | const identifier = this.factory.createIdentifier(ast.name);
|
25921 | this.setSourceMapRange(identifier, ast.sourceSpan);
|
25922 | return identifier;
|
25923 | }
|
25924 | visitWriteVarExpr(expr, context) {
|
25925 | const assignment = this.factory.createAssignment(this.setSourceMapRange(this.factory.createIdentifier(expr.name), expr.sourceSpan), expr.value.visitExpression(this, context));
|
25926 | return context.isStatement ? assignment :
|
25927 | this.factory.createParenthesizedExpression(assignment);
|
25928 | }
|
25929 | visitWriteKeyExpr(expr, context) {
|
25930 | const exprContext = context.withExpressionMode;
|
25931 | const target = this.factory.createElementAccess(expr.receiver.visitExpression(this, exprContext), expr.index.visitExpression(this, exprContext));
|
25932 | const assignment = this.factory.createAssignment(target, expr.value.visitExpression(this, exprContext));
|
25933 | return context.isStatement ? assignment :
|
25934 | this.factory.createParenthesizedExpression(assignment);
|
25935 | }
|
25936 | visitWritePropExpr(expr, context) {
|
25937 | const target = this.factory.createPropertyAccess(expr.receiver.visitExpression(this, context), expr.name);
|
25938 | return this.factory.createAssignment(target, expr.value.visitExpression(this, context));
|
25939 | }
|
25940 | visitInvokeMethodExpr(ast, context) {
|
25941 | const target = ast.receiver.visitExpression(this, context);
|
25942 | return this.setSourceMapRange(this.factory.createCallExpression(ast.name !== null ? this.factory.createPropertyAccess(target, ast.name) : target, ast.args.map(arg => arg.visitExpression(this, context)),
|
25943 | /* pure */ false), ast.sourceSpan);
|
25944 | }
|
25945 | visitInvokeFunctionExpr(ast, context) {
|
25946 | return this.setSourceMapRange(this.factory.createCallExpression(ast.fn.visitExpression(this, context), ast.args.map(arg => arg.visitExpression(this, context)), ast.pure), ast.sourceSpan);
|
25947 | }
|
25948 | visitTaggedTemplateExpr(ast, context) {
|
25949 | return this.setSourceMapRange(this.createTaggedTemplateExpression(ast.tag.visitExpression(this, context), {
|
25950 | elements: ast.template.elements.map(e => {
|
25951 | var _a;
|
25952 | return createTemplateElement({
|
25953 | cooked: e.text,
|
25954 | raw: e.rawText,
|
25955 | range: (_a = e.sourceSpan) !== null && _a !== void 0 ? _a : ast.sourceSpan,
|
25956 | });
|
25957 | }),
|
25958 | expressions: ast.template.expressions.map(e => e.visitExpression(this, context))
|
25959 | }), ast.sourceSpan);
|
25960 | }
|
25961 | visitInstantiateExpr(ast, context) {
|
25962 | return this.factory.createNewExpression(ast.classExpr.visitExpression(this, context), ast.args.map(arg => arg.visitExpression(this, context)));
|
25963 | }
|
25964 | visitLiteralExpr(ast, _context) {
|
25965 | return this.setSourceMapRange(this.factory.createLiteral(ast.value), ast.sourceSpan);
|
25966 | }
|
25967 | visitLocalizedString(ast, context) {
|
25968 | // A `$localize` message consists of `messageParts` and `expressions`, which get interleaved
|
25969 | // together. The interleaved pieces look like:
|
25970 | // `[messagePart0, expression0, messagePart1, expression1, messagePart2]`
|
25971 | //
|
25972 | // Note that there is always a message part at the start and end, and so therefore
|
25973 | // `messageParts.length === expressions.length + 1`.
|
25974 | //
|
25975 | // Each message part may be prefixed with "metadata", which is wrapped in colons (:) delimiters.
|
25976 | // The metadata is attached to the first and subsequent message parts by calls to
|
25977 | // `serializeI18nHead()` and `serializeI18nTemplatePart()` respectively.
|
25978 | //
|
25979 | // The first message part (i.e. `ast.messageParts[0]`) is used to initialize `messageParts`
|
25980 | // array.
|
25981 | const elements = [createTemplateElement(ast.serializeI18nHead())];
|
25982 | const expressions = [];
|
25983 | for (let i = 0; i < ast.expressions.length; i++) {
|
25984 | const placeholder = this.setSourceMapRange(ast.expressions[i].visitExpression(this, context), ast.getPlaceholderSourceSpan(i));
|
25985 | expressions.push(placeholder);
|
25986 | elements.push(createTemplateElement(ast.serializeI18nTemplatePart(i + 1)));
|
25987 | }
|
25988 | const localizeTag = this.factory.createIdentifier('$localize');
|
25989 | return this.setSourceMapRange(this.createTaggedTemplateExpression(localizeTag, { elements, expressions }), ast.sourceSpan);
|
25990 | }
|
25991 | createTaggedTemplateExpression(tag, template) {
|
25992 | return this.downlevelTaggedTemplates ? this.createES5TaggedTemplateFunctionCall(tag, template) :
|
25993 | this.factory.createTaggedTemplate(tag, template);
|
25994 | }
|
25995 | /**
|
25996 | * Translate the tagged template literal into a call that is compatible with ES5, using the
|
25997 | * imported `__makeTemplateObject` helper for ES5 formatted output.
|
25998 | */
|
25999 | createES5TaggedTemplateFunctionCall(tagHandler, { elements, expressions }) {
|
26000 | // Ensure that the `__makeTemplateObject()` helper has been imported.
|
26001 | const { moduleImport, symbol } = this.imports.generateNamedImport('tslib', '__makeTemplateObject');
|
26002 | const __makeTemplateObjectHelper = (moduleImport === null) ?
|
26003 | this.factory.createIdentifier(symbol) :
|
26004 | this.factory.createPropertyAccess(moduleImport, symbol);
|
26005 | // Collect up the cooked and raw strings into two separate arrays.
|
26006 | const cooked = [];
|
26007 | const raw = [];
|
26008 | for (const element of elements) {
|
26009 | cooked.push(this.factory.setSourceMapRange(this.factory.createLiteral(element.cooked), element.range));
|
26010 | raw.push(this.factory.setSourceMapRange(this.factory.createLiteral(element.raw), element.range));
|
26011 | }
|
26012 | // Generate the helper call in the form: `__makeTemplateObject([cooked], [raw]);`
|
26013 | const templateHelperCall = this.factory.createCallExpression(__makeTemplateObjectHelper, [this.factory.createArrayLiteral(cooked), this.factory.createArrayLiteral(raw)],
|
26014 | /* pure */ false);
|
26015 | // Finally create the tagged handler call in the form:
|
26016 | // `tag(__makeTemplateObject([cooked], [raw]), ...expressions);`
|
26017 | return this.factory.createCallExpression(tagHandler, [templateHelperCall, ...expressions],
|
26018 | /* pure */ false);
|
26019 | }
|
26020 | visitExternalExpr(ast, _context) {
|
26021 | if (ast.value.name === null) {
|
26022 | if (ast.value.moduleName === null) {
|
26023 | throw new Error('Invalid import without name nor moduleName');
|
26024 | }
|
26025 | return this.imports.generateNamespaceImport(ast.value.moduleName);
|
26026 | }
|
26027 | // If a moduleName is specified, this is a normal import. If there's no module name, it's a
|
26028 | // reference to a global/ambient symbol.
|
26029 | if (ast.value.moduleName !== null) {
|
26030 | // This is a normal import. Find the imported module.
|
26031 | const { moduleImport, symbol } = this.imports.generateNamedImport(ast.value.moduleName, ast.value.name);
|
26032 | if (moduleImport === null) {
|
26033 | // The symbol was ambient after all.
|
26034 | return this.factory.createIdentifier(symbol);
|
26035 | }
|
26036 | else {
|
26037 | return this.factory.createPropertyAccess(moduleImport, symbol);
|
26038 | }
|
26039 | }
|
26040 | else {
|
26041 | // The symbol is ambient, so just reference it.
|
26042 | return this.factory.createIdentifier(ast.value.name);
|
26043 | }
|
26044 | }
|
26045 | visitConditionalExpr(ast, context) {
|
26046 | let cond = ast.condition.visitExpression(this, context);
|
26047 | // Ordinarily the ternary operator is right-associative. The following are equivalent:
|
26048 | // `a ? b : c ? d : e` => `a ? b : (c ? d : e)`
|
26049 | //
|
26050 | // However, occasionally Angular needs to produce a left-associative conditional, such as in
|
26051 | // the case of a null-safe navigation production: `{{a?.b ? c : d}}`. This template produces
|
26052 | // a ternary of the form:
|
26053 | // `a == null ? null : rest of expression`
|
26054 | // If the rest of the expression is also a ternary though, this would produce the form:
|
26055 | // `a == null ? null : a.b ? c : d`
|
26056 | // which, if left as right-associative, would be incorrectly associated as:
|
26057 | // `a == null ? null : (a.b ? c : d)`
|
26058 | //
|
26059 | // In such cases, the left-associativity needs to be enforced with parentheses:
|
26060 | // `(a == null ? null : a.b) ? c : d`
|
26061 | //
|
26062 | // Such parentheses could always be included in the condition (guaranteeing correct behavior) in
|
26063 | // all cases, but this has a code size cost. Instead, parentheses are added only when a
|
26064 | // conditional expression is directly used as the condition of another.
|
26065 | //
|
26066 | // TODO(alxhub): investigate better logic for precendence of conditional operators
|
26067 | if (ast.condition instanceof ConditionalExpr) {
|
26068 | // The condition of this ternary needs to be wrapped in parentheses to maintain
|
26069 | // left-associativity.
|
26070 | cond = this.factory.createParenthesizedExpression(cond);
|
26071 | }
|
26072 | return this.factory.createConditional(cond, ast.trueCase.visitExpression(this, context), ast.falseCase.visitExpression(this, context));
|
26073 | }
|
26074 | visitNotExpr(ast, context) {
|
26075 | return this.factory.createUnaryExpression('!', ast.condition.visitExpression(this, context));
|
26076 | }
|
26077 | visitAssertNotNullExpr(ast, context) {
|
26078 | return ast.condition.visitExpression(this, context);
|
26079 | }
|
26080 | visitCastExpr(ast, context) {
|
26081 | return ast.value.visitExpression(this, context);
|
26082 | }
|
26083 | visitFunctionExpr(ast, context) {
|
26084 | var _a;
|
26085 | return this.factory.createFunctionExpression((_a = ast.name) !== null && _a !== void 0 ? _a : null, ast.params.map(param => param.name), this.factory.createBlock(this.visitStatements(ast.statements, context)));
|
26086 | }
|
26087 | visitBinaryOperatorExpr(ast, context) {
|
26088 | if (!BINARY_OPERATORS$1.has(ast.operator)) {
|
26089 | throw new Error(`Unknown binary operator: ${BinaryOperator[ast.operator]}`);
|
26090 | }
|
26091 | return this.factory.createBinaryExpression(ast.lhs.visitExpression(this, context), BINARY_OPERATORS$1.get(ast.operator), ast.rhs.visitExpression(this, context));
|
26092 | }
|
26093 | visitReadPropExpr(ast, context) {
|
26094 | return this.factory.createPropertyAccess(ast.receiver.visitExpression(this, context), ast.name);
|
26095 | }
|
26096 | visitReadKeyExpr(ast, context) {
|
26097 | return this.factory.createElementAccess(ast.receiver.visitExpression(this, context), ast.index.visitExpression(this, context));
|
26098 | }
|
26099 | visitLiteralArrayExpr(ast, context) {
|
26100 | return this.factory.createArrayLiteral(ast.entries.map(expr => this.setSourceMapRange(expr.visitExpression(this, context), ast.sourceSpan)));
|
26101 | }
|
26102 | visitLiteralMapExpr(ast, context) {
|
26103 | const properties = ast.entries.map(entry => {
|
26104 | return {
|
26105 | propertyName: entry.key,
|
26106 | quoted: entry.quoted,
|
26107 | value: entry.value.visitExpression(this, context)
|
26108 | };
|
26109 | });
|
26110 | return this.setSourceMapRange(this.factory.createObjectLiteral(properties), ast.sourceSpan);
|
26111 | }
|
26112 | visitCommaExpr(ast, context) {
|
26113 | throw new Error('Method not implemented.');
|
26114 | }
|
26115 | visitWrappedNodeExpr(ast, _context) {
|
26116 | this.recordWrappedNodeExpr(ast.node);
|
26117 | return ast.node;
|
26118 | }
|
26119 | visitTypeofExpr(ast, context) {
|
26120 | return this.factory.createTypeOfExpression(ast.expr.visitExpression(this, context));
|
26121 | }
|
26122 | visitUnaryOperatorExpr(ast, context) {
|
26123 | if (!UNARY_OPERATORS$1.has(ast.operator)) {
|
26124 | throw new Error(`Unknown unary operator: ${UnaryOperator[ast.operator]}`);
|
26125 | }
|
26126 | return this.factory.createUnaryExpression(UNARY_OPERATORS$1.get(ast.operator), ast.expr.visitExpression(this, context));
|
26127 | }
|
26128 | visitStatements(statements, context) {
|
26129 | return statements.map(stmt => stmt.visitStatement(this, context))
|
26130 | .filter(stmt => stmt !== undefined);
|
26131 | }
|
26132 | setSourceMapRange(ast, span) {
|
26133 | return this.factory.setSourceMapRange(ast, createRange(span));
|
26134 | }
|
26135 | attachComments(statement, leadingComments) {
|
26136 | if (leadingComments !== undefined) {
|
26137 | this.factory.attachComments(statement, leadingComments);
|
26138 | }
|
26139 | return statement;
|
26140 | }
|
26141 | }
|
26142 | /**
|
26143 | * Convert a cooked-raw string object into one that can be used by the AST factories.
|
26144 | */
|
26145 | function createTemplateElement({ cooked, raw, range }) {
|
26146 | return { cooked, raw, range: createRange(range) };
|
26147 | }
|
26148 | /**
|
26149 | * Convert an OutputAST source-span into a range that can be used by the AST factories.
|
26150 | */
|
26151 | function createRange(span) {
|
26152 | if (span === null) {
|
26153 | return null;
|
26154 | }
|
26155 | const { start, end } = span;
|
26156 | const { url, content } = start.file;
|
26157 | if (!url) {
|
26158 | return null;
|
26159 | }
|
26160 | return {
|
26161 | url,
|
26162 | content,
|
26163 | start: { offset: start.offset, line: start.line, column: start.col },
|
26164 | end: { offset: end.offset, line: end.line, column: end.col },
|
26165 | };
|
26166 | }
|
26167 |
|
26168 | /**
|
26169 | * @license
|
26170 | * Copyright Google LLC All Rights Reserved.
|
26171 | *
|
26172 | * Use of this source code is governed by an MIT-style license that can be
|
26173 | * found in the LICENSE file at https://angular.io/license
|
26174 | */
|
26175 | function translateType(type, imports) {
|
26176 | return type.visitType(new TypeTranslatorVisitor(imports), new Context(false));
|
26177 | }
|
26178 | class TypeTranslatorVisitor {
|
26179 | constructor(imports) {
|
26180 | this.imports = imports;
|
26181 | }
|
26182 | visitBuiltinType(type, context) {
|
26183 | switch (type.name) {
|
26184 | case BuiltinTypeName.Bool:
|
26185 | return ts$1.createKeywordTypeNode(ts$1.SyntaxKind.BooleanKeyword);
|
26186 | case BuiltinTypeName.Dynamic:
|
26187 | return ts$1.createKeywordTypeNode(ts$1.SyntaxKind.AnyKeyword);
|
26188 | case BuiltinTypeName.Int:
|
26189 | case BuiltinTypeName.Number:
|
26190 | return ts$1.createKeywordTypeNode(ts$1.SyntaxKind.NumberKeyword);
|
26191 | case BuiltinTypeName.String:
|
26192 | return ts$1.createKeywordTypeNode(ts$1.SyntaxKind.StringKeyword);
|
26193 | case BuiltinTypeName.None:
|
26194 | return ts$1.createKeywordTypeNode(ts$1.SyntaxKind.NeverKeyword);
|
26195 | default:
|
26196 | throw new Error(`Unsupported builtin type: ${BuiltinTypeName[type.name]}`);
|
26197 | }
|
26198 | }
|
26199 | visitExpressionType(type, context) {
|
26200 | const typeNode = this.translateExpression(type.value, context);
|
26201 | if (type.typeParams === null) {
|
26202 | return typeNode;
|
26203 | }
|
26204 | if (!ts$1.isTypeReferenceNode(typeNode)) {
|
26205 | throw new Error('An ExpressionType with type arguments must translate into a TypeReferenceNode');
|
26206 | }
|
26207 | else if (typeNode.typeArguments !== undefined) {
|
26208 | throw new Error(`An ExpressionType with type arguments cannot have multiple levels of type arguments`);
|
26209 | }
|
26210 | const typeArgs = type.typeParams.map(param => this.translateType(param, context));
|
26211 | return ts$1.createTypeReferenceNode(typeNode.typeName, typeArgs);
|
26212 | }
|
26213 | visitArrayType(type, context) {
|
26214 | return ts$1.createArrayTypeNode(this.translateType(type.of, context));
|
26215 | }
|
26216 | visitMapType(type, context) {
|
26217 | const parameter = ts$1.createParameter(undefined, undefined, undefined, 'key', undefined, ts$1.createKeywordTypeNode(ts$1.SyntaxKind.StringKeyword));
|
26218 | const typeArgs = type.valueType !== null ?
|
26219 | this.translateType(type.valueType, context) :
|
26220 | ts$1.createKeywordTypeNode(ts$1.SyntaxKind.UnknownKeyword);
|
26221 | const indexSignature = ts$1.createIndexSignature(undefined, undefined, [parameter], typeArgs);
|
26222 | return ts$1.createTypeLiteralNode([indexSignature]);
|
26223 | }
|
26224 | visitReadVarExpr(ast, context) {
|
26225 | if (ast.name === null) {
|
26226 | throw new Error(`ReadVarExpr with no variable name in type`);
|
26227 | }
|
26228 | return ts$1.createTypeQueryNode(ts$1.createIdentifier(ast.name));
|
26229 | }
|
26230 | visitWriteVarExpr(expr, context) {
|
26231 | throw new Error('Method not implemented.');
|
26232 | }
|
26233 | visitWriteKeyExpr(expr, context) {
|
26234 | throw new Error('Method not implemented.');
|
26235 | }
|
26236 | visitWritePropExpr(expr, context) {
|
26237 | throw new Error('Method not implemented.');
|
26238 | }
|
26239 | visitInvokeMethodExpr(ast, context) {
|
26240 | throw new Error('Method not implemented.');
|
26241 | }
|
26242 | visitInvokeFunctionExpr(ast, context) {
|
26243 | throw new Error('Method not implemented.');
|
26244 | }
|
26245 | visitTaggedTemplateExpr(ast, context) {
|
26246 | throw new Error('Method not implemented.');
|
26247 | }
|
26248 | visitInstantiateExpr(ast, context) {
|
26249 | throw new Error('Method not implemented.');
|
26250 | }
|
26251 | visitLiteralExpr(ast, context) {
|
26252 | if (ast.value === null) {
|
26253 | return ts$1.createLiteralTypeNode(ts$1.createNull());
|
26254 | }
|
26255 | else if (ast.value === undefined) {
|
26256 | return ts$1.createKeywordTypeNode(ts$1.SyntaxKind.UndefinedKeyword);
|
26257 | }
|
26258 | else if (typeof ast.value === 'boolean') {
|
26259 | return ts$1.createLiteralTypeNode(ts$1.createLiteral(ast.value));
|
26260 | }
|
26261 | else if (typeof ast.value === 'number') {
|
26262 | return ts$1.createLiteralTypeNode(ts$1.createLiteral(ast.value));
|
26263 | }
|
26264 | else {
|
26265 | return ts$1.createLiteralTypeNode(ts$1.createLiteral(ast.value));
|
26266 | }
|
26267 | }
|
26268 | visitLocalizedString(ast, context) {
|
26269 | throw new Error('Method not implemented.');
|
26270 | }
|
26271 | visitExternalExpr(ast, context) {
|
26272 | if (ast.value.moduleName === null || ast.value.name === null) {
|
26273 | throw new Error(`Import unknown module or symbol`);
|
26274 | }
|
26275 | const { moduleImport, symbol } = this.imports.generateNamedImport(ast.value.moduleName, ast.value.name);
|
26276 | const symbolIdentifier = ts$1.createIdentifier(symbol);
|
26277 | const typeName = moduleImport ? ts$1.createQualifiedName(moduleImport, symbolIdentifier) : symbolIdentifier;
|
26278 | const typeArguments = ast.typeParams !== null ?
|
26279 | ast.typeParams.map(type => this.translateType(type, context)) :
|
26280 | undefined;
|
26281 | return ts$1.createTypeReferenceNode(typeName, typeArguments);
|
26282 | }
|
26283 | visitConditionalExpr(ast, context) {
|
26284 | throw new Error('Method not implemented.');
|
26285 | }
|
26286 | visitNotExpr(ast, context) {
|
26287 | throw new Error('Method not implemented.');
|
26288 | }
|
26289 | visitAssertNotNullExpr(ast, context) {
|
26290 | throw new Error('Method not implemented.');
|
26291 | }
|
26292 | visitCastExpr(ast, context) {
|
26293 | throw new Error('Method not implemented.');
|
26294 | }
|
26295 | visitFunctionExpr(ast, context) {
|
26296 | throw new Error('Method not implemented.');
|
26297 | }
|
26298 | visitUnaryOperatorExpr(ast, context) {
|
26299 | throw new Error('Method not implemented.');
|
26300 | }
|
26301 | visitBinaryOperatorExpr(ast, context) {
|
26302 | throw new Error('Method not implemented.');
|
26303 | }
|
26304 | visitReadPropExpr(ast, context) {
|
26305 | throw new Error('Method not implemented.');
|
26306 | }
|
26307 | visitReadKeyExpr(ast, context) {
|
26308 | throw new Error('Method not implemented.');
|
26309 | }
|
26310 | visitLiteralArrayExpr(ast, context) {
|
26311 | const values = ast.entries.map(expr => this.translateExpression(expr, context));
|
26312 | return ts$1.createTupleTypeNode(values);
|
26313 | }
|
26314 | visitLiteralMapExpr(ast, context) {
|
26315 | const entries = ast.entries.map(entry => {
|
26316 | const { key, quoted } = entry;
|
26317 | const type = this.translateExpression(entry.value, context);
|
26318 | return ts$1.createPropertySignature(
|
26319 | /* modifiers */ undefined,
|
26320 | /* name */ quoted ? ts$1.createStringLiteral(key) : key,
|
26321 | /* questionToken */ undefined,
|
26322 | /* type */ type,
|
26323 | /* initializer */ undefined);
|
26324 | });
|
26325 | return ts$1.createTypeLiteralNode(entries);
|
26326 | }
|
26327 | visitCommaExpr(ast, context) {
|
26328 | throw new Error('Method not implemented.');
|
26329 | }
|
26330 | visitWrappedNodeExpr(ast, context) {
|
26331 | const node = ast.node;
|
26332 | if (ts$1.isEntityName(node)) {
|
26333 | return ts$1.createTypeReferenceNode(node, /* typeArguments */ undefined);
|
26334 | }
|
26335 | else if (ts$1.isTypeNode(node)) {
|
26336 | return node;
|
26337 | }
|
26338 | else if (ts$1.isLiteralExpression(node)) {
|
26339 | return ts$1.createLiteralTypeNode(node);
|
26340 | }
|
26341 | else {
|
26342 | throw new Error(`Unsupported WrappedNodeExpr in TypeTranslatorVisitor: ${ts$1.SyntaxKind[node.kind]}`);
|
26343 | }
|
26344 | }
|
26345 | visitTypeofExpr(ast, context) {
|
26346 | const typeNode = this.translateExpression(ast.expr, context);
|
26347 | if (!ts$1.isTypeReferenceNode(typeNode)) {
|
26348 | throw new Error(`The target of a typeof expression must be a type reference, but it was
|
26349 | ${ts$1.SyntaxKind[typeNode.kind]}`);
|
26350 | }
|
26351 | return ts$1.createTypeQueryNode(typeNode.typeName);
|
26352 | }
|
26353 | translateType(type, context) {
|
26354 | const typeNode = type.visitType(this, context);
|
26355 | if (!ts$1.isTypeNode(typeNode)) {
|
26356 | throw new Error(`A Type must translate to a TypeNode, but was ${ts$1.SyntaxKind[typeNode.kind]}`);
|
26357 | }
|
26358 | return typeNode;
|
26359 | }
|
26360 | translateExpression(expr, context) {
|
26361 | const typeNode = expr.visitExpression(this, context);
|
26362 | if (!ts$1.isTypeNode(typeNode)) {
|
26363 | throw new Error(`An Expression must translate to a TypeNode, but was ${ts$1.SyntaxKind[typeNode.kind]}`);
|
26364 | }
|
26365 | return typeNode;
|
26366 | }
|
26367 | }
|
26368 |
|
26369 | /**
|
26370 | * @license
|
26371 | * Copyright Google LLC All Rights Reserved.
|
26372 | *
|
26373 | * Use of this source code is governed by an MIT-style license that can be
|
26374 | * found in the LICENSE file at https://angular.io/license
|
26375 | */
|
26376 | const UNARY_OPERATORS$2 = {
|
26377 | '+': ts$1.SyntaxKind.PlusToken,
|
26378 | '-': ts$1.SyntaxKind.MinusToken,
|
26379 | '!': ts$1.SyntaxKind.ExclamationToken,
|
26380 | };
|
26381 | const BINARY_OPERATORS$2 = {
|
26382 | '&&': ts$1.SyntaxKind.AmpersandAmpersandToken,
|
26383 | '>': ts$1.SyntaxKind.GreaterThanToken,
|
26384 | '>=': ts$1.SyntaxKind.GreaterThanEqualsToken,
|
26385 | '&': ts$1.SyntaxKind.AmpersandToken,
|
26386 | '/': ts$1.SyntaxKind.SlashToken,
|
26387 | '==': ts$1.SyntaxKind.EqualsEqualsToken,
|
26388 | '===': ts$1.SyntaxKind.EqualsEqualsEqualsToken,
|
26389 | '<': ts$1.SyntaxKind.LessThanToken,
|
26390 | '<=': ts$1.SyntaxKind.LessThanEqualsToken,
|
26391 | '-': ts$1.SyntaxKind.MinusToken,
|
26392 | '%': ts$1.SyntaxKind.PercentToken,
|
26393 | '*': ts$1.SyntaxKind.AsteriskToken,
|
26394 | '!=': ts$1.SyntaxKind.ExclamationEqualsToken,
|
26395 | '!==': ts$1.SyntaxKind.ExclamationEqualsEqualsToken,
|
26396 | '||': ts$1.SyntaxKind.BarBarToken,
|
26397 | '+': ts$1.SyntaxKind.PlusToken,
|
26398 | };
|
26399 | const VAR_TYPES = {
|
26400 | 'const': ts$1.NodeFlags.Const,
|
26401 | 'let': ts$1.NodeFlags.Let,
|
26402 | 'var': ts$1.NodeFlags.None,
|
26403 | };
|
26404 | /**
|
26405 | * A TypeScript flavoured implementation of the AstFactory.
|
26406 | */
|
26407 | class TypeScriptAstFactory {
|
26408 | constructor() {
|
26409 | this.externalSourceFiles = new Map();
|
26410 | this.attachComments = attachComments;
|
26411 | this.createArrayLiteral = ts$1.createArrayLiteral;
|
26412 | this.createConditional = ts$1.createConditional;
|
26413 | this.createElementAccess = ts$1.createElementAccess;
|
26414 | this.createExpressionStatement = ts$1.createExpressionStatement;
|
26415 | this.createIdentifier = ts$1.createIdentifier;
|
26416 | this.createParenthesizedExpression = ts$1.createParen;
|
26417 | this.createPropertyAccess = ts$1.createPropertyAccess;
|
26418 | this.createThrowStatement = ts$1.createThrow;
|
26419 | this.createTypeOfExpression = ts$1.createTypeOf;
|
26420 | }
|
26421 | createAssignment(target, value) {
|
26422 | return ts$1.createBinary(target, ts$1.SyntaxKind.EqualsToken, value);
|
26423 | }
|
26424 | createBinaryExpression(leftOperand, operator, rightOperand) {
|
26425 | return ts$1.createBinary(leftOperand, BINARY_OPERATORS$2[operator], rightOperand);
|
26426 | }
|
26427 | createBlock(body) {
|
26428 | return ts$1.createBlock(body);
|
26429 | }
|
26430 | createCallExpression(callee, args, pure) {
|
26431 | const call = ts$1.createCall(callee, undefined, args);
|
26432 | if (pure) {
|
26433 | ts$1.addSyntheticLeadingComment(call, ts$1.SyntaxKind.MultiLineCommentTrivia, '@__PURE__', /* trailing newline */ false);
|
26434 | }
|
26435 | return call;
|
26436 | }
|
26437 | createFunctionDeclaration(functionName, parameters, body) {
|
26438 | if (!ts$1.isBlock(body)) {
|
26439 | throw new Error(`Invalid syntax, expected a block, but got ${ts$1.SyntaxKind[body.kind]}.`);
|
26440 | }
|
26441 | return ts$1.createFunctionDeclaration(undefined, undefined, undefined, functionName, undefined, parameters.map(param => ts$1.createParameter(undefined, undefined, undefined, param)), undefined, body);
|
26442 | }
|
26443 | createFunctionExpression(functionName, parameters, body) {
|
26444 | if (!ts$1.isBlock(body)) {
|
26445 | throw new Error(`Invalid syntax, expected a block, but got ${ts$1.SyntaxKind[body.kind]}.`);
|
26446 | }
|
26447 | return ts$1.createFunctionExpression(undefined, undefined, functionName !== null && functionName !== void 0 ? functionName : undefined, undefined, parameters.map(param => ts$1.createParameter(undefined, undefined, undefined, param)), undefined, body);
|
26448 | }
|
26449 | createIfStatement(condition, thenStatement, elseStatement) {
|
26450 | return ts$1.createIf(condition, thenStatement, elseStatement !== null && elseStatement !== void 0 ? elseStatement : undefined);
|
26451 | }
|
26452 | createLiteral(value) {
|
26453 | if (value === undefined) {
|
26454 | return ts$1.createIdentifier('undefined');
|
26455 | }
|
26456 | else if (value === null) {
|
26457 | return ts$1.createNull();
|
26458 | }
|
26459 | else {
|
26460 | return ts$1.createLiteral(value);
|
26461 | }
|
26462 | }
|
26463 | createNewExpression(expression, args) {
|
26464 | return ts$1.createNew(expression, undefined, args);
|
26465 | }
|
26466 | createObjectLiteral(properties) {
|
26467 | return ts$1.createObjectLiteral(properties.map(prop => ts$1.createPropertyAssignment(prop.quoted ? ts$1.createLiteral(prop.propertyName) :
|
26468 | ts$1.createIdentifier(prop.propertyName), prop.value)));
|
26469 | }
|
26470 | createReturnStatement(expression) {
|
26471 | return ts$1.createReturn(expression !== null && expression !== void 0 ? expression : undefined);
|
26472 | }
|
26473 | createTaggedTemplate(tag, template) {
|
26474 | let templateLiteral;
|
26475 | const length = template.elements.length;
|
26476 | const head = template.elements[0];
|
26477 | if (length === 1) {
|
26478 | templateLiteral = ts$1.createNoSubstitutionTemplateLiteral(head.cooked, head.raw);
|
26479 | }
|
26480 | else {
|
26481 | const spans = [];
|
26482 | // Create the middle parts
|
26483 | for (let i = 1; i < length - 1; i++) {
|
26484 | const { cooked, raw, range } = template.elements[i];
|
26485 | const middle = createTemplateMiddle(cooked, raw);
|
26486 | if (range !== null) {
|
26487 | this.setSourceMapRange(middle, range);
|
26488 | }
|
26489 | spans.push(ts$1.createTemplateSpan(template.expressions[i - 1], middle));
|
26490 | }
|
26491 | // Create the tail part
|
26492 | const resolvedExpression = template.expressions[length - 2];
|
26493 | const templatePart = template.elements[length - 1];
|
26494 | const templateTail = createTemplateTail(templatePart.cooked, templatePart.raw);
|
26495 | if (templatePart.range !== null) {
|
26496 | this.setSourceMapRange(templateTail, templatePart.range);
|
26497 | }
|
26498 | spans.push(ts$1.createTemplateSpan(resolvedExpression, templateTail));
|
26499 | // Put it all together
|
26500 | templateLiteral =
|
26501 | ts$1.createTemplateExpression(ts$1.createTemplateHead(head.cooked, head.raw), spans);
|
26502 | }
|
26503 | if (head.range !== null) {
|
26504 | this.setSourceMapRange(templateLiteral, head.range);
|
26505 | }
|
26506 | return ts$1.createTaggedTemplate(tag, templateLiteral);
|
26507 | }
|
26508 | createUnaryExpression(operator, operand) {
|
26509 | return ts$1.createPrefix(UNARY_OPERATORS$2[operator], operand);
|
26510 | }
|
26511 | createVariableDeclaration(variableName, initializer, type) {
|
26512 | return ts$1.createVariableStatement(undefined, ts$1.createVariableDeclarationList([ts$1.createVariableDeclaration(variableName, undefined, initializer !== null && initializer !== void 0 ? initializer : undefined)], VAR_TYPES[type]));
|
26513 | }
|
26514 | setSourceMapRange(node, sourceMapRange) {
|
26515 | if (sourceMapRange === null) {
|
26516 | return node;
|
26517 | }
|
26518 | const url = sourceMapRange.url;
|
26519 | if (!this.externalSourceFiles.has(url)) {
|
26520 | this.externalSourceFiles.set(url, ts$1.createSourceMapSource(url, sourceMapRange.content, pos => pos));
|
26521 | }
|
26522 | const source = this.externalSourceFiles.get(url);
|
26523 | ts$1.setSourceMapRange(node, { pos: sourceMapRange.start.offset, end: sourceMapRange.end.offset, source });
|
26524 | return node;
|
26525 | }
|
26526 | }
|
26527 | // HACK: Use this in place of `ts.createTemplateMiddle()`.
|
26528 | // Revert once https://github.com/microsoft/TypeScript/issues/35374 is fixed.
|
26529 | function createTemplateMiddle(cooked, raw) {
|
26530 | const node = ts$1.createTemplateHead(cooked, raw);
|
26531 | node.kind = ts$1.SyntaxKind.TemplateMiddle;
|
26532 | return node;
|
26533 | }
|
26534 | // HACK: Use this in place of `ts.createTemplateTail()`.
|
26535 | // Revert once https://github.com/microsoft/TypeScript/issues/35374 is fixed.
|
26536 | function createTemplateTail(cooked, raw) {
|
26537 | const node = ts$1.createTemplateHead(cooked, raw);
|
26538 | node.kind = ts$1.SyntaxKind.TemplateTail;
|
26539 | return node;
|
26540 | }
|
26541 | /**
|
26542 | * Attach the given `leadingComments` to the `statement` node.
|
26543 | *
|
26544 | * @param statement The statement that will have comments attached.
|
26545 | * @param leadingComments The comments to attach to the statement.
|
26546 | */
|
26547 | function attachComments(statement, leadingComments) {
|
26548 | for (const comment of leadingComments) {
|
26549 | const commentKind = comment.multiline ? ts$1.SyntaxKind.MultiLineCommentTrivia :
|
26550 | ts$1.SyntaxKind.SingleLineCommentTrivia;
|
26551 | if (comment.multiline) {
|
26552 | ts$1.addSyntheticLeadingComment(statement, commentKind, comment.toString(), comment.trailingNewline);
|
26553 | }
|
26554 | else {
|
26555 | for (const line of comment.toString().split('\n')) {
|
26556 | ts$1.addSyntheticLeadingComment(statement, commentKind, line, comment.trailingNewline);
|
26557 | }
|
26558 | }
|
26559 | }
|
26560 | }
|
26561 |
|
26562 | /**
|
26563 | * @license
|
26564 | * Copyright Google LLC All Rights Reserved.
|
26565 | *
|
26566 | * Use of this source code is governed by an MIT-style license that can be
|
26567 | * found in the LICENSE file at https://angular.io/license
|
26568 | */
|
26569 | function translateExpression(expression, imports, options = {}) {
|
26570 | return expression.visitExpression(new ExpressionTranslatorVisitor(new TypeScriptAstFactory(), imports, options), new Context(false));
|
26571 | }
|
26572 | function translateStatement(statement, imports, options = {}) {
|
26573 | return statement.visitStatement(new ExpressionTranslatorVisitor(new TypeScriptAstFactory(), imports, options), new Context(true));
|
26574 | }
|
26575 |
|
26576 | /**
|
26577 | * @license
|
26578 | * Copyright Google LLC All Rights Reserved.
|
26579 | *
|
26580 | * Use of this source code is governed by an MIT-style license that can be
|
26581 | * found in the LICENSE file at https://angular.io/license
|
26582 | */
|
26583 | /**
|
26584 | * Adds extra imports in the import manage for this source file, after the existing imports
|
26585 | * and before the module body.
|
26586 | * Can optionally add extra statements (e.g. new constants) before the body as well.
|
26587 | */
|
26588 | function addImports(importManager, sf, extraStatements = []) {
|
26589 | // Generate the import statements to prepend.
|
26590 | const addedImports = importManager.getAllImports(sf.fileName).map(i => {
|
26591 | const qualifier = ts$1.createIdentifier(i.qualifier.text);
|
26592 | const importClause = ts$1.createImportClause(
|
26593 | /* name */ undefined,
|
26594 | /* namedBindings */ ts$1.createNamespaceImport(qualifier));
|
26595 | const decl = ts$1.createImportDeclaration(
|
26596 | /* decorators */ undefined,
|
26597 | /* modifiers */ undefined,
|
26598 | /* importClause */ importClause,
|
26599 | /* moduleSpecifier */ ts$1.createLiteral(i.specifier));
|
26600 | // Set the qualifier's original TS node to the `ts.ImportDeclaration`. This allows downstream
|
26601 | // transforms such as tsickle to properly process references to this import.
|
26602 | //
|
26603 | // This operation is load-bearing in g3 as some imported modules contain special metadata
|
26604 | // generated by clutz, which tsickle uses to transform imports and references to those imports.
|
26605 | //
|
26606 | // TODO(alxhub): add a test for this when tsickle is updated externally to depend on this
|
26607 | // behavior.
|
26608 | ts$1.setOriginalNode(i.qualifier, decl);
|
26609 | return decl;
|
26610 | });
|
26611 | // Filter out the existing imports and the source file body. All new statements
|
26612 | // will be inserted between them.
|
26613 | const existingImports = sf.statements.filter(stmt => isImportStatement(stmt));
|
26614 | const body = sf.statements.filter(stmt => !isImportStatement(stmt));
|
26615 | // Prepend imports if needed.
|
26616 | if (addedImports.length > 0) {
|
26617 | // If we prepend imports, we also prepend NotEmittedStatement to use it as an anchor
|
26618 | // for @fileoverview Closure annotation. If there is no @fileoverview annotations, this
|
26619 | // statement would be a noop.
|
26620 | const fileoverviewAnchorStmt = ts$1.createNotEmittedStatement(sf);
|
26621 | return ts$1.updateSourceFileNode(sf, ts$1.createNodeArray([
|
26622 | fileoverviewAnchorStmt, ...existingImports, ...addedImports, ...extraStatements, ...body
|
26623 | ]));
|
26624 | }
|
26625 | return sf;
|
26626 | }
|
26627 | function isImportStatement(stmt) {
|
26628 | return ts$1.isImportDeclaration(stmt) || ts$1.isImportEqualsDeclaration(stmt) ||
|
26629 | ts$1.isNamespaceImport(stmt);
|
26630 | }
|
26631 |
|
26632 | /**
|
26633 | * @license
|
26634 | * Copyright Google LLC All Rights Reserved.
|
26635 | *
|
26636 | * Use of this source code is governed by an MIT-style license that can be
|
26637 | * found in the LICENSE file at https://angular.io/license
|
26638 | */
|
26639 | /**
|
26640 | * Keeps track of `DtsTransform`s per source file, so that it is known which source files need to
|
26641 | * have their declaration file transformed.
|
26642 | */
|
26643 | class DtsTransformRegistry {
|
26644 | constructor() {
|
26645 | this.ivyDeclarationTransforms = new Map();
|
26646 | this.returnTypeTransforms = new Map();
|
26647 | }
|
26648 | getIvyDeclarationTransform(sf) {
|
26649 | if (!this.ivyDeclarationTransforms.has(sf)) {
|
26650 | this.ivyDeclarationTransforms.set(sf, new IvyDeclarationDtsTransform());
|
26651 | }
|
26652 | return this.ivyDeclarationTransforms.get(sf);
|
26653 | }
|
26654 | getReturnTypeTransform(sf) {
|
26655 | if (!this.returnTypeTransforms.has(sf)) {
|
26656 | this.returnTypeTransforms.set(sf, new ReturnTypeTransform());
|
26657 | }
|
26658 | return this.returnTypeTransforms.get(sf);
|
26659 | }
|
26660 | /**
|
26661 | * Gets the dts transforms to be applied for the given source file, or `null` if no transform is
|
26662 | * necessary.
|
26663 | */
|
26664 | getAllTransforms(sf) {
|
26665 | // No need to transform if it's not a declarations file, or if no changes have been requested
|
26666 | // to the input file. Due to the way TypeScript afterDeclarations transformers work, the
|
26667 | // `ts.SourceFile` path is the same as the original .ts. The only way we know it's actually a
|
26668 | // declaration file is via the `isDeclarationFile` property.
|
26669 | if (!sf.isDeclarationFile) {
|
26670 | return null;
|
26671 | }
|
26672 | const originalSf = ts$1.getOriginalNode(sf);
|
26673 | let transforms = null;
|
26674 | if (this.ivyDeclarationTransforms.has(originalSf)) {
|
26675 | transforms = [];
|
26676 | transforms.push(this.ivyDeclarationTransforms.get(originalSf));
|
26677 | }
|
26678 | if (this.returnTypeTransforms.has(originalSf)) {
|
26679 | transforms = transforms || [];
|
26680 | transforms.push(this.returnTypeTransforms.get(originalSf));
|
26681 | }
|
26682 | return transforms;
|
26683 | }
|
26684 | }
|
26685 | function declarationTransformFactory(transformRegistry, importRewriter, importPrefix) {
|
26686 | return (context) => {
|
26687 | const transformer = new DtsTransformer(context, importRewriter, importPrefix);
|
26688 | return (fileOrBundle) => {
|
26689 | if (ts$1.isBundle(fileOrBundle)) {
|
26690 | // Only attempt to transform source files.
|
26691 | return fileOrBundle;
|
26692 | }
|
26693 | const transforms = transformRegistry.getAllTransforms(fileOrBundle);
|
26694 | if (transforms === null) {
|
26695 | return fileOrBundle;
|
26696 | }
|
26697 | return transformer.transform(fileOrBundle, transforms);
|
26698 | };
|
26699 | };
|
26700 | }
|
26701 | /**
|
26702 | * Processes .d.ts file text and adds static field declarations, with types.
|
26703 | */
|
26704 | class DtsTransformer {
|
26705 | constructor(ctx, importRewriter, importPrefix) {
|
26706 | this.ctx = ctx;
|
26707 | this.importRewriter = importRewriter;
|
26708 | this.importPrefix = importPrefix;
|
26709 | }
|
26710 | /**
|
26711 | * Transform the declaration file and add any declarations which were recorded.
|
26712 | */
|
26713 | transform(sf, transforms) {
|
26714 | const imports = new ImportManager(this.importRewriter, this.importPrefix);
|
26715 | const visitor = (node) => {
|
26716 | if (ts$1.isClassDeclaration(node)) {
|
26717 | return this.transformClassDeclaration(node, transforms, imports);
|
26718 | }
|
26719 | else if (ts$1.isFunctionDeclaration(node)) {
|
26720 | return this.transformFunctionDeclaration(node, transforms, imports);
|
26721 | }
|
26722 | else {
|
26723 | // Otherwise return node as is.
|
26724 | return ts$1.visitEachChild(node, visitor, this.ctx);
|
26725 | }
|
26726 | };
|
26727 | // Recursively scan through the AST and process all nodes as desired.
|
26728 | sf = ts$1.visitNode(sf, visitor);
|
26729 | // Add new imports for this file.
|
26730 | return addImports(imports, sf);
|
26731 | }
|
26732 | transformClassDeclaration(clazz, transforms, imports) {
|
26733 | let elements = clazz.members;
|
26734 | let elementsChanged = false;
|
26735 | for (const transform of transforms) {
|
26736 | if (transform.transformClassElement !== undefined) {
|
26737 | for (let i = 0; i < elements.length; i++) {
|
26738 | const res = transform.transformClassElement(elements[i], imports);
|
26739 | if (res !== elements[i]) {
|
26740 | if (!elementsChanged) {
|
26741 | elements = [...elements];
|
26742 | elementsChanged = true;
|
26743 | }
|
26744 | elements[i] = res;
|
26745 | }
|
26746 | }
|
26747 | }
|
26748 | }
|
26749 | let newClazz = clazz;
|
26750 | for (const transform of transforms) {
|
26751 | if (transform.transformClass !== undefined) {
|
26752 | // If no DtsTransform has changed the class yet, then the (possibly mutated) elements have
|
26753 | // not yet been incorporated. Otherwise, `newClazz.members` holds the latest class members.
|
26754 | const inputMembers = (clazz === newClazz ? elements : newClazz.members);
|
26755 | newClazz = transform.transformClass(newClazz, inputMembers, imports);
|
26756 | }
|
26757 | }
|
26758 | // If some elements have been transformed but the class itself has not been transformed, create
|
26759 | // an updated class declaration with the updated elements.
|
26760 | if (elementsChanged && clazz === newClazz) {
|
26761 | newClazz = ts$1.updateClassDeclaration(
|
26762 | /* node */ clazz,
|
26763 | /* decorators */ clazz.decorators,
|
26764 | /* modifiers */ clazz.modifiers,
|
26765 | /* name */ clazz.name,
|
26766 | /* typeParameters */ clazz.typeParameters,
|
26767 | /* heritageClauses */ clazz.heritageClauses,
|
26768 | /* members */ elements);
|
26769 | }
|
26770 | return newClazz;
|
26771 | }
|
26772 | transformFunctionDeclaration(declaration, transforms, imports) {
|
26773 | let newDecl = declaration;
|
26774 | for (const transform of transforms) {
|
26775 | if (transform.transformFunctionDeclaration !== undefined) {
|
26776 | newDecl = transform.transformFunctionDeclaration(newDecl, imports);
|
26777 | }
|
26778 | }
|
26779 | return newDecl;
|
26780 | }
|
26781 | }
|
26782 | class IvyDeclarationDtsTransform {
|
26783 | constructor() {
|
26784 | this.declarationFields = new Map();
|
26785 | }
|
26786 | addFields(decl, fields) {
|
26787 | this.declarationFields.set(decl, fields);
|
26788 | }
|
26789 | transformClass(clazz, members, imports) {
|
26790 | const original = ts$1.getOriginalNode(clazz);
|
26791 | if (!this.declarationFields.has(original)) {
|
26792 | return clazz;
|
26793 | }
|
26794 | const fields = this.declarationFields.get(original);
|
26795 | const newMembers = fields.map(decl => {
|
26796 | const modifiers = [ts$1.createModifier(ts$1.SyntaxKind.StaticKeyword)];
|
26797 | const typeRef = translateType(decl.type, imports);
|
26798 | markForEmitAsSingleLine(typeRef);
|
26799 | return ts$1.createProperty(
|
26800 | /* decorators */ undefined,
|
26801 | /* modifiers */ modifiers,
|
26802 | /* name */ decl.name,
|
26803 | /* questionOrExclamationToken */ undefined,
|
26804 | /* type */ typeRef,
|
26805 | /* initializer */ undefined);
|
26806 | });
|
26807 | return ts$1.updateClassDeclaration(
|
26808 | /* node */ clazz,
|
26809 | /* decorators */ clazz.decorators,
|
26810 | /* modifiers */ clazz.modifiers,
|
26811 | /* name */ clazz.name,
|
26812 | /* typeParameters */ clazz.typeParameters,
|
26813 | /* heritageClauses */ clazz.heritageClauses,
|
26814 | /* members */ [...members, ...newMembers]);
|
26815 | }
|
26816 | }
|
26817 | function markForEmitAsSingleLine(node) {
|
26818 | ts$1.setEmitFlags(node, ts$1.EmitFlags.SingleLine);
|
26819 | ts$1.forEachChild(node, markForEmitAsSingleLine);
|
26820 | }
|
26821 | class ReturnTypeTransform {
|
26822 | constructor() {
|
26823 | this.typeReplacements = new Map();
|
26824 | }
|
26825 | addTypeReplacement(declaration, type) {
|
26826 | this.typeReplacements.set(declaration, type);
|
26827 | }
|
26828 | transformClassElement(element, imports) {
|
26829 | if (ts$1.isMethodDeclaration(element)) {
|
26830 | const original = ts$1.getOriginalNode(element, ts$1.isMethodDeclaration);
|
26831 | if (!this.typeReplacements.has(original)) {
|
26832 | return element;
|
26833 | }
|
26834 | const returnType = this.typeReplacements.get(original);
|
26835 | const tsReturnType = translateType(returnType, imports);
|
26836 | return ts$1.updateMethod(element, element.decorators, element.modifiers, element.asteriskToken, element.name, element.questionToken, element.typeParameters, element.parameters, tsReturnType, element.body);
|
26837 | }
|
26838 | return element;
|
26839 | }
|
26840 | transformFunctionDeclaration(element, imports) {
|
26841 | const original = ts$1.getOriginalNode(element);
|
26842 | if (!this.typeReplacements.has(original)) {
|
26843 | return element;
|
26844 | }
|
26845 | const returnType = this.typeReplacements.get(original);
|
26846 | const tsReturnType = translateType(returnType, imports);
|
26847 | return ts$1.updateFunctionDeclaration(
|
26848 | /* node */ element,
|
26849 | /* decorators */ element.decorators,
|
26850 | /* modifiers */ element.modifiers,
|
26851 | /* asteriskToken */ element.asteriskToken,
|
26852 | /* name */ element.name,
|
26853 | /* typeParameters */ element.typeParameters,
|
26854 | /* parameters */ element.parameters,
|
26855 | /* type */ tsReturnType,
|
26856 | /* body */ element.body);
|
26857 | }
|
26858 | }
|
26859 |
|
26860 | /**
|
26861 | * @license
|
26862 | * Copyright Google LLC All Rights Reserved.
|
26863 | *
|
26864 | * Use of this source code is governed by an MIT-style license that can be
|
26865 | * found in the LICENSE file at https://angular.io/license
|
26866 | */
|
26867 | /**
|
26868 | * Visit a node with the given visitor and return a transformed copy.
|
26869 | */
|
26870 | function visit(node, visitor, context) {
|
26871 | return visitor._visit(node, context);
|
26872 | }
|
26873 | /**
|
26874 | * Abstract base class for visitors, which processes certain nodes specially to allow insertion
|
26875 | * of other nodes before them.
|
26876 | */
|
26877 | class Visitor {
|
26878 | constructor() {
|
26879 | /**
|
26880 | * Maps statements to an array of statements that should be inserted before them.
|
26881 | */
|
26882 | this._before = new Map();
|
26883 | /**
|
26884 | * Maps statements to an array of statements that should be inserted after them.
|
26885 | */
|
26886 | this._after = new Map();
|
26887 | }
|
26888 | /**
|
26889 | * Visit a class declaration, returning at least the transformed declaration and optionally other
|
26890 | * nodes to insert before the declaration.
|
26891 | */
|
26892 | visitClassDeclaration(node) {
|
26893 | return { node };
|
26894 | }
|
26895 | _visitListEntryNode(node, visitor) {
|
26896 | const result = visitor(node);
|
26897 | if (result.before !== undefined) {
|
26898 | // Record that some nodes should be inserted before the given declaration. The declaration's
|
26899 | // parent's _visit call is responsible for performing this insertion.
|
26900 | this._before.set(result.node, result.before);
|
26901 | }
|
26902 | if (result.after !== undefined) {
|
26903 | // Same with nodes that should be inserted after.
|
26904 | this._after.set(result.node, result.after);
|
26905 | }
|
26906 | return result.node;
|
26907 | }
|
26908 | /**
|
26909 | * Visit types of nodes which don't have their own explicit visitor.
|
26910 | */
|
26911 | visitOtherNode(node) {
|
26912 | return node;
|
26913 | }
|
26914 | /**
|
26915 | * @internal
|
26916 | */
|
26917 | _visit(node, context) {
|
26918 | // First, visit the node. visitedNode starts off as `null` but should be set after visiting
|
26919 | // is completed.
|
26920 | let visitedNode = null;
|
26921 | node = ts$1.visitEachChild(node, child => this._visit(child, context), context);
|
26922 | if (ts$1.isClassDeclaration(node)) {
|
26923 | visitedNode =
|
26924 | this._visitListEntryNode(node, (node) => this.visitClassDeclaration(node));
|
26925 | }
|
26926 | else {
|
26927 | visitedNode = this.visitOtherNode(node);
|
26928 | }
|
26929 | // If the visited node has a `statements` array then process them, maybe replacing the visited
|
26930 | // node and adding additional statements.
|
26931 | if (hasStatements(visitedNode)) {
|
26932 | visitedNode = this._maybeProcessStatements(visitedNode);
|
26933 | }
|
26934 | return visitedNode;
|
26935 | }
|
26936 | _maybeProcessStatements(node) {
|
26937 | // Shortcut - if every statement doesn't require nodes to be prepended or appended,
|
26938 | // this is a no-op.
|
26939 | if (node.statements.every(stmt => !this._before.has(stmt) && !this._after.has(stmt))) {
|
26940 | return node;
|
26941 | }
|
26942 | // There are statements to prepend, so clone the original node.
|
26943 | const clone = ts$1.getMutableClone(node);
|
26944 | // Build a new list of statements and patch it onto the clone.
|
26945 | const newStatements = [];
|
26946 | clone.statements.forEach(stmt => {
|
26947 | if (this._before.has(stmt)) {
|
26948 | newStatements.push(...this._before.get(stmt));
|
26949 | this._before.delete(stmt);
|
26950 | }
|
26951 | newStatements.push(stmt);
|
26952 | if (this._after.has(stmt)) {
|
26953 | newStatements.push(...this._after.get(stmt));
|
26954 | this._after.delete(stmt);
|
26955 | }
|
26956 | });
|
26957 | clone.statements = ts$1.createNodeArray(newStatements, node.statements.hasTrailingComma);
|
26958 | return clone;
|
26959 | }
|
26960 | }
|
26961 | function hasStatements(node) {
|
26962 | const block = node;
|
26963 | return block.statements !== undefined && Array.isArray(block.statements);
|
26964 | }
|
26965 |
|
26966 | /**
|
26967 | * @license
|
26968 | * Copyright Google LLC All Rights Reserved.
|
26969 | *
|
26970 | * Use of this source code is governed by an MIT-style license that can be
|
26971 | * found in the LICENSE file at https://angular.io/license
|
26972 | */
|
26973 | const NO_DECORATORS = new Set();
|
26974 | const CLOSURE_FILE_OVERVIEW_REGEXP = /\s+@fileoverview\s+/i;
|
26975 | function ivyTransformFactory(compilation, reflector, importRewriter, defaultImportRecorder, isCore, isClosureCompilerEnabled) {
|
26976 | const recordWrappedNodeExpr = createRecorderFn(defaultImportRecorder);
|
26977 | return (context) => {
|
26978 | return (file) => {
|
26979 | return transformIvySourceFile(compilation, context, reflector, importRewriter, file, isCore, isClosureCompilerEnabled, recordWrappedNodeExpr);
|
26980 | };
|
26981 | };
|
26982 | }
|
26983 | /**
|
26984 | * Visits all classes, performs Ivy compilation where Angular decorators are present and collects
|
26985 | * result in a Map that associates a ts.ClassDeclaration with Ivy compilation results. This visitor
|
26986 | * does NOT perform any TS transformations.
|
26987 | */
|
26988 | class IvyCompilationVisitor extends Visitor {
|
26989 | constructor(compilation, constantPool) {
|
26990 | super();
|
26991 | this.compilation = compilation;
|
26992 | this.constantPool = constantPool;
|
26993 | this.classCompilationMap = new Map();
|
26994 | }
|
26995 | visitClassDeclaration(node) {
|
26996 | // Determine if this class has an Ivy field that needs to be added, and compile the field
|
26997 | // to an expression if so.
|
26998 | const result = this.compilation.compile(node, this.constantPool);
|
26999 | if (result !== null) {
|
27000 | this.classCompilationMap.set(node, result);
|
27001 | }
|
27002 | return { node };
|
27003 | }
|
27004 | }
|
27005 | /**
|
27006 | * Visits all classes and performs transformation of corresponding TS nodes based on the Ivy
|
27007 | * compilation results (provided as an argument).
|
27008 | */
|
27009 | class IvyTransformationVisitor extends Visitor {
|
27010 | constructor(compilation, classCompilationMap, reflector, importManager, recordWrappedNodeExpr, isClosureCompilerEnabled, isCore) {
|
27011 | super();
|
27012 | this.compilation = compilation;
|
27013 | this.classCompilationMap = classCompilationMap;
|
27014 | this.reflector = reflector;
|
27015 | this.importManager = importManager;
|
27016 | this.recordWrappedNodeExpr = recordWrappedNodeExpr;
|
27017 | this.isClosureCompilerEnabled = isClosureCompilerEnabled;
|
27018 | this.isCore = isCore;
|
27019 | }
|
27020 | visitClassDeclaration(node) {
|
27021 | // If this class is not registered in the map, it means that it doesn't have Angular decorators,
|
27022 | // thus no further processing is required.
|
27023 | if (!this.classCompilationMap.has(node)) {
|
27024 | return { node };
|
27025 | }
|
27026 | // There is at least one field to add.
|
27027 | const statements = [];
|
27028 | const members = [...node.members];
|
27029 | for (const field of this.classCompilationMap.get(node)) {
|
27030 | // Translate the initializer for the field into TS nodes.
|
27031 | const exprNode = translateExpression(field.initializer, this.importManager, { recordWrappedNodeExpr: this.recordWrappedNodeExpr });
|
27032 | // Create a static property declaration for the new field.
|
27033 | const property = ts$1.createProperty(undefined, [ts$1.createToken(ts$1.SyntaxKind.StaticKeyword)], field.name, undefined, undefined, exprNode);
|
27034 | if (this.isClosureCompilerEnabled) {
|
27035 | // Closure compiler transforms the form `Service.ɵprov = X` into `Service$ɵprov = X`. To
|
27036 | // prevent this transformation, such assignments need to be annotated with @nocollapse.
|
27037 | // Note that tsickle is typically responsible for adding such annotations, however it
|
27038 | // doesn't yet handle synthetic fields added during other transformations.
|
27039 | ts$1.addSyntheticLeadingComment(property, ts$1.SyntaxKind.MultiLineCommentTrivia, '* @nocollapse ',
|
27040 | /* hasTrailingNewLine */ false);
|
27041 | }
|
27042 | field.statements
|
27043 | .map(stmt => translateStatement(stmt, this.importManager, { recordWrappedNodeExpr: this.recordWrappedNodeExpr }))
|
27044 | .forEach(stmt => statements.push(stmt));
|
27045 | members.push(property);
|
27046 | }
|
27047 | // Replace the class declaration with an updated version.
|
27048 | node = ts$1.updateClassDeclaration(node,
|
27049 | // Remove the decorator which triggered this compilation, leaving the others alone.
|
27050 | maybeFilterDecorator(node.decorators, this.compilation.decoratorsFor(node)), node.modifiers, node.name, node.typeParameters, node.heritageClauses || [],
|
27051 | // Map over the class members and remove any Angular decorators from them.
|
27052 | members.map(member => this._stripAngularDecorators(member)));
|
27053 | return { node, after: statements };
|
27054 | }
|
27055 | /**
|
27056 | * Return all decorators on a `Declaration` which are from @angular/core, or an empty set if none
|
27057 | * are.
|
27058 | */
|
27059 | _angularCoreDecorators(decl) {
|
27060 | const decorators = this.reflector.getDecoratorsOfDeclaration(decl);
|
27061 | if (decorators === null) {
|
27062 | return NO_DECORATORS;
|
27063 | }
|
27064 | const coreDecorators = decorators.filter(dec => this.isCore || isFromAngularCore(dec))
|
27065 | .map(dec => dec.node);
|
27066 | if (coreDecorators.length > 0) {
|
27067 | return new Set(coreDecorators);
|
27068 | }
|
27069 | else {
|
27070 | return NO_DECORATORS;
|
27071 | }
|
27072 | }
|
27073 | /**
|
27074 | * Given a `ts.Node`, filter the decorators array and return a version containing only non-Angular
|
27075 | * decorators.
|
27076 | *
|
27077 | * If all decorators are removed (or none existed in the first place), this method returns
|
27078 | * `undefined`.
|
27079 | */
|
27080 | _nonCoreDecoratorsOnly(node) {
|
27081 | // Shortcut if the node has no decorators.
|
27082 | if (node.decorators === undefined) {
|
27083 | return undefined;
|
27084 | }
|
27085 | // Build a Set of the decorators on this node from @angular/core.
|
27086 | const coreDecorators = this._angularCoreDecorators(node);
|
27087 | if (coreDecorators.size === node.decorators.length) {
|
27088 | // If all decorators are to be removed, return `undefined`.
|
27089 | return undefined;
|
27090 | }
|
27091 | else if (coreDecorators.size === 0) {
|
27092 | // If no decorators need to be removed, return the original decorators array.
|
27093 | return node.decorators;
|
27094 | }
|
27095 | // Filter out the core decorators.
|
27096 | const filtered = node.decorators.filter(dec => !coreDecorators.has(dec));
|
27097 | // If no decorators survive, return `undefined`. This can only happen if a core decorator is
|
27098 | // repeated on the node.
|
27099 | if (filtered.length === 0) {
|
27100 | return undefined;
|
27101 | }
|
27102 | // Create a new `NodeArray` with the filtered decorators that sourcemaps back to the original.
|
27103 | const array = ts$1.createNodeArray(filtered);
|
27104 | array.pos = node.decorators.pos;
|
27105 | array.end = node.decorators.end;
|
27106 | return array;
|
27107 | }
|
27108 | /**
|
27109 | * Remove Angular decorators from a `ts.Node` in a shallow manner.
|
27110 | *
|
27111 | * This will remove decorators from class elements (getters, setters, properties, methods) as well
|
27112 | * as parameters of constructors.
|
27113 | */
|
27114 | _stripAngularDecorators(node) {
|
27115 | if (ts$1.isParameter(node)) {
|
27116 | // Strip decorators from parameters (probably of the constructor).
|
27117 | node = ts$1.updateParameter(node, this._nonCoreDecoratorsOnly(node), node.modifiers, node.dotDotDotToken, node.name, node.questionToken, node.type, node.initializer);
|
27118 | }
|
27119 | else if (ts$1.isMethodDeclaration(node) && node.decorators !== undefined) {
|
27120 | // Strip decorators of methods.
|
27121 | node = ts$1.updateMethod(node, this._nonCoreDecoratorsOnly(node), node.modifiers, node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, node.type, node.body);
|
27122 | }
|
27123 | else if (ts$1.isPropertyDeclaration(node) && node.decorators !== undefined) {
|
27124 | // Strip decorators of properties.
|
27125 | node = ts$1.updateProperty(node, this._nonCoreDecoratorsOnly(node), node.modifiers, node.name, node.questionToken, node.type, node.initializer);
|
27126 | }
|
27127 | else if (ts$1.isGetAccessor(node)) {
|
27128 | // Strip decorators of getters.
|
27129 | node = ts$1.updateGetAccessor(node, this._nonCoreDecoratorsOnly(node), node.modifiers, node.name, node.parameters, node.type, node.body);
|
27130 | }
|
27131 | else if (ts$1.isSetAccessor(node)) {
|
27132 | // Strip decorators of setters.
|
27133 | node = ts$1.updateSetAccessor(node, this._nonCoreDecoratorsOnly(node), node.modifiers, node.name, node.parameters, node.body);
|
27134 | }
|
27135 | else if (ts$1.isConstructorDeclaration(node)) {
|
27136 | // For constructors, strip decorators of the parameters.
|
27137 | const parameters = node.parameters.map(param => this._stripAngularDecorators(param));
|
27138 | node =
|
27139 | ts$1.updateConstructor(node, node.decorators, node.modifiers, parameters, node.body);
|
27140 | }
|
27141 | return node;
|
27142 | }
|
27143 | }
|
27144 | /**
|
27145 | * A transformer which operates on ts.SourceFiles and applies changes from an `IvyCompilation`.
|
27146 | */
|
27147 | function transformIvySourceFile(compilation, context, reflector, importRewriter, file, isCore, isClosureCompilerEnabled, recordWrappedNodeExpr) {
|
27148 | const constantPool = new ConstantPool(isClosureCompilerEnabled);
|
27149 | const importManager = new ImportManager(importRewriter);
|
27150 | // The transformation process consists of 2 steps:
|
27151 | //
|
27152 | // 1. Visit all classes, perform compilation and collect the results.
|
27153 | // 2. Perform actual transformation of required TS nodes using compilation results from the first
|
27154 | // step.
|
27155 | //
|
27156 | // This is needed to have all `o.Expression`s generated before any TS transforms happen. This
|
27157 | // allows `ConstantPool` to properly identify expressions that can be shared across multiple
|
27158 | // components declared in the same file.
|
27159 | // Step 1. Go though all classes in AST, perform compilation and collect the results.
|
27160 | const compilationVisitor = new IvyCompilationVisitor(compilation, constantPool);
|
27161 | visit(file, compilationVisitor, context);
|
27162 | // Step 2. Scan through the AST again and perform transformations based on Ivy compilation
|
27163 | // results obtained at Step 1.
|
27164 | const transformationVisitor = new IvyTransformationVisitor(compilation, compilationVisitor.classCompilationMap, reflector, importManager, recordWrappedNodeExpr, isClosureCompilerEnabled, isCore);
|
27165 | let sf = visit(file, transformationVisitor, context);
|
27166 | // Generate the constant statements first, as they may involve adding additional imports
|
27167 | // to the ImportManager.
|
27168 | const downlevelTranslatedCode = getLocalizeCompileTarget(context) < ts$1.ScriptTarget.ES2015;
|
27169 | const constants = constantPool.statements.map(stmt => translateStatement(stmt, importManager, {
|
27170 | recordWrappedNodeExpr,
|
27171 | downlevelTaggedTemplates: downlevelTranslatedCode,
|
27172 | downlevelVariableDeclarations: downlevelTranslatedCode,
|
27173 | }));
|
27174 | // Preserve @fileoverview comments required by Closure, since the location might change as a
|
27175 | // result of adding extra imports and constant pool statements.
|
27176 | const fileOverviewMeta = isClosureCompilerEnabled ? getFileOverviewComment(sf.statements) : null;
|
27177 | // Add new imports for this file.
|
27178 | sf = addImports(importManager, sf, constants);
|
27179 | if (fileOverviewMeta !== null) {
|
27180 | setFileOverviewComment(sf, fileOverviewMeta);
|
27181 | }
|
27182 | return sf;
|
27183 | }
|
27184 | /**
|
27185 | * Compute the correct target output for `$localize` messages generated by Angular
|
27186 | *
|
27187 | * In some versions of TypeScript, the transformation of synthetic `$localize` tagged template
|
27188 | * literals is broken. See https://github.com/microsoft/TypeScript/issues/38485
|
27189 | *
|
27190 | * Here we compute what the expected final output target of the compilation will
|
27191 | * be so that we can generate ES5 compliant `$localize` calls instead of relying upon TS to do the
|
27192 | * downleveling for us.
|
27193 | */
|
27194 | function getLocalizeCompileTarget(context) {
|
27195 | const target = context.getCompilerOptions().target || ts$1.ScriptTarget.ES2015;
|
27196 | return target !== ts$1.ScriptTarget.JSON ? target : ts$1.ScriptTarget.ES2015;
|
27197 | }
|
27198 | function getFileOverviewComment(statements) {
|
27199 | if (statements.length > 0) {
|
27200 | const host = statements[0];
|
27201 | let trailing = false;
|
27202 | let comments = ts$1.getSyntheticLeadingComments(host);
|
27203 | // If @fileoverview tag is not found in source file, tsickle produces fake node with trailing
|
27204 | // comment and inject it at the very beginning of the generated file. So we need to check for
|
27205 | // leading as well as trailing comments.
|
27206 | if (!comments || comments.length === 0) {
|
27207 | trailing = true;
|
27208 | comments = ts$1.getSyntheticTrailingComments(host);
|
27209 | }
|
27210 | if (comments && comments.length > 0 && CLOSURE_FILE_OVERVIEW_REGEXP.test(comments[0].text)) {
|
27211 | return { comments, host, trailing };
|
27212 | }
|
27213 | }
|
27214 | return null;
|
27215 | }
|
27216 | function setFileOverviewComment(sf, fileoverview) {
|
27217 | const { comments, host, trailing } = fileoverview;
|
27218 | // If host statement is no longer the first one, it means that extra statements were added at the
|
27219 | // very beginning, so we need to relocate @fileoverview comment and cleanup the original statement
|
27220 | // that hosted it.
|
27221 | if (sf.statements.length > 0 && host !== sf.statements[0]) {
|
27222 | if (trailing) {
|
27223 | ts$1.setSyntheticTrailingComments(host, undefined);
|
27224 | }
|
27225 | else {
|
27226 | ts$1.setSyntheticLeadingComments(host, undefined);
|
27227 | }
|
27228 | ts$1.setSyntheticLeadingComments(sf.statements[0], comments);
|
27229 | }
|
27230 | }
|
27231 | function maybeFilterDecorator(decorators, toRemove) {
|
27232 | if (decorators === undefined) {
|
27233 | return undefined;
|
27234 | }
|
27235 | const filtered = decorators.filter(dec => toRemove.find(decToRemove => ts$1.getOriginalNode(dec) === decToRemove) === undefined);
|
27236 | if (filtered.length === 0) {
|
27237 | return undefined;
|
27238 | }
|
27239 | return ts$1.createNodeArray(filtered);
|
27240 | }
|
27241 | function isFromAngularCore(decorator) {
|
27242 | return decorator.import !== null && decorator.import.from === '@angular/core';
|
27243 | }
|
27244 | function createRecorderFn(defaultImportRecorder) {
|
27245 | return expr => {
|
27246 | if (ts$1.isIdentifier(expr)) {
|
27247 | defaultImportRecorder.recordUsedIdentifier(expr);
|
27248 | }
|
27249 | };
|
27250 | }
|
27251 |
|
27252 | /**
|
27253 | * @license
|
27254 | * Copyright Google LLC All Rights Reserved.
|
27255 | *
|
27256 | * Use of this source code is governed by an MIT-style license that can be
|
27257 | * found in the LICENSE file at https://angular.io/license
|
27258 | */
|
27259 | let _tsSourceMapBug29300Fixed;
|
27260 | /**
|
27261 | * Test the current version of TypeScript to see if it has fixed the external SourceMap
|
27262 | * file bug: https://github.com/Microsoft/TypeScript/issues/29300.
|
27263 | *
|
27264 | * The bug is fixed in TS 3.3+ but this check avoid us having to rely upon the version number,
|
27265 | * and allows us to gracefully fail if the TS version still has the bug.
|
27266 | *
|
27267 | * We check for the bug by compiling a very small program `a;` and transforming it to `b;`,
|
27268 | * where we map the new `b` identifier to an external source file, which has different lines to
|
27269 | * the original source file. If the bug is fixed then the output SourceMap should contain
|
27270 | * mappings that correspond ot the correct line/col pairs for this transformed node.
|
27271 | *
|
27272 | * @returns true if the bug is fixed.
|
27273 | */
|
27274 | function tsSourceMapBug29300Fixed() {
|
27275 | if (_tsSourceMapBug29300Fixed === undefined) {
|
27276 | let writtenFiles = {};
|
27277 | const sourceFile = ts$1.createSourceFile('test.ts', 'a;', ts$1.ScriptTarget.ES2015, true, ts$1.ScriptKind.TS);
|
27278 | const host = {
|
27279 | getSourceFile() {
|
27280 | return sourceFile;
|
27281 | },
|
27282 | fileExists() {
|
27283 | return true;
|
27284 | },
|
27285 | readFile() {
|
27286 | return '';
|
27287 | },
|
27288 | writeFile(fileName, data) {
|
27289 | writtenFiles[fileName] = data;
|
27290 | },
|
27291 | getDefaultLibFileName() {
|
27292 | return '';
|
27293 | },
|
27294 | getCurrentDirectory() {
|
27295 | return '';
|
27296 | },
|
27297 | getDirectories() {
|
27298 | return [];
|
27299 | },
|
27300 | getCanonicalFileName() {
|
27301 | return '';
|
27302 | },
|
27303 | useCaseSensitiveFileNames() {
|
27304 | return true;
|
27305 | },
|
27306 | getNewLine() {
|
27307 | return '\n';
|
27308 | },
|
27309 | };
|
27310 | const transform = (context) => {
|
27311 | return (node) => ts$1.visitNode(node, visitor);
|
27312 | function visitor(node) {
|
27313 | if (ts$1.isIdentifier(node) && node.text === 'a') {
|
27314 | const newNode = ts$1.createIdentifier('b');
|
27315 | ts$1.setSourceMapRange(newNode, {
|
27316 | pos: 16,
|
27317 | end: 16,
|
27318 | source: ts$1.createSourceMapSource('test.html', 'abc\ndef\nghi\njkl\nmno\npqr')
|
27319 | });
|
27320 | return newNode;
|
27321 | }
|
27322 | return ts$1.visitEachChild(node, visitor, context);
|
27323 | }
|
27324 | };
|
27325 | const program = ts$1.createProgram(['test.ts'], { sourceMap: true }, host);
|
27326 | program.emit(sourceFile, undefined, undefined, undefined, { after: [transform] });
|
27327 | // The first two mappings in the source map should look like:
|
27328 | // [0,1,4,0] col 0 => source file 1, row 4, column 0)
|
27329 | // [1,0,0,0] col 1 => source file 1, row 4, column 0)
|
27330 | _tsSourceMapBug29300Fixed = /ACIA,CAAA/.test(writtenFiles['test.js.map']);
|
27331 | }
|
27332 | return _tsSourceMapBug29300Fixed;
|
27333 | }
|
27334 |
|
27335 | /**
|
27336 | * @license
|
27337 | * Copyright Google LLC All Rights Reserved.
|
27338 | *
|
27339 | * Use of this source code is governed by an MIT-style license that can be
|
27340 | * found in the LICENSE file at https://angular.io/license
|
27341 | */
|
27342 | function getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore) {
|
27343 | const deps = [];
|
27344 | const errors = [];
|
27345 | let ctorParams = reflector.getConstructorParameters(clazz);
|
27346 | if (ctorParams === null) {
|
27347 | if (reflector.hasBaseClass(clazz)) {
|
27348 | return null;
|
27349 | }
|
27350 | else {
|
27351 | ctorParams = [];
|
27352 | }
|
27353 | }
|
27354 | ctorParams.forEach((param, idx) => {
|
27355 | let token = valueReferenceToExpression(param.typeValueReference, defaultImportRecorder);
|
27356 | let attribute = null;
|
27357 | let optional = false, self = false, skipSelf = false, host = false;
|
27358 | let resolved = R3ResolvedDependencyType.Token;
|
27359 | (param.decorators || []).filter(dec => isCore || isAngularCore(dec)).forEach(dec => {
|
27360 | const name = isCore || dec.import === null ? dec.name : dec.import.name;
|
27361 | if (name === 'Inject') {
|
27362 | if (dec.args === null || dec.args.length !== 1) {
|
27363 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARITY_WRONG, Decorator.nodeForError(dec), `Unexpected number of arguments to @Inject().`);
|
27364 | }
|
27365 | token = new WrappedNodeExpr(dec.args[0]);
|
27366 | }
|
27367 | else if (name === 'Optional') {
|
27368 | optional = true;
|
27369 | }
|
27370 | else if (name === 'SkipSelf') {
|
27371 | skipSelf = true;
|
27372 | }
|
27373 | else if (name === 'Self') {
|
27374 | self = true;
|
27375 | }
|
27376 | else if (name === 'Host') {
|
27377 | host = true;
|
27378 | }
|
27379 | else if (name === 'Attribute') {
|
27380 | if (dec.args === null || dec.args.length !== 1) {
|
27381 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARITY_WRONG, Decorator.nodeForError(dec), `Unexpected number of arguments to @Attribute().`);
|
27382 | }
|
27383 | const attributeName = dec.args[0];
|
27384 | token = new WrappedNodeExpr(attributeName);
|
27385 | if (ts$1.isStringLiteralLike(attributeName)) {
|
27386 | attribute = new LiteralExpr(attributeName.text);
|
27387 | }
|
27388 | else {
|
27389 | attribute = new WrappedNodeExpr(ts$1.createKeywordTypeNode(ts$1.SyntaxKind.UnknownKeyword));
|
27390 | }
|
27391 | resolved = R3ResolvedDependencyType.Attribute;
|
27392 | }
|
27393 | else {
|
27394 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_UNEXPECTED, Decorator.nodeForError(dec), `Unexpected decorator ${name} on parameter.`);
|
27395 | }
|
27396 | });
|
27397 | if (token instanceof ExternalExpr && token.value.name === 'ChangeDetectorRef' &&
|
27398 | token.value.moduleName === '@angular/core') {
|
27399 | resolved = R3ResolvedDependencyType.ChangeDetectorRef;
|
27400 | }
|
27401 | if (token === null) {
|
27402 | if (param.typeValueReference.kind !== 2 /* UNAVAILABLE */) {
|
27403 | throw new Error('Illegal state: expected value reference to be unavailable if no token is present');
|
27404 | }
|
27405 | errors.push({
|
27406 | index: idx,
|
27407 | param,
|
27408 | reason: param.typeValueReference.reason,
|
27409 | });
|
27410 | }
|
27411 | else {
|
27412 | deps.push({ token, attribute, optional, self, skipSelf, host, resolved });
|
27413 | }
|
27414 | });
|
27415 | if (errors.length === 0) {
|
27416 | return { deps };
|
27417 | }
|
27418 | else {
|
27419 | return { deps: null, errors };
|
27420 | }
|
27421 | }
|
27422 | function valueReferenceToExpression(valueRef, defaultImportRecorder) {
|
27423 | if (valueRef.kind === 2 /* UNAVAILABLE */) {
|
27424 | return null;
|
27425 | }
|
27426 | else if (valueRef.kind === 0 /* LOCAL */) {
|
27427 | if (defaultImportRecorder !== null && valueRef.defaultImportStatement !== null &&
|
27428 | ts$1.isIdentifier(valueRef.expression)) {
|
27429 | defaultImportRecorder.recordImportedIdentifier(valueRef.expression, valueRef.defaultImportStatement);
|
27430 | }
|
27431 | return new WrappedNodeExpr(valueRef.expression);
|
27432 | }
|
27433 | else {
|
27434 | let importExpr = new ExternalExpr({ moduleName: valueRef.moduleName, name: valueRef.importedName });
|
27435 | if (valueRef.nestedPath !== null) {
|
27436 | for (const property of valueRef.nestedPath) {
|
27437 | importExpr = new ReadPropExpr(importExpr, property);
|
27438 | }
|
27439 | }
|
27440 | return importExpr;
|
27441 | }
|
27442 | }
|
27443 | /**
|
27444 | * Convert `ConstructorDeps` into the `R3DependencyMetadata` array for those deps if they're valid,
|
27445 | * or into an `'invalid'` signal if they're not.
|
27446 | *
|
27447 | * This is a companion function to `validateConstructorDependencies` which accepts invalid deps.
|
27448 | */
|
27449 | function unwrapConstructorDependencies(deps) {
|
27450 | if (deps === null) {
|
27451 | return null;
|
27452 | }
|
27453 | else if (deps.deps !== null) {
|
27454 | // These constructor dependencies are valid.
|
27455 | return deps.deps;
|
27456 | }
|
27457 | else {
|
27458 | // These deps are invalid.
|
27459 | return 'invalid';
|
27460 | }
|
27461 | }
|
27462 | function getValidConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore) {
|
27463 | return validateConstructorDependencies(clazz, getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore));
|
27464 | }
|
27465 | /**
|
27466 | * Validate that `ConstructorDeps` does not have any invalid dependencies and convert them into the
|
27467 | * `R3DependencyMetadata` array if so, or raise a diagnostic if some deps are invalid.
|
27468 | *
|
27469 | * This is a companion function to `unwrapConstructorDependencies` which does not accept invalid
|
27470 | * deps.
|
27471 | */
|
27472 | function validateConstructorDependencies(clazz, deps) {
|
27473 | if (deps === null) {
|
27474 | return null;
|
27475 | }
|
27476 | else if (deps.deps !== null) {
|
27477 | return deps.deps;
|
27478 | }
|
27479 | else {
|
27480 | // TODO(alxhub): this cast is necessary because the g3 typescript version doesn't narrow here.
|
27481 | // There is at least one error.
|
27482 | const error = deps.errors[0];
|
27483 | throw createUnsuitableInjectionTokenError(clazz, error);
|
27484 | }
|
27485 | }
|
27486 | /**
|
27487 | * Creates a fatal error with diagnostic for an invalid injection token.
|
27488 | * @param clazz The class for which the injection token was unavailable.
|
27489 | * @param error The reason why no valid injection token is available.
|
27490 | */
|
27491 | function createUnsuitableInjectionTokenError(clazz, error) {
|
27492 | const { param, index, reason } = error;
|
27493 | let chainMessage = undefined;
|
27494 | let hints = undefined;
|
27495 | switch (reason.kind) {
|
27496 | case 5 /* UNSUPPORTED */:
|
27497 | chainMessage = 'Consider using the @Inject decorator to specify an injection token.';
|
27498 | hints = [
|
27499 | makeRelatedInformation(reason.typeNode, 'This type is not supported as injection token.'),
|
27500 | ];
|
27501 | break;
|
27502 | case 1 /* NO_VALUE_DECLARATION */:
|
27503 | chainMessage = 'Consider using the @Inject decorator to specify an injection token.';
|
27504 | hints = [
|
27505 | makeRelatedInformation(reason.typeNode, 'This type does not have a value, so it cannot be used as injection token.'),
|
27506 | ];
|
27507 | if (reason.decl !== null) {
|
27508 | hints.push(makeRelatedInformation(reason.decl, 'The type is declared here.'));
|
27509 | }
|
27510 | break;
|
27511 | case 2 /* TYPE_ONLY_IMPORT */:
|
27512 | chainMessage =
|
27513 | 'Consider changing the type-only import to a regular import, or use the @Inject decorator to specify an injection token.';
|
27514 | hints = [
|
27515 | makeRelatedInformation(reason.typeNode, 'This type is imported using a type-only import, which prevents it from being usable as an injection token.'),
|
27516 | makeRelatedInformation(reason.importClause, 'The type-only import occurs here.'),
|
27517 | ];
|
27518 | break;
|
27519 | case 4 /* NAMESPACE */:
|
27520 | chainMessage = 'Consider using the @Inject decorator to specify an injection token.';
|
27521 | hints = [
|
27522 | makeRelatedInformation(reason.typeNode, 'This type corresponds with a namespace, which cannot be used as injection token.'),
|
27523 | makeRelatedInformation(reason.importClause, 'The namespace import occurs here.'),
|
27524 | ];
|
27525 | break;
|
27526 | case 3 /* UNKNOWN_REFERENCE */:
|
27527 | chainMessage = 'The type should reference a known declaration.';
|
27528 | hints = [makeRelatedInformation(reason.typeNode, 'This type could not be resolved.')];
|
27529 | break;
|
27530 | case 0 /* MISSING_TYPE */:
|
27531 | chainMessage =
|
27532 | 'Consider adding a type to the parameter or use the @Inject decorator to specify an injection token.';
|
27533 | break;
|
27534 | }
|
27535 | const chain = {
|
27536 | messageText: `No suitable injection token for parameter '${param.name || index}' of class '${clazz.name.text}'.`,
|
27537 | category: ts$1.DiagnosticCategory.Error,
|
27538 | code: 0,
|
27539 | next: [{
|
27540 | messageText: chainMessage,
|
27541 | category: ts$1.DiagnosticCategory.Message,
|
27542 | code: 0,
|
27543 | }],
|
27544 | };
|
27545 | return new FatalDiagnosticError(ErrorCode.PARAM_MISSING_TOKEN, param.nameNode, chain, hints);
|
27546 | }
|
27547 | function toR3Reference(valueRef, typeRef, valueContext, typeContext, refEmitter) {
|
27548 | const value = refEmitter.emit(valueRef, valueContext);
|
27549 | const type = refEmitter.emit(typeRef, typeContext, ImportFlags.ForceNewImport | ImportFlags.AllowTypeImports);
|
27550 | if (value === null || type === null) {
|
27551 | throw new Error(`Could not refer to ${ts$1.SyntaxKind[valueRef.node.kind]}`);
|
27552 | }
|
27553 | return { value, type };
|
27554 | }
|
27555 | function isAngularCore(decorator) {
|
27556 | return decorator.import !== null && decorator.import.from === '@angular/core';
|
27557 | }
|
27558 | function isAngularCoreReference(reference, symbolName) {
|
27559 | return reference.ownedByModuleGuess === '@angular/core' && reference.debugName === symbolName;
|
27560 | }
|
27561 | function findAngularDecorator(decorators, name, isCore) {
|
27562 | return decorators.find(decorator => isAngularDecorator(decorator, name, isCore));
|
27563 | }
|
27564 | function isAngularDecorator(decorator, name, isCore) {
|
27565 | if (isCore) {
|
27566 | return decorator.name === name;
|
27567 | }
|
27568 | else if (isAngularCore(decorator)) {
|
27569 | return decorator.import.name === name;
|
27570 | }
|
27571 | return false;
|
27572 | }
|
27573 | /**
|
27574 | * Unwrap a `ts.Expression`, removing outer type-casts or parentheses until the expression is in its
|
27575 | * lowest level form.
|
27576 | *
|
27577 | * For example, the expression "(foo as Type)" unwraps to "foo".
|
27578 | */
|
27579 | function unwrapExpression(node) {
|
27580 | while (ts$1.isAsExpression(node) || ts$1.isParenthesizedExpression(node)) {
|
27581 | node = node.expression;
|
27582 | }
|
27583 | return node;
|
27584 | }
|
27585 | function expandForwardRef(arg) {
|
27586 | arg = unwrapExpression(arg);
|
27587 | if (!ts$1.isArrowFunction(arg) && !ts$1.isFunctionExpression(arg)) {
|
27588 | return null;
|
27589 | }
|
27590 | const body = arg.body;
|
27591 | // Either the body is a ts.Expression directly, or a block with a single return statement.
|
27592 | if (ts$1.isBlock(body)) {
|
27593 | // Block body - look for a single return statement.
|
27594 | if (body.statements.length !== 1) {
|
27595 | return null;
|
27596 | }
|
27597 | const stmt = body.statements[0];
|
27598 | if (!ts$1.isReturnStatement(stmt) || stmt.expression === undefined) {
|
27599 | return null;
|
27600 | }
|
27601 | return stmt.expression;
|
27602 | }
|
27603 | else {
|
27604 | // Shorthand body - return as an expression.
|
27605 | return body;
|
27606 | }
|
27607 | }
|
27608 | /**
|
27609 | * Possibly resolve a forwardRef() expression into the inner value.
|
27610 | *
|
27611 | * @param node the forwardRef() expression to resolve
|
27612 | * @param reflector a ReflectionHost
|
27613 | * @returns the resolved expression, if the original expression was a forwardRef(), or the original
|
27614 | * expression otherwise
|
27615 | */
|
27616 | function unwrapForwardRef(node, reflector) {
|
27617 | node = unwrapExpression(node);
|
27618 | if (!ts$1.isCallExpression(node) || node.arguments.length !== 1) {
|
27619 | return node;
|
27620 | }
|
27621 | const fn = ts$1.isPropertyAccessExpression(node.expression) ? node.expression.name : node.expression;
|
27622 | if (!ts$1.isIdentifier(fn)) {
|
27623 | return node;
|
27624 | }
|
27625 | const expr = expandForwardRef(node.arguments[0]);
|
27626 | if (expr === null) {
|
27627 | return node;
|
27628 | }
|
27629 | const imp = reflector.getImportOfIdentifier(fn);
|
27630 | if (imp === null || imp.from !== '@angular/core' || imp.name !== 'forwardRef') {
|
27631 | return node;
|
27632 | }
|
27633 | else {
|
27634 | return expr;
|
27635 | }
|
27636 | }
|
27637 | /**
|
27638 | * A foreign function resolver for `staticallyResolve` which unwraps forwardRef() expressions.
|
27639 | *
|
27640 | * @param ref a Reference to the declaration of the function being called (which might be
|
27641 | * forwardRef)
|
27642 | * @param args the arguments to the invocation of the forwardRef expression
|
27643 | * @returns an unwrapped argument if `ref` pointed to forwardRef, or null otherwise
|
27644 | */
|
27645 | function forwardRefResolver(ref, args) {
|
27646 | if (!isAngularCoreReference(ref, 'forwardRef') || args.length !== 1) {
|
27647 | return null;
|
27648 | }
|
27649 | return expandForwardRef(args[0]);
|
27650 | }
|
27651 | /**
|
27652 | * Combines an array of resolver functions into a one.
|
27653 | * @param resolvers Resolvers to be combined.
|
27654 | */
|
27655 | function combineResolvers(resolvers) {
|
27656 | return (ref, args) => {
|
27657 | for (const resolver of resolvers) {
|
27658 | const resolved = resolver(ref, args);
|
27659 | if (resolved !== null) {
|
27660 | return resolved;
|
27661 | }
|
27662 | }
|
27663 | return null;
|
27664 | };
|
27665 | }
|
27666 | function isExpressionForwardReference(expr, context, contextSource) {
|
27667 | if (isWrappedTsNodeExpr(expr)) {
|
27668 | const node = ts$1.getOriginalNode(expr.node);
|
27669 | return node.getSourceFile() === contextSource && context.pos < node.pos;
|
27670 | }
|
27671 | else {
|
27672 | return false;
|
27673 | }
|
27674 | }
|
27675 | function isWrappedTsNodeExpr(expr) {
|
27676 | return expr instanceof WrappedNodeExpr;
|
27677 | }
|
27678 | function readBaseClass$1(node, reflector, evaluator) {
|
27679 | const baseExpression = reflector.getBaseClassExpression(node);
|
27680 | if (baseExpression !== null) {
|
27681 | const baseClass = evaluator.evaluate(baseExpression);
|
27682 | if (baseClass instanceof Reference$1 && reflector.isClass(baseClass.node)) {
|
27683 | return baseClass;
|
27684 | }
|
27685 | else {
|
27686 | return 'dynamic';
|
27687 | }
|
27688 | }
|
27689 | return null;
|
27690 | }
|
27691 | const parensWrapperTransformerFactory = (context) => {
|
27692 | const visitor = (node) => {
|
27693 | const visited = ts$1.visitEachChild(node, visitor, context);
|
27694 | if (ts$1.isArrowFunction(visited) || ts$1.isFunctionExpression(visited)) {
|
27695 | return ts$1.createParen(visited);
|
27696 | }
|
27697 | return visited;
|
27698 | };
|
27699 | return (node) => ts$1.visitEachChild(node, visitor, context);
|
27700 | };
|
27701 | /**
|
27702 | * Wraps all functions in a given expression in parentheses. This is needed to avoid problems
|
27703 | * where Tsickle annotations added between analyse and transform phases in Angular may trigger
|
27704 | * automatic semicolon insertion, e.g. if a function is the expression in a `return` statement.
|
27705 | * More
|
27706 | * info can be found in Tsickle source code here:
|
27707 | * https://github.com/angular/tsickle/blob/d7974262571c8a17d684e5ba07680e1b1993afdd/src/jsdoc_transformer.ts#L1021
|
27708 | *
|
27709 | * @param expression Expression where functions should be wrapped in parentheses
|
27710 | */
|
27711 | function wrapFunctionExpressionsInParens(expression) {
|
27712 | return ts$1.transform(expression, [parensWrapperTransformerFactory]).transformed[0];
|
27713 | }
|
27714 | /**
|
27715 | * Create a `ts.Diagnostic` which indicates the given class is part of the declarations of two or
|
27716 | * more NgModules.
|
27717 | *
|
27718 | * The resulting `ts.Diagnostic` will have a context entry for each NgModule showing the point where
|
27719 | * the directive/pipe exists in its `declarations` (if possible).
|
27720 | */
|
27721 | function makeDuplicateDeclarationError(node, data, kind) {
|
27722 | const context = [];
|
27723 | for (const decl of data) {
|
27724 | if (decl.rawDeclarations === null) {
|
27725 | continue;
|
27726 | }
|
27727 | // Try to find the reference to the declaration within the declarations array, to hang the
|
27728 | // error there. If it can't be found, fall back on using the NgModule's name.
|
27729 | const contextNode = decl.ref.getOriginForDiagnostics(decl.rawDeclarations, decl.ngModule.name);
|
27730 | context.push(makeRelatedInformation(contextNode, `'${node.name.text}' is listed in the declarations of the NgModule '${decl.ngModule.name.text}'.`));
|
27731 | }
|
27732 | // Finally, produce the diagnostic.
|
27733 | return makeDiagnostic(ErrorCode.NGMODULE_DECLARATION_NOT_UNIQUE, node.name, `The ${kind} '${node.name.text}' is declared by more than one NgModule.`, context);
|
27734 | }
|
27735 | /**
|
27736 | * Resolves the given `rawProviders` into `ClassDeclarations` and returns
|
27737 | * a set containing those that are known to require a factory definition.
|
27738 | * @param rawProviders Expression that declared the providers array in the source.
|
27739 | */
|
27740 | function resolveProvidersRequiringFactory(rawProviders, reflector, evaluator) {
|
27741 | const providers = new Set();
|
27742 | const resolvedProviders = evaluator.evaluate(rawProviders);
|
27743 | if (!Array.isArray(resolvedProviders)) {
|
27744 | return providers;
|
27745 | }
|
27746 | resolvedProviders.forEach(function processProviders(provider) {
|
27747 | let tokenClass = null;
|
27748 | if (Array.isArray(provider)) {
|
27749 | // If we ran into an array, recurse into it until we've resolve all the classes.
|
27750 | provider.forEach(processProviders);
|
27751 | }
|
27752 | else if (provider instanceof Reference$1) {
|
27753 | tokenClass = provider;
|
27754 | }
|
27755 | else if (provider instanceof Map && provider.has('useClass') && !provider.has('deps')) {
|
27756 | const useExisting = provider.get('useClass');
|
27757 | if (useExisting instanceof Reference$1) {
|
27758 | tokenClass = useExisting;
|
27759 | }
|
27760 | }
|
27761 | // TODO(alxhub): there was a bug where `getConstructorParameters` would return `null` for a
|
27762 | // class in a .d.ts file, always, even if the class had a constructor. This was fixed for
|
27763 | // `getConstructorParameters`, but that fix causes more classes to be recognized here as needing
|
27764 | // provider checks, which is a breaking change in g3. Avoid this breakage for now by skipping
|
27765 | // classes from .d.ts files here directly, until g3 can be cleaned up.
|
27766 | if (tokenClass !== null && !tokenClass.node.getSourceFile().isDeclarationFile &&
|
27767 | reflector.isClass(tokenClass.node)) {
|
27768 | const constructorParameters = reflector.getConstructorParameters(tokenClass.node);
|
27769 | // Note that we only want to capture providers with a non-trivial constructor,
|
27770 | // because they're the ones that might be using DI and need to be decorated.
|
27771 | if (constructorParameters !== null && constructorParameters.length > 0) {
|
27772 | providers.add(tokenClass);
|
27773 | }
|
27774 | }
|
27775 | });
|
27776 | return providers;
|
27777 | }
|
27778 | /**
|
27779 | * Create an R3Reference for a class.
|
27780 | *
|
27781 | * The `value` is the exported declaration of the class from its source file.
|
27782 | * The `type` is an expression that would be used by ngcc in the typings (.d.ts) files.
|
27783 | */
|
27784 | function wrapTypeReference(reflector, clazz) {
|
27785 | const dtsClass = reflector.getDtsDeclaration(clazz);
|
27786 | const value = new WrappedNodeExpr(clazz.name);
|
27787 | const type = dtsClass !== null && isNamedClassDeclaration(dtsClass) ?
|
27788 | new WrappedNodeExpr(dtsClass.name) :
|
27789 | value;
|
27790 | return { value, type };
|
27791 | }
|
27792 | /** Creates a ParseSourceSpan for a TypeScript node. */
|
27793 | function createSourceSpan(node) {
|
27794 | const sf = node.getSourceFile();
|
27795 | const [startOffset, endOffset] = [node.getStart(), node.getEnd()];
|
27796 | const { line: startLine, character: startCol } = sf.getLineAndCharacterOfPosition(startOffset);
|
27797 | const { line: endLine, character: endCol } = sf.getLineAndCharacterOfPosition(endOffset);
|
27798 | const parseSf = new ParseSourceFile(sf.getFullText(), sf.fileName);
|
27799 | // +1 because values are zero-indexed.
|
27800 | return new ParseSourceSpan(new ParseLocation(parseSf, startOffset, startLine + 1, startCol + 1), new ParseLocation(parseSf, endOffset, endLine + 1, endCol + 1));
|
27801 | }
|
27802 |
|
27803 | /**
|
27804 | * @license
|
27805 | * Copyright Google LLC All Rights Reserved.
|
27806 | *
|
27807 | * Use of this source code is governed by an MIT-style license that can be
|
27808 | * found in the LICENSE file at https://angular.io/license
|
27809 | */
|
27810 | /**
|
27811 | * Creates a `FatalDiagnosticError` for a node that did not evaluate to the expected type. The
|
27812 | * diagnostic that is created will include details on why the value is incorrect, i.e. it includes
|
27813 | * a representation of the actual type that was unsupported, or in the case of a dynamic value the
|
27814 | * trace to the node where the dynamic value originated.
|
27815 | *
|
27816 | * @param node The node for which the diagnostic should be produced.
|
27817 | * @param value The evaluated value that has the wrong type.
|
27818 | * @param messageText The message text of the error.
|
27819 | */
|
27820 | function createValueHasWrongTypeError(node, value, messageText) {
|
27821 | var _a;
|
27822 | let chainedMessage;
|
27823 | let relatedInformation;
|
27824 | if (value instanceof DynamicValue) {
|
27825 | chainedMessage = 'Value could not be determined statically.';
|
27826 | relatedInformation = traceDynamicValue(node, value);
|
27827 | }
|
27828 | else if (value instanceof Reference$1) {
|
27829 | const target = value.debugName !== null ? `'${value.debugName}'` : 'an anonymous declaration';
|
27830 | chainedMessage = `Value is a reference to ${target}.`;
|
27831 | const referenceNode = (_a = identifierOfNode(value.node)) !== null && _a !== void 0 ? _a : value.node;
|
27832 | relatedInformation = [makeRelatedInformation(referenceNode, 'Reference is declared here.')];
|
27833 | }
|
27834 | else {
|
27835 | chainedMessage = `Value is of type '${describeResolvedType(value)}'.`;
|
27836 | }
|
27837 | const chain = {
|
27838 | messageText,
|
27839 | category: ts$1.DiagnosticCategory.Error,
|
27840 | code: 0,
|
27841 | next: [{
|
27842 | messageText: chainedMessage,
|
27843 | category: ts$1.DiagnosticCategory.Message,
|
27844 | code: 0,
|
27845 | }]
|
27846 | };
|
27847 | return new FatalDiagnosticError(ErrorCode.VALUE_HAS_WRONG_TYPE, node, chain, relatedInformation);
|
27848 | }
|
27849 | /**
|
27850 | * Gets the diagnostics for a set of provider classes.
|
27851 | * @param providerClasses Classes that should be checked.
|
27852 | * @param providersDeclaration Node that declares the providers array.
|
27853 | * @param registry Registry that keeps track of the registered injectable classes.
|
27854 | */
|
27855 | function getProviderDiagnostics(providerClasses, providersDeclaration, registry) {
|
27856 | const diagnostics = [];
|
27857 | for (const provider of providerClasses) {
|
27858 | if (registry.isInjectable(provider.node)) {
|
27859 | continue;
|
27860 | }
|
27861 | const contextNode = provider.getOriginForDiagnostics(providersDeclaration);
|
27862 | diagnostics.push(makeDiagnostic(ErrorCode.UNDECORATED_PROVIDER, contextNode, `The class '${provider.node.name
|
27863 | .text}' cannot be created via dependency injection, as it does not have an Angular decorator. This will result in an error at runtime.
|
27864 |
|
27865 | Either add the @Injectable() decorator to '${provider.node.name
|
27866 | .text}', or configure a different provider (such as a provider with 'useFactory').
|
27867 | `, [makeRelatedInformation(provider.node, `'${provider.node.name.text}' is declared here.`)]));
|
27868 | }
|
27869 | return diagnostics;
|
27870 | }
|
27871 | function getDirectiveDiagnostics(node, reader, evaluator, reflector, scopeRegistry, kind) {
|
27872 | let diagnostics = [];
|
27873 | const addDiagnostics = (more) => {
|
27874 | if (more === null) {
|
27875 | return;
|
27876 | }
|
27877 | else if (diagnostics === null) {
|
27878 | diagnostics = Array.isArray(more) ? more : [more];
|
27879 | }
|
27880 | else if (Array.isArray(more)) {
|
27881 | diagnostics.push(...more);
|
27882 | }
|
27883 | else {
|
27884 | diagnostics.push(more);
|
27885 | }
|
27886 | };
|
27887 | const duplicateDeclarations = scopeRegistry.getDuplicateDeclarations(node);
|
27888 | if (duplicateDeclarations !== null) {
|
27889 | addDiagnostics(makeDuplicateDeclarationError(node, duplicateDeclarations, kind));
|
27890 | }
|
27891 | addDiagnostics(checkInheritanceOfDirective(node, reader, reflector, evaluator));
|
27892 | return diagnostics;
|
27893 | }
|
27894 | function getUndecoratedClassWithAngularFeaturesDiagnostic(node) {
|
27895 | return makeDiagnostic(ErrorCode.UNDECORATED_CLASS_USING_ANGULAR_FEATURES, node.name, `Class is using Angular features but is not decorated. Please add an explicit ` +
|
27896 | `Angular decorator.`);
|
27897 | }
|
27898 | function checkInheritanceOfDirective(node, reader, reflector, evaluator) {
|
27899 | if (!reflector.isClass(node) || reflector.getConstructorParameters(node) !== null) {
|
27900 | // We should skip nodes that aren't classes. If a constructor exists, then no base class
|
27901 | // definition is required on the runtime side - it's legal to inherit from any class.
|
27902 | return null;
|
27903 | }
|
27904 | // The extends clause is an expression which can be as dynamic as the user wants. Try to
|
27905 | // evaluate it, but fall back on ignoring the clause if it can't be understood. This is a View
|
27906 | // Engine compatibility hack: View Engine ignores 'extends' expressions that it cannot understand.
|
27907 | let baseClass = readBaseClass$1(node, reflector, evaluator);
|
27908 | while (baseClass !== null) {
|
27909 | if (baseClass === 'dynamic') {
|
27910 | return null;
|
27911 | }
|
27912 | // We can skip the base class if it has metadata.
|
27913 | const baseClassMeta = reader.getDirectiveMetadata(baseClass);
|
27914 | if (baseClassMeta !== null) {
|
27915 | return null;
|
27916 | }
|
27917 | // If the base class has a blank constructor we can skip it since it can't be using DI.
|
27918 | const baseClassConstructorParams = reflector.getConstructorParameters(baseClass.node);
|
27919 | const newParentClass = readBaseClass$1(baseClass.node, reflector, evaluator);
|
27920 | if (baseClassConstructorParams !== null && baseClassConstructorParams.length > 0) {
|
27921 | // This class has a non-trivial constructor, that's an error!
|
27922 | return getInheritedUndecoratedCtorDiagnostic(node, baseClass, reader);
|
27923 | }
|
27924 | else if (baseClassConstructorParams !== null || newParentClass === null) {
|
27925 | // This class has a trivial constructor, or no constructor + is the
|
27926 | // top of the inheritance chain, so it's okay.
|
27927 | return null;
|
27928 | }
|
27929 | // Go up the chain and continue
|
27930 | baseClass = newParentClass;
|
27931 | }
|
27932 | return null;
|
27933 | }
|
27934 | function getInheritedUndecoratedCtorDiagnostic(node, baseClass, reader) {
|
27935 | const subclassMeta = reader.getDirectiveMetadata(new Reference$1(node));
|
27936 | const dirOrComp = subclassMeta.isComponent ? 'Component' : 'Directive';
|
27937 | const baseClassName = baseClass.debugName;
|
27938 | return makeDiagnostic(ErrorCode.DIRECTIVE_INHERITS_UNDECORATED_CTOR, node.name, `The ${dirOrComp.toLowerCase()} ${node.name.text} inherits its constructor from ${baseClassName}, ` +
|
27939 | `but the latter does not have an Angular decorator of its own. Dependency injection will not be able to ` +
|
27940 | `resolve the parameters of ${baseClassName}'s constructor. Either add a @Directive decorator ` +
|
27941 | `to ${baseClassName}, or add an explicit constructor to ${node.name.text}.`);
|
27942 | }
|
27943 |
|
27944 | /**
|
27945 | * @license
|
27946 | * Copyright Google LLC All Rights Reserved.
|
27947 | *
|
27948 | * Use of this source code is governed by an MIT-style license that can be
|
27949 | * found in the LICENSE file at https://angular.io/license
|
27950 | */
|
27951 | function compileNgFactoryDefField(metadata) {
|
27952 | const res = compileFactoryFunction(metadata);
|
27953 | return { name: 'ɵfac', initializer: res.factory, statements: res.statements, type: res.type };
|
27954 | }
|
27955 |
|
27956 | /**
|
27957 | * @license
|
27958 | * Copyright Google LLC All Rights Reserved.
|
27959 | *
|
27960 | * Use of this source code is governed by an MIT-style license that can be
|
27961 | * found in the LICENSE file at https://angular.io/license
|
27962 | */
|
27963 | /**
|
27964 | * Given a class declaration, generate a call to `setClassMetadata` with the Angular metadata
|
27965 | * present on the class or its member fields. An ngDevMode guard is used to allow the call to be
|
27966 | * tree-shaken away, as the `setClassMetadata` invocation is only needed for testing purposes.
|
27967 | *
|
27968 | * If no such metadata is present, this function returns `null`. Otherwise, the call is returned
|
27969 | * as a `Statement` for inclusion along with the class.
|
27970 | */
|
27971 | function generateSetClassMetadataCall(clazz, reflection, defaultImportRecorder, isCore, annotateForClosureCompiler) {
|
27972 | if (!reflection.isClass(clazz)) {
|
27973 | return null;
|
27974 | }
|
27975 | const id = reflection.getAdjacentNameOfClass(clazz);
|
27976 | // Reflect over the class decorators. If none are present, or those that are aren't from
|
27977 | // Angular, then return null. Otherwise, turn them into metadata.
|
27978 | const classDecorators = reflection.getDecoratorsOfDeclaration(clazz);
|
27979 | if (classDecorators === null) {
|
27980 | return null;
|
27981 | }
|
27982 | const ngClassDecorators = classDecorators.filter(dec => isAngularDecorator$1(dec, isCore))
|
27983 | .map(decorator => decoratorToMetadata(decorator, annotateForClosureCompiler))
|
27984 | // Since the `setClassMetadata` call is intended to be emitted after the class
|
27985 | // declaration, we have to strip references to the existing identifiers or
|
27986 | // TypeScript might generate invalid code when it emits to JS. In particular
|
27987 | // this can break when emitting a class to ES5 which has a custom decorator
|
27988 | // and is referenced inside of its own metadata (see #39509 for more information).
|
27989 | .map(decorator => removeIdentifierReferences(decorator, id.text));
|
27990 | if (ngClassDecorators.length === 0) {
|
27991 | return null;
|
27992 | }
|
27993 | const metaDecorators = ts$1.createArrayLiteral(ngClassDecorators);
|
27994 | // Convert the constructor parameters to metadata, passing null if none are present.
|
27995 | let metaCtorParameters = new LiteralExpr(null);
|
27996 | const classCtorParameters = reflection.getConstructorParameters(clazz);
|
27997 | if (classCtorParameters !== null) {
|
27998 | const ctorParameters = classCtorParameters.map(param => ctorParameterToMetadata(param, defaultImportRecorder, isCore));
|
27999 | metaCtorParameters = new FunctionExpr([], [
|
28000 | new ReturnStatement(new LiteralArrayExpr(ctorParameters)),
|
28001 | ]);
|
28002 | }
|
28003 | // Do the same for property decorators.
|
28004 | let metaPropDecorators = ts$1.createNull();
|
28005 | const classMembers = reflection.getMembersOfClass(clazz).filter(member => !member.isStatic && member.decorators !== null && member.decorators.length > 0);
|
28006 | const duplicateDecoratedMemberNames = classMembers.map(member => member.name).filter((name, i, arr) => arr.indexOf(name) < i);
|
28007 | if (duplicateDecoratedMemberNames.length > 0) {
|
28008 | // This should theoretically never happen, because the only way to have duplicate instance
|
28009 | // member names is getter/setter pairs and decorators cannot appear in both a getter and the
|
28010 | // corresponding setter.
|
28011 | throw new Error(`Duplicate decorated properties found on class '${clazz.name.text}': ` +
|
28012 | duplicateDecoratedMemberNames.join(', '));
|
28013 | }
|
28014 | const decoratedMembers = classMembers.map(member => { var _a; return classMemberToMetadata((_a = member.nameNode) !== null && _a !== void 0 ? _a : member.name, member.decorators, isCore); });
|
28015 | if (decoratedMembers.length > 0) {
|
28016 | metaPropDecorators = ts$1.createObjectLiteral(decoratedMembers);
|
28017 | }
|
28018 | // Generate a pure call to setClassMetadata with the class identifier and its metadata.
|
28019 | const setClassMetadata = new ExternalExpr(Identifiers.setClassMetadata);
|
28020 | const fnCall = new InvokeFunctionExpr(
|
28021 | /* fn */ setClassMetadata,
|
28022 | /* args */
|
28023 | [
|
28024 | new WrappedNodeExpr(id),
|
28025 | new WrappedNodeExpr(metaDecorators),
|
28026 | metaCtorParameters,
|
28027 | new WrappedNodeExpr(metaPropDecorators),
|
28028 | ]);
|
28029 | const iife = new FunctionExpr([], [devOnlyGuardedExpression(fnCall).toStmt()]);
|
28030 | return iife.callFn([]).toStmt();
|
28031 | }
|
28032 | /**
|
28033 | * Convert a reflected constructor parameter to metadata.
|
28034 | */
|
28035 | function ctorParameterToMetadata(param, defaultImportRecorder, isCore) {
|
28036 | // Parameters sometimes have a type that can be referenced. If so, then use it, otherwise
|
28037 | // its type is undefined.
|
28038 | const type = param.typeValueReference.kind !== 2 /* UNAVAILABLE */ ?
|
28039 | valueReferenceToExpression(param.typeValueReference, defaultImportRecorder) :
|
28040 | new LiteralExpr(undefined);
|
28041 | const mapEntries = [
|
28042 | { key: 'type', value: type, quoted: false },
|
28043 | ];
|
28044 | // If the parameter has decorators, include the ones from Angular.
|
28045 | if (param.decorators !== null) {
|
28046 | const ngDecorators = param.decorators.filter(dec => isAngularDecorator$1(dec, isCore))
|
28047 | .map((decorator) => decoratorToMetadata(decorator));
|
28048 | const value = new WrappedNodeExpr(ts$1.createArrayLiteral(ngDecorators));
|
28049 | mapEntries.push({ key: 'decorators', value, quoted: false });
|
28050 | }
|
28051 | return literalMap(mapEntries);
|
28052 | }
|
28053 | /**
|
28054 | * Convert a reflected class member to metadata.
|
28055 | */
|
28056 | function classMemberToMetadata(name, decorators, isCore) {
|
28057 | const ngDecorators = decorators.filter(dec => isAngularDecorator$1(dec, isCore))
|
28058 | .map((decorator) => decoratorToMetadata(decorator));
|
28059 | const decoratorMeta = ts$1.createArrayLiteral(ngDecorators);
|
28060 | return ts$1.createPropertyAssignment(name, decoratorMeta);
|
28061 | }
|
28062 | /**
|
28063 | * Convert a reflected decorator to metadata.
|
28064 | */
|
28065 | function decoratorToMetadata(decorator, wrapFunctionsInParens) {
|
28066 | if (decorator.identifier === null) {
|
28067 | throw new Error('Illegal state: synthesized decorator cannot be emitted in class metadata.');
|
28068 | }
|
28069 | // Decorators have a type.
|
28070 | const properties = [
|
28071 | ts$1.createPropertyAssignment('type', ts$1.getMutableClone(decorator.identifier)),
|
28072 | ];
|
28073 | // Sometimes they have arguments.
|
28074 | if (decorator.args !== null && decorator.args.length > 0) {
|
28075 | const args = decorator.args.map(arg => {
|
28076 | const expr = ts$1.getMutableClone(arg);
|
28077 | return wrapFunctionsInParens ? wrapFunctionExpressionsInParens(expr) : expr;
|
28078 | });
|
28079 | properties.push(ts$1.createPropertyAssignment('args', ts$1.createArrayLiteral(args)));
|
28080 | }
|
28081 | return ts$1.createObjectLiteral(properties, true);
|
28082 | }
|
28083 | /**
|
28084 | * Whether a given decorator should be treated as an Angular decorator.
|
28085 | *
|
28086 | * Either it's used in @angular/core, or it's imported from there.
|
28087 | */
|
28088 | function isAngularDecorator$1(decorator, isCore) {
|
28089 | return isCore || (decorator.import !== null && decorator.import.from === '@angular/core');
|
28090 | }
|
28091 | /**
|
28092 | * Recursively recreates all of the `Identifier` descendant nodes with a particular name inside
|
28093 | * of an AST node, thus removing any references to them. Useful if a particular node has to be t
|
28094 | * aken from one place any emitted to another one exactly as it has been written.
|
28095 | */
|
28096 | function removeIdentifierReferences(node, name) {
|
28097 | const result = ts$1.transform(node, [context => root => ts$1.visitNode(root, function walk(current) {
|
28098 | return ts$1.isIdentifier(current) && current.text === name ?
|
28099 | ts$1.createIdentifier(current.text) :
|
28100 | ts$1.visitEachChild(current, walk, context);
|
28101 | })]);
|
28102 | return result.transformed[0];
|
28103 | }
|
28104 |
|
28105 | /**
|
28106 | * @license
|
28107 | * Copyright Google LLC All Rights Reserved.
|
28108 | *
|
28109 | * Use of this source code is governed by an MIT-style license that can be
|
28110 | * found in the LICENSE file at https://angular.io/license
|
28111 | */
|
28112 | const EMPTY_OBJECT = {};
|
28113 | const FIELD_DECORATORS = [
|
28114 | 'Input', 'Output', 'ViewChild', 'ViewChildren', 'ContentChild', 'ContentChildren', 'HostBinding',
|
28115 | 'HostListener'
|
28116 | ];
|
28117 | const LIFECYCLE_HOOKS = new Set([
|
28118 | 'ngOnChanges', 'ngOnInit', 'ngOnDestroy', 'ngDoCheck', 'ngAfterViewInit', 'ngAfterViewChecked',
|
28119 | 'ngAfterContentInit', 'ngAfterContentChecked'
|
28120 | ]);
|
28121 | class DirectiveDecoratorHandler {
|
28122 | constructor(reflector, evaluator, metaRegistry, scopeRegistry, metaReader, defaultImportRecorder, injectableRegistry, isCore, annotateForClosureCompiler, compileUndecoratedClassesWithAngularFeatures) {
|
28123 | this.reflector = reflector;
|
28124 | this.evaluator = evaluator;
|
28125 | this.metaRegistry = metaRegistry;
|
28126 | this.scopeRegistry = scopeRegistry;
|
28127 | this.metaReader = metaReader;
|
28128 | this.defaultImportRecorder = defaultImportRecorder;
|
28129 | this.injectableRegistry = injectableRegistry;
|
28130 | this.isCore = isCore;
|
28131 | this.annotateForClosureCompiler = annotateForClosureCompiler;
|
28132 | this.compileUndecoratedClassesWithAngularFeatures = compileUndecoratedClassesWithAngularFeatures;
|
28133 | this.precedence = HandlerPrecedence.PRIMARY;
|
28134 | this.name = DirectiveDecoratorHandler.name;
|
28135 | }
|
28136 | detect(node, decorators) {
|
28137 | // If a class is undecorated but uses Angular features, we detect it as an
|
28138 | // abstract directive. This is an unsupported pattern as of v10, but we want
|
28139 | // to still detect these patterns so that we can report diagnostics, or compile
|
28140 | // them for backwards compatibility in ngcc.
|
28141 | if (!decorators) {
|
28142 | const angularField = this.findClassFieldWithAngularFeatures(node);
|
28143 | return angularField ? { trigger: angularField.node, decorator: null, metadata: null } :
|
28144 | undefined;
|
28145 | }
|
28146 | else {
|
28147 | const decorator = findAngularDecorator(decorators, 'Directive', this.isCore);
|
28148 | return decorator ? { trigger: decorator.node, decorator, metadata: decorator } : undefined;
|
28149 | }
|
28150 | }
|
28151 | analyze(node, decorator, flags = HandlerFlags.NONE) {
|
28152 | // Skip processing of the class declaration if compilation of undecorated classes
|
28153 | // with Angular features is disabled. Previously in ngtsc, such classes have always
|
28154 | // been processed, but we want to enforce a consistent decorator mental model.
|
28155 | // See: https://v9.angular.io/guide/migration-undecorated-classes.
|
28156 | if (this.compileUndecoratedClassesWithAngularFeatures === false && decorator === null) {
|
28157 | return { diagnostics: [getUndecoratedClassWithAngularFeaturesDiagnostic(node)] };
|
28158 | }
|
28159 | const directiveResult = extractDirectiveMetadata(node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore, flags, this.annotateForClosureCompiler);
|
28160 | if (directiveResult === undefined) {
|
28161 | return {};
|
28162 | }
|
28163 | const analysis = directiveResult.metadata;
|
28164 | let providersRequiringFactory = null;
|
28165 | if (directiveResult !== undefined && directiveResult.decorator.has('providers')) {
|
28166 | providersRequiringFactory = resolveProvidersRequiringFactory(directiveResult.decorator.get('providers'), this.reflector, this.evaluator);
|
28167 | }
|
28168 | return {
|
28169 | analysis: {
|
28170 | inputs: directiveResult.inputs,
|
28171 | outputs: directiveResult.outputs,
|
28172 | meta: analysis,
|
28173 | metadataStmt: generateSetClassMetadataCall(node, this.reflector, this.defaultImportRecorder, this.isCore, this.annotateForClosureCompiler),
|
28174 | baseClass: readBaseClass$1(node, this.reflector, this.evaluator),
|
28175 | typeCheckMeta: extractDirectiveTypeCheckMeta(node, directiveResult.inputs, this.reflector),
|
28176 | providersRequiringFactory,
|
28177 | isPoisoned: false,
|
28178 | isStructural: directiveResult.isStructural,
|
28179 | }
|
28180 | };
|
28181 | }
|
28182 | register(node, analysis) {
|
28183 | // Register this directive's information with the `MetadataRegistry`. This ensures that
|
28184 | // the information about the directive is available during the compile() phase.
|
28185 | const ref = new Reference$1(node);
|
28186 | this.metaRegistry.registerDirectiveMetadata(Object.assign(Object.assign({ ref, name: node.name.text, selector: analysis.meta.selector, exportAs: analysis.meta.exportAs, inputs: analysis.inputs, outputs: analysis.outputs, queries: analysis.meta.queries.map(query => query.propertyName), isComponent: false, baseClass: analysis.baseClass }, analysis.typeCheckMeta), { isPoisoned: analysis.isPoisoned, isStructural: analysis.isStructural }));
|
28187 | this.injectableRegistry.registerInjectable(node);
|
28188 | }
|
28189 | resolve(node, analysis) {
|
28190 | const diagnostics = [];
|
28191 | if (analysis.providersRequiringFactory !== null &&
|
28192 | analysis.meta.providers instanceof WrappedNodeExpr) {
|
28193 | const providerDiagnostics = getProviderDiagnostics(analysis.providersRequiringFactory, analysis.meta.providers.node, this.injectableRegistry);
|
28194 | diagnostics.push(...providerDiagnostics);
|
28195 | }
|
28196 | const directiveDiagnostics = getDirectiveDiagnostics(node, this.metaReader, this.evaluator, this.reflector, this.scopeRegistry, 'Directive');
|
28197 | if (directiveDiagnostics !== null) {
|
28198 | diagnostics.push(...directiveDiagnostics);
|
28199 | }
|
28200 | return { diagnostics: diagnostics.length > 0 ? diagnostics : undefined };
|
28201 | }
|
28202 | compileFull(node, analysis, resolution, pool) {
|
28203 | const def = compileDirectiveFromMetadata(analysis.meta, pool, makeBindingParser());
|
28204 | return this.compileDirective(analysis, def);
|
28205 | }
|
28206 | compilePartial(node, analysis, resolution) {
|
28207 | const def = compileDeclareDirectiveFromMetadata(analysis.meta);
|
28208 | return this.compileDirective(analysis, def);
|
28209 | }
|
28210 | compileDirective(analysis, { expression: initializer, type }) {
|
28211 | const factoryRes = compileNgFactoryDefField(Object.assign(Object.assign({}, analysis.meta), { injectFn: Identifiers.directiveInject, target: R3FactoryTarget.Directive }));
|
28212 | if (analysis.metadataStmt !== null) {
|
28213 | factoryRes.statements.push(analysis.metadataStmt);
|
28214 | }
|
28215 | return [
|
28216 | factoryRes, {
|
28217 | name: 'ɵdir',
|
28218 | initializer,
|
28219 | statements: [],
|
28220 | type,
|
28221 | }
|
28222 | ];
|
28223 | }
|
28224 | /**
|
28225 | * Checks if a given class uses Angular features and returns the TypeScript node
|
28226 | * that indicated the usage. Classes are considered using Angular features if they
|
28227 | * contain class members that are either decorated with a known Angular decorator,
|
28228 | * or if they correspond to a known Angular lifecycle hook.
|
28229 | */
|
28230 | findClassFieldWithAngularFeatures(node) {
|
28231 | return this.reflector.getMembersOfClass(node).find(member => {
|
28232 | if (!member.isStatic && member.kind === ClassMemberKind.Method &&
|
28233 | LIFECYCLE_HOOKS.has(member.name)) {
|
28234 | return true;
|
28235 | }
|
28236 | if (member.decorators) {
|
28237 | return member.decorators.some(decorator => FIELD_DECORATORS.some(decoratorName => isAngularDecorator(decorator, decoratorName, this.isCore)));
|
28238 | }
|
28239 | return false;
|
28240 | });
|
28241 | }
|
28242 | }
|
28243 | /**
|
28244 | * Helper function to extract metadata from a `Directive` or `Component`. `Directive`s without a
|
28245 | * selector are allowed to be used for abstract base classes. These abstract directives should not
|
28246 | * appear in the declarations of an `NgModule` and additional verification is done when processing
|
28247 | * the module.
|
28248 | */
|
28249 | function extractDirectiveMetadata(clazz, decorator, reflector, evaluator, defaultImportRecorder, isCore, flags, annotateForClosureCompiler, defaultSelector = null) {
|
28250 | let directive;
|
28251 | if (decorator === null || decorator.args === null || decorator.args.length === 0) {
|
28252 | directive = new Map();
|
28253 | }
|
28254 | else if (decorator.args.length !== 1) {
|
28255 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARITY_WRONG, Decorator.nodeForError(decorator), `Incorrect number of arguments to @${decorator.name} decorator`);
|
28256 | }
|
28257 | else {
|
28258 | const meta = unwrapExpression(decorator.args[0]);
|
28259 | if (!ts$1.isObjectLiteralExpression(meta)) {
|
28260 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARG_NOT_LITERAL, meta, `@${decorator.name} argument must be an object literal`);
|
28261 | }
|
28262 | directive = reflectObjectLiteral(meta);
|
28263 | }
|
28264 | if (directive.has('jit')) {
|
28265 | // The only allowed value is true, so there's no need to expand further.
|
28266 | return undefined;
|
28267 | }
|
28268 | const members = reflector.getMembersOfClass(clazz);
|
28269 | // Precompute a list of ts.ClassElements that have decorators. This includes things like @Input,
|
28270 | // @Output, @HostBinding, etc.
|
28271 | const decoratedElements = members.filter(member => !member.isStatic && member.decorators !== null);
|
28272 | const coreModule = isCore ? undefined : '@angular/core';
|
28273 | // Construct the map of inputs both from the @Directive/@Component
|
28274 | // decorator, and the decorated
|
28275 | // fields.
|
28276 | const inputsFromMeta = parseFieldToPropertyMapping(directive, 'inputs', evaluator);
|
28277 | const inputsFromFields = parseDecoratedFields(filterToMembersWithDecorator(decoratedElements, 'Input', coreModule), evaluator, resolveInput);
|
28278 | // And outputs.
|
28279 | const outputsFromMeta = parseFieldToPropertyMapping(directive, 'outputs', evaluator);
|
28280 | const outputsFromFields = parseDecoratedFields(filterToMembersWithDecorator(decoratedElements, 'Output', coreModule), evaluator, resolveOutput);
|
28281 | // Construct the list of queries.
|
28282 | const contentChildFromFields = queriesFromFields(filterToMembersWithDecorator(decoratedElements, 'ContentChild', coreModule), reflector, evaluator);
|
28283 | const contentChildrenFromFields = queriesFromFields(filterToMembersWithDecorator(decoratedElements, 'ContentChildren', coreModule), reflector, evaluator);
|
28284 | const queries = [...contentChildFromFields, ...contentChildrenFromFields];
|
28285 | // Construct the list of view queries.
|
28286 | const viewChildFromFields = queriesFromFields(filterToMembersWithDecorator(decoratedElements, 'ViewChild', coreModule), reflector, evaluator);
|
28287 | const viewChildrenFromFields = queriesFromFields(filterToMembersWithDecorator(decoratedElements, 'ViewChildren', coreModule), reflector, evaluator);
|
28288 | const viewQueries = [...viewChildFromFields, ...viewChildrenFromFields];
|
28289 | if (directive.has('queries')) {
|
28290 | const queriesFromDecorator = extractQueriesFromDecorator(directive.get('queries'), reflector, evaluator, isCore);
|
28291 | queries.push(...queriesFromDecorator.content);
|
28292 | viewQueries.push(...queriesFromDecorator.view);
|
28293 | }
|
28294 | // Parse the selector.
|
28295 | let selector = defaultSelector;
|
28296 | if (directive.has('selector')) {
|
28297 | const expr = directive.get('selector');
|
28298 | const resolved = evaluator.evaluate(expr);
|
28299 | if (typeof resolved !== 'string') {
|
28300 | throw createValueHasWrongTypeError(expr, resolved, `selector must be a string`);
|
28301 | }
|
28302 | // use default selector in case selector is an empty string
|
28303 | selector = resolved === '' ? defaultSelector : resolved;
|
28304 | if (!selector) {
|
28305 | throw new FatalDiagnosticError(ErrorCode.DIRECTIVE_MISSING_SELECTOR, expr, `Directive ${clazz.name.text} has no selector, please add it!`);
|
28306 | }
|
28307 | }
|
28308 | const host = extractHostBindings$1(decoratedElements, evaluator, coreModule, directive);
|
28309 | const providers = directive.has('providers') ?
|
28310 | new WrappedNodeExpr(annotateForClosureCompiler ?
|
28311 | wrapFunctionExpressionsInParens(directive.get('providers')) :
|
28312 | directive.get('providers')) :
|
28313 | null;
|
28314 | // Determine if `ngOnChanges` is a lifecycle hook defined on the component.
|
28315 | const usesOnChanges = members.some(member => !member.isStatic && member.kind === ClassMemberKind.Method &&
|
28316 | member.name === 'ngOnChanges');
|
28317 | // Parse exportAs.
|
28318 | let exportAs = null;
|
28319 | if (directive.has('exportAs')) {
|
28320 | const expr = directive.get('exportAs');
|
28321 | const resolved = evaluator.evaluate(expr);
|
28322 | if (typeof resolved !== 'string') {
|
28323 | throw createValueHasWrongTypeError(expr, resolved, `exportAs must be a string`);
|
28324 | }
|
28325 | exportAs = resolved.split(',').map(part => part.trim());
|
28326 | }
|
28327 | const rawCtorDeps = getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
28328 | let ctorDeps;
|
28329 | // Non-abstract directives (those with a selector) require valid constructor dependencies, whereas
|
28330 | // abstract directives are allowed to have invalid dependencies, given that a subclass may call
|
28331 | // the constructor explicitly.
|
28332 | if (selector !== null) {
|
28333 | ctorDeps = validateConstructorDependencies(clazz, rawCtorDeps);
|
28334 | }
|
28335 | else {
|
28336 | ctorDeps = unwrapConstructorDependencies(rawCtorDeps);
|
28337 | }
|
28338 | const isStructural = ctorDeps !== null && ctorDeps !== 'invalid' && ctorDeps.some(dep => {
|
28339 | if (dep.resolved !== R3ResolvedDependencyType.Token || !(dep.token instanceof ExternalExpr)) {
|
28340 | return false;
|
28341 | }
|
28342 | if (dep.token.value.moduleName !== '@angular/core' || dep.token.value.name !== 'TemplateRef') {
|
28343 | return false;
|
28344 | }
|
28345 | return true;
|
28346 | });
|
28347 | // Detect if the component inherits from another class
|
28348 | const usesInheritance = reflector.hasBaseClass(clazz);
|
28349 | const type = wrapTypeReference(reflector, clazz);
|
28350 | const internalType = new WrappedNodeExpr(reflector.getInternalNameOfClass(clazz));
|
28351 | const inputs = ClassPropertyMapping.fromMappedObject(Object.assign(Object.assign({}, inputsFromMeta), inputsFromFields));
|
28352 | const outputs = ClassPropertyMapping.fromMappedObject(Object.assign(Object.assign({}, outputsFromMeta), outputsFromFields));
|
28353 | const metadata = {
|
28354 | name: clazz.name.text,
|
28355 | deps: ctorDeps,
|
28356 | host,
|
28357 | lifecycle: {
|
28358 | usesOnChanges,
|
28359 | },
|
28360 | inputs: inputs.toJointMappedObject(),
|
28361 | outputs: outputs.toDirectMappedObject(),
|
28362 | queries,
|
28363 | viewQueries,
|
28364 | selector,
|
28365 | fullInheritance: !!(flags & HandlerFlags.FULL_INHERITANCE),
|
28366 | type,
|
28367 | internalType,
|
28368 | typeArgumentCount: reflector.getGenericArityOfClass(clazz) || 0,
|
28369 | typeSourceSpan: createSourceSpan(clazz.name),
|
28370 | usesInheritance,
|
28371 | exportAs,
|
28372 | providers
|
28373 | };
|
28374 | return {
|
28375 | decorator: directive,
|
28376 | metadata,
|
28377 | inputs,
|
28378 | outputs,
|
28379 | isStructural,
|
28380 | };
|
28381 | }
|
28382 | function extractQueryMetadata(exprNode, name, args, propertyName, reflector, evaluator) {
|
28383 | if (args.length === 0) {
|
28384 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARITY_WRONG, exprNode, `@${name} must have arguments`);
|
28385 | }
|
28386 | const first = name === 'ViewChild' || name === 'ContentChild';
|
28387 | const node = unwrapForwardRef(args[0], reflector);
|
28388 | const arg = evaluator.evaluate(node);
|
28389 | /** Whether or not this query should collect only static results (see view/api.ts) */
|
28390 | let isStatic = false;
|
28391 | // Extract the predicate
|
28392 | let predicate = null;
|
28393 | if (arg instanceof Reference$1 || arg instanceof DynamicValue) {
|
28394 | // References and predicates that could not be evaluated statically are emitted as is.
|
28395 | predicate = new WrappedNodeExpr(node);
|
28396 | }
|
28397 | else if (typeof arg === 'string') {
|
28398 | predicate = [arg];
|
28399 | }
|
28400 | else if (isStringArrayOrDie(arg, `@${name} predicate`, node)) {
|
28401 | predicate = arg;
|
28402 | }
|
28403 | else {
|
28404 | throw createValueHasWrongTypeError(node, arg, `@${name} predicate cannot be interpreted`);
|
28405 | }
|
28406 | // Extract the read and descendants options.
|
28407 | let read = null;
|
28408 | // The default value for descendants is true for every decorator except @ContentChildren.
|
28409 | let descendants = name !== 'ContentChildren';
|
28410 | let emitDistinctChangesOnly = emitDistinctChangesOnlyDefaultValue;
|
28411 | if (args.length === 2) {
|
28412 | const optionsExpr = unwrapExpression(args[1]);
|
28413 | if (!ts$1.isObjectLiteralExpression(optionsExpr)) {
|
28414 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARG_NOT_LITERAL, optionsExpr, `@${name} options must be an object literal`);
|
28415 | }
|
28416 | const options = reflectObjectLiteral(optionsExpr);
|
28417 | if (options.has('read')) {
|
28418 | read = new WrappedNodeExpr(options.get('read'));
|
28419 | }
|
28420 | if (options.has('descendants')) {
|
28421 | const descendantsExpr = options.get('descendants');
|
28422 | const descendantsValue = evaluator.evaluate(descendantsExpr);
|
28423 | if (typeof descendantsValue !== 'boolean') {
|
28424 | throw createValueHasWrongTypeError(descendantsExpr, descendantsValue, `@${name} options.descendants must be a boolean`);
|
28425 | }
|
28426 | descendants = descendantsValue;
|
28427 | }
|
28428 | if (options.has('emitDistinctChangesOnly')) {
|
28429 | const emitDistinctChangesOnlyExpr = options.get('emitDistinctChangesOnly');
|
28430 | const emitDistinctChangesOnlyValue = evaluator.evaluate(emitDistinctChangesOnlyExpr);
|
28431 | if (typeof emitDistinctChangesOnlyValue !== 'boolean') {
|
28432 | throw createValueHasWrongTypeError(emitDistinctChangesOnlyExpr, emitDistinctChangesOnlyValue, `@${name} options.emitDistinctChangesOnlys must be a boolean`);
|
28433 | }
|
28434 | emitDistinctChangesOnly = emitDistinctChangesOnlyValue;
|
28435 | }
|
28436 | if (options.has('static')) {
|
28437 | const staticValue = evaluator.evaluate(options.get('static'));
|
28438 | if (typeof staticValue !== 'boolean') {
|
28439 | throw createValueHasWrongTypeError(node, staticValue, `@${name} options.static must be a boolean`);
|
28440 | }
|
28441 | isStatic = staticValue;
|
28442 | }
|
28443 | }
|
28444 | else if (args.length > 2) {
|
28445 | // Too many arguments.
|
28446 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARITY_WRONG, node, `@${name} has too many arguments`);
|
28447 | }
|
28448 | return {
|
28449 | propertyName,
|
28450 | predicate,
|
28451 | first,
|
28452 | descendants,
|
28453 | read,
|
28454 | static: isStatic,
|
28455 | emitDistinctChangesOnly,
|
28456 | };
|
28457 | }
|
28458 | function extractQueriesFromDecorator(queryData, reflector, evaluator, isCore) {
|
28459 | const content = [], view = [];
|
28460 | if (!ts$1.isObjectLiteralExpression(queryData)) {
|
28461 | throw new FatalDiagnosticError(ErrorCode.VALUE_HAS_WRONG_TYPE, queryData, 'Decorator queries metadata must be an object literal');
|
28462 | }
|
28463 | reflectObjectLiteral(queryData).forEach((queryExpr, propertyName) => {
|
28464 | queryExpr = unwrapExpression(queryExpr);
|
28465 | if (!ts$1.isNewExpression(queryExpr)) {
|
28466 | throw new FatalDiagnosticError(ErrorCode.VALUE_HAS_WRONG_TYPE, queryData, 'Decorator query metadata must be an instance of a query type');
|
28467 | }
|
28468 | const queryType = ts$1.isPropertyAccessExpression(queryExpr.expression) ?
|
28469 | queryExpr.expression.name :
|
28470 | queryExpr.expression;
|
28471 | if (!ts$1.isIdentifier(queryType)) {
|
28472 | throw new FatalDiagnosticError(ErrorCode.VALUE_HAS_WRONG_TYPE, queryData, 'Decorator query metadata must be an instance of a query type');
|
28473 | }
|
28474 | const type = reflector.getImportOfIdentifier(queryType);
|
28475 | if (type === null || (!isCore && type.from !== '@angular/core') ||
|
28476 | !QUERY_TYPES.has(type.name)) {
|
28477 | throw new FatalDiagnosticError(ErrorCode.VALUE_HAS_WRONG_TYPE, queryData, 'Decorator query metadata must be an instance of a query type');
|
28478 | }
|
28479 | const query = extractQueryMetadata(queryExpr, type.name, queryExpr.arguments || [], propertyName, reflector, evaluator);
|
28480 | if (type.name.startsWith('Content')) {
|
28481 | content.push(query);
|
28482 | }
|
28483 | else {
|
28484 | view.push(query);
|
28485 | }
|
28486 | });
|
28487 | return { content, view };
|
28488 | }
|
28489 | function isStringArrayOrDie(value, name, node) {
|
28490 | if (!Array.isArray(value)) {
|
28491 | return false;
|
28492 | }
|
28493 | for (let i = 0; i < value.length; i++) {
|
28494 | if (typeof value[i] !== 'string') {
|
28495 | throw createValueHasWrongTypeError(node, value[i], `Failed to resolve ${name} at position ${i} to a string`);
|
28496 | }
|
28497 | }
|
28498 | return true;
|
28499 | }
|
28500 | function parseFieldArrayValue(directive, field, evaluator) {
|
28501 | if (!directive.has(field)) {
|
28502 | return null;
|
28503 | }
|
28504 | // Resolve the field of interest from the directive metadata to a string[].
|
28505 | const expression = directive.get(field);
|
28506 | const value = evaluator.evaluate(expression);
|
28507 | if (!isStringArrayOrDie(value, field, expression)) {
|
28508 | throw createValueHasWrongTypeError(expression, value, `Failed to resolve @Directive.${field} to a string array`);
|
28509 | }
|
28510 | return value;
|
28511 | }
|
28512 | /**
|
28513 | * Interpret property mapping fields on the decorator (e.g. inputs or outputs) and return the
|
28514 | * correctly shaped metadata object.
|
28515 | */
|
28516 | function parseFieldToPropertyMapping(directive, field, evaluator) {
|
28517 | const metaValues = parseFieldArrayValue(directive, field, evaluator);
|
28518 | if (!metaValues) {
|
28519 | return EMPTY_OBJECT;
|
28520 | }
|
28521 | return metaValues.reduce((results, value) => {
|
28522 | // Either the value is 'field' or 'field: property'. In the first case, `property` will
|
28523 | // be undefined, in which case the field name should also be used as the property name.
|
28524 | const [field, property] = value.split(':', 2).map(str => str.trim());
|
28525 | results[field] = property || field;
|
28526 | return results;
|
28527 | }, {});
|
28528 | }
|
28529 | /**
|
28530 | * Parse property decorators (e.g. `Input` or `Output`) and return the correctly shaped metadata
|
28531 | * object.
|
28532 | */
|
28533 | function parseDecoratedFields(fields, evaluator, mapValueResolver) {
|
28534 | return fields.reduce((results, field) => {
|
28535 | const fieldName = field.member.name;
|
28536 | field.decorators.forEach(decorator => {
|
28537 | // The decorator either doesn't have an argument (@Input()) in which case the property
|
28538 | // name is used, or it has one argument (@Output('named')).
|
28539 | if (decorator.args == null || decorator.args.length === 0) {
|
28540 | results[fieldName] = fieldName;
|
28541 | }
|
28542 | else if (decorator.args.length === 1) {
|
28543 | const property = evaluator.evaluate(decorator.args[0]);
|
28544 | if (typeof property !== 'string') {
|
28545 | throw createValueHasWrongTypeError(Decorator.nodeForError(decorator), property, `@${decorator.name} decorator argument must resolve to a string`);
|
28546 | }
|
28547 | results[fieldName] = mapValueResolver(property, fieldName);
|
28548 | }
|
28549 | else {
|
28550 | // Too many arguments.
|
28551 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARITY_WRONG, Decorator.nodeForError(decorator), `@${decorator.name} can have at most one argument, got ${decorator.args.length} argument(s)`);
|
28552 | }
|
28553 | });
|
28554 | return results;
|
28555 | }, {});
|
28556 | }
|
28557 | function resolveInput(publicName, internalName) {
|
28558 | return [publicName, internalName];
|
28559 | }
|
28560 | function resolveOutput(publicName, internalName) {
|
28561 | return publicName;
|
28562 | }
|
28563 | function queriesFromFields(fields, reflector, evaluator) {
|
28564 | return fields.map(({ member, decorators }) => {
|
28565 | const decorator = decorators[0];
|
28566 | const node = member.node || Decorator.nodeForError(decorator);
|
28567 | // Throw in case of `@Input() @ContentChild('foo') foo: any`, which is not supported in Ivy
|
28568 | if (member.decorators.some(v => v.name === 'Input')) {
|
28569 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_COLLISION, node, 'Cannot combine @Input decorators with query decorators');
|
28570 | }
|
28571 | if (decorators.length !== 1) {
|
28572 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_COLLISION, node, 'Cannot have multiple query decorators on the same class member');
|
28573 | }
|
28574 | else if (!isPropertyTypeMember(member)) {
|
28575 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_UNEXPECTED, node, 'Query decorator must go on a property-type member');
|
28576 | }
|
28577 | return extractQueryMetadata(node, decorator.name, decorator.args || [], member.name, reflector, evaluator);
|
28578 | });
|
28579 | }
|
28580 | function isPropertyTypeMember(member) {
|
28581 | return member.kind === ClassMemberKind.Getter || member.kind === ClassMemberKind.Setter ||
|
28582 | member.kind === ClassMemberKind.Property;
|
28583 | }
|
28584 | function evaluateHostExpressionBindings(hostExpr, evaluator) {
|
28585 | const hostMetaMap = evaluator.evaluate(hostExpr);
|
28586 | if (!(hostMetaMap instanceof Map)) {
|
28587 | throw createValueHasWrongTypeError(hostExpr, hostMetaMap, `Decorator host metadata must be an object`);
|
28588 | }
|
28589 | const hostMetadata = {};
|
28590 | hostMetaMap.forEach((value, key) => {
|
28591 | // Resolve Enum references to their declared value.
|
28592 | if (value instanceof EnumValue) {
|
28593 | value = value.resolved;
|
28594 | }
|
28595 | if (typeof key !== 'string') {
|
28596 | throw createValueHasWrongTypeError(hostExpr, key, `Decorator host metadata must be a string -> string object, but found unparseable key`);
|
28597 | }
|
28598 | if (typeof value == 'string') {
|
28599 | hostMetadata[key] = value;
|
28600 | }
|
28601 | else if (value instanceof DynamicValue) {
|
28602 | hostMetadata[key] = new WrappedNodeExpr(value.node);
|
28603 | }
|
28604 | else {
|
28605 | throw createValueHasWrongTypeError(hostExpr, value, `Decorator host metadata must be a string -> string object, but found unparseable value`);
|
28606 | }
|
28607 | });
|
28608 | const bindings = parseHostBindings(hostMetadata);
|
28609 | const errors = verifyHostBindings(bindings, createSourceSpan(hostExpr));
|
28610 | if (errors.length > 0) {
|
28611 | throw new FatalDiagnosticError(
|
28612 | // TODO: provide more granular diagnostic and output specific host expression that
|
28613 | // triggered an error instead of the whole host object.
|
28614 | ErrorCode.HOST_BINDING_PARSE_ERROR, hostExpr, errors.map((error) => error.msg).join('\n'));
|
28615 | }
|
28616 | return bindings;
|
28617 | }
|
28618 | function extractHostBindings$1(members, evaluator, coreModule, metadata) {
|
28619 | let bindings;
|
28620 | if (metadata && metadata.has('host')) {
|
28621 | bindings = evaluateHostExpressionBindings(metadata.get('host'), evaluator);
|
28622 | }
|
28623 | else {
|
28624 | bindings = parseHostBindings({});
|
28625 | }
|
28626 | filterToMembersWithDecorator(members, 'HostBinding', coreModule)
|
28627 | .forEach(({ member, decorators }) => {
|
28628 | decorators.forEach(decorator => {
|
28629 | let hostPropertyName = member.name;
|
28630 | if (decorator.args !== null && decorator.args.length > 0) {
|
28631 | if (decorator.args.length !== 1) {
|
28632 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARITY_WRONG, Decorator.nodeForError(decorator), `@HostBinding can have at most one argument, got ${decorator.args.length} argument(s)`);
|
28633 | }
|
28634 | const resolved = evaluator.evaluate(decorator.args[0]);
|
28635 | if (typeof resolved !== 'string') {
|
28636 | throw createValueHasWrongTypeError(Decorator.nodeForError(decorator), resolved, `@HostBinding's argument must be a string`);
|
28637 | }
|
28638 | hostPropertyName = resolved;
|
28639 | }
|
28640 | // Since this is a decorator, we know that the value is a class member. Always access it
|
28641 | // through `this` so that further down the line it can't be confused for a literal value
|
28642 | // (e.g. if there's a property called `true`). There is no size penalty, because all
|
28643 | // values (except literals) are converted to `ctx.propName` eventually.
|
28644 | bindings.properties[hostPropertyName] = getSafePropertyAccessString('this', member.name);
|
28645 | });
|
28646 | });
|
28647 | filterToMembersWithDecorator(members, 'HostListener', coreModule)
|
28648 | .forEach(({ member, decorators }) => {
|
28649 | decorators.forEach(decorator => {
|
28650 | let eventName = member.name;
|
28651 | let args = [];
|
28652 | if (decorator.args !== null && decorator.args.length > 0) {
|
28653 | if (decorator.args.length > 2) {
|
28654 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARITY_WRONG, decorator.args[2], `@HostListener can have at most two arguments`);
|
28655 | }
|
28656 | const resolved = evaluator.evaluate(decorator.args[0]);
|
28657 | if (typeof resolved !== 'string') {
|
28658 | throw createValueHasWrongTypeError(decorator.args[0], resolved, `@HostListener's event name argument must be a string`);
|
28659 | }
|
28660 | eventName = resolved;
|
28661 | if (decorator.args.length === 2) {
|
28662 | const expression = decorator.args[1];
|
28663 | const resolvedArgs = evaluator.evaluate(decorator.args[1]);
|
28664 | if (!isStringArrayOrDie(resolvedArgs, '@HostListener.args', expression)) {
|
28665 | throw createValueHasWrongTypeError(decorator.args[1], resolvedArgs, `@HostListener's second argument must be a string array`);
|
28666 | }
|
28667 | args = resolvedArgs;
|
28668 | }
|
28669 | }
|
28670 | bindings.listeners[eventName] = `${member.name}(${args.join(',')})`;
|
28671 | });
|
28672 | });
|
28673 | return bindings;
|
28674 | }
|
28675 | const QUERY_TYPES = new Set([
|
28676 | 'ContentChild',
|
28677 | 'ContentChildren',
|
28678 | 'ViewChild',
|
28679 | 'ViewChildren',
|
28680 | ]);
|
28681 |
|
28682 | /**
|
28683 | * @license
|
28684 | * Copyright Google LLC All Rights Reserved.
|
28685 | *
|
28686 | * Use of this source code is governed by an MIT-style license that can be
|
28687 | * found in the LICENSE file at https://angular.io/license
|
28688 | */
|
28689 | const EMPTY_MAP = new Map();
|
28690 | const EMPTY_ARRAY = [];
|
28691 | /**
|
28692 | * `DecoratorHandler` which handles the `@Component` annotation.
|
28693 | */
|
28694 | class ComponentDecoratorHandler {
|
28695 | constructor(reflector, evaluator, metaRegistry, metaReader, scopeReader, scopeRegistry, typeCheckScopeRegistry, resourceRegistry, isCore, resourceLoader, rootDirs, defaultPreserveWhitespaces, i18nUseExternalIds, enableI18nLegacyMessageIdFormat, usePoisonedData, i18nNormalizeLineEndingsInICUs, moduleResolver, cycleAnalyzer, cycleHandlingStrategy, refEmitter, defaultImportRecorder, depTracker, injectableRegistry, annotateForClosureCompiler) {
|
28696 | this.reflector = reflector;
|
28697 | this.evaluator = evaluator;
|
28698 | this.metaRegistry = metaRegistry;
|
28699 | this.metaReader = metaReader;
|
28700 | this.scopeReader = scopeReader;
|
28701 | this.scopeRegistry = scopeRegistry;
|
28702 | this.typeCheckScopeRegistry = typeCheckScopeRegistry;
|
28703 | this.resourceRegistry = resourceRegistry;
|
28704 | this.isCore = isCore;
|
28705 | this.resourceLoader = resourceLoader;
|
28706 | this.rootDirs = rootDirs;
|
28707 | this.defaultPreserveWhitespaces = defaultPreserveWhitespaces;
|
28708 | this.i18nUseExternalIds = i18nUseExternalIds;
|
28709 | this.enableI18nLegacyMessageIdFormat = enableI18nLegacyMessageIdFormat;
|
28710 | this.usePoisonedData = usePoisonedData;
|
28711 | this.i18nNormalizeLineEndingsInICUs = i18nNormalizeLineEndingsInICUs;
|
28712 | this.moduleResolver = moduleResolver;
|
28713 | this.cycleAnalyzer = cycleAnalyzer;
|
28714 | this.cycleHandlingStrategy = cycleHandlingStrategy;
|
28715 | this.refEmitter = refEmitter;
|
28716 | this.defaultImportRecorder = defaultImportRecorder;
|
28717 | this.depTracker = depTracker;
|
28718 | this.injectableRegistry = injectableRegistry;
|
28719 | this.annotateForClosureCompiler = annotateForClosureCompiler;
|
28720 | this.literalCache = new Map();
|
28721 | this.elementSchemaRegistry = new DomElementSchemaRegistry();
|
28722 | /**
|
28723 | * During the asynchronous preanalyze phase, it's necessary to parse the template to extract
|
28724 | * any potential <link> tags which might need to be loaded. This cache ensures that work is not
|
28725 | * thrown away, and the parsed template is reused during the analyze phase.
|
28726 | */
|
28727 | this.preanalyzeTemplateCache = new Map();
|
28728 | this.precedence = HandlerPrecedence.PRIMARY;
|
28729 | this.name = ComponentDecoratorHandler.name;
|
28730 | }
|
28731 | detect(node, decorators) {
|
28732 | if (!decorators) {
|
28733 | return undefined;
|
28734 | }
|
28735 | const decorator = findAngularDecorator(decorators, 'Component', this.isCore);
|
28736 | if (decorator !== undefined) {
|
28737 | return {
|
28738 | trigger: decorator.node,
|
28739 | decorator,
|
28740 | metadata: decorator,
|
28741 | };
|
28742 | }
|
28743 | else {
|
28744 | return undefined;
|
28745 | }
|
28746 | }
|
28747 | preanalyze(node, decorator) {
|
28748 | // In preanalyze, resource URLs associated with the component are asynchronously preloaded via
|
28749 | // the resourceLoader. This is the only time async operations are allowed for a component.
|
28750 | // These resources are:
|
28751 | //
|
28752 | // - the templateUrl, if there is one
|
28753 | // - any styleUrls if present
|
28754 | // - any stylesheets referenced from <link> tags in the template itself
|
28755 | //
|
28756 | // As a result of the last one, the template must be parsed as part of preanalysis to extract
|
28757 | // <link> tags, which may involve waiting for the templateUrl to be resolved first.
|
28758 | // If preloading isn't possible, then skip this step.
|
28759 | if (!this.resourceLoader.canPreload) {
|
28760 | return undefined;
|
28761 | }
|
28762 | const meta = this._resolveLiteral(decorator);
|
28763 | const component = reflectObjectLiteral(meta);
|
28764 | const containingFile = node.getSourceFile().fileName;
|
28765 | const resolveStyleUrl = (styleUrl, nodeForError, resourceType) => {
|
28766 | const resourceUrl = this._resolveResourceOrThrow(styleUrl, containingFile, nodeForError, resourceType);
|
28767 | return this.resourceLoader.preload(resourceUrl);
|
28768 | };
|
28769 | // A Promise that waits for the template and all <link>ed styles within it to be preloaded.
|
28770 | const templateAndTemplateStyleResources = this._preloadAndParseTemplate(node, decorator, component, containingFile)
|
28771 | .then((template) => {
|
28772 | if (template === null) {
|
28773 | return undefined;
|
28774 | }
|
28775 | const nodeForError = getTemplateDeclarationNodeForError(template.declaration);
|
28776 | return Promise
|
28777 | .all(template.styleUrls.map(styleUrl => resolveStyleUrl(styleUrl, nodeForError, 1 /* StylesheetFromTemplate */)))
|
28778 | .then(() => undefined);
|
28779 | });
|
28780 | // Extract all the styleUrls in the decorator.
|
28781 | const componentStyleUrls = this._extractComponentStyleUrls(component);
|
28782 | if (componentStyleUrls === null) {
|
28783 | // A fast path exists if there are no styleUrls, to just wait for
|
28784 | // templateAndTemplateStyleResources.
|
28785 | return templateAndTemplateStyleResources;
|
28786 | }
|
28787 | else {
|
28788 | // Wait for both the template and all styleUrl resources to resolve.
|
28789 | return Promise
|
28790 | .all([
|
28791 | templateAndTemplateStyleResources,
|
28792 | ...componentStyleUrls.map(styleUrl => resolveStyleUrl(styleUrl.url, styleUrl.nodeForError, 2 /* StylesheetFromDecorator */))
|
28793 | ])
|
28794 | .then(() => undefined);
|
28795 | }
|
28796 | }
|
28797 | analyze(node, decorator, flags = HandlerFlags.NONE) {
|
28798 | var _a;
|
28799 | const containingFile = node.getSourceFile().fileName;
|
28800 | this.literalCache.delete(decorator);
|
28801 | // @Component inherits @Directive, so begin by extracting the @Directive metadata and building
|
28802 | // on it.
|
28803 | const directiveResult = extractDirectiveMetadata(node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore, flags, this.annotateForClosureCompiler, this.elementSchemaRegistry.getDefaultComponentElementName());
|
28804 | if (directiveResult === undefined) {
|
28805 | // `extractDirectiveMetadata` returns undefined when the @Directive has `jit: true`. In this
|
28806 | // case, compilation of the decorator is skipped. Returning an empty object signifies
|
28807 | // that no analysis was produced.
|
28808 | return {};
|
28809 | }
|
28810 | // Next, read the `@Component`-specific fields.
|
28811 | const { decorator: component, metadata, inputs, outputs } = directiveResult;
|
28812 | // Go through the root directories for this project, and select the one with the smallest
|
28813 | // relative path representation.
|
28814 | const relativeContextFilePath = this.rootDirs.reduce((previous, rootDir) => {
|
28815 | const candidate = relative(absoluteFrom(rootDir), absoluteFrom(containingFile));
|
28816 | if (previous === undefined || candidate.length < previous.length) {
|
28817 | return candidate;
|
28818 | }
|
28819 | else {
|
28820 | return previous;
|
28821 | }
|
28822 | }, undefined);
|
28823 | // Note that we could technically combine the `viewProvidersRequiringFactory` and
|
28824 | // `providersRequiringFactory` into a single set, but we keep the separate so that
|
28825 | // we can distinguish where an error is coming from when logging the diagnostics in `resolve`.
|
28826 | let viewProvidersRequiringFactory = null;
|
28827 | let providersRequiringFactory = null;
|
28828 | let wrappedViewProviders = null;
|
28829 | if (component.has('viewProviders')) {
|
28830 | const viewProviders = component.get('viewProviders');
|
28831 | viewProvidersRequiringFactory =
|
28832 | resolveProvidersRequiringFactory(viewProviders, this.reflector, this.evaluator);
|
28833 | wrappedViewProviders = new WrappedNodeExpr(this.annotateForClosureCompiler ? wrapFunctionExpressionsInParens(viewProviders) :
|
28834 | viewProviders);
|
28835 | }
|
28836 | if (component.has('providers')) {
|
28837 | providersRequiringFactory = resolveProvidersRequiringFactory(component.get('providers'), this.reflector, this.evaluator);
|
28838 | }
|
28839 | // Parse the template.
|
28840 | // If a preanalyze phase was executed, the template may already exist in parsed form, so check
|
28841 | // the preanalyzeTemplateCache.
|
28842 | // Extract a closure of the template parsing code so that it can be reparsed with different
|
28843 | // options if needed, like in the indexing pipeline.
|
28844 | let template;
|
28845 | if (this.preanalyzeTemplateCache.has(node)) {
|
28846 | // The template was parsed in preanalyze. Use it and delete it to save memory.
|
28847 | const preanalyzed = this.preanalyzeTemplateCache.get(node);
|
28848 | this.preanalyzeTemplateCache.delete(node);
|
28849 | template = preanalyzed;
|
28850 | }
|
28851 | else {
|
28852 | const templateDecl = this.parseTemplateDeclaration(decorator, component, containingFile);
|
28853 | template = this.extractTemplate(node, templateDecl);
|
28854 | }
|
28855 | const templateResource = template.isInline ? { path: null, expression: component.get('template') } : {
|
28856 | path: absoluteFrom(template.declaration.resolvedTemplateUrl),
|
28857 | expression: template.sourceMapping.node
|
28858 | };
|
28859 | // Figure out the set of styles. The ordering here is important: external resources (styleUrls)
|
28860 | // precede inline styles, and styles defined in the template override styles defined in the
|
28861 | // component.
|
28862 | let styles = [];
|
28863 | const styleResources = this._extractStyleResources(component, containingFile);
|
28864 | const styleUrls = [
|
28865 | ...this._extractComponentStyleUrls(component), ...this._extractTemplateStyleUrls(template)
|
28866 | ];
|
28867 | for (const styleUrl of styleUrls) {
|
28868 | const resourceType = styleUrl.source === 2 /* StylesheetFromDecorator */ ?
|
28869 | 2 /* StylesheetFromDecorator */ :
|
28870 | 1 /* StylesheetFromTemplate */;
|
28871 | const resourceUrl = this._resolveResourceOrThrow(styleUrl.url, containingFile, styleUrl.nodeForError, resourceType);
|
28872 | const resourceStr = this.resourceLoader.load(resourceUrl);
|
28873 | styles.push(resourceStr);
|
28874 | if (this.depTracker !== null) {
|
28875 | this.depTracker.addResourceDependency(node.getSourceFile(), absoluteFrom(resourceUrl));
|
28876 | }
|
28877 | }
|
28878 | let inlineStyles = null;
|
28879 | if (component.has('styles')) {
|
28880 | const litStyles = parseFieldArrayValue(component, 'styles', this.evaluator);
|
28881 | if (litStyles !== null) {
|
28882 | inlineStyles = [...litStyles];
|
28883 | styles.push(...litStyles);
|
28884 | }
|
28885 | }
|
28886 | if (template.styles.length > 0) {
|
28887 | styles.push(...template.styles);
|
28888 | }
|
28889 | const encapsulation = this._resolveEnumValue(component, 'encapsulation', 'ViewEncapsulation') || 0;
|
28890 | const changeDetection = this._resolveEnumValue(component, 'changeDetection', 'ChangeDetectionStrategy');
|
28891 | let animations = null;
|
28892 | if (component.has('animations')) {
|
28893 | animations = new WrappedNodeExpr(component.get('animations'));
|
28894 | }
|
28895 | const output = {
|
28896 | analysis: {
|
28897 | baseClass: readBaseClass$1(node, this.reflector, this.evaluator),
|
28898 | inputs,
|
28899 | outputs,
|
28900 | meta: Object.assign(Object.assign({}, metadata), { template: {
|
28901 | nodes: template.nodes,
|
28902 | ngContentSelectors: template.ngContentSelectors,
|
28903 | }, encapsulation, interpolation: (_a = template.interpolationConfig) !== null && _a !== void 0 ? _a : DEFAULT_INTERPOLATION_CONFIG, styles,
|
28904 | // These will be replaced during the compilation step, after all `NgModule`s have been
|
28905 | // analyzed and the full compilation scope for the component can be realized.
|
28906 | animations, viewProviders: wrappedViewProviders, i18nUseExternalIds: this.i18nUseExternalIds, relativeContextFilePath }),
|
28907 | typeCheckMeta: extractDirectiveTypeCheckMeta(node, inputs, this.reflector),
|
28908 | metadataStmt: generateSetClassMetadataCall(node, this.reflector, this.defaultImportRecorder, this.isCore, this.annotateForClosureCompiler),
|
28909 | template,
|
28910 | providersRequiringFactory,
|
28911 | viewProvidersRequiringFactory,
|
28912 | inlineStyles,
|
28913 | styleUrls,
|
28914 | resources: {
|
28915 | styles: styleResources,
|
28916 | template: templateResource,
|
28917 | },
|
28918 | isPoisoned: false,
|
28919 | },
|
28920 | };
|
28921 | if (changeDetection !== null) {
|
28922 | output.analysis.meta.changeDetection = changeDetection;
|
28923 | }
|
28924 | return output;
|
28925 | }
|
28926 | register(node, analysis) {
|
28927 | // Register this component's information with the `MetadataRegistry`. This ensures that
|
28928 | // the information about the component is available during the compile() phase.
|
28929 | const ref = new Reference$1(node);
|
28930 | this.metaRegistry.registerDirectiveMetadata(Object.assign(Object.assign({ ref, name: node.name.text, selector: analysis.meta.selector, exportAs: analysis.meta.exportAs, inputs: analysis.inputs, outputs: analysis.outputs, queries: analysis.meta.queries.map(query => query.propertyName), isComponent: true, baseClass: analysis.baseClass }, analysis.typeCheckMeta), { isPoisoned: analysis.isPoisoned, isStructural: false }));
|
28931 | this.resourceRegistry.registerResources(analysis.resources, node);
|
28932 | this.injectableRegistry.registerInjectable(node);
|
28933 | }
|
28934 | index(context, node, analysis) {
|
28935 | if (analysis.isPoisoned && !this.usePoisonedData) {
|
28936 | return null;
|
28937 | }
|
28938 | const scope = this.scopeReader.getScopeForComponent(node);
|
28939 | const selector = analysis.meta.selector;
|
28940 | const matcher = new SelectorMatcher();
|
28941 | if (scope !== null) {
|
28942 | if ((scope.compilation.isPoisoned || scope.exported.isPoisoned) && !this.usePoisonedData) {
|
28943 | // Don't bother indexing components which had erroneous scopes, unless specifically
|
28944 | // requested.
|
28945 | return null;
|
28946 | }
|
28947 | for (const directive of scope.compilation.directives) {
|
28948 | if (directive.selector !== null) {
|
28949 | matcher.addSelectables(CssSelector.parse(directive.selector), directive);
|
28950 | }
|
28951 | }
|
28952 | }
|
28953 | const binder = new R3TargetBinder(matcher);
|
28954 | const boundTemplate = binder.bind({ template: analysis.template.diagNodes });
|
28955 | context.addComponent({
|
28956 | declaration: node,
|
28957 | selector,
|
28958 | boundTemplate,
|
28959 | templateMeta: {
|
28960 | isInline: analysis.template.isInline,
|
28961 | file: analysis.template.file,
|
28962 | },
|
28963 | });
|
28964 | }
|
28965 | typeCheck(ctx, node, meta) {
|
28966 | if (this.typeCheckScopeRegistry === null || !ts$1.isClassDeclaration(node)) {
|
28967 | return;
|
28968 | }
|
28969 | if (meta.isPoisoned && !this.usePoisonedData) {
|
28970 | return;
|
28971 | }
|
28972 | const scope = this.typeCheckScopeRegistry.getTypeCheckScope(node);
|
28973 | if (scope.isPoisoned && !this.usePoisonedData) {
|
28974 | // Don't type-check components that had errors in their scopes, unless requested.
|
28975 | return;
|
28976 | }
|
28977 | const binder = new R3TargetBinder(scope.matcher);
|
28978 | ctx.addTemplate(new Reference$1(node), binder, meta.template.diagNodes, scope.pipes, scope.schemas, meta.template.sourceMapping, meta.template.file, meta.template.errors);
|
28979 | }
|
28980 | resolve(node, analysis) {
|
28981 | if (analysis.isPoisoned && !this.usePoisonedData) {
|
28982 | return {};
|
28983 | }
|
28984 | const context = node.getSourceFile();
|
28985 | // Check whether this component was registered with an NgModule. If so, it should be compiled
|
28986 | // under that module's compilation scope.
|
28987 | const scope = this.scopeReader.getScopeForComponent(node);
|
28988 | let metadata = analysis.meta;
|
28989 | const data = {
|
28990 | directives: EMPTY_ARRAY,
|
28991 | pipes: EMPTY_MAP,
|
28992 | declarationListEmitMode: 0 /* Direct */,
|
28993 | };
|
28994 | if (scope !== null && (!scope.compilation.isPoisoned || this.usePoisonedData)) {
|
28995 | const matcher = new SelectorMatcher();
|
28996 | for (const dir of scope.compilation.directives) {
|
28997 | if (dir.selector !== null) {
|
28998 | matcher.addSelectables(CssSelector.parse(dir.selector), dir);
|
28999 | }
|
29000 | }
|
29001 | const pipes = new Map();
|
29002 | for (const pipe of scope.compilation.pipes) {
|
29003 | pipes.set(pipe.name, pipe.ref);
|
29004 | }
|
29005 | // Next, the component template AST is bound using the R3TargetBinder. This produces a
|
29006 | // BoundTarget, which is similar to a ts.TypeChecker.
|
29007 | const binder = new R3TargetBinder(matcher);
|
29008 | const bound = binder.bind({ template: metadata.template.nodes });
|
29009 | const usedDirectives = bound.getUsedDirectives().map(directive => {
|
29010 | return {
|
29011 | ref: directive.ref,
|
29012 | type: this.refEmitter.emit(directive.ref, context),
|
29013 | selector: directive.selector,
|
29014 | inputs: directive.inputs.propertyNames,
|
29015 | outputs: directive.outputs.propertyNames,
|
29016 | exportAs: directive.exportAs,
|
29017 | isComponent: directive.isComponent,
|
29018 | };
|
29019 | });
|
29020 | const usedPipes = [];
|
29021 | for (const pipeName of bound.getUsedPipes()) {
|
29022 | if (!pipes.has(pipeName)) {
|
29023 | continue;
|
29024 | }
|
29025 | const pipe = pipes.get(pipeName);
|
29026 | usedPipes.push({
|
29027 | ref: pipe,
|
29028 | pipeName,
|
29029 | expression: this.refEmitter.emit(pipe, context),
|
29030 | });
|
29031 | }
|
29032 | // Scan through the directives/pipes actually used in the template and check whether any
|
29033 | // import which needs to be generated would create a cycle.
|
29034 | const cyclesFromDirectives = new Map();
|
29035 | for (const usedDirective of usedDirectives) {
|
29036 | const cycle = this._checkForCyclicImport(usedDirective.ref, usedDirective.type, context);
|
29037 | if (cycle !== null) {
|
29038 | cyclesFromDirectives.set(usedDirective, cycle);
|
29039 | }
|
29040 | }
|
29041 | const cyclesFromPipes = new Map();
|
29042 | for (const usedPipe of usedPipes) {
|
29043 | const cycle = this._checkForCyclicImport(usedPipe.ref, usedPipe.expression, context);
|
29044 | if (cycle !== null) {
|
29045 | cyclesFromPipes.set(usedPipe, cycle);
|
29046 | }
|
29047 | }
|
29048 | if (cyclesFromDirectives.size === 0 && cyclesFromPipes.size === 0) {
|
29049 | // No cycle was detected. Record the imports that need to be created in the cycle detector
|
29050 | // so that future cyclic import checks consider their production.
|
29051 | for (const { type } of usedDirectives) {
|
29052 | this._recordSyntheticImport(type, context);
|
29053 | }
|
29054 | for (const { expression } of usedPipes) {
|
29055 | this._recordSyntheticImport(expression, context);
|
29056 | }
|
29057 | // Check whether the directive/pipe arrays in ɵcmp need to be wrapped in closures.
|
29058 | // This is required if any directive/pipe reference is to a declaration in the same file
|
29059 | // but declared after this component.
|
29060 | const wrapDirectivesAndPipesInClosure = usedDirectives.some(dir => isExpressionForwardReference(dir.type, node.name, context)) ||
|
29061 | usedPipes.some(pipe => isExpressionForwardReference(pipe.expression, node.name, context));
|
29062 | data.directives = usedDirectives;
|
29063 | data.pipes = new Map(usedPipes.map(pipe => [pipe.pipeName, pipe.expression]));
|
29064 | data.declarationListEmitMode = wrapDirectivesAndPipesInClosure ?
|
29065 | 1 /* Closure */ :
|
29066 | 0 /* Direct */;
|
29067 | }
|
29068 | else {
|
29069 | if (this.cycleHandlingStrategy === 0 /* UseRemoteScoping */) {
|
29070 | // Declaring the directiveDefs/pipeDefs arrays directly would require imports that would
|
29071 | // create a cycle. Instead, mark this component as requiring remote scoping, so that the
|
29072 | // NgModule file will take care of setting the directives for the component.
|
29073 | this.scopeRegistry.setComponentRemoteScope(node, usedDirectives.map(dir => dir.ref), usedPipes.map(pipe => pipe.ref));
|
29074 | }
|
29075 | else {
|
29076 | // We are not able to handle this cycle so throw an error.
|
29077 | const relatedMessages = [];
|
29078 | for (const [dir, cycle] of cyclesFromDirectives) {
|
29079 | relatedMessages.push(makeCyclicImportInfo(dir.ref, dir.isComponent ? 'component' : 'directive', cycle));
|
29080 | }
|
29081 | for (const [pipe, cycle] of cyclesFromPipes) {
|
29082 | relatedMessages.push(makeCyclicImportInfo(pipe.ref, 'pipe', cycle));
|
29083 | }
|
29084 | throw new FatalDiagnosticError(ErrorCode.IMPORT_CYCLE_DETECTED, node, 'One or more import cycles would need to be created to compile this component, ' +
|
29085 | 'which is not supported by the current compiler configuration.', relatedMessages);
|
29086 | }
|
29087 | }
|
29088 | }
|
29089 | const diagnostics = [];
|
29090 | if (analysis.providersRequiringFactory !== null &&
|
29091 | analysis.meta.providers instanceof WrappedNodeExpr) {
|
29092 | const providerDiagnostics = getProviderDiagnostics(analysis.providersRequiringFactory, analysis.meta.providers.node, this.injectableRegistry);
|
29093 | diagnostics.push(...providerDiagnostics);
|
29094 | }
|
29095 | if (analysis.viewProvidersRequiringFactory !== null &&
|
29096 | analysis.meta.viewProviders instanceof WrappedNodeExpr) {
|
29097 | const viewProviderDiagnostics = getProviderDiagnostics(analysis.viewProvidersRequiringFactory, analysis.meta.viewProviders.node, this.injectableRegistry);
|
29098 | diagnostics.push(...viewProviderDiagnostics);
|
29099 | }
|
29100 | const directiveDiagnostics = getDirectiveDiagnostics(node, this.metaReader, this.evaluator, this.reflector, this.scopeRegistry, 'Component');
|
29101 | if (directiveDiagnostics !== null) {
|
29102 | diagnostics.push(...directiveDiagnostics);
|
29103 | }
|
29104 | if (diagnostics.length > 0) {
|
29105 | return { diagnostics };
|
29106 | }
|
29107 | return { data };
|
29108 | }
|
29109 | updateResources(node, analysis) {
|
29110 | const containingFile = node.getSourceFile().fileName;
|
29111 | // If the template is external, re-parse it.
|
29112 | const templateDecl = analysis.template.declaration;
|
29113 | if (!templateDecl.isInline) {
|
29114 | analysis.template = this.extractTemplate(node, templateDecl);
|
29115 | }
|
29116 | // Update any external stylesheets and rebuild the combined 'styles' list.
|
29117 | // TODO(alxhub): write tests for styles when the primary compiler uses the updateResources path
|
29118 | let styles = [];
|
29119 | if (analysis.styleUrls !== null) {
|
29120 | for (const styleUrl of analysis.styleUrls) {
|
29121 | const resourceType = styleUrl.source === 2 /* StylesheetFromDecorator */ ?
|
29122 | 2 /* StylesheetFromDecorator */ :
|
29123 | 1 /* StylesheetFromTemplate */;
|
29124 | const resolvedStyleUrl = this._resolveResourceOrThrow(styleUrl.url, containingFile, styleUrl.nodeForError, resourceType);
|
29125 | const styleText = this.resourceLoader.load(resolvedStyleUrl);
|
29126 | styles.push(styleText);
|
29127 | }
|
29128 | }
|
29129 | if (analysis.inlineStyles !== null) {
|
29130 | for (const styleText of analysis.inlineStyles) {
|
29131 | styles.push(styleText);
|
29132 | }
|
29133 | }
|
29134 | for (const styleText of analysis.template.styles) {
|
29135 | styles.push(styleText);
|
29136 | }
|
29137 | analysis.meta.styles = styles;
|
29138 | }
|
29139 | compileFull(node, analysis, resolution, pool) {
|
29140 | if (analysis.template.errors !== null && analysis.template.errors.length > 0) {
|
29141 | return [];
|
29142 | }
|
29143 | const meta = Object.assign(Object.assign({}, analysis.meta), resolution);
|
29144 | const def = compileComponentFromMetadata(meta, pool, makeBindingParser());
|
29145 | return this.compileComponent(analysis, def);
|
29146 | }
|
29147 | compilePartial(node, analysis, resolution) {
|
29148 | if (analysis.template.errors !== null && analysis.template.errors.length > 0) {
|
29149 | return [];
|
29150 | }
|
29151 | const meta = Object.assign(Object.assign({}, analysis.meta), resolution);
|
29152 | const def = compileDeclareComponentFromMetadata(meta, analysis.template);
|
29153 | return this.compileComponent(analysis, def);
|
29154 | }
|
29155 | compileComponent(analysis, { expression: initializer, type }) {
|
29156 | const factoryRes = compileNgFactoryDefField(Object.assign(Object.assign({}, analysis.meta), { injectFn: Identifiers.directiveInject, target: R3FactoryTarget.Component }));
|
29157 | if (analysis.metadataStmt !== null) {
|
29158 | factoryRes.statements.push(analysis.metadataStmt);
|
29159 | }
|
29160 | return [
|
29161 | factoryRes, {
|
29162 | name: 'ɵcmp',
|
29163 | initializer,
|
29164 | statements: [],
|
29165 | type,
|
29166 | }
|
29167 | ];
|
29168 | }
|
29169 | _resolveLiteral(decorator) {
|
29170 | if (this.literalCache.has(decorator)) {
|
29171 | return this.literalCache.get(decorator);
|
29172 | }
|
29173 | if (decorator.args === null || decorator.args.length !== 1) {
|
29174 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARITY_WRONG, Decorator.nodeForError(decorator), `Incorrect number of arguments to @Component decorator`);
|
29175 | }
|
29176 | const meta = unwrapExpression(decorator.args[0]);
|
29177 | if (!ts$1.isObjectLiteralExpression(meta)) {
|
29178 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARG_NOT_LITERAL, meta, `Decorator argument must be literal.`);
|
29179 | }
|
29180 | this.literalCache.set(decorator, meta);
|
29181 | return meta;
|
29182 | }
|
29183 | _resolveEnumValue(component, field, enumSymbolName) {
|
29184 | let resolved = null;
|
29185 | if (component.has(field)) {
|
29186 | const expr = component.get(field);
|
29187 | const value = this.evaluator.evaluate(expr);
|
29188 | if (value instanceof EnumValue && isAngularCoreReference(value.enumRef, enumSymbolName)) {
|
29189 | resolved = value.resolved;
|
29190 | }
|
29191 | else {
|
29192 | throw createValueHasWrongTypeError(expr, value, `${field} must be a member of ${enumSymbolName} enum from @angular/core`);
|
29193 | }
|
29194 | }
|
29195 | return resolved;
|
29196 | }
|
29197 | _extractComponentStyleUrls(component) {
|
29198 | if (!component.has('styleUrls')) {
|
29199 | return [];
|
29200 | }
|
29201 | return this._extractStyleUrlsFromExpression(component.get('styleUrls'));
|
29202 | }
|
29203 | _extractStyleUrlsFromExpression(styleUrlsExpr) {
|
29204 | const styleUrls = [];
|
29205 | if (ts$1.isArrayLiteralExpression(styleUrlsExpr)) {
|
29206 | for (const styleUrlExpr of styleUrlsExpr.elements) {
|
29207 | if (ts$1.isSpreadElement(styleUrlExpr)) {
|
29208 | styleUrls.push(...this._extractStyleUrlsFromExpression(styleUrlExpr.expression));
|
29209 | }
|
29210 | else {
|
29211 | const styleUrl = this.evaluator.evaluate(styleUrlExpr);
|
29212 | if (typeof styleUrl !== 'string') {
|
29213 | throw createValueHasWrongTypeError(styleUrlExpr, styleUrl, 'styleUrl must be a string');
|
29214 | }
|
29215 | styleUrls.push({
|
29216 | url: styleUrl,
|
29217 | source: 2 /* StylesheetFromDecorator */,
|
29218 | nodeForError: styleUrlExpr,
|
29219 | });
|
29220 | }
|
29221 | }
|
29222 | }
|
29223 | else {
|
29224 | const evaluatedStyleUrls = this.evaluator.evaluate(styleUrlsExpr);
|
29225 | if (!isStringArray(evaluatedStyleUrls)) {
|
29226 | throw createValueHasWrongTypeError(styleUrlsExpr, evaluatedStyleUrls, 'styleUrls must be an array of strings');
|
29227 | }
|
29228 | for (const styleUrl of evaluatedStyleUrls) {
|
29229 | styleUrls.push({
|
29230 | url: styleUrl,
|
29231 | source: 2 /* StylesheetFromDecorator */,
|
29232 | nodeForError: styleUrlsExpr,
|
29233 | });
|
29234 | }
|
29235 | }
|
29236 | return styleUrls;
|
29237 | }
|
29238 | _extractStyleResources(component, containingFile) {
|
29239 | const styles = new Set();
|
29240 | function stringLiteralElements(array) {
|
29241 | return array.elements.filter((e) => ts$1.isStringLiteralLike(e));
|
29242 | }
|
29243 | // If styleUrls is a literal array, process each resource url individually and
|
29244 | // register ones that are string literals.
|
29245 | const styleUrlsExpr = component.get('styleUrls');
|
29246 | if (styleUrlsExpr !== undefined && ts$1.isArrayLiteralExpression(styleUrlsExpr)) {
|
29247 | for (const expression of stringLiteralElements(styleUrlsExpr)) {
|
29248 | const resourceUrl = this._resolveResourceOrThrow(expression.text, containingFile, expression, 2 /* StylesheetFromDecorator */);
|
29249 | styles.add({ path: absoluteFrom(resourceUrl), expression });
|
29250 | }
|
29251 | }
|
29252 | const stylesExpr = component.get('styles');
|
29253 | if (stylesExpr !== undefined && ts$1.isArrayLiteralExpression(stylesExpr)) {
|
29254 | for (const expression of stringLiteralElements(stylesExpr)) {
|
29255 | styles.add({ path: null, expression });
|
29256 | }
|
29257 | }
|
29258 | return styles;
|
29259 | }
|
29260 | _preloadAndParseTemplate(node, decorator, component, containingFile) {
|
29261 | if (component.has('templateUrl')) {
|
29262 | // Extract the templateUrl and preload it.
|
29263 | const templateUrlExpr = component.get('templateUrl');
|
29264 | const templateUrl = this.evaluator.evaluate(templateUrlExpr);
|
29265 | if (typeof templateUrl !== 'string') {
|
29266 | throw createValueHasWrongTypeError(templateUrlExpr, templateUrl, 'templateUrl must be a string');
|
29267 | }
|
29268 | const resourceUrl = this._resolveResourceOrThrow(templateUrl, containingFile, templateUrlExpr, 0 /* Template */);
|
29269 | const templatePromise = this.resourceLoader.preload(resourceUrl);
|
29270 | // If the preload worked, then actually load and parse the template, and wait for any style
|
29271 | // URLs to resolve.
|
29272 | if (templatePromise !== undefined) {
|
29273 | return templatePromise.then(() => {
|
29274 | const templateDecl = this.parseTemplateDeclaration(decorator, component, containingFile);
|
29275 | const template = this.extractTemplate(node, templateDecl);
|
29276 | this.preanalyzeTemplateCache.set(node, template);
|
29277 | return template;
|
29278 | });
|
29279 | }
|
29280 | else {
|
29281 | return Promise.resolve(null);
|
29282 | }
|
29283 | }
|
29284 | else {
|
29285 | const templateDecl = this.parseTemplateDeclaration(decorator, component, containingFile);
|
29286 | const template = this.extractTemplate(node, templateDecl);
|
29287 | this.preanalyzeTemplateCache.set(node, template);
|
29288 | return Promise.resolve(template);
|
29289 | }
|
29290 | }
|
29291 | extractTemplate(node, template) {
|
29292 | if (template.isInline) {
|
29293 | let templateStr;
|
29294 | let templateLiteral = null;
|
29295 | let templateUrl = '';
|
29296 | let templateRange = null;
|
29297 | let sourceMapping;
|
29298 | let escapedString = false;
|
29299 | // We only support SourceMaps for inline templates that are simple string literals.
|
29300 | if (ts$1.isStringLiteral(template.expression) ||
|
29301 | ts$1.isNoSubstitutionTemplateLiteral(template.expression)) {
|
29302 | // the start and end of the `templateExpr` node includes the quotation marks, which we must
|
29303 | // strip
|
29304 | templateRange = getTemplateRange(template.expression);
|
29305 | templateStr = template.expression.getSourceFile().text;
|
29306 | templateLiteral = template.expression;
|
29307 | templateUrl = template.templateUrl;
|
29308 | escapedString = true;
|
29309 | sourceMapping = {
|
29310 | type: 'direct',
|
29311 | node: template.expression,
|
29312 | };
|
29313 | }
|
29314 | else {
|
29315 | const resolvedTemplate = this.evaluator.evaluate(template.expression);
|
29316 | if (typeof resolvedTemplate !== 'string') {
|
29317 | throw createValueHasWrongTypeError(template.expression, resolvedTemplate, 'template must be a string');
|
29318 | }
|
29319 | templateStr = resolvedTemplate;
|
29320 | sourceMapping = {
|
29321 | type: 'indirect',
|
29322 | node: template.expression,
|
29323 | componentClass: node,
|
29324 | template: templateStr,
|
29325 | };
|
29326 | }
|
29327 | return Object.assign(Object.assign({}, this._parseTemplate(template, templateStr, templateRange, escapedString)), { sourceMapping, declaration: template });
|
29328 | }
|
29329 | else {
|
29330 | const templateStr = this.resourceLoader.load(template.resolvedTemplateUrl);
|
29331 | if (this.depTracker !== null) {
|
29332 | this.depTracker.addResourceDependency(node.getSourceFile(), absoluteFrom(template.resolvedTemplateUrl));
|
29333 | }
|
29334 | return Object.assign(Object.assign({}, this._parseTemplate(template, templateStr, /* templateRange */ null,
|
29335 | /* escapedString */ false)), { sourceMapping: {
|
29336 | type: 'external',
|
29337 | componentClass: node,
|
29338 | // TODO(alxhub): TS in g3 is unable to make this inference on its own, so cast it here
|
29339 | // until g3 is able to figure this out.
|
29340 | node: template.templateUrlExpression,
|
29341 | template: templateStr,
|
29342 | templateUrl: template.resolvedTemplateUrl,
|
29343 | }, declaration: template });
|
29344 | }
|
29345 | }
|
29346 | _parseTemplate(template, templateStr, templateRange, escapedString) {
|
29347 | // We always normalize line endings if the template has been escaped (i.e. is inline).
|
29348 | const i18nNormalizeLineEndingsInICUs = escapedString || this.i18nNormalizeLineEndingsInICUs;
|
29349 | const parsedTemplate = parseTemplate(templateStr, template.sourceMapUrl, {
|
29350 | preserveWhitespaces: template.preserveWhitespaces,
|
29351 | interpolationConfig: template.interpolationConfig,
|
29352 | range: templateRange !== null && templateRange !== void 0 ? templateRange : undefined,
|
29353 | escapedString,
|
29354 | enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
|
29355 | i18nNormalizeLineEndingsInICUs,
|
29356 | isInline: template.isInline,
|
29357 | alwaysAttemptHtmlToR3AstConversion: this.usePoisonedData,
|
29358 | });
|
29359 | // Unfortunately, the primary parse of the template above may not contain accurate source map
|
29360 | // information. If used directly, it would result in incorrect code locations in template
|
29361 | // errors, etc. There are three main problems:
|
29362 | //
|
29363 | // 1. `preserveWhitespaces: false` annihilates the correctness of template source mapping, as
|
29364 | // the whitespace transformation changes the contents of HTML text nodes before they're
|
29365 | // parsed into Angular expressions.
|
29366 | // 2. `preserveLineEndings: false` causes growing misalignments in templates that use '\r\n'
|
29367 | // line endings, by normalizing them to '\n'.
|
29368 | // 3. By default, the template parser strips leading trivia characters (like spaces, tabs, and
|
29369 | // newlines). This also destroys source mapping information.
|
29370 | //
|
29371 | // In order to guarantee the correctness of diagnostics, templates are parsed a second time
|
29372 | // with the above options set to preserve source mappings.
|
29373 | const { nodes: diagNodes } = parseTemplate(templateStr, template.sourceMapUrl, {
|
29374 | preserveWhitespaces: true,
|
29375 | preserveLineEndings: true,
|
29376 | interpolationConfig: template.interpolationConfig,
|
29377 | range: templateRange !== null && templateRange !== void 0 ? templateRange : undefined,
|
29378 | escapedString,
|
29379 | enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
|
29380 | i18nNormalizeLineEndingsInICUs,
|
29381 | leadingTriviaChars: [],
|
29382 | isInline: template.isInline,
|
29383 | alwaysAttemptHtmlToR3AstConversion: this.usePoisonedData,
|
29384 | });
|
29385 | return Object.assign(Object.assign({}, parsedTemplate), { diagNodes, template: template.isInline ? new WrappedNodeExpr(template.expression) : templateStr, templateUrl: template.resolvedTemplateUrl, isInline: template.isInline, file: new ParseSourceFile(templateStr, template.resolvedTemplateUrl) });
|
29386 | }
|
29387 | parseTemplateDeclaration(decorator, component, containingFile) {
|
29388 | let preserveWhitespaces = this.defaultPreserveWhitespaces;
|
29389 | if (component.has('preserveWhitespaces')) {
|
29390 | const expr = component.get('preserveWhitespaces');
|
29391 | const value = this.evaluator.evaluate(expr);
|
29392 | if (typeof value !== 'boolean') {
|
29393 | throw createValueHasWrongTypeError(expr, value, 'preserveWhitespaces must be a boolean');
|
29394 | }
|
29395 | preserveWhitespaces = value;
|
29396 | }
|
29397 | let interpolationConfig = DEFAULT_INTERPOLATION_CONFIG;
|
29398 | if (component.has('interpolation')) {
|
29399 | const expr = component.get('interpolation');
|
29400 | const value = this.evaluator.evaluate(expr);
|
29401 | if (!Array.isArray(value) || value.length !== 2 ||
|
29402 | !value.every(element => typeof element === 'string')) {
|
29403 | throw createValueHasWrongTypeError(expr, value, 'interpolation must be an array with 2 elements of string type');
|
29404 | }
|
29405 | interpolationConfig = InterpolationConfig.fromArray(value);
|
29406 | }
|
29407 | if (component.has('templateUrl')) {
|
29408 | const templateUrlExpr = component.get('templateUrl');
|
29409 | const templateUrl = this.evaluator.evaluate(templateUrlExpr);
|
29410 | if (typeof templateUrl !== 'string') {
|
29411 | throw createValueHasWrongTypeError(templateUrlExpr, templateUrl, 'templateUrl must be a string');
|
29412 | }
|
29413 | const resourceUrl = this._resolveResourceOrThrow(templateUrl, containingFile, templateUrlExpr, 0 /* Template */);
|
29414 | return {
|
29415 | isInline: false,
|
29416 | interpolationConfig,
|
29417 | preserveWhitespaces,
|
29418 | templateUrl,
|
29419 | templateUrlExpression: templateUrlExpr,
|
29420 | resolvedTemplateUrl: resourceUrl,
|
29421 | sourceMapUrl: sourceMapUrl(resourceUrl),
|
29422 | };
|
29423 | }
|
29424 | else if (component.has('template')) {
|
29425 | return {
|
29426 | isInline: true,
|
29427 | interpolationConfig,
|
29428 | preserveWhitespaces,
|
29429 | expression: component.get('template'),
|
29430 | templateUrl: containingFile,
|
29431 | resolvedTemplateUrl: containingFile,
|
29432 | sourceMapUrl: containingFile,
|
29433 | };
|
29434 | }
|
29435 | else {
|
29436 | throw new FatalDiagnosticError(ErrorCode.COMPONENT_MISSING_TEMPLATE, Decorator.nodeForError(decorator), 'component is missing a template');
|
29437 | }
|
29438 | }
|
29439 | _expressionToImportedFile(expr, origin) {
|
29440 | if (!(expr instanceof ExternalExpr)) {
|
29441 | return null;
|
29442 | }
|
29443 | // Figure out what file is being imported.
|
29444 | return this.moduleResolver.resolveModule(expr.value.moduleName, origin.fileName);
|
29445 | }
|
29446 | /**
|
29447 | * Check whether adding an import from `origin` to the source-file corresponding to `expr` would
|
29448 | * create a cyclic import.
|
29449 | *
|
29450 | * @returns a `Cycle` object if a cycle would be created, otherwise `null`.
|
29451 | */
|
29452 | _checkForCyclicImport(ref, expr, origin) {
|
29453 | const importedFile = this._expressionToImportedFile(expr, origin);
|
29454 | if (importedFile === null) {
|
29455 | return null;
|
29456 | }
|
29457 | // Check whether the import is legal.
|
29458 | return this.cycleAnalyzer.wouldCreateCycle(origin, importedFile);
|
29459 | }
|
29460 | _recordSyntheticImport(expr, origin) {
|
29461 | const imported = this._expressionToImportedFile(expr, origin);
|
29462 | if (imported === null) {
|
29463 | return;
|
29464 | }
|
29465 | this.cycleAnalyzer.recordSyntheticImport(origin, imported);
|
29466 | }
|
29467 | /**
|
29468 | * Resolve the url of a resource relative to the file that contains the reference to it.
|
29469 | *
|
29470 | * Throws a FatalDiagnosticError when unable to resolve the file.
|
29471 | */
|
29472 | _resolveResourceOrThrow(file, basePath, nodeForError, resourceType) {
|
29473 | try {
|
29474 | return this.resourceLoader.resolve(file, basePath);
|
29475 | }
|
29476 | catch (e) {
|
29477 | let errorText;
|
29478 | switch (resourceType) {
|
29479 | case 0 /* Template */:
|
29480 | errorText = `Could not find template file '${file}'.`;
|
29481 | break;
|
29482 | case 1 /* StylesheetFromTemplate */:
|
29483 | errorText = `Could not find stylesheet file '${file}' linked from the template.`;
|
29484 | break;
|
29485 | case 2 /* StylesheetFromDecorator */:
|
29486 | errorText = `Could not find stylesheet file '${file}'.`;
|
29487 | break;
|
29488 | }
|
29489 | throw new FatalDiagnosticError(ErrorCode.COMPONENT_RESOURCE_NOT_FOUND, nodeForError, errorText);
|
29490 | }
|
29491 | }
|
29492 | _extractTemplateStyleUrls(template) {
|
29493 | if (template.styleUrls === null) {
|
29494 | return [];
|
29495 | }
|
29496 | const nodeForError = getTemplateDeclarationNodeForError(template.declaration);
|
29497 | return template.styleUrls.map(url => ({ url, source: 1 /* StylesheetFromTemplate */, nodeForError }));
|
29498 | }
|
29499 | }
|
29500 | function getTemplateRange(templateExpr) {
|
29501 | const startPos = templateExpr.getStart() + 1;
|
29502 | const { line, character } = ts$1.getLineAndCharacterOfPosition(templateExpr.getSourceFile(), startPos);
|
29503 | return {
|
29504 | startPos,
|
29505 | startLine: line,
|
29506 | startCol: character,
|
29507 | endPos: templateExpr.getEnd() - 1,
|
29508 | };
|
29509 | }
|
29510 | function sourceMapUrl(resourceUrl) {
|
29511 | if (!tsSourceMapBug29300Fixed()) {
|
29512 | // By removing the template URL we are telling the translator not to try to
|
29513 | // map the external source file to the generated code, since the version
|
29514 | // of TS that is running does not support it.
|
29515 | return '';
|
29516 | }
|
29517 | else {
|
29518 | return resourceUrl;
|
29519 | }
|
29520 | }
|
29521 | /** Determines if the result of an evaluation is a string array. */
|
29522 | function isStringArray(resolvedValue) {
|
29523 | return Array.isArray(resolvedValue) && resolvedValue.every(elem => typeof elem === 'string');
|
29524 | }
|
29525 | /** Determines the node to use for debugging purposes for the given TemplateDeclaration. */
|
29526 | function getTemplateDeclarationNodeForError(declaration) {
|
29527 | // TODO(zarend): Change this to if/else when that is compatible with g3. This uses a switch
|
29528 | // because if/else fails to compile on g3. That is because g3 compiles this in non-strict mode
|
29529 | // where type inference does not work correctly.
|
29530 | switch (declaration.isInline) {
|
29531 | case true:
|
29532 | return declaration.expression;
|
29533 | case false:
|
29534 | return declaration.templateUrlExpression;
|
29535 | }
|
29536 | }
|
29537 | /**
|
29538 | * Generate a diagnostic related information object that describes a potential cyclic import path.
|
29539 | */
|
29540 | function makeCyclicImportInfo(ref, type, cycle) {
|
29541 | const name = ref.debugName || '(unknown)';
|
29542 | const path = cycle.getPath().map(sf => sf.fileName).join(' -> ');
|
29543 | const message = `The ${type} '${name}' is used in the template but importing it would create a cycle: `;
|
29544 | return makeRelatedInformation(ref.node, message + path);
|
29545 | }
|
29546 |
|
29547 | /**
|
29548 | * @license
|
29549 | * Copyright Google LLC All Rights Reserved.
|
29550 | *
|
29551 | * Use of this source code is governed by an MIT-style license that can be
|
29552 | * found in the LICENSE file at https://angular.io/license
|
29553 | */
|
29554 | /**
|
29555 | * Adapts the `compileIvyInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
|
29556 | */
|
29557 | class InjectableDecoratorHandler {
|
29558 | constructor(reflector, defaultImportRecorder, isCore, strictCtorDeps, injectableRegistry,
|
29559 | /**
|
29560 | * What to do if the injectable already contains a ɵprov property.
|
29561 | *
|
29562 | * If true then an error diagnostic is reported.
|
29563 | * If false then there is no error and a new ɵprov property is not added.
|
29564 | */
|
29565 | errorOnDuplicateProv = true) {
|
29566 | this.reflector = reflector;
|
29567 | this.defaultImportRecorder = defaultImportRecorder;
|
29568 | this.isCore = isCore;
|
29569 | this.strictCtorDeps = strictCtorDeps;
|
29570 | this.injectableRegistry = injectableRegistry;
|
29571 | this.errorOnDuplicateProv = errorOnDuplicateProv;
|
29572 | this.precedence = HandlerPrecedence.SHARED;
|
29573 | this.name = InjectableDecoratorHandler.name;
|
29574 | }
|
29575 | detect(node, decorators) {
|
29576 | if (!decorators) {
|
29577 | return undefined;
|
29578 | }
|
29579 | const decorator = findAngularDecorator(decorators, 'Injectable', this.isCore);
|
29580 | if (decorator !== undefined) {
|
29581 | return {
|
29582 | trigger: decorator.node,
|
29583 | decorator: decorator,
|
29584 | metadata: decorator,
|
29585 | };
|
29586 | }
|
29587 | else {
|
29588 | return undefined;
|
29589 | }
|
29590 | }
|
29591 | analyze(node, decorator) {
|
29592 | const meta = extractInjectableMetadata(node, decorator, this.reflector);
|
29593 | const decorators = this.reflector.getDecoratorsOfDeclaration(node);
|
29594 | return {
|
29595 | analysis: {
|
29596 | meta,
|
29597 | ctorDeps: extractInjectableCtorDeps(node, meta, decorator, this.reflector, this.defaultImportRecorder, this.isCore, this.strictCtorDeps),
|
29598 | metadataStmt: generateSetClassMetadataCall(node, this.reflector, this.defaultImportRecorder, this.isCore),
|
29599 | // Avoid generating multiple factories if a class has
|
29600 | // more Angular decorators, apart from Injectable.
|
29601 | needsFactory: !decorators ||
|
29602 | decorators.every(current => !isAngularCore(current) || current.name === 'Injectable')
|
29603 | },
|
29604 | };
|
29605 | }
|
29606 | register(node) {
|
29607 | this.injectableRegistry.registerInjectable(node);
|
29608 | }
|
29609 | compileFull(node, analysis) {
|
29610 | const res = compileInjectable(analysis.meta);
|
29611 | const statements = res.statements;
|
29612 | const results = [];
|
29613 | if (analysis.needsFactory) {
|
29614 | const meta = analysis.meta;
|
29615 | const factoryRes = compileNgFactoryDefField({
|
29616 | name: meta.name,
|
29617 | type: meta.type,
|
29618 | internalType: meta.internalType,
|
29619 | typeArgumentCount: meta.typeArgumentCount,
|
29620 | deps: analysis.ctorDeps,
|
29621 | injectFn: Identifiers.inject,
|
29622 | target: R3FactoryTarget.Injectable,
|
29623 | });
|
29624 | if (analysis.metadataStmt !== null) {
|
29625 | factoryRes.statements.push(analysis.metadataStmt);
|
29626 | }
|
29627 | results.push(factoryRes);
|
29628 | }
|
29629 | const ɵprov = this.reflector.getMembersOfClass(node).find(member => member.name === 'ɵprov');
|
29630 | if (ɵprov !== undefined && this.errorOnDuplicateProv) {
|
29631 | throw new FatalDiagnosticError(ErrorCode.INJECTABLE_DUPLICATE_PROV, ɵprov.nameNode || ɵprov.node || node, 'Injectables cannot contain a static ɵprov property, because the compiler is going to generate one.');
|
29632 | }
|
29633 | if (ɵprov === undefined) {
|
29634 | // Only add a new ɵprov if there is not one already
|
29635 | results.push({ name: 'ɵprov', initializer: res.expression, statements, type: res.type });
|
29636 | }
|
29637 | return results;
|
29638 | }
|
29639 | }
|
29640 | /**
|
29641 | * Read metadata from the `@Injectable` decorator and produce the `IvyInjectableMetadata`, the
|
29642 | * input metadata needed to run `compileIvyInjectable`.
|
29643 | *
|
29644 | * A `null` return value indicates this is @Injectable has invalid data.
|
29645 | */
|
29646 | function extractInjectableMetadata(clazz, decorator, reflector) {
|
29647 | const name = clazz.name.text;
|
29648 | const type = wrapTypeReference(reflector, clazz);
|
29649 | const internalType = new WrappedNodeExpr(reflector.getInternalNameOfClass(clazz));
|
29650 | const typeArgumentCount = reflector.getGenericArityOfClass(clazz) || 0;
|
29651 | if (decorator.args === null) {
|
29652 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_NOT_CALLED, Decorator.nodeForError(decorator), '@Injectable must be called');
|
29653 | }
|
29654 | if (decorator.args.length === 0) {
|
29655 | return {
|
29656 | name,
|
29657 | type,
|
29658 | typeArgumentCount,
|
29659 | internalType,
|
29660 | providedIn: new LiteralExpr(null),
|
29661 | };
|
29662 | }
|
29663 | else if (decorator.args.length === 1) {
|
29664 | const metaNode = decorator.args[0];
|
29665 | // Firstly make sure the decorator argument is an inline literal - if not, it's illegal to
|
29666 | // transport references from one location to another. This is the problem that lowering
|
29667 | // used to solve - if this restriction proves too undesirable we can re-implement lowering.
|
29668 | if (!ts$1.isObjectLiteralExpression(metaNode)) {
|
29669 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARG_NOT_LITERAL, metaNode, `@Injectable argument must be an object literal`);
|
29670 | }
|
29671 | // Resolve the fields of the literal into a map of field name to expression.
|
29672 | const meta = reflectObjectLiteral(metaNode);
|
29673 | let providedIn = new LiteralExpr(null);
|
29674 | if (meta.has('providedIn')) {
|
29675 | providedIn = new WrappedNodeExpr(meta.get('providedIn'));
|
29676 | }
|
29677 | let userDeps = undefined;
|
29678 | if ((meta.has('useClass') || meta.has('useFactory')) && meta.has('deps')) {
|
29679 | const depsExpr = meta.get('deps');
|
29680 | if (!ts$1.isArrayLiteralExpression(depsExpr)) {
|
29681 | throw new FatalDiagnosticError(ErrorCode.VALUE_NOT_LITERAL, depsExpr, `@Injectable deps metadata must be an inline array`);
|
29682 | }
|
29683 | userDeps = depsExpr.elements.map(dep => getDep(dep, reflector));
|
29684 | }
|
29685 | if (meta.has('useValue')) {
|
29686 | return {
|
29687 | name,
|
29688 | type,
|
29689 | typeArgumentCount,
|
29690 | internalType,
|
29691 | providedIn,
|
29692 | useValue: new WrappedNodeExpr(unwrapForwardRef(meta.get('useValue'), reflector)),
|
29693 | };
|
29694 | }
|
29695 | else if (meta.has('useExisting')) {
|
29696 | return {
|
29697 | name,
|
29698 | type,
|
29699 | typeArgumentCount,
|
29700 | internalType,
|
29701 | providedIn,
|
29702 | useExisting: new WrappedNodeExpr(unwrapForwardRef(meta.get('useExisting'), reflector)),
|
29703 | };
|
29704 | }
|
29705 | else if (meta.has('useClass')) {
|
29706 | return {
|
29707 | name,
|
29708 | type,
|
29709 | typeArgumentCount,
|
29710 | internalType,
|
29711 | providedIn,
|
29712 | useClass: new WrappedNodeExpr(unwrapForwardRef(meta.get('useClass'), reflector)),
|
29713 | userDeps,
|
29714 | };
|
29715 | }
|
29716 | else if (meta.has('useFactory')) {
|
29717 | // useFactory is special - the 'deps' property must be analyzed.
|
29718 | const factory = new WrappedNodeExpr(meta.get('useFactory'));
|
29719 | return {
|
29720 | name,
|
29721 | type,
|
29722 | typeArgumentCount,
|
29723 | internalType,
|
29724 | providedIn,
|
29725 | useFactory: factory,
|
29726 | userDeps,
|
29727 | };
|
29728 | }
|
29729 | else {
|
29730 | return { name, type, typeArgumentCount, internalType, providedIn };
|
29731 | }
|
29732 | }
|
29733 | else {
|
29734 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARITY_WRONG, decorator.args[2], 'Too many arguments to @Injectable');
|
29735 | }
|
29736 | }
|
29737 | function extractInjectableCtorDeps(clazz, meta, decorator, reflector, defaultImportRecorder, isCore, strictCtorDeps) {
|
29738 | if (decorator.args === null) {
|
29739 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_NOT_CALLED, Decorator.nodeForError(decorator), '@Injectable must be called');
|
29740 | }
|
29741 | let ctorDeps = null;
|
29742 | if (decorator.args.length === 0) {
|
29743 | // Ideally, using @Injectable() would have the same effect as using @Injectable({...}), and be
|
29744 | // subject to the same validation. However, existing Angular code abuses @Injectable, applying
|
29745 | // it to things like abstract classes with constructors that were never meant for use with
|
29746 | // Angular's DI.
|
29747 | //
|
29748 | // To deal with this, @Injectable() without an argument is more lenient, and if the
|
29749 | // constructor signature does not work for DI then a factory definition (ɵfac) that throws is
|
29750 | // generated.
|
29751 | if (strictCtorDeps) {
|
29752 | ctorDeps = getValidConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
29753 | }
|
29754 | else {
|
29755 | ctorDeps = unwrapConstructorDependencies(getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore));
|
29756 | }
|
29757 | return ctorDeps;
|
29758 | }
|
29759 | else if (decorator.args.length === 1) {
|
29760 | const rawCtorDeps = getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
29761 | if (strictCtorDeps && meta.useValue === undefined && meta.useExisting === undefined &&
|
29762 | meta.useClass === undefined && meta.useFactory === undefined) {
|
29763 | // Since use* was not provided, validate the deps according to strictCtorDeps.
|
29764 | ctorDeps = validateConstructorDependencies(clazz, rawCtorDeps);
|
29765 | }
|
29766 | else {
|
29767 | ctorDeps = unwrapConstructorDependencies(rawCtorDeps);
|
29768 | }
|
29769 | }
|
29770 | return ctorDeps;
|
29771 | }
|
29772 | function getDep(dep, reflector) {
|
29773 | const meta = {
|
29774 | token: new WrappedNodeExpr(dep),
|
29775 | attribute: null,
|
29776 | host: false,
|
29777 | resolved: R3ResolvedDependencyType.Token,
|
29778 | optional: false,
|
29779 | self: false,
|
29780 | skipSelf: false,
|
29781 | };
|
29782 | function maybeUpdateDecorator(dec, reflector, token) {
|
29783 | const source = reflector.getImportOfIdentifier(dec);
|
29784 | if (source === null || source.from !== '@angular/core') {
|
29785 | return;
|
29786 | }
|
29787 | switch (source.name) {
|
29788 | case 'Inject':
|
29789 | if (token !== undefined) {
|
29790 | meta.token = new WrappedNodeExpr(token);
|
29791 | }
|
29792 | break;
|
29793 | case 'Optional':
|
29794 | meta.optional = true;
|
29795 | break;
|
29796 | case 'SkipSelf':
|
29797 | meta.skipSelf = true;
|
29798 | break;
|
29799 | case 'Self':
|
29800 | meta.self = true;
|
29801 | break;
|
29802 | }
|
29803 | }
|
29804 | if (ts$1.isArrayLiteralExpression(dep)) {
|
29805 | dep.elements.forEach(el => {
|
29806 | if (ts$1.isIdentifier(el)) {
|
29807 | maybeUpdateDecorator(el, reflector);
|
29808 | }
|
29809 | else if (ts$1.isNewExpression(el) && ts$1.isIdentifier(el.expression)) {
|
29810 | const token = el.arguments && el.arguments.length > 0 && el.arguments[0] || undefined;
|
29811 | maybeUpdateDecorator(el.expression, reflector, token);
|
29812 | }
|
29813 | });
|
29814 | }
|
29815 | return meta;
|
29816 | }
|
29817 |
|
29818 | /**
|
29819 | * @license
|
29820 | * Copyright Google LLC All Rights Reserved.
|
29821 | *
|
29822 | * Use of this source code is governed by an MIT-style license that can be
|
29823 | * found in the LICENSE file at https://angular.io/license
|
29824 | */
|
29825 | /**
|
29826 | * Compiles @NgModule annotations to ngModuleDef fields.
|
29827 | *
|
29828 | * TODO(alxhub): handle injector side of things as well.
|
29829 | */
|
29830 | class NgModuleDecoratorHandler {
|
29831 | constructor(reflector, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry, isCore, routeAnalyzer, refEmitter, factoryTracker, defaultImportRecorder, annotateForClosureCompiler, injectableRegistry, localeId) {
|
29832 | this.reflector = reflector;
|
29833 | this.evaluator = evaluator;
|
29834 | this.metaReader = metaReader;
|
29835 | this.metaRegistry = metaRegistry;
|
29836 | this.scopeRegistry = scopeRegistry;
|
29837 | this.referencesRegistry = referencesRegistry;
|
29838 | this.isCore = isCore;
|
29839 | this.routeAnalyzer = routeAnalyzer;
|
29840 | this.refEmitter = refEmitter;
|
29841 | this.factoryTracker = factoryTracker;
|
29842 | this.defaultImportRecorder = defaultImportRecorder;
|
29843 | this.annotateForClosureCompiler = annotateForClosureCompiler;
|
29844 | this.injectableRegistry = injectableRegistry;
|
29845 | this.localeId = localeId;
|
29846 | this.precedence = HandlerPrecedence.PRIMARY;
|
29847 | this.name = NgModuleDecoratorHandler.name;
|
29848 | }
|
29849 | detect(node, decorators) {
|
29850 | if (!decorators) {
|
29851 | return undefined;
|
29852 | }
|
29853 | const decorator = findAngularDecorator(decorators, 'NgModule', this.isCore);
|
29854 | if (decorator !== undefined) {
|
29855 | return {
|
29856 | trigger: decorator.node,
|
29857 | decorator: decorator,
|
29858 | metadata: decorator,
|
29859 | };
|
29860 | }
|
29861 | else {
|
29862 | return undefined;
|
29863 | }
|
29864 | }
|
29865 | analyze(node, decorator) {
|
29866 | const name = node.name.text;
|
29867 | if (decorator.args === null || decorator.args.length > 1) {
|
29868 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARITY_WRONG, Decorator.nodeForError(decorator), `Incorrect number of arguments to @NgModule decorator`);
|
29869 | }
|
29870 | // @NgModule can be invoked without arguments. In case it is, pretend as if a blank object
|
29871 | // literal was specified. This simplifies the code below.
|
29872 | const meta = decorator.args.length === 1 ? unwrapExpression(decorator.args[0]) :
|
29873 | ts$1.createObjectLiteral([]);
|
29874 | if (!ts$1.isObjectLiteralExpression(meta)) {
|
29875 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARG_NOT_LITERAL, meta, '@NgModule argument must be an object literal');
|
29876 | }
|
29877 | const ngModule = reflectObjectLiteral(meta);
|
29878 | if (ngModule.has('jit')) {
|
29879 | // The only allowed value is true, so there's no need to expand further.
|
29880 | return {};
|
29881 | }
|
29882 | const moduleResolvers = combineResolvers([
|
29883 | ref => this._extractModuleFromModuleWithProvidersFn(ref.node),
|
29884 | forwardRefResolver,
|
29885 | ]);
|
29886 | const diagnostics = [];
|
29887 | // Extract the module declarations, imports, and exports.
|
29888 | let declarationRefs = [];
|
29889 | let rawDeclarations = null;
|
29890 | if (ngModule.has('declarations')) {
|
29891 | rawDeclarations = ngModule.get('declarations');
|
29892 | const declarationMeta = this.evaluator.evaluate(rawDeclarations, forwardRefResolver);
|
29893 | declarationRefs =
|
29894 | this.resolveTypeList(rawDeclarations, declarationMeta, name, 'declarations');
|
29895 | // Look through the declarations to make sure they're all a part of the current compilation.
|
29896 | for (const ref of declarationRefs) {
|
29897 | if (ref.node.getSourceFile().isDeclarationFile) {
|
29898 | const errorNode = ref.getOriginForDiagnostics(rawDeclarations);
|
29899 | diagnostics.push(makeDiagnostic(ErrorCode.NGMODULE_INVALID_DECLARATION, errorNode, `Cannot declare '${ref.node.name
|
29900 | .text}' in an NgModule as it's not a part of the current compilation.`, [makeRelatedInformation(ref.node.name, `'${ref.node.name.text}' is declared here.`)]));
|
29901 | }
|
29902 | }
|
29903 | }
|
29904 | if (diagnostics.length > 0) {
|
29905 | return { diagnostics };
|
29906 | }
|
29907 | let importRefs = [];
|
29908 | let rawImports = null;
|
29909 | if (ngModule.has('imports')) {
|
29910 | rawImports = ngModule.get('imports');
|
29911 | const importsMeta = this.evaluator.evaluate(rawImports, moduleResolvers);
|
29912 | importRefs = this.resolveTypeList(rawImports, importsMeta, name, 'imports');
|
29913 | }
|
29914 | let exportRefs = [];
|
29915 | let rawExports = null;
|
29916 | if (ngModule.has('exports')) {
|
29917 | rawExports = ngModule.get('exports');
|
29918 | const exportsMeta = this.evaluator.evaluate(rawExports, moduleResolvers);
|
29919 | exportRefs = this.resolveTypeList(rawExports, exportsMeta, name, 'exports');
|
29920 | this.referencesRegistry.add(node, ...exportRefs);
|
29921 | }
|
29922 | let bootstrapRefs = [];
|
29923 | if (ngModule.has('bootstrap')) {
|
29924 | const expr = ngModule.get('bootstrap');
|
29925 | const bootstrapMeta = this.evaluator.evaluate(expr, forwardRefResolver);
|
29926 | bootstrapRefs = this.resolveTypeList(expr, bootstrapMeta, name, 'bootstrap');
|
29927 | }
|
29928 | const schemas = [];
|
29929 | if (ngModule.has('schemas')) {
|
29930 | const rawExpr = ngModule.get('schemas');
|
29931 | const result = this.evaluator.evaluate(rawExpr);
|
29932 | if (!Array.isArray(result)) {
|
29933 | throw createValueHasWrongTypeError(rawExpr, result, `NgModule.schemas must be an array`);
|
29934 | }
|
29935 | for (const schemaRef of result) {
|
29936 | if (!(schemaRef instanceof Reference$1)) {
|
29937 | throw createValueHasWrongTypeError(rawExpr, result, 'NgModule.schemas must be an array of schemas');
|
29938 | }
|
29939 | const id = schemaRef.getIdentityIn(schemaRef.node.getSourceFile());
|
29940 | if (id === null || schemaRef.ownedByModuleGuess !== '@angular/core') {
|
29941 | throw createValueHasWrongTypeError(rawExpr, result, 'NgModule.schemas must be an array of schemas');
|
29942 | }
|
29943 | // Since `id` is the `ts.Identifer` within the schema ref's declaration file, it's safe to
|
29944 | // use `id.text` here to figure out which schema is in use. Even if the actual reference was
|
29945 | // renamed when the user imported it, these names will match.
|
29946 | switch (id.text) {
|
29947 | case 'CUSTOM_ELEMENTS_SCHEMA':
|
29948 | schemas.push(CUSTOM_ELEMENTS_SCHEMA);
|
29949 | break;
|
29950 | case 'NO_ERRORS_SCHEMA':
|
29951 | schemas.push(NO_ERRORS_SCHEMA);
|
29952 | break;
|
29953 | default:
|
29954 | throw createValueHasWrongTypeError(rawExpr, schemaRef, `'${schemaRef.debugName}' is not a valid NgModule schema`);
|
29955 | }
|
29956 | }
|
29957 | }
|
29958 | const id = ngModule.has('id') ? new WrappedNodeExpr(ngModule.get('id')) : null;
|
29959 | const valueContext = node.getSourceFile();
|
29960 | let typeContext = valueContext;
|
29961 | const typeNode = this.reflector.getDtsDeclaration(node);
|
29962 | if (typeNode !== null) {
|
29963 | typeContext = typeNode.getSourceFile();
|
29964 | }
|
29965 | const bootstrap = bootstrapRefs.map(bootstrap => this._toR3Reference(bootstrap, valueContext, typeContext));
|
29966 | const declarations = declarationRefs.map(decl => this._toR3Reference(decl, valueContext, typeContext));
|
29967 | const imports = importRefs.map(imp => this._toR3Reference(imp, valueContext, typeContext));
|
29968 | const exports = exportRefs.map(exp => this._toR3Reference(exp, valueContext, typeContext));
|
29969 | const isForwardReference = (ref) => isExpressionForwardReference(ref.value, node.name, valueContext);
|
29970 | const containsForwardDecls = bootstrap.some(isForwardReference) ||
|
29971 | declarations.some(isForwardReference) || imports.some(isForwardReference) ||
|
29972 | exports.some(isForwardReference);
|
29973 | const type = wrapTypeReference(this.reflector, node);
|
29974 | const internalType = new WrappedNodeExpr(this.reflector.getInternalNameOfClass(node));
|
29975 | const adjacentType = new WrappedNodeExpr(this.reflector.getAdjacentNameOfClass(node));
|
29976 | const ngModuleDef = {
|
29977 | type,
|
29978 | internalType,
|
29979 | adjacentType,
|
29980 | bootstrap,
|
29981 | declarations,
|
29982 | exports,
|
29983 | imports,
|
29984 | containsForwardDecls,
|
29985 | id,
|
29986 | emitInline: false,
|
29987 | // TODO: to be implemented as a part of FW-1004.
|
29988 | schemas: [],
|
29989 | };
|
29990 | const rawProviders = ngModule.has('providers') ? ngModule.get('providers') : null;
|
29991 | const wrapperProviders = rawProviders !== null ?
|
29992 | new WrappedNodeExpr(this.annotateForClosureCompiler ? wrapFunctionExpressionsInParens(rawProviders) :
|
29993 | rawProviders) :
|
29994 | null;
|
29995 | // At this point, only add the module's imports as the injectors' imports. Any exported modules
|
29996 | // are added during `resolve`, as we need scope information to be able to filter out directives
|
29997 | // and pipes from the module exports.
|
29998 | const injectorImports = [];
|
29999 | if (ngModule.has('imports')) {
|
30000 | injectorImports.push(new WrappedNodeExpr(ngModule.get('imports')));
|
30001 | }
|
30002 | if (this.routeAnalyzer !== null) {
|
30003 | this.routeAnalyzer.add(node.getSourceFile(), name, rawImports, rawExports, rawProviders);
|
30004 | }
|
30005 | const ngInjectorDef = {
|
30006 | name,
|
30007 | type,
|
30008 | internalType,
|
30009 | deps: getValidConstructorDependencies(node, this.reflector, this.defaultImportRecorder, this.isCore),
|
30010 | providers: wrapperProviders,
|
30011 | imports: injectorImports,
|
30012 | };
|
30013 | return {
|
30014 | analysis: {
|
30015 | id,
|
30016 | schemas: schemas,
|
30017 | mod: ngModuleDef,
|
30018 | inj: ngInjectorDef,
|
30019 | declarations: declarationRefs,
|
30020 | rawDeclarations,
|
30021 | imports: importRefs,
|
30022 | exports: exportRefs,
|
30023 | providers: rawProviders,
|
30024 | providersRequiringFactory: rawProviders ?
|
30025 | resolveProvidersRequiringFactory(rawProviders, this.reflector, this.evaluator) :
|
30026 | null,
|
30027 | metadataStmt: generateSetClassMetadataCall(node, this.reflector, this.defaultImportRecorder, this.isCore, this.annotateForClosureCompiler),
|
30028 | factorySymbolName: node.name.text,
|
30029 | },
|
30030 | };
|
30031 | }
|
30032 | register(node, analysis) {
|
30033 | // Register this module's information with the LocalModuleScopeRegistry. This ensures that
|
30034 | // during the compile() phase, the module's metadata is available for selector scope
|
30035 | // computation.
|
30036 | this.metaRegistry.registerNgModuleMetadata({
|
30037 | ref: new Reference$1(node),
|
30038 | schemas: analysis.schemas,
|
30039 | declarations: analysis.declarations,
|
30040 | imports: analysis.imports,
|
30041 | exports: analysis.exports,
|
30042 | rawDeclarations: analysis.rawDeclarations,
|
30043 | });
|
30044 | if (this.factoryTracker !== null) {
|
30045 | this.factoryTracker.track(node.getSourceFile(), {
|
30046 | name: analysis.factorySymbolName,
|
30047 | hasId: analysis.id !== null,
|
30048 | });
|
30049 | }
|
30050 | this.injectableRegistry.registerInjectable(node);
|
30051 | }
|
30052 | resolve(node, analysis) {
|
30053 | const scope = this.scopeRegistry.getScopeOfModule(node);
|
30054 | const diagnostics = [];
|
30055 | const scopeDiagnostics = this.scopeRegistry.getDiagnosticsOfModule(node);
|
30056 | if (scopeDiagnostics !== null) {
|
30057 | diagnostics.push(...scopeDiagnostics);
|
30058 | }
|
30059 | if (analysis.providersRequiringFactory !== null) {
|
30060 | const providerDiagnostics = getProviderDiagnostics(analysis.providersRequiringFactory, analysis.providers, this.injectableRegistry);
|
30061 | diagnostics.push(...providerDiagnostics);
|
30062 | }
|
30063 | const data = {
|
30064 | injectorImports: [],
|
30065 | };
|
30066 | if (scope !== null && !scope.compilation.isPoisoned) {
|
30067 | // Using the scope information, extend the injector's imports using the modules that are
|
30068 | // specified as module exports.
|
30069 | const context = getSourceFile(node);
|
30070 | for (const exportRef of analysis.exports) {
|
30071 | if (isNgModule(exportRef.node, scope.compilation)) {
|
30072 | data.injectorImports.push(this.refEmitter.emit(exportRef, context));
|
30073 | }
|
30074 | }
|
30075 | for (const decl of analysis.declarations) {
|
30076 | const metadata = this.metaReader.getDirectiveMetadata(decl);
|
30077 | if (metadata !== null && metadata.selector === null) {
|
30078 | throw new FatalDiagnosticError(ErrorCode.DIRECTIVE_MISSING_SELECTOR, decl.node, `Directive ${decl.node.name.text} has no selector, please add it!`);
|
30079 | }
|
30080 | }
|
30081 | }
|
30082 | if (diagnostics.length > 0) {
|
30083 | return { diagnostics };
|
30084 | }
|
30085 | if (scope === null || scope.compilation.isPoisoned || scope.exported.isPoisoned ||
|
30086 | scope.reexports === null) {
|
30087 | return { data };
|
30088 | }
|
30089 | else {
|
30090 | return {
|
30091 | data,
|
30092 | reexports: scope.reexports,
|
30093 | };
|
30094 | }
|
30095 | }
|
30096 | compileFull(node, analysis, resolution) {
|
30097 | // Merge the injector imports (which are 'exports' that were later found to be NgModules)
|
30098 | // computed during resolution with the ones from analysis.
|
30099 | const ngInjectorDef = compileInjector(Object.assign(Object.assign({}, analysis.inj), { imports: [...analysis.inj.imports, ...resolution.injectorImports] }));
|
30100 | const ngModuleDef = compileNgModule(analysis.mod);
|
30101 | const ngModuleStatements = ngModuleDef.additionalStatements;
|
30102 | if (analysis.metadataStmt !== null) {
|
30103 | ngModuleStatements.push(analysis.metadataStmt);
|
30104 | }
|
30105 | const context = getSourceFile(node);
|
30106 | for (const decl of analysis.declarations) {
|
30107 | const remoteScope = this.scopeRegistry.getRemoteScope(decl.node);
|
30108 | if (remoteScope !== null) {
|
30109 | const directives = remoteScope.directives.map(directive => this.refEmitter.emit(directive, context));
|
30110 | const pipes = remoteScope.pipes.map(pipe => this.refEmitter.emit(pipe, context));
|
30111 | const directiveArray = new LiteralArrayExpr(directives);
|
30112 | const pipesArray = new LiteralArrayExpr(pipes);
|
30113 | const declExpr = this.refEmitter.emit(decl, context);
|
30114 | const setComponentScope = new ExternalExpr(Identifiers$1.setComponentScope);
|
30115 | const callExpr = new InvokeFunctionExpr(setComponentScope, [declExpr, directiveArray, pipesArray]);
|
30116 | ngModuleStatements.push(callExpr.toStmt());
|
30117 | }
|
30118 | }
|
30119 | const res = [
|
30120 | {
|
30121 | name: 'ɵmod',
|
30122 | initializer: ngModuleDef.expression,
|
30123 | statements: ngModuleStatements,
|
30124 | type: ngModuleDef.type,
|
30125 | },
|
30126 | {
|
30127 | name: 'ɵinj',
|
30128 | initializer: ngInjectorDef.expression,
|
30129 | statements: ngInjectorDef.statements,
|
30130 | type: ngInjectorDef.type,
|
30131 | }
|
30132 | ];
|
30133 | if (this.localeId) {
|
30134 | res.push({
|
30135 | name: 'ɵloc',
|
30136 | initializer: new LiteralExpr(this.localeId),
|
30137 | statements: [],
|
30138 | type: STRING_TYPE
|
30139 | });
|
30140 | }
|
30141 | return res;
|
30142 | }
|
30143 | _toR3Reference(valueRef, valueContext, typeContext) {
|
30144 | if (valueRef.hasOwningModuleGuess) {
|
30145 | return toR3Reference(valueRef, valueRef, valueContext, valueContext, this.refEmitter);
|
30146 | }
|
30147 | else {
|
30148 | let typeRef = valueRef;
|
30149 | let typeNode = this.reflector.getDtsDeclaration(typeRef.node);
|
30150 | if (typeNode !== null && isNamedClassDeclaration(typeNode)) {
|
30151 | typeRef = new Reference$1(typeNode);
|
30152 | }
|
30153 | return toR3Reference(valueRef, typeRef, valueContext, typeContext, this.refEmitter);
|
30154 | }
|
30155 | }
|
30156 | /**
|
30157 | * Given a `FunctionDeclaration`, `MethodDeclaration` or `FunctionExpression`, check if it is
|
30158 | * typed as a `ModuleWithProviders` and return an expression referencing the module if available.
|
30159 | */
|
30160 | _extractModuleFromModuleWithProvidersFn(node) {
|
30161 | const type = node.type || null;
|
30162 | return type &&
|
30163 | (this._reflectModuleFromTypeParam(type, node) || this._reflectModuleFromLiteralType(type));
|
30164 | }
|
30165 | /**
|
30166 | * Retrieve an `NgModule` identifier (T) from the specified `type`, if it is of the form:
|
30167 | * `ModuleWithProviders<T>`
|
30168 | * @param type The type to reflect on.
|
30169 | * @returns the identifier of the NgModule type if found, or null otherwise.
|
30170 | */
|
30171 | _reflectModuleFromTypeParam(type, node) {
|
30172 | // Examine the type of the function to see if it's a ModuleWithProviders reference.
|
30173 | if (!ts$1.isTypeReferenceNode(type)) {
|
30174 | return null;
|
30175 | }
|
30176 | const typeName = type &&
|
30177 | (ts$1.isIdentifier(type.typeName) && type.typeName ||
|
30178 | ts$1.isQualifiedName(type.typeName) && type.typeName.right) ||
|
30179 | null;
|
30180 | if (typeName === null) {
|
30181 | return null;
|
30182 | }
|
30183 | // Look at the type itself to see where it comes from.
|
30184 | const id = this.reflector.getImportOfIdentifier(typeName);
|
30185 | // If it's not named ModuleWithProviders, bail.
|
30186 | if (id === null || id.name !== 'ModuleWithProviders') {
|
30187 | return null;
|
30188 | }
|
30189 | // If it's not from @angular/core, bail.
|
30190 | if (!this.isCore && id.from !== '@angular/core') {
|
30191 | return null;
|
30192 | }
|
30193 | // If there's no type parameter specified, bail.
|
30194 | if (type.typeArguments === undefined || type.typeArguments.length !== 1) {
|
30195 | const parent = ts$1.isMethodDeclaration(node) && ts$1.isClassDeclaration(node.parent) ? node.parent : null;
|
30196 | const symbolName = (parent && parent.name ? parent.name.getText() + '.' : '') +
|
30197 | (node.name ? node.name.getText() : 'anonymous');
|
30198 | throw new FatalDiagnosticError(ErrorCode.NGMODULE_MODULE_WITH_PROVIDERS_MISSING_GENERIC, type, `${symbolName} returns a ModuleWithProviders type without a generic type argument. ` +
|
30199 | `Please add a generic type argument to the ModuleWithProviders type. If this ` +
|
30200 | `occurrence is in library code you don't control, please contact the library authors.`);
|
30201 | }
|
30202 | const arg = type.typeArguments[0];
|
30203 | return typeNodeToValueExpr(arg);
|
30204 | }
|
30205 | /**
|
30206 | * Retrieve an `NgModule` identifier (T) from the specified `type`, if it is of the form:
|
30207 | * `A|B|{ngModule: T}|C`.
|
30208 | * @param type The type to reflect on.
|
30209 | * @returns the identifier of the NgModule type if found, or null otherwise.
|
30210 | */
|
30211 | _reflectModuleFromLiteralType(type) {
|
30212 | if (!ts$1.isIntersectionTypeNode(type)) {
|
30213 | return null;
|
30214 | }
|
30215 | for (const t of type.types) {
|
30216 | if (ts$1.isTypeLiteralNode(t)) {
|
30217 | for (const m of t.members) {
|
30218 | const ngModuleType = ts$1.isPropertySignature(m) && ts$1.isIdentifier(m.name) &&
|
30219 | m.name.text === 'ngModule' && m.type ||
|
30220 | null;
|
30221 | const ngModuleExpression = ngModuleType && typeNodeToValueExpr(ngModuleType);
|
30222 | if (ngModuleExpression) {
|
30223 | return ngModuleExpression;
|
30224 | }
|
30225 | }
|
30226 | }
|
30227 | }
|
30228 | return null;
|
30229 | }
|
30230 | // Verify that a "Declaration" reference is a `ClassDeclaration` reference.
|
30231 | isClassDeclarationReference(ref) {
|
30232 | return this.reflector.isClass(ref.node);
|
30233 | }
|
30234 | /**
|
30235 | * Compute a list of `Reference`s from a resolved metadata value.
|
30236 | */
|
30237 | resolveTypeList(expr, resolvedList, className, arrayName) {
|
30238 | const refList = [];
|
30239 | if (!Array.isArray(resolvedList)) {
|
30240 | throw createValueHasWrongTypeError(expr, resolvedList, `Expected array when reading the NgModule.${arrayName} of ${className}`);
|
30241 | }
|
30242 | resolvedList.forEach((entry, idx) => {
|
30243 | // Unwrap ModuleWithProviders for modules that are locally declared (and thus static
|
30244 | // resolution was able to descend into the function and return an object literal, a Map).
|
30245 | if (entry instanceof Map && entry.has('ngModule')) {
|
30246 | entry = entry.get('ngModule');
|
30247 | }
|
30248 | if (Array.isArray(entry)) {
|
30249 | // Recurse into nested arrays.
|
30250 | refList.push(...this.resolveTypeList(expr, entry, className, arrayName));
|
30251 | }
|
30252 | else if (entry instanceof Reference$1) {
|
30253 | if (!this.isClassDeclarationReference(entry)) {
|
30254 | throw createValueHasWrongTypeError(entry.node, entry, `Value at position ${idx} in the NgModule.${arrayName} of ${className} is not a class`);
|
30255 | }
|
30256 | refList.push(entry);
|
30257 | }
|
30258 | else {
|
30259 | // TODO(alxhub): Produce a better diagnostic here - the array index may be an inner array.
|
30260 | throw createValueHasWrongTypeError(expr, entry, `Value at position ${idx} in the NgModule.${arrayName} of ${className} is not a reference`);
|
30261 | }
|
30262 | });
|
30263 | return refList;
|
30264 | }
|
30265 | }
|
30266 | function isNgModule(node, compilation) {
|
30267 | return !compilation.directives.some(directive => directive.ref.node === node) &&
|
30268 | !compilation.pipes.some(pipe => pipe.ref.node === node);
|
30269 | }
|
30270 |
|
30271 | /**
|
30272 | * @license
|
30273 | * Copyright Google LLC All Rights Reserved.
|
30274 | *
|
30275 | * Use of this source code is governed by an MIT-style license that can be
|
30276 | * found in the LICENSE file at https://angular.io/license
|
30277 | */
|
30278 | class PipeDecoratorHandler {
|
30279 | constructor(reflector, evaluator, metaRegistry, scopeRegistry, defaultImportRecorder, injectableRegistry, isCore) {
|
30280 | this.reflector = reflector;
|
30281 | this.evaluator = evaluator;
|
30282 | this.metaRegistry = metaRegistry;
|
30283 | this.scopeRegistry = scopeRegistry;
|
30284 | this.defaultImportRecorder = defaultImportRecorder;
|
30285 | this.injectableRegistry = injectableRegistry;
|
30286 | this.isCore = isCore;
|
30287 | this.precedence = HandlerPrecedence.PRIMARY;
|
30288 | this.name = PipeDecoratorHandler.name;
|
30289 | }
|
30290 | detect(node, decorators) {
|
30291 | if (!decorators) {
|
30292 | return undefined;
|
30293 | }
|
30294 | const decorator = findAngularDecorator(decorators, 'Pipe', this.isCore);
|
30295 | if (decorator !== undefined) {
|
30296 | return {
|
30297 | trigger: decorator.node,
|
30298 | decorator: decorator,
|
30299 | metadata: decorator,
|
30300 | };
|
30301 | }
|
30302 | else {
|
30303 | return undefined;
|
30304 | }
|
30305 | }
|
30306 | analyze(clazz, decorator) {
|
30307 | const name = clazz.name.text;
|
30308 | const type = wrapTypeReference(this.reflector, clazz);
|
30309 | const internalType = new WrappedNodeExpr(this.reflector.getInternalNameOfClass(clazz));
|
30310 | if (decorator.args === null) {
|
30311 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_NOT_CALLED, Decorator.nodeForError(decorator), `@Pipe must be called`);
|
30312 | }
|
30313 | if (decorator.args.length !== 1) {
|
30314 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARITY_WRONG, Decorator.nodeForError(decorator), '@Pipe must have exactly one argument');
|
30315 | }
|
30316 | const meta = unwrapExpression(decorator.args[0]);
|
30317 | if (!ts$1.isObjectLiteralExpression(meta)) {
|
30318 | throw new FatalDiagnosticError(ErrorCode.DECORATOR_ARG_NOT_LITERAL, meta, '@Pipe must have a literal argument');
|
30319 | }
|
30320 | const pipe = reflectObjectLiteral(meta);
|
30321 | if (!pipe.has('name')) {
|
30322 | throw new FatalDiagnosticError(ErrorCode.PIPE_MISSING_NAME, meta, `@Pipe decorator is missing name field`);
|
30323 | }
|
30324 | const pipeNameExpr = pipe.get('name');
|
30325 | const pipeName = this.evaluator.evaluate(pipeNameExpr);
|
30326 | if (typeof pipeName !== 'string') {
|
30327 | throw createValueHasWrongTypeError(pipeNameExpr, pipeName, `@Pipe.name must be a string`);
|
30328 | }
|
30329 | let pure = true;
|
30330 | if (pipe.has('pure')) {
|
30331 | const expr = pipe.get('pure');
|
30332 | const pureValue = this.evaluator.evaluate(expr);
|
30333 | if (typeof pureValue !== 'boolean') {
|
30334 | throw createValueHasWrongTypeError(expr, pureValue, `@Pipe.pure must be a boolean`);
|
30335 | }
|
30336 | pure = pureValue;
|
30337 | }
|
30338 | return {
|
30339 | analysis: {
|
30340 | meta: {
|
30341 | name,
|
30342 | type,
|
30343 | internalType,
|
30344 | typeArgumentCount: this.reflector.getGenericArityOfClass(clazz) || 0,
|
30345 | pipeName,
|
30346 | deps: getValidConstructorDependencies(clazz, this.reflector, this.defaultImportRecorder, this.isCore),
|
30347 | pure,
|
30348 | },
|
30349 | metadataStmt: generateSetClassMetadataCall(clazz, this.reflector, this.defaultImportRecorder, this.isCore),
|
30350 | },
|
30351 | };
|
30352 | }
|
30353 | register(node, analysis) {
|
30354 | const ref = new Reference$1(node);
|
30355 | this.metaRegistry.registerPipeMetadata({ ref, name: analysis.meta.pipeName });
|
30356 | this.injectableRegistry.registerInjectable(node);
|
30357 | }
|
30358 | resolve(node) {
|
30359 | const duplicateDeclData = this.scopeRegistry.getDuplicateDeclarations(node);
|
30360 | if (duplicateDeclData !== null) {
|
30361 | // This pipe was declared twice (or more).
|
30362 | return {
|
30363 | diagnostics: [makeDuplicateDeclarationError(node, duplicateDeclData, 'Pipe')],
|
30364 | };
|
30365 | }
|
30366 | return {};
|
30367 | }
|
30368 | compileFull(node, analysis) {
|
30369 | const res = compilePipeFromMetadata(analysis.meta);
|
30370 | return this.compilePipe(analysis, res);
|
30371 | }
|
30372 | compilePartial(node, analysis) {
|
30373 | const res = compileDeclarePipeFromMetadata(analysis.meta);
|
30374 | return this.compilePipe(analysis, res);
|
30375 | }
|
30376 | compilePipe(analysis, def) {
|
30377 | const factoryRes = compileNgFactoryDefField(Object.assign(Object.assign({}, analysis.meta), { injectFn: Identifiers.directiveInject, target: R3FactoryTarget.Pipe }));
|
30378 | if (analysis.metadataStmt !== null) {
|
30379 | factoryRes.statements.push(analysis.metadataStmt);
|
30380 | }
|
30381 | return [
|
30382 | factoryRes, {
|
30383 | name: 'ɵpipe',
|
30384 | initializer: def.expression,
|
30385 | statements: [],
|
30386 | type: def.type,
|
30387 | }
|
30388 | ];
|
30389 | }
|
30390 | }
|
30391 |
|
30392 | /**
|
30393 | * @license
|
30394 | * Copyright Google LLC All Rights Reserved.
|
30395 | *
|
30396 | * Use of this source code is governed by an MIT-style license that can be
|
30397 | * found in the LICENSE file at https://angular.io/license
|
30398 | */
|
30399 | /**
|
30400 | * This registry does nothing, since ngtsc does not currently need
|
30401 | * this functionality.
|
30402 | * The ngcc tool implements a working version for its purposes.
|
30403 | */
|
30404 | class NoopReferencesRegistry {
|
30405 | add(source, ...references) { }
|
30406 | }
|
30407 |
|
30408 | /**
|
30409 | * @license
|
30410 | * Copyright Google LLC All Rights Reserved.
|
30411 | *
|
30412 | * Use of this source code is governed by an MIT-style license that can be
|
30413 | * found in the LICENSE file at https://angular.io/license
|
30414 | */
|
30415 | /**
|
30416 | * Analyzes a `ts.Program` for cycles.
|
30417 | */
|
30418 | class CycleAnalyzer {
|
30419 | constructor(importGraph) {
|
30420 | this.importGraph = importGraph;
|
30421 | }
|
30422 | /**
|
30423 | * Check for a cycle to be created in the `ts.Program` by adding an import between `from` and
|
30424 | * `to`.
|
30425 | *
|
30426 | * @returns a `Cycle` object if an import between `from` and `to` would create a cycle; `null`
|
30427 | * otherwise.
|
30428 | */
|
30429 | wouldCreateCycle(from, to) {
|
30430 | // Import of 'from' -> 'to' is illegal if an edge 'to' -> 'from' already exists.
|
30431 | return this.importGraph.transitiveImportsOf(to).has(from) ?
|
30432 | new Cycle(this.importGraph, from, to) :
|
30433 | null;
|
30434 | }
|
30435 | /**
|
30436 | * Record a synthetic import from `from` to `to`.
|
30437 | *
|
30438 | * This is an import that doesn't exist in the `ts.Program` but will be considered as part of the
|
30439 | * import graph for cycle creation.
|
30440 | */
|
30441 | recordSyntheticImport(from, to) {
|
30442 | this.importGraph.addSyntheticImport(from, to);
|
30443 | }
|
30444 | }
|
30445 | /**
|
30446 | * Represents an import cycle between `from` and `to` in the program.
|
30447 | *
|
30448 | * This class allows us to do the work to compute the cyclic path between `from` and `to` only if
|
30449 | * needed.
|
30450 | */
|
30451 | class Cycle {
|
30452 | constructor(importGraph, from, to) {
|
30453 | this.importGraph = importGraph;
|
30454 | this.from = from;
|
30455 | this.to = to;
|
30456 | }
|
30457 | /**
|
30458 | * Compute an array of source-files that illustrates the cyclic path between `from` and `to`.
|
30459 | *
|
30460 | * Note that a `Cycle` will not be created unless a path is available between `to` and `from`,
|
30461 | * so `findPath()` will never return `null`.
|
30462 | */
|
30463 | getPath() {
|
30464 | return [this.from, ...this.importGraph.findPath(this.to, this.from)];
|
30465 | }
|
30466 | }
|
30467 |
|
30468 | /**
|
30469 | * @license
|
30470 | * Copyright Google LLC All Rights Reserved.
|
30471 | *
|
30472 | * Use of this source code is governed by an MIT-style license that can be
|
30473 | * found in the LICENSE file at https://angular.io/license
|
30474 | */
|
30475 | /**
|
30476 | * A cached graph of imports in the `ts.Program`.
|
30477 | *
|
30478 | * The `ImportGraph` keeps track of dependencies (imports) of individual `ts.SourceFile`s. Only
|
30479 | * dependencies within the same program are tracked; imports into packages on NPM are not.
|
30480 | */
|
30481 | class ImportGraph {
|
30482 | constructor(resolver) {
|
30483 | this.resolver = resolver;
|
30484 | this.map = new Map();
|
30485 | }
|
30486 | /**
|
30487 | * List the direct (not transitive) imports of a given `ts.SourceFile`.
|
30488 | *
|
30489 | * This operation is cached.
|
30490 | */
|
30491 | importsOf(sf) {
|
30492 | if (!this.map.has(sf)) {
|
30493 | this.map.set(sf, this.scanImports(sf));
|
30494 | }
|
30495 | return this.map.get(sf);
|
30496 | }
|
30497 | /**
|
30498 | * Lists the transitive imports of a given `ts.SourceFile`.
|
30499 | */
|
30500 | transitiveImportsOf(sf) {
|
30501 | const imports = new Set();
|
30502 | this.transitiveImportsOfHelper(sf, imports);
|
30503 | return imports;
|
30504 | }
|
30505 | transitiveImportsOfHelper(sf, results) {
|
30506 | if (results.has(sf)) {
|
30507 | return;
|
30508 | }
|
30509 | results.add(sf);
|
30510 | this.importsOf(sf).forEach(imported => {
|
30511 | this.transitiveImportsOfHelper(imported, results);
|
30512 | });
|
30513 | }
|
30514 | /**
|
30515 | * Find an import path from the `start` SourceFile to the `end` SourceFile.
|
30516 | *
|
30517 | * This function implements a breadth first search that results in finding the
|
30518 | * shortest path between the `start` and `end` points.
|
30519 | *
|
30520 | * @param start the starting point of the path.
|
30521 | * @param end the ending point of the path.
|
30522 | * @returns an array of source files that connect the `start` and `end` source files, or `null` if
|
30523 | * no path could be found.
|
30524 | */
|
30525 | findPath(start, end) {
|
30526 | if (start === end) {
|
30527 | // Escape early for the case where `start` and `end` are the same.
|
30528 | return [start];
|
30529 | }
|
30530 | const found = new Set([start]);
|
30531 | const queue = [new Found(start, null)];
|
30532 | while (queue.length > 0) {
|
30533 | const current = queue.shift();
|
30534 | const imports = this.importsOf(current.sourceFile);
|
30535 | for (const importedFile of imports) {
|
30536 | if (!found.has(importedFile)) {
|
30537 | const next = new Found(importedFile, current);
|
30538 | if (next.sourceFile === end) {
|
30539 | // We have hit the target `end` path so we can stop here.
|
30540 | return next.toPath();
|
30541 | }
|
30542 | found.add(importedFile);
|
30543 | queue.push(next);
|
30544 | }
|
30545 | }
|
30546 | }
|
30547 | return null;
|
30548 | }
|
30549 | /**
|
30550 | * Add a record of an import from `sf` to `imported`, that's not present in the original
|
30551 | * `ts.Program` but will be remembered by the `ImportGraph`.
|
30552 | */
|
30553 | addSyntheticImport(sf, imported) {
|
30554 | if (isLocalFile(imported)) {
|
30555 | this.importsOf(sf).add(imported);
|
30556 | }
|
30557 | }
|
30558 | scanImports(sf) {
|
30559 | const imports = new Set();
|
30560 | // Look through the source file for import statements.
|
30561 | sf.statements.forEach(stmt => {
|
30562 | if ((ts$1.isImportDeclaration(stmt) || ts$1.isExportDeclaration(stmt)) &&
|
30563 | stmt.moduleSpecifier !== undefined && ts$1.isStringLiteral(stmt.moduleSpecifier)) {
|
30564 | // Resolve the module to a file, and check whether that file is in the ts.Program.
|
30565 | const moduleName = stmt.moduleSpecifier.text;
|
30566 | const moduleFile = this.resolver.resolveModule(moduleName, sf.fileName);
|
30567 | if (moduleFile !== null && isLocalFile(moduleFile)) {
|
30568 | // Record this local import.
|
30569 | imports.add(moduleFile);
|
30570 | }
|
30571 | }
|
30572 | });
|
30573 | return imports;
|
30574 | }
|
30575 | }
|
30576 | function isLocalFile(sf) {
|
30577 | return !sf.fileName.endsWith('.d.ts');
|
30578 | }
|
30579 | /**
|
30580 | * A helper class to track which SourceFiles are being processed when searching for a path in
|
30581 | * `getPath()` above.
|
30582 | */
|
30583 | class Found {
|
30584 | constructor(sourceFile, parent) {
|
30585 | this.sourceFile = sourceFile;
|
30586 | this.parent = parent;
|
30587 | }
|
30588 | /**
|
30589 | * Back track through this found SourceFile and its ancestors to generate an array of
|
30590 | * SourceFiles that form am import path between two SourceFiles.
|
30591 | */
|
30592 | toPath() {
|
30593 | const array = [];
|
30594 | let current = this;
|
30595 | while (current !== null) {
|
30596 | array.push(current.sourceFile);
|
30597 | current = current.parent;
|
30598 | }
|
30599 | // Pushing and then reversing, O(n), rather than unshifting repeatedly, O(n^2), avoids
|
30600 | // manipulating the array on every iteration: https://stackoverflow.com/a/26370620
|
30601 | return array.reverse();
|
30602 | }
|
30603 | }
|
30604 |
|
30605 | /**
|
30606 | * @license
|
30607 | * Copyright Google LLC All Rights Reserved.
|
30608 | *
|
30609 | * Use of this source code is governed by an MIT-style license that can be
|
30610 | * found in the LICENSE file at https://angular.io/license
|
30611 | */
|
30612 | /**
|
30613 | * Produce `ts.Diagnostic`s for classes that are visible from exported types (e.g. directives
|
30614 | * exposed by exported `NgModule`s) that are not themselves exported.
|
30615 | *
|
30616 | * This function reconciles two concepts:
|
30617 | *
|
30618 | * A class is Exported if it's exported from the main library `entryPoint` file.
|
30619 | * A class is Visible if, via Angular semantics, a downstream consumer can import an Exported class
|
30620 | * and be affected by the class in question. For example, an Exported NgModule may expose a
|
30621 | * directive class to its consumers. Consumers that import the NgModule may have the directive
|
30622 | * applied to elements in their templates. In this case, the directive is considered Visible.
|
30623 | *
|
30624 | * `checkForPrivateExports` attempts to verify that all Visible classes are Exported, and report
|
30625 | * `ts.Diagnostic`s for those that aren't.
|
30626 | *
|
30627 | * @param entryPoint `ts.SourceFile` of the library's entrypoint, which should export the library's
|
30628 | * public API.
|
30629 | * @param checker `ts.TypeChecker` for the current program.
|
30630 | * @param refGraph `ReferenceGraph` tracking the visibility of Angular types.
|
30631 | * @returns an array of `ts.Diagnostic`s representing errors when visible classes are not exported
|
30632 | * properly.
|
30633 | */
|
30634 | function checkForPrivateExports(entryPoint, checker, refGraph) {
|
30635 | const diagnostics = [];
|
30636 | // Firstly, compute the exports of the entry point. These are all the Exported classes.
|
30637 | const topLevelExports = new Set();
|
30638 | // Do this via `ts.TypeChecker.getExportsOfModule`.
|
30639 | const moduleSymbol = checker.getSymbolAtLocation(entryPoint);
|
30640 | if (moduleSymbol === undefined) {
|
30641 | throw new Error(`Internal error: failed to get symbol for entrypoint`);
|
30642 | }
|
30643 | const exportedSymbols = checker.getExportsOfModule(moduleSymbol);
|
30644 | // Loop through the exported symbols, de-alias if needed, and add them to `topLevelExports`.
|
30645 | // TODO(alxhub): use proper iteration when build.sh is removed. (#27762)
|
30646 | exportedSymbols.forEach(symbol => {
|
30647 | if (symbol.flags & ts$1.SymbolFlags.Alias) {
|
30648 | symbol = checker.getAliasedSymbol(symbol);
|
30649 | }
|
30650 | const decl = symbol.valueDeclaration;
|
30651 | if (decl !== undefined) {
|
30652 | topLevelExports.add(decl);
|
30653 | }
|
30654 | });
|
30655 | // Next, go through each exported class and expand it to the set of classes it makes Visible,
|
30656 | // using the `ReferenceGraph`. For each Visible class, verify that it's also Exported, and queue
|
30657 | // an error if it isn't. `checkedSet` ensures only one error is queued per class.
|
30658 | const checkedSet = new Set();
|
30659 | // Loop through each Exported class.
|
30660 | // TODO(alxhub): use proper iteration when the legacy build is removed. (#27762)
|
30661 | topLevelExports.forEach(mainExport => {
|
30662 | // Loop through each class made Visible by the Exported class.
|
30663 | refGraph.transitiveReferencesOf(mainExport).forEach(transitiveReference => {
|
30664 | // Skip classes which have already been checked.
|
30665 | if (checkedSet.has(transitiveReference)) {
|
30666 | return;
|
30667 | }
|
30668 | checkedSet.add(transitiveReference);
|
30669 | // Verify that the Visible class is also Exported.
|
30670 | if (!topLevelExports.has(transitiveReference)) {
|
30671 | // This is an error, `mainExport` makes `transitiveReference` Visible, but
|
30672 | // `transitiveReference` is not Exported from the entrypoint. Construct a diagnostic to
|
30673 | // give to the user explaining the situation.
|
30674 | const descriptor = getDescriptorOfDeclaration(transitiveReference);
|
30675 | const name = getNameOfDeclaration(transitiveReference);
|
30676 | // Construct the path of visibility, from `mainExport` to `transitiveReference`.
|
30677 | let visibleVia = 'NgModule exports';
|
30678 | const transitivePath = refGraph.pathFrom(mainExport, transitiveReference);
|
30679 | if (transitivePath !== null) {
|
30680 | visibleVia = transitivePath.map(seg => getNameOfDeclaration(seg)).join(' -> ');
|
30681 | }
|
30682 | const diagnostic = Object.assign(Object.assign({ category: ts$1.DiagnosticCategory.Error, code: ngErrorCode(ErrorCode.SYMBOL_NOT_EXPORTED), file: transitiveReference.getSourceFile() }, getPosOfDeclaration(transitiveReference)), { messageText: `Unsupported private ${descriptor} ${name}. This ${descriptor} is visible to consumers via ${visibleVia}, but is not exported from the top-level library entrypoint.` });
|
30683 | diagnostics.push(diagnostic);
|
30684 | }
|
30685 | });
|
30686 | });
|
30687 | return diagnostics;
|
30688 | }
|
30689 | function getPosOfDeclaration(decl) {
|
30690 | const node = getIdentifierOfDeclaration(decl) || decl;
|
30691 | return {
|
30692 | start: node.getStart(),
|
30693 | length: node.getEnd() + 1 - node.getStart(),
|
30694 | };
|
30695 | }
|
30696 | function getIdentifierOfDeclaration(decl) {
|
30697 | if ((ts$1.isClassDeclaration(decl) || ts$1.isVariableDeclaration(decl) ||
|
30698 | ts$1.isFunctionDeclaration(decl)) &&
|
30699 | decl.name !== undefined && ts$1.isIdentifier(decl.name)) {
|
30700 | return decl.name;
|
30701 | }
|
30702 | else {
|
30703 | return null;
|
30704 | }
|
30705 | }
|
30706 | function getNameOfDeclaration(decl) {
|
30707 | const id = getIdentifierOfDeclaration(decl);
|
30708 | return id !== null ? id.text : '(unnamed)';
|
30709 | }
|
30710 | function getDescriptorOfDeclaration(decl) {
|
30711 | switch (decl.kind) {
|
30712 | case ts$1.SyntaxKind.ClassDeclaration:
|
30713 | return 'class';
|
30714 | case ts$1.SyntaxKind.FunctionDeclaration:
|
30715 | return 'function';
|
30716 | case ts$1.SyntaxKind.VariableDeclaration:
|
30717 | return 'variable';
|
30718 | case ts$1.SyntaxKind.EnumDeclaration:
|
30719 | return 'enum';
|
30720 | default:
|
30721 | return 'declaration';
|
30722 | }
|
30723 | }
|
30724 |
|
30725 | /**
|
30726 | * @license
|
30727 | * Copyright Google LLC All Rights Reserved.
|
30728 | *
|
30729 | * Use of this source code is governed by an MIT-style license that can be
|
30730 | * found in the LICENSE file at https://angular.io/license
|
30731 | */
|
30732 | class ReferenceGraph {
|
30733 | constructor() {
|
30734 | this.references = new Map();
|
30735 | }
|
30736 | add(from, to) {
|
30737 | if (!this.references.has(from)) {
|
30738 | this.references.set(from, new Set());
|
30739 | }
|
30740 | this.references.get(from).add(to);
|
30741 | }
|
30742 | transitiveReferencesOf(target) {
|
30743 | const set = new Set();
|
30744 | this.collectTransitiveReferences(set, target);
|
30745 | return set;
|
30746 | }
|
30747 | pathFrom(source, target) {
|
30748 | return this.collectPathFrom(source, target, new Set());
|
30749 | }
|
30750 | collectPathFrom(source, target, seen) {
|
30751 | if (source === target) {
|
30752 | // Looking for a path from the target to itself - that path is just the target. This is the
|
30753 | // "base case" of the search.
|
30754 | return [target];
|
30755 | }
|
30756 | else if (seen.has(source)) {
|
30757 | // The search has already looked through this source before.
|
30758 | return null;
|
30759 | }
|
30760 | // Consider outgoing edges from `source`.
|
30761 | seen.add(source);
|
30762 | if (!this.references.has(source)) {
|
30763 | // There are no outgoing edges from `source`.
|
30764 | return null;
|
30765 | }
|
30766 | else {
|
30767 | // Look through the outgoing edges of `source`.
|
30768 | // TODO(alxhub): use proper iteration when the legacy build is removed. (#27762)
|
30769 | let candidatePath = null;
|
30770 | this.references.get(source).forEach(edge => {
|
30771 | // Early exit if a path has already been found.
|
30772 | if (candidatePath !== null) {
|
30773 | return;
|
30774 | }
|
30775 | // Look for a path from this outgoing edge to `target`.
|
30776 | const partialPath = this.collectPathFrom(edge, target, seen);
|
30777 | if (partialPath !== null) {
|
30778 | // A path exists from `edge` to `target`. Insert `source` at the beginning.
|
30779 | candidatePath = [source, ...partialPath];
|
30780 | }
|
30781 | });
|
30782 | return candidatePath;
|
30783 | }
|
30784 | }
|
30785 | collectTransitiveReferences(set, decl) {
|
30786 | if (this.references.has(decl)) {
|
30787 | // TODO(alxhub): use proper iteration when the legacy build is removed. (#27762)
|
30788 | this.references.get(decl).forEach(ref => {
|
30789 | if (!set.has(ref)) {
|
30790 | set.add(ref);
|
30791 | this.collectTransitiveReferences(set, ref);
|
30792 | }
|
30793 | });
|
30794 | }
|
30795 | }
|
30796 | }
|
30797 |
|
30798 | /**
|
30799 | * @license
|
30800 | * Copyright Google LLC All Rights Reserved.
|
30801 | *
|
30802 | * Use of this source code is governed by an MIT-style license that can be
|
30803 | * found in the LICENSE file at https://angular.io/license
|
30804 | */
|
30805 | /**
|
30806 | * An implementation of the `DependencyTracker` dependency graph API.
|
30807 | *
|
30808 | * The `FileDependencyGraph`'s primary job is to determine whether a given file has "logically"
|
30809 | * changed, given the set of physical changes (direct changes to files on disk).
|
30810 | *
|
30811 | * A file is logically changed if at least one of three conditions is met:
|
30812 | *
|
30813 | * 1. The file itself has physically changed.
|
30814 | * 2. One of its dependencies has physically changed.
|
30815 | * 3. One of its resource dependencies has physically changed.
|
30816 | */
|
30817 | class FileDependencyGraph {
|
30818 | constructor() {
|
30819 | this.nodes = new Map();
|
30820 | }
|
30821 | addDependency(from, on) {
|
30822 | this.nodeFor(from).dependsOn.add(on.fileName);
|
30823 | }
|
30824 | addResourceDependency(from, resource) {
|
30825 | this.nodeFor(from).usesResources.add(resource);
|
30826 | }
|
30827 | addTransitiveDependency(from, on) {
|
30828 | const nodeFrom = this.nodeFor(from);
|
30829 | nodeFrom.dependsOn.add(on.fileName);
|
30830 | const nodeOn = this.nodeFor(on);
|
30831 | for (const dep of nodeOn.dependsOn) {
|
30832 | nodeFrom.dependsOn.add(dep);
|
30833 | }
|
30834 | }
|
30835 | addTransitiveResources(from, resourcesOf) {
|
30836 | const nodeFrom = this.nodeFor(from);
|
30837 | const nodeOn = this.nodeFor(resourcesOf);
|
30838 | for (const dep of nodeOn.usesResources) {
|
30839 | nodeFrom.usesResources.add(dep);
|
30840 | }
|
30841 | }
|
30842 | recordDependencyAnalysisFailure(file) {
|
30843 | this.nodeFor(file).failedAnalysis = true;
|
30844 | }
|
30845 | getResourceDependencies(from) {
|
30846 | const node = this.nodes.get(from);
|
30847 | return node ? [...node.usesResources] : [];
|
30848 | }
|
30849 | isStale(sf, changedTsPaths, changedResources) {
|
30850 | return isLogicallyChanged(sf, this.nodeFor(sf), changedTsPaths, EMPTY_SET, changedResources);
|
30851 | }
|
30852 | /**
|
30853 | * Update the current dependency graph from a previous one, incorporating a set of physical
|
30854 | * changes.
|
30855 | *
|
30856 | * This method performs two tasks:
|
30857 | *
|
30858 | * 1. For files which have not logically changed, their dependencies from `previous` are added to
|
30859 | * `this` graph.
|
30860 | * 2. For files which have logically changed, they're added to a set of logically changed files
|
30861 | * which is eventually returned.
|
30862 | *
|
30863 | * In essence, for build `n`, this method performs:
|
30864 | *
|
30865 | * G(n) + L(n) = G(n - 1) + P(n)
|
30866 | *
|
30867 | * where:
|
30868 | *
|
30869 | * G(n) = the dependency graph of build `n`
|
30870 | * L(n) = the logically changed files from build n - 1 to build n.
|
30871 | * P(n) = the physically changed files from build n - 1 to build n.
|
30872 | */
|
30873 | updateWithPhysicalChanges(previous, changedTsPaths, deletedTsPaths, changedResources) {
|
30874 | const logicallyChanged = new Set();
|
30875 | for (const sf of previous.nodes.keys()) {
|
30876 | const node = previous.nodeFor(sf);
|
30877 | if (isLogicallyChanged(sf, node, changedTsPaths, deletedTsPaths, changedResources)) {
|
30878 | logicallyChanged.add(sf.fileName);
|
30879 | }
|
30880 | else if (!deletedTsPaths.has(sf.fileName)) {
|
30881 | this.nodes.set(sf, {
|
30882 | dependsOn: new Set(node.dependsOn),
|
30883 | usesResources: new Set(node.usesResources),
|
30884 | failedAnalysis: false,
|
30885 | });
|
30886 | }
|
30887 | }
|
30888 | return logicallyChanged;
|
30889 | }
|
30890 | nodeFor(sf) {
|
30891 | if (!this.nodes.has(sf)) {
|
30892 | this.nodes.set(sf, {
|
30893 | dependsOn: new Set(),
|
30894 | usesResources: new Set(),
|
30895 | failedAnalysis: false,
|
30896 | });
|
30897 | }
|
30898 | return this.nodes.get(sf);
|
30899 | }
|
30900 | }
|
30901 | /**
|
30902 | * Determine whether `sf` has logically changed, given its dependencies and the set of physically
|
30903 | * changed files and resources.
|
30904 | */
|
30905 | function isLogicallyChanged(sf, node, changedTsPaths, deletedTsPaths, changedResources) {
|
30906 | // A file is assumed to have logically changed if its dependencies could not be determined
|
30907 | // accurately.
|
30908 | if (node.failedAnalysis) {
|
30909 | return true;
|
30910 | }
|
30911 | // A file is logically changed if it has physically changed itself (including being deleted).
|
30912 | if (changedTsPaths.has(sf.fileName) || deletedTsPaths.has(sf.fileName)) {
|
30913 | return true;
|
30914 | }
|
30915 | // A file is logically changed if one of its dependencies has physically changed.
|
30916 | for (const dep of node.dependsOn) {
|
30917 | if (changedTsPaths.has(dep) || deletedTsPaths.has(dep)) {
|
30918 | return true;
|
30919 | }
|
30920 | }
|
30921 | // A file is logically changed if one of its resources has physically changed.
|
30922 | for (const dep of node.usesResources) {
|
30923 | if (changedResources.has(dep)) {
|
30924 | return true;
|
30925 | }
|
30926 | }
|
30927 | return false;
|
30928 | }
|
30929 | const EMPTY_SET = new Set();
|
30930 |
|
30931 | /**
|
30932 | * @license
|
30933 | * Copyright Google LLC All Rights Reserved.
|
30934 | *
|
30935 | * Use of this source code is governed by an MIT-style license that can be
|
30936 | * found in the LICENSE file at https://angular.io/license
|
30937 | */
|
30938 | /**
|
30939 | * Drives an incremental build, by tracking changes and determining which files need to be emitted.
|
30940 | */
|
30941 | class IncrementalDriver {
|
30942 | constructor(state, allTsFiles, depGraph, logicalChanges) {
|
30943 | this.allTsFiles = allTsFiles;
|
30944 | this.depGraph = depGraph;
|
30945 | this.logicalChanges = logicalChanges;
|
30946 | this.state = state;
|
30947 | }
|
30948 | /**
|
30949 | * Construct an `IncrementalDriver` with a starting state that incorporates the results of a
|
30950 | * previous build.
|
30951 | *
|
30952 | * The previous build's `BuildState` is reconciled with the new program's changes, and the results
|
30953 | * are merged into the new build's `PendingBuildState`.
|
30954 | */
|
30955 | static reconcile(oldProgram, oldDriver, newProgram, modifiedResourceFiles) {
|
30956 | // Initialize the state of the current build based on the previous one.
|
30957 | let state;
|
30958 | if (oldDriver.state.kind === BuildStateKind.Pending) {
|
30959 | // The previous build never made it past the pending state. Reuse it as the starting state for
|
30960 | // this build.
|
30961 | state = oldDriver.state;
|
30962 | }
|
30963 | else {
|
30964 | // The previous build was successfully analyzed. `pendingEmit` is the only state carried
|
30965 | // forward into this build.
|
30966 | state = {
|
30967 | kind: BuildStateKind.Pending,
|
30968 | pendingEmit: oldDriver.state.pendingEmit,
|
30969 | changedResourcePaths: new Set(),
|
30970 | changedTsPaths: new Set(),
|
30971 | lastGood: oldDriver.state.lastGood,
|
30972 | };
|
30973 | }
|
30974 | // Merge the freshly modified resource files with any prior ones.
|
30975 | if (modifiedResourceFiles !== null) {
|
30976 | for (const resFile of modifiedResourceFiles) {
|
30977 | state.changedResourcePaths.add(absoluteFrom(resFile));
|
30978 | }
|
30979 | }
|
30980 | // Next, process the files in the new program, with a couple of goals:
|
30981 | // 1) Determine which TS files have changed, if any, and merge them into `changedTsFiles`.
|
30982 | // 2) Produce a list of TS files which no longer exist in the program (they've been deleted
|
30983 | // since the previous compilation). These need to be removed from the state tracking to avoid
|
30984 | // leaking memory.
|
30985 | // All files in the old program, for easy detection of changes.
|
30986 | const oldFiles = new Set(oldProgram.getSourceFiles());
|
30987 | // Assume all the old files were deleted to begin with. Only TS files are tracked.
|
30988 | const deletedTsPaths = new Set(tsOnlyFiles(oldProgram).map(sf => sf.fileName));
|
30989 | for (const newFile of newProgram.getSourceFiles()) {
|
30990 | if (!newFile.isDeclarationFile) {
|
30991 | // This file exists in the new program, so remove it from `deletedTsPaths`.
|
30992 | deletedTsPaths.delete(newFile.fileName);
|
30993 | }
|
30994 | if (oldFiles.has(newFile)) {
|
30995 | // This file hasn't changed; no need to look at it further.
|
30996 | continue;
|
30997 | }
|
30998 | // The file has changed since the last successful build. The appropriate reaction depends on
|
30999 | // what kind of file it is.
|
31000 | if (!newFile.isDeclarationFile) {
|
31001 | // It's a .ts file, so track it as a change.
|
31002 | state.changedTsPaths.add(newFile.fileName);
|
31003 | }
|
31004 | else {
|
31005 | // It's a .d.ts file. Currently the compiler does not do a great job of tracking
|
31006 | // dependencies on .d.ts files, so bail out of incremental builds here and do a full build.
|
31007 | // This usually only happens if something in node_modules changes.
|
31008 | return IncrementalDriver.fresh(newProgram);
|
31009 | }
|
31010 | }
|
31011 | // The next step is to remove any deleted files from the state.
|
31012 | for (const filePath of deletedTsPaths) {
|
31013 | state.pendingEmit.delete(filePath);
|
31014 | // Even if the file doesn't exist in the current compilation, it still might have been changed
|
31015 | // in a previous one, so delete it from the set of changed TS files, just in case.
|
31016 | state.changedTsPaths.delete(filePath);
|
31017 | }
|
31018 | // Now, changedTsPaths contains physically changed TS paths. Use the previous program's logical
|
31019 | // dependency graph to determine logically changed files.
|
31020 | const depGraph = new FileDependencyGraph();
|
31021 | // If a previous compilation exists, use its dependency graph to determine the set of logically
|
31022 | // changed files.
|
31023 | let logicalChanges = null;
|
31024 | if (state.lastGood !== null) {
|
31025 | // Extract the set of logically changed files. At the same time, this operation populates the
|
31026 | // current (fresh) dependency graph with information about those files which have not
|
31027 | // logically changed.
|
31028 | logicalChanges = depGraph.updateWithPhysicalChanges(state.lastGood.depGraph, state.changedTsPaths, deletedTsPaths, state.changedResourcePaths);
|
31029 | for (const fileName of state.changedTsPaths) {
|
31030 | logicalChanges.add(fileName);
|
31031 | }
|
31032 | // Any logically changed files need to be re-emitted. Most of the time this would happen
|
31033 | // regardless because the new dependency graph would _also_ identify the file as stale.
|
31034 | // However there are edge cases such as removing a component from an NgModule without adding
|
31035 | // it to another one, where the previous graph identifies the file as logically changed, but
|
31036 | // the new graph (which does not have that edge) fails to identify that the file should be
|
31037 | // re-emitted.
|
31038 | for (const change of logicalChanges) {
|
31039 | state.pendingEmit.add(change);
|
31040 | }
|
31041 | }
|
31042 | // `state` now reflects the initial pending state of the current compilation.
|
31043 | return new IncrementalDriver(state, new Set(tsOnlyFiles(newProgram)), depGraph, logicalChanges);
|
31044 | }
|
31045 | static fresh(program) {
|
31046 | // Initialize the set of files which need to be emitted to the set of all TS files in the
|
31047 | // program.
|
31048 | const tsFiles = tsOnlyFiles(program);
|
31049 | const state = {
|
31050 | kind: BuildStateKind.Pending,
|
31051 | pendingEmit: new Set(tsFiles.map(sf => sf.fileName)),
|
31052 | changedResourcePaths: new Set(),
|
31053 | changedTsPaths: new Set(),
|
31054 | lastGood: null,
|
31055 | };
|
31056 | return new IncrementalDriver(state, new Set(tsFiles), new FileDependencyGraph(), /* logicalChanges */ null);
|
31057 | }
|
31058 | recordSuccessfulAnalysis(traitCompiler) {
|
31059 | if (this.state.kind !== BuildStateKind.Pending) {
|
31060 | // Changes have already been incorporated.
|
31061 | return;
|
31062 | }
|
31063 | const pendingEmit = this.state.pendingEmit;
|
31064 | const state = this.state;
|
31065 | for (const sf of this.allTsFiles) {
|
31066 | if (this.depGraph.isStale(sf, state.changedTsPaths, state.changedResourcePaths)) {
|
31067 | // Something has changed which requires this file be re-emitted.
|
31068 | pendingEmit.add(sf.fileName);
|
31069 | }
|
31070 | }
|
31071 | // Update the state to an `AnalyzedBuildState`.
|
31072 | this.state = {
|
31073 | kind: BuildStateKind.Analyzed,
|
31074 | pendingEmit,
|
31075 | // Since this compilation was successfully analyzed, update the "last good" artifacts to the
|
31076 | // ones from the current compilation.
|
31077 | lastGood: {
|
31078 | depGraph: this.depGraph,
|
31079 | traitCompiler: traitCompiler,
|
31080 | typeCheckingResults: null,
|
31081 | },
|
31082 | priorTypeCheckingResults: this.state.lastGood !== null ? this.state.lastGood.typeCheckingResults : null,
|
31083 | };
|
31084 | }
|
31085 | recordSuccessfulTypeCheck(results) {
|
31086 | if (this.state.lastGood === null || this.state.kind !== BuildStateKind.Analyzed) {
|
31087 | return;
|
31088 | }
|
31089 | this.state.lastGood.typeCheckingResults = results;
|
31090 | }
|
31091 | recordSuccessfulEmit(sf) {
|
31092 | this.state.pendingEmit.delete(sf.fileName);
|
31093 | }
|
31094 | safeToSkipEmit(sf) {
|
31095 | return !this.state.pendingEmit.has(sf.fileName);
|
31096 | }
|
31097 | priorWorkFor(sf) {
|
31098 | if (this.state.lastGood === null || this.logicalChanges === null) {
|
31099 | // There is no previous good build, so no prior work exists.
|
31100 | return null;
|
31101 | }
|
31102 | else if (this.logicalChanges.has(sf.fileName)) {
|
31103 | // Prior work might exist, but would be stale as the file in question has logically changed.
|
31104 | return null;
|
31105 | }
|
31106 | else {
|
31107 | // Prior work might exist, and if it does then it's usable!
|
31108 | return this.state.lastGood.traitCompiler.recordsFor(sf);
|
31109 | }
|
31110 | }
|
31111 | priorTypeCheckingResultsFor(sf) {
|
31112 | if (this.state.kind !== BuildStateKind.Analyzed ||
|
31113 | this.state.priorTypeCheckingResults === null || this.logicalChanges === null) {
|
31114 | return null;
|
31115 | }
|
31116 | if (this.logicalChanges.has(sf.fileName)) {
|
31117 | return null;
|
31118 | }
|
31119 | const fileName = absoluteFromSourceFile(sf);
|
31120 | if (!this.state.priorTypeCheckingResults.has(fileName)) {
|
31121 | return null;
|
31122 | }
|
31123 | const data = this.state.priorTypeCheckingResults.get(fileName);
|
31124 | if (data.hasInlines) {
|
31125 | return null;
|
31126 | }
|
31127 | return data;
|
31128 | }
|
31129 | }
|
31130 | var BuildStateKind;
|
31131 | (function (BuildStateKind) {
|
31132 | BuildStateKind[BuildStateKind["Pending"] = 0] = "Pending";
|
31133 | BuildStateKind[BuildStateKind["Analyzed"] = 1] = "Analyzed";
|
31134 | })(BuildStateKind || (BuildStateKind = {}));
|
31135 | function tsOnlyFiles(program) {
|
31136 | return program.getSourceFiles().filter(sf => !sf.isDeclarationFile);
|
31137 | }
|
31138 |
|
31139 | /**
|
31140 | * @license
|
31141 | * Copyright Google LLC All Rights Reserved.
|
31142 | *
|
31143 | * Use of this source code is governed by an MIT-style license that can be
|
31144 | * found in the LICENSE file at https://angular.io/license
|
31145 | */
|
31146 | /**
|
31147 | * Tracks an `IncrementalDriver` within the strategy itself.
|
31148 | */
|
31149 | class TrackedIncrementalBuildStrategy {
|
31150 | constructor() {
|
31151 | this.driver = null;
|
31152 | this.isSet = false;
|
31153 | }
|
31154 | getIncrementalDriver() {
|
31155 | return this.driver;
|
31156 | }
|
31157 | setIncrementalDriver(driver) {
|
31158 | this.driver = driver;
|
31159 | this.isSet = true;
|
31160 | }
|
31161 | toNextBuildStrategy() {
|
31162 | const strategy = new TrackedIncrementalBuildStrategy();
|
31163 | // Only reuse a driver that was explicitly set via `setIncrementalDriver`.
|
31164 | strategy.driver = this.isSet ? this.driver : null;
|
31165 | return strategy;
|
31166 | }
|
31167 | }
|
31168 |
|
31169 | /**
|
31170 | * @license
|
31171 | * Copyright Google LLC All Rights Reserved.
|
31172 | *
|
31173 | * Use of this source code is governed by an MIT-style license that can be
|
31174 | * found in the LICENSE file at https://angular.io/license
|
31175 | */
|
31176 | /**
|
31177 | * Describes the kind of identifier found in a template.
|
31178 | */
|
31179 | var IdentifierKind;
|
31180 | (function (IdentifierKind) {
|
31181 | IdentifierKind[IdentifierKind["Property"] = 0] = "Property";
|
31182 | IdentifierKind[IdentifierKind["Method"] = 1] = "Method";
|
31183 | IdentifierKind[IdentifierKind["Element"] = 2] = "Element";
|
31184 | IdentifierKind[IdentifierKind["Template"] = 3] = "Template";
|
31185 | IdentifierKind[IdentifierKind["Attribute"] = 4] = "Attribute";
|
31186 | IdentifierKind[IdentifierKind["Reference"] = 5] = "Reference";
|
31187 | IdentifierKind[IdentifierKind["Variable"] = 6] = "Variable";
|
31188 | })(IdentifierKind || (IdentifierKind = {}));
|
31189 | /**
|
31190 | * Describes the absolute byte offsets of a text anchor in a source code.
|
31191 | */
|
31192 | class AbsoluteSourceSpan$1 {
|
31193 | constructor(start, end) {
|
31194 | this.start = start;
|
31195 | this.end = end;
|
31196 | }
|
31197 | }
|
31198 |
|
31199 | /**
|
31200 | * @license
|
31201 | * Copyright Google LLC All Rights Reserved.
|
31202 | *
|
31203 | * Use of this source code is governed by an MIT-style license that can be
|
31204 | * found in the LICENSE file at https://angular.io/license
|
31205 | */
|
31206 | /**
|
31207 | * A context for storing indexing infromation about components of a program.
|
31208 | *
|
31209 | * An `IndexingContext` collects component and template analysis information from
|
31210 | * `DecoratorHandler`s and exposes them to be indexed.
|
31211 | */
|
31212 | class IndexingContext {
|
31213 | constructor() {
|
31214 | this.components = new Set();
|
31215 | }
|
31216 | /**
|
31217 | * Adds a component to the context.
|
31218 | */
|
31219 | addComponent(info) {
|
31220 | this.components.add(info);
|
31221 | }
|
31222 | }
|
31223 |
|
31224 | /**
|
31225 | * @license
|
31226 | * Copyright Google LLC All Rights Reserved.
|
31227 | *
|
31228 | * Use of this source code is governed by an MIT-style license that can be
|
31229 | * found in the LICENSE file at https://angular.io/license
|
31230 | */
|
31231 | /**
|
31232 | * Visits the AST of an Angular template syntax expression, finding interesting
|
31233 | * entities (variable references, etc.). Creates an array of Entities found in
|
31234 | * the expression, with the location of the Entities being relative to the
|
31235 | * expression.
|
31236 | *
|
31237 | * Visiting `text {{prop}}` will return
|
31238 | * `[TopLevelIdentifier {name: 'prop', span: {start: 7, end: 11}}]`.
|
31239 | */
|
31240 | class ExpressionVisitor extends RecursiveAstVisitor {
|
31241 | constructor(expressionStr, absoluteOffset, boundTemplate, targetToIdentifier) {
|
31242 | super();
|
31243 | this.expressionStr = expressionStr;
|
31244 | this.absoluteOffset = absoluteOffset;
|
31245 | this.boundTemplate = boundTemplate;
|
31246 | this.targetToIdentifier = targetToIdentifier;
|
31247 | this.identifiers = [];
|
31248 | }
|
31249 | /**
|
31250 | * Returns identifiers discovered in an expression.
|
31251 | *
|
31252 | * @param ast expression AST to visit
|
31253 | * @param source expression AST source code
|
31254 | * @param absoluteOffset absolute byte offset from start of the file to the start of the AST
|
31255 | * source code.
|
31256 | * @param boundTemplate bound target of the entire template, which can be used to query for the
|
31257 | * entities expressions target.
|
31258 | * @param targetToIdentifier closure converting a template target node to its identifier.
|
31259 | */
|
31260 | static getIdentifiers(ast, source, absoluteOffset, boundTemplate, targetToIdentifier) {
|
31261 | const visitor = new ExpressionVisitor(source, absoluteOffset, boundTemplate, targetToIdentifier);
|
31262 | visitor.visit(ast);
|
31263 | return visitor.identifiers;
|
31264 | }
|
31265 | visit(ast) {
|
31266 | ast.visit(this);
|
31267 | }
|
31268 | visitMethodCall(ast, context) {
|
31269 | this.visitIdentifier(ast, IdentifierKind.Method);
|
31270 | super.visitMethodCall(ast, context);
|
31271 | }
|
31272 | visitPropertyRead(ast, context) {
|
31273 | this.visitIdentifier(ast, IdentifierKind.Property);
|
31274 | super.visitPropertyRead(ast, context);
|
31275 | }
|
31276 | visitPropertyWrite(ast, context) {
|
31277 | this.visitIdentifier(ast, IdentifierKind.Property);
|
31278 | super.visitPropertyWrite(ast, context);
|
31279 | }
|
31280 | /**
|
31281 | * Visits an identifier, adding it to the identifier store if it is useful for indexing.
|
31282 | *
|
31283 | * @param ast expression AST the identifier is in
|
31284 | * @param kind identifier kind
|
31285 | */
|
31286 | visitIdentifier(ast, kind) {
|
31287 | // The definition of a non-top-level property such as `bar` in `{{foo.bar}}` is currently
|
31288 | // impossible to determine by an indexer and unsupported by the indexing module.
|
31289 | // The indexing module also does not currently support references to identifiers declared in the
|
31290 | // template itself, which have a non-null expression target.
|
31291 | if (!(ast.receiver instanceof ImplicitReceiver)) {
|
31292 | return;
|
31293 | }
|
31294 | // The source span of the requested AST starts at a location that is offset from the expression.
|
31295 | const identifierStart = ast.sourceSpan.start - this.absoluteOffset;
|
31296 | if (!this.expressionStr.substring(identifierStart).startsWith(ast.name)) {
|
31297 | throw new Error(`Impossible state: "${ast.name}" not found in "${this.expressionStr}" at location ${identifierStart}`);
|
31298 | }
|
31299 | // Join the relative position of the expression within a node with the absolute position
|
31300 | // of the node to get the absolute position of the expression in the source code.
|
31301 | const absoluteStart = this.absoluteOffset + identifierStart;
|
31302 | const span = new AbsoluteSourceSpan$1(absoluteStart, absoluteStart + ast.name.length);
|
31303 | const targetAst = this.boundTemplate.getExpressionTarget(ast);
|
31304 | const target = targetAst ? this.targetToIdentifier(targetAst) : null;
|
31305 | const identifier = {
|
31306 | name: ast.name,
|
31307 | span,
|
31308 | kind,
|
31309 | target,
|
31310 | };
|
31311 | this.identifiers.push(identifier);
|
31312 | }
|
31313 | }
|
31314 | /**
|
31315 | * Visits the AST of a parsed Angular template. Discovers and stores
|
31316 | * identifiers of interest, deferring to an `ExpressionVisitor` as needed.
|
31317 | */
|
31318 | class TemplateVisitor extends RecursiveVisitor {
|
31319 | /**
|
31320 | * Creates a template visitor for a bound template target. The bound target can be used when
|
31321 | * deferred to the expression visitor to get information about the target of an expression.
|
31322 | *
|
31323 | * @param boundTemplate bound template target
|
31324 | */
|
31325 | constructor(boundTemplate) {
|
31326 | super();
|
31327 | this.boundTemplate = boundTemplate;
|
31328 | // Identifiers of interest found in the template.
|
31329 | this.identifiers = new Set();
|
31330 | // Map of targets in a template to their identifiers.
|
31331 | this.targetIdentifierCache = new Map();
|
31332 | // Map of elements and templates to their identifiers.
|
31333 | this.elementAndTemplateIdentifierCache = new Map();
|
31334 | }
|
31335 | /**
|
31336 | * Visits a node in the template.
|
31337 | *
|
31338 | * @param node node to visit
|
31339 | */
|
31340 | visit(node) {
|
31341 | node.visit(this);
|
31342 | }
|
31343 | visitAll(nodes) {
|
31344 | nodes.forEach(node => this.visit(node));
|
31345 | }
|
31346 | /**
|
31347 | * Add an identifier for an HTML element and visit its children recursively.
|
31348 | *
|
31349 | * @param element
|
31350 | */
|
31351 | visitElement(element) {
|
31352 | const elementIdentifier = this.elementOrTemplateToIdentifier(element);
|
31353 | this.identifiers.add(elementIdentifier);
|
31354 | this.visitAll(element.references);
|
31355 | this.visitAll(element.inputs);
|
31356 | this.visitAll(element.attributes);
|
31357 | this.visitAll(element.children);
|
31358 | this.visitAll(element.outputs);
|
31359 | }
|
31360 | visitTemplate(template) {
|
31361 | const templateIdentifier = this.elementOrTemplateToIdentifier(template);
|
31362 | this.identifiers.add(templateIdentifier);
|
31363 | this.visitAll(template.variables);
|
31364 | this.visitAll(template.attributes);
|
31365 | this.visitAll(template.templateAttrs);
|
31366 | this.visitAll(template.children);
|
31367 | this.visitAll(template.references);
|
31368 | }
|
31369 | visitBoundAttribute(attribute) {
|
31370 | // If the bound attribute has no value, it cannot have any identifiers in the value expression.
|
31371 | if (attribute.valueSpan === undefined) {
|
31372 | return;
|
31373 | }
|
31374 | const identifiers = ExpressionVisitor.getIdentifiers(attribute.value, attribute.valueSpan.toString(), attribute.valueSpan.start.offset, this.boundTemplate, this.targetToIdentifier.bind(this));
|
31375 | identifiers.forEach(id => this.identifiers.add(id));
|
31376 | }
|
31377 | visitBoundEvent(attribute) {
|
31378 | this.visitExpression(attribute.handler);
|
31379 | }
|
31380 | visitBoundText(text) {
|
31381 | this.visitExpression(text.value);
|
31382 | }
|
31383 | visitReference(reference) {
|
31384 | const referenceIdentifer = this.targetToIdentifier(reference);
|
31385 | this.identifiers.add(referenceIdentifer);
|
31386 | }
|
31387 | visitVariable(variable) {
|
31388 | const variableIdentifier = this.targetToIdentifier(variable);
|
31389 | this.identifiers.add(variableIdentifier);
|
31390 | }
|
31391 | /** Creates an identifier for a template element or template node. */
|
31392 | elementOrTemplateToIdentifier(node) {
|
31393 | // If this node has already been seen, return the cached result.
|
31394 | if (this.elementAndTemplateIdentifierCache.has(node)) {
|
31395 | return this.elementAndTemplateIdentifierCache.get(node);
|
31396 | }
|
31397 | let name;
|
31398 | let kind;
|
31399 | if (node instanceof Template) {
|
31400 | name = node.tagName;
|
31401 | kind = IdentifierKind.Template;
|
31402 | }
|
31403 | else {
|
31404 | name = node.name;
|
31405 | kind = IdentifierKind.Element;
|
31406 | }
|
31407 | const sourceSpan = node.startSourceSpan;
|
31408 | // An element's or template's source span can be of the form `<element>`, `<element />`, or
|
31409 | // `<element></element>`. Only the selector is interesting to the indexer, so the source is
|
31410 | // searched for the first occurrence of the element (selector) name.
|
31411 | const start = this.getStartLocation(name, sourceSpan);
|
31412 | const absoluteSpan = new AbsoluteSourceSpan$1(start, start + name.length);
|
31413 | // Record the nodes's attributes, which an indexer can later traverse to see if any of them
|
31414 | // specify a used directive on the node.
|
31415 | const attributes = node.attributes.map(({ name, sourceSpan }) => {
|
31416 | return {
|
31417 | name,
|
31418 | span: new AbsoluteSourceSpan$1(sourceSpan.start.offset, sourceSpan.end.offset),
|
31419 | kind: IdentifierKind.Attribute,
|
31420 | };
|
31421 | });
|
31422 | const usedDirectives = this.boundTemplate.getDirectivesOfNode(node) || [];
|
31423 | const identifier = {
|
31424 | name,
|
31425 | span: absoluteSpan,
|
31426 | kind,
|
31427 | attributes: new Set(attributes),
|
31428 | usedDirectives: new Set(usedDirectives.map(dir => {
|
31429 | return {
|
31430 | node: dir.ref.node,
|
31431 | selector: dir.selector,
|
31432 | };
|
31433 | })),
|
31434 | };
|
31435 | this.elementAndTemplateIdentifierCache.set(node, identifier);
|
31436 | return identifier;
|
31437 | }
|
31438 | /** Creates an identifier for a template reference or template variable target. */
|
31439 | targetToIdentifier(node) {
|
31440 | // If this node has already been seen, return the cached result.
|
31441 | if (this.targetIdentifierCache.has(node)) {
|
31442 | return this.targetIdentifierCache.get(node);
|
31443 | }
|
31444 | const { name, sourceSpan } = node;
|
31445 | const start = this.getStartLocation(name, sourceSpan);
|
31446 | const span = new AbsoluteSourceSpan$1(start, start + name.length);
|
31447 | let identifier;
|
31448 | if (node instanceof Reference) {
|
31449 | // If the node is a reference, we care about its target. The target can be an element, a
|
31450 | // template, a directive applied on a template or element (in which case the directive field
|
31451 | // is non-null), or nothing at all.
|
31452 | const refTarget = this.boundTemplate.getReferenceTarget(node);
|
31453 | let target = null;
|
31454 | if (refTarget) {
|
31455 | if (refTarget instanceof Element || refTarget instanceof Template) {
|
31456 | target = {
|
31457 | node: this.elementOrTemplateToIdentifier(refTarget),
|
31458 | directive: null,
|
31459 | };
|
31460 | }
|
31461 | else {
|
31462 | target = {
|
31463 | node: this.elementOrTemplateToIdentifier(refTarget.node),
|
31464 | directive: refTarget.directive.ref.node,
|
31465 | };
|
31466 | }
|
31467 | }
|
31468 | identifier = {
|
31469 | name,
|
31470 | span,
|
31471 | kind: IdentifierKind.Reference,
|
31472 | target,
|
31473 | };
|
31474 | }
|
31475 | else {
|
31476 | identifier = {
|
31477 | name,
|
31478 | span,
|
31479 | kind: IdentifierKind.Variable,
|
31480 | };
|
31481 | }
|
31482 | this.targetIdentifierCache.set(node, identifier);
|
31483 | return identifier;
|
31484 | }
|
31485 | /** Gets the start location of a string in a SourceSpan */
|
31486 | getStartLocation(name, context) {
|
31487 | const localStr = context.toString();
|
31488 | if (!localStr.includes(name)) {
|
31489 | throw new Error(`Impossible state: "${name}" not found in "${localStr}"`);
|
31490 | }
|
31491 | return context.start.offset + localStr.indexOf(name);
|
31492 | }
|
31493 | /**
|
31494 | * Visits a node's expression and adds its identifiers, if any, to the visitor's state.
|
31495 | * Only ASTs with information about the expression source and its location are visited.
|
31496 | *
|
31497 | * @param node node whose expression to visit
|
31498 | */
|
31499 | visitExpression(ast) {
|
31500 | // Only include ASTs that have information about their source and absolute source spans.
|
31501 | if (ast instanceof ASTWithSource && ast.source !== null) {
|
31502 | // Make target to identifier mapping closure stateful to this visitor instance.
|
31503 | const targetToIdentifier = this.targetToIdentifier.bind(this);
|
31504 | const absoluteOffset = ast.sourceSpan.start;
|
31505 | const identifiers = ExpressionVisitor.getIdentifiers(ast, ast.source, absoluteOffset, this.boundTemplate, targetToIdentifier);
|
31506 | identifiers.forEach(id => this.identifiers.add(id));
|
31507 | }
|
31508 | }
|
31509 | }
|
31510 | /**
|
31511 | * Traverses a template AST and builds identifiers discovered in it.
|
31512 | *
|
31513 | * @param boundTemplate bound template target, which can be used for querying expression targets.
|
31514 | * @return identifiers in template
|
31515 | */
|
31516 | function getTemplateIdentifiers(boundTemplate) {
|
31517 | const visitor = new TemplateVisitor(boundTemplate);
|
31518 | if (boundTemplate.target.template !== undefined) {
|
31519 | visitor.visitAll(boundTemplate.target.template);
|
31520 | }
|
31521 | return visitor.identifiers;
|
31522 | }
|
31523 |
|
31524 | /**
|
31525 | * @license
|
31526 | * Copyright Google LLC All Rights Reserved.
|
31527 | *
|
31528 | * Use of this source code is governed by an MIT-style license that can be
|
31529 | * found in the LICENSE file at https://angular.io/license
|
31530 | */
|
31531 | /**
|
31532 | * Generates `IndexedComponent` entries from a `IndexingContext`, which has information
|
31533 | * about components discovered in the program registered in it.
|
31534 | *
|
31535 | * The context must be populated before `generateAnalysis` is called.
|
31536 | */
|
31537 | function generateAnalysis(context) {
|
31538 | const analysis = new Map();
|
31539 | context.components.forEach(({ declaration, selector, boundTemplate, templateMeta }) => {
|
31540 | const name = declaration.name.getText();
|
31541 | const usedComponents = new Set();
|
31542 | const usedDirs = boundTemplate.getUsedDirectives();
|
31543 | usedDirs.forEach(dir => {
|
31544 | if (dir.isComponent) {
|
31545 | usedComponents.add(dir.ref.node);
|
31546 | }
|
31547 | });
|
31548 | // Get source files for the component and the template. If the template is inline, its source
|
31549 | // file is the component's.
|
31550 | const componentFile = new ParseSourceFile(declaration.getSourceFile().getFullText(), declaration.getSourceFile().fileName);
|
31551 | let templateFile;
|
31552 | if (templateMeta.isInline) {
|
31553 | templateFile = componentFile;
|
31554 | }
|
31555 | else {
|
31556 | templateFile = templateMeta.file;
|
31557 | }
|
31558 | analysis.set(declaration, {
|
31559 | name,
|
31560 | selector,
|
31561 | file: componentFile,
|
31562 | template: {
|
31563 | identifiers: getTemplateIdentifiers(boundTemplate),
|
31564 | usedComponents,
|
31565 | isInline: templateMeta.isInline,
|
31566 | file: templateFile,
|
31567 | },
|
31568 | });
|
31569 | });
|
31570 | return analysis;
|
31571 | }
|
31572 |
|
31573 | /**
|
31574 | * @license
|
31575 | * Copyright Google LLC All Rights Reserved.
|
31576 | *
|
31577 | * Use of this source code is governed by an MIT-style license that can be
|
31578 | * found in the LICENSE file at https://angular.io/license
|
31579 | */
|
31580 | class ModuleWithProvidersScanner {
|
31581 | constructor(host, evaluator, emitter) {
|
31582 | this.host = host;
|
31583 | this.evaluator = evaluator;
|
31584 | this.emitter = emitter;
|
31585 | }
|
31586 | scan(sf, dts) {
|
31587 | for (const stmt of sf.statements) {
|
31588 | this.visitStatement(dts, stmt);
|
31589 | }
|
31590 | }
|
31591 | visitStatement(dts, stmt) {
|
31592 | // Detect whether a statement is exported, which is used as one of the hints whether to look
|
31593 | // more closely at possible MWP functions within. This is a syntactic check, not a semantic
|
31594 | // check, so it won't detect cases like:
|
31595 | //
|
31596 | // var X = ...;
|
31597 | // export {X}
|
31598 | //
|
31599 | // This is intentional, because the alternative is slow and this will catch 99% of the cases we
|
31600 | // need to handle.
|
31601 | const isExported = stmt.modifiers !== undefined &&
|
31602 | stmt.modifiers.some(mod => mod.kind === ts$1.SyntaxKind.ExportKeyword);
|
31603 | if (!isExported) {
|
31604 | return;
|
31605 | }
|
31606 | if (ts$1.isClassDeclaration(stmt)) {
|
31607 | for (const member of stmt.members) {
|
31608 | if (!ts$1.isMethodDeclaration(member) || !isStatic(member)) {
|
31609 | continue;
|
31610 | }
|
31611 | this.visitFunctionOrMethodDeclaration(dts, member);
|
31612 | }
|
31613 | }
|
31614 | else if (ts$1.isFunctionDeclaration(stmt)) {
|
31615 | this.visitFunctionOrMethodDeclaration(dts, stmt);
|
31616 | }
|
31617 | }
|
31618 | visitFunctionOrMethodDeclaration(dts, decl) {
|
31619 | // First, some sanity. This should have a method body with a single return statement.
|
31620 | if (decl.body === undefined || decl.body.statements.length !== 1) {
|
31621 | return;
|
31622 | }
|
31623 | const retStmt = decl.body.statements[0];
|
31624 | if (!ts$1.isReturnStatement(retStmt) || retStmt.expression === undefined) {
|
31625 | return;
|
31626 | }
|
31627 | const retValue = retStmt.expression;
|
31628 | // Now, look at the return type of the method. Maybe bail if the type is already marked, or if
|
31629 | // it's incompatible with a MWP function.
|
31630 | const returnType = this.returnTypeOf(decl);
|
31631 | if (returnType === ReturnType.OTHER || returnType === ReturnType.MWP_WITH_TYPE) {
|
31632 | // Don't process this declaration, it either already declares the right return type, or an
|
31633 | // incompatible one.
|
31634 | return;
|
31635 | }
|
31636 | const value = this.evaluator.evaluate(retValue);
|
31637 | if (!(value instanceof Map) || !value.has('ngModule')) {
|
31638 | // The return value does not provide sufficient information to be able to add a generic type.
|
31639 | return;
|
31640 | }
|
31641 | if (returnType === ReturnType.INFERRED && !isModuleWithProvidersType(value)) {
|
31642 | // The return type is inferred but the returned object is not of the correct shape, so we
|
31643 | // shouldn's modify the return type to become `ModuleWithProviders`.
|
31644 | return;
|
31645 | }
|
31646 | // The return type has been verified to represent the `ModuleWithProviders` type, but either the
|
31647 | // return type is inferred or the generic type argument is missing. In both cases, a new return
|
31648 | // type is created where the `ngModule` type is included as generic type argument.
|
31649 | const ngModule = value.get('ngModule');
|
31650 | if (!(ngModule instanceof Reference$1) || !ts$1.isClassDeclaration(ngModule.node)) {
|
31651 | return;
|
31652 | }
|
31653 | const ngModuleExpr = this.emitter.emit(ngModule, decl.getSourceFile(), ImportFlags.ForceNewImport);
|
31654 | const ngModuleType = new ExpressionType(ngModuleExpr);
|
31655 | const mwpNgType = new ExpressionType(new ExternalExpr(Identifiers$1.ModuleWithProviders), [ /* modifiers */], [ngModuleType]);
|
31656 | dts.addTypeReplacement(decl, mwpNgType);
|
31657 | }
|
31658 | returnTypeOf(decl) {
|
31659 | if (decl.type === undefined) {
|
31660 | return ReturnType.INFERRED;
|
31661 | }
|
31662 | else if (!ts$1.isTypeReferenceNode(decl.type)) {
|
31663 | return ReturnType.OTHER;
|
31664 | }
|
31665 | // Try to figure out if the type is of a familiar form, something that looks like it was
|
31666 | // imported.
|
31667 | let typeId;
|
31668 | if (ts$1.isIdentifier(decl.type.typeName)) {
|
31669 | // def: ModuleWithProviders
|
31670 | typeId = decl.type.typeName;
|
31671 | }
|
31672 | else if (ts$1.isQualifiedName(decl.type.typeName) && ts$1.isIdentifier(decl.type.typeName.left)) {
|
31673 | // def: i0.ModuleWithProviders
|
31674 | typeId = decl.type.typeName.right;
|
31675 | }
|
31676 | else {
|
31677 | return ReturnType.OTHER;
|
31678 | }
|
31679 | const importDecl = this.host.getImportOfIdentifier(typeId);
|
31680 | if (importDecl === null || importDecl.from !== '@angular/core' ||
|
31681 | importDecl.name !== 'ModuleWithProviders') {
|
31682 | return ReturnType.OTHER;
|
31683 | }
|
31684 | if (decl.type.typeArguments === undefined || decl.type.typeArguments.length === 0) {
|
31685 | // The return type is indeed ModuleWithProviders, but no generic type parameter was found.
|
31686 | return ReturnType.MWP_NO_TYPE;
|
31687 | }
|
31688 | else {
|
31689 | // The return type is ModuleWithProviders, and the user has already specified a generic type.
|
31690 | return ReturnType.MWP_WITH_TYPE;
|
31691 | }
|
31692 | }
|
31693 | }
|
31694 | var ReturnType;
|
31695 | (function (ReturnType) {
|
31696 | ReturnType[ReturnType["INFERRED"] = 0] = "INFERRED";
|
31697 | ReturnType[ReturnType["MWP_NO_TYPE"] = 1] = "MWP_NO_TYPE";
|
31698 | ReturnType[ReturnType["MWP_WITH_TYPE"] = 2] = "MWP_WITH_TYPE";
|
31699 | ReturnType[ReturnType["OTHER"] = 3] = "OTHER";
|
31700 | })(ReturnType || (ReturnType = {}));
|
31701 | /** Whether the resolved value map represents a ModuleWithProviders object */
|
31702 | function isModuleWithProvidersType(value) {
|
31703 | const ngModule = value.has('ngModule');
|
31704 | const providers = value.has('providers');
|
31705 | return ngModule && (value.size === 1 || (providers && value.size === 2));
|
31706 | }
|
31707 | function isStatic(node) {
|
31708 | return node.modifiers !== undefined &&
|
31709 | node.modifiers.some(mod => mod.kind === ts$1.SyntaxKind.StaticKeyword);
|
31710 | }
|
31711 |
|
31712 | const NOOP_PERF_RECORDER = {
|
31713 | enabled: false,
|
31714 | mark: (name, node, category, detail) => { },
|
31715 | start: (name, node, category, detail) => {
|
31716 | return 0;
|
31717 | },
|
31718 | stop: (span) => { },
|
31719 | };
|
31720 |
|
31721 | /**
|
31722 | * @license
|
31723 | * Copyright Google LLC All Rights Reserved.
|
31724 | *
|
31725 | * Use of this source code is governed by an MIT-style license that can be
|
31726 | * found in the LICENSE file at https://angular.io/license
|
31727 | */
|
31728 | var PerfLogEventType;
|
31729 | (function (PerfLogEventType) {
|
31730 | PerfLogEventType[PerfLogEventType["SPAN_OPEN"] = 0] = "SPAN_OPEN";
|
31731 | PerfLogEventType[PerfLogEventType["SPAN_CLOSE"] = 1] = "SPAN_CLOSE";
|
31732 | PerfLogEventType[PerfLogEventType["MARK"] = 2] = "MARK";
|
31733 | })(PerfLogEventType || (PerfLogEventType = {}));
|
31734 |
|
31735 | /**
|
31736 | * @license
|
31737 | * Copyright Google LLC All Rights Reserved.
|
31738 | *
|
31739 | * Use of this source code is governed by an MIT-style license that can be
|
31740 | * found in the LICENSE file at https://angular.io/license
|
31741 | */
|
31742 | const CSS_PREPROCESSOR_EXT = /(\.scss|\.sass|\.less|\.styl)$/;
|
31743 | const RESOURCE_MARKER = '.$ngresource$';
|
31744 | const RESOURCE_MARKER_TS = RESOURCE_MARKER + '.ts';
|
31745 | /**
|
31746 | * `ResourceLoader` which delegates to an `NgCompilerAdapter`'s resource loading methods.
|
31747 | */
|
31748 | class AdapterResourceLoader {
|
31749 | constructor(adapter, options) {
|
31750 | this.adapter = adapter;
|
31751 | this.options = options;
|
31752 | this.cache = new Map();
|
31753 | this.fetching = new Map();
|
31754 | this.lookupResolutionHost = createLookupResolutionHost(this.adapter);
|
31755 | this.canPreload = !!this.adapter.readResource;
|
31756 | }
|
31757 | /**
|
31758 | * Resolve the url of a resource relative to the file that contains the reference to it.
|
31759 | * The return value of this method can be used in the `load()` and `preload()` methods.
|
31760 | *
|
31761 | * Uses the provided CompilerHost if it supports mapping resources to filenames.
|
31762 | * Otherwise, uses a fallback mechanism that searches the module resolution candidates.
|
31763 | *
|
31764 | * @param url The, possibly relative, url of the resource.
|
31765 | * @param fromFile The path to the file that contains the URL of the resource.
|
31766 | * @returns A resolved url of resource.
|
31767 | * @throws An error if the resource cannot be resolved.
|
31768 | */
|
31769 | resolve(url, fromFile) {
|
31770 | let resolvedUrl = null;
|
31771 | if (this.adapter.resourceNameToFileName) {
|
31772 | resolvedUrl = this.adapter.resourceNameToFileName(url, fromFile);
|
31773 | }
|
31774 | else {
|
31775 | resolvedUrl = this.fallbackResolve(url, fromFile);
|
31776 | }
|
31777 | if (resolvedUrl === null) {
|
31778 | throw new Error(`HostResourceResolver: could not resolve ${url} in context of ${fromFile})`);
|
31779 | }
|
31780 | return resolvedUrl;
|
31781 | }
|
31782 | /**
|
31783 | * Preload the specified resource, asynchronously.
|
31784 | *
|
31785 | * Once the resource is loaded, its value is cached so it can be accessed synchronously via the
|
31786 | * `load()` method.
|
31787 | *
|
31788 | * @param resolvedUrl The url (resolved by a call to `resolve()`) of the resource to preload.
|
31789 | * @returns A Promise that is resolved once the resource has been loaded or `undefined` if the
|
31790 | * file has already been loaded.
|
31791 | * @throws An Error if pre-loading is not available.
|
31792 | */
|
31793 | preload(resolvedUrl) {
|
31794 | if (!this.adapter.readResource) {
|
31795 | throw new Error('HostResourceLoader: the CompilerHost provided does not support pre-loading resources.');
|
31796 | }
|
31797 | if (this.cache.has(resolvedUrl)) {
|
31798 | return undefined;
|
31799 | }
|
31800 | else if (this.fetching.has(resolvedUrl)) {
|
31801 | return this.fetching.get(resolvedUrl);
|
31802 | }
|
31803 | const result = this.adapter.readResource(resolvedUrl);
|
31804 | if (typeof result === 'string') {
|
31805 | this.cache.set(resolvedUrl, result);
|
31806 | return undefined;
|
31807 | }
|
31808 | else {
|
31809 | const fetchCompletion = result.then(str => {
|
31810 | this.fetching.delete(resolvedUrl);
|
31811 | this.cache.set(resolvedUrl, str);
|
31812 | });
|
31813 | this.fetching.set(resolvedUrl, fetchCompletion);
|
31814 | return fetchCompletion;
|
31815 | }
|
31816 | }
|
31817 | /**
|
31818 | * Load the resource at the given url, synchronously.
|
31819 | *
|
31820 | * The contents of the resource may have been cached by a previous call to `preload()`.
|
31821 | *
|
31822 | * @param resolvedUrl The url (resolved by a call to `resolve()`) of the resource to load.
|
31823 | * @returns The contents of the resource.
|
31824 | */
|
31825 | load(resolvedUrl) {
|
31826 | if (this.cache.has(resolvedUrl)) {
|
31827 | return this.cache.get(resolvedUrl);
|
31828 | }
|
31829 | const result = this.adapter.readResource ? this.adapter.readResource(resolvedUrl) :
|
31830 | this.adapter.readFile(resolvedUrl);
|
31831 | if (typeof result !== 'string') {
|
31832 | throw new Error(`HostResourceLoader: loader(${resolvedUrl}) returned a Promise`);
|
31833 | }
|
31834 | this.cache.set(resolvedUrl, result);
|
31835 | return result;
|
31836 | }
|
31837 | /**
|
31838 | * Invalidate the entire resource cache.
|
31839 | */
|
31840 | invalidate() {
|
31841 | this.cache.clear();
|
31842 | }
|
31843 | /**
|
31844 | * Attempt to resolve `url` in the context of `fromFile`, while respecting the rootDirs
|
31845 | * option from the tsconfig. First, normalize the file name.
|
31846 | */
|
31847 | fallbackResolve(url, fromFile) {
|
31848 | let candidateLocations;
|
31849 | if (url.startsWith('/')) {
|
31850 | // This path is not really an absolute path, but instead the leading '/' means that it's
|
31851 | // rooted in the project rootDirs. So look for it according to the rootDirs.
|
31852 | candidateLocations = this.getRootedCandidateLocations(url);
|
31853 | }
|
31854 | else {
|
31855 | // This path is a "relative" path and can be resolved as such. To make this easier on the
|
31856 | // downstream resolver, the './' prefix is added if missing to distinguish these paths from
|
31857 | // absolute node_modules paths.
|
31858 | if (!url.startsWith('.')) {
|
31859 | url = `./${url}`;
|
31860 | }
|
31861 | candidateLocations = this.getResolvedCandidateLocations(url, fromFile);
|
31862 | }
|
31863 | for (const candidate of candidateLocations) {
|
31864 | if (this.adapter.fileExists(candidate)) {
|
31865 | return candidate;
|
31866 | }
|
31867 | else if (CSS_PREPROCESSOR_EXT.test(candidate)) {
|
31868 | /**
|
31869 | * If the user specified styleUrl points to *.scss, but the Sass compiler was run before
|
31870 | * Angular, then the resource may have been generated as *.css. Simply try the resolution
|
31871 | * again.
|
31872 | */
|
31873 | const cssFallbackUrl = candidate.replace(CSS_PREPROCESSOR_EXT, '.css');
|
31874 | if (this.adapter.fileExists(cssFallbackUrl)) {
|
31875 | return cssFallbackUrl;
|
31876 | }
|
31877 | }
|
31878 | }
|
31879 | return null;
|
31880 | }
|
31881 | getRootedCandidateLocations(url) {
|
31882 | // The path already starts with '/', so add a '.' to make it relative.
|
31883 | const segment = ('.' + url);
|
31884 | return this.adapter.rootDirs.map(rootDir => join(rootDir, segment));
|
31885 | }
|
31886 | /**
|
31887 | * TypeScript provides utilities to resolve module names, but not resource files (which aren't
|
31888 | * a part of the ts.Program). However, TypeScript's module resolution can be used creatively
|
31889 | * to locate where resource files should be expected to exist. Since module resolution returns
|
31890 | * a list of file names that were considered, the loader can enumerate the possible locations
|
31891 | * for the file by setting up a module resolution for it that will fail.
|
31892 | */
|
31893 | getResolvedCandidateLocations(url, fromFile) {
|
31894 | // clang-format off
|
31895 | const failedLookup = ts$1.resolveModuleName(url + RESOURCE_MARKER, fromFile, this.options, this.lookupResolutionHost);
|
31896 | // clang-format on
|
31897 | if (failedLookup.failedLookupLocations === undefined) {
|
31898 | throw new Error(`Internal error: expected to find failedLookupLocations during resolution of resource '${url}' in context of ${fromFile}`);
|
31899 | }
|
31900 | return failedLookup.failedLookupLocations
|
31901 | .filter(candidate => candidate.endsWith(RESOURCE_MARKER_TS))
|
31902 | .map(candidate => candidate.slice(0, -RESOURCE_MARKER_TS.length));
|
31903 | }
|
31904 | }
|
31905 | /**
|
31906 | * Derives a `ts.ModuleResolutionHost` from a compiler adapter that recognizes the special resource
|
31907 | * marker and does not go to the filesystem for these requests, as they are known not to exist.
|
31908 | */
|
31909 | function createLookupResolutionHost(adapter) {
|
31910 | var _a, _b, _c;
|
31911 | return {
|
31912 | directoryExists(directoryName) {
|
31913 | if (directoryName.includes(RESOURCE_MARKER)) {
|
31914 | return false;
|
31915 | }
|
31916 | else if (adapter.directoryExists !== undefined) {
|
31917 | return adapter.directoryExists(directoryName);
|
31918 | }
|
31919 | else {
|
31920 | // TypeScript's module resolution logic assumes that the directory exists when no host
|
31921 | // implementation is available.
|
31922 | return true;
|
31923 | }
|
31924 | },
|
31925 | fileExists(fileName) {
|
31926 | if (fileName.includes(RESOURCE_MARKER)) {
|
31927 | return false;
|
31928 | }
|
31929 | else {
|
31930 | return adapter.fileExists(fileName);
|
31931 | }
|
31932 | },
|
31933 | readFile: adapter.readFile.bind(adapter),
|
31934 | getCurrentDirectory: adapter.getCurrentDirectory.bind(adapter),
|
31935 | getDirectories: (_a = adapter.getDirectories) === null || _a === void 0 ? void 0 : _a.bind(adapter),
|
31936 | realpath: (_b = adapter.realpath) === null || _b === void 0 ? void 0 : _b.bind(adapter),
|
31937 | trace: (_c = adapter.trace) === null || _c === void 0 ? void 0 : _c.bind(adapter),
|
31938 | };
|
31939 | }
|
31940 |
|
31941 | /**
|
31942 | * @license
|
31943 | * Copyright Google LLC All Rights Reserved.
|
31944 | *
|
31945 | * Use of this source code is governed by an MIT-style license that can be
|
31946 | * found in the LICENSE file at https://angular.io/license
|
31947 | */
|
31948 | class RouterEntryPointImpl {
|
31949 | constructor(filePath, moduleName) {
|
31950 | this.filePath = filePath;
|
31951 | this.moduleName = moduleName;
|
31952 | }
|
31953 | get name() {
|
31954 | return this.moduleName;
|
31955 | }
|
31956 | // For debugging purposes.
|
31957 | toString() {
|
31958 | return `RouterEntryPoint(name: ${this.name}, filePath: ${this.filePath})`;
|
31959 | }
|
31960 | }
|
31961 | class RouterEntryPointManager {
|
31962 | constructor(moduleResolver) {
|
31963 | this.moduleResolver = moduleResolver;
|
31964 | this.map = new Map();
|
31965 | }
|
31966 | resolveLoadChildrenIdentifier(loadChildrenIdentifier, context) {
|
31967 | const [relativeFile, moduleName] = loadChildrenIdentifier.split('#');
|
31968 | if (moduleName === undefined) {
|
31969 | return null;
|
31970 | }
|
31971 | const resolvedSf = this.moduleResolver.resolveModule(relativeFile, context.fileName);
|
31972 | if (resolvedSf === null) {
|
31973 | return null;
|
31974 | }
|
31975 | return this.fromNgModule(resolvedSf, moduleName);
|
31976 | }
|
31977 | fromNgModule(sf, moduleName) {
|
31978 | const key = entryPointKeyFor(sf.fileName, moduleName);
|
31979 | if (!this.map.has(key)) {
|
31980 | this.map.set(key, new RouterEntryPointImpl(sf.fileName, moduleName));
|
31981 | }
|
31982 | return this.map.get(key);
|
31983 | }
|
31984 | }
|
31985 | function entryPointKeyFor(filePath, moduleName) {
|
31986 | // Drop the extension to be compatible with how cli calls `listLazyRoutes(entryRoute)`.
|
31987 | return `${filePath.replace(/\.tsx?$/i, '')}#${moduleName}`;
|
31988 | }
|
31989 |
|
31990 | /**
|
31991 | * @license
|
31992 | * Copyright Google LLC All Rights Reserved.
|
31993 | *
|
31994 | * Use of this source code is governed by an MIT-style license that can be
|
31995 | * found in the LICENSE file at https://angular.io/license
|
31996 | */
|
31997 | const ROUTES_MARKER = '__ngRoutesMarker__';
|
31998 | function scanForCandidateTransitiveModules(expr, evaluator) {
|
31999 | if (expr === null) {
|
32000 | return [];
|
32001 | }
|
32002 | const candidateModuleKeys = [];
|
32003 | const entries = evaluator.evaluate(expr);
|
32004 | function recursivelyAddModules(entry) {
|
32005 | if (Array.isArray(entry)) {
|
32006 | for (const e of entry) {
|
32007 | recursivelyAddModules(e);
|
32008 | }
|
32009 | }
|
32010 | else if (entry instanceof Map) {
|
32011 | if (entry.has('ngModule')) {
|
32012 | recursivelyAddModules(entry.get('ngModule'));
|
32013 | }
|
32014 | }
|
32015 | else if ((entry instanceof Reference$1) && hasIdentifier(entry.node)) {
|
32016 | const filePath = entry.node.getSourceFile().fileName;
|
32017 | const moduleName = entry.node.name.text;
|
32018 | candidateModuleKeys.push(entryPointKeyFor(filePath, moduleName));
|
32019 | }
|
32020 | }
|
32021 | recursivelyAddModules(entries);
|
32022 | return candidateModuleKeys;
|
32023 | }
|
32024 | function scanForRouteEntryPoints(ngModule, moduleName, data, entryPointManager, evaluator) {
|
32025 | const loadChildrenIdentifiers = [];
|
32026 | const from = entryPointManager.fromNgModule(ngModule, moduleName);
|
32027 | if (data.providers !== null) {
|
32028 | loadChildrenIdentifiers.push(...scanForProviders(data.providers, evaluator));
|
32029 | }
|
32030 | if (data.imports !== null) {
|
32031 | loadChildrenIdentifiers.push(...scanForRouterModuleUsage(data.imports, evaluator));
|
32032 | }
|
32033 | if (data.exports !== null) {
|
32034 | loadChildrenIdentifiers.push(...scanForRouterModuleUsage(data.exports, evaluator));
|
32035 | }
|
32036 | const routes = [];
|
32037 | for (const loadChildren of loadChildrenIdentifiers) {
|
32038 | const resolvedTo = entryPointManager.resolveLoadChildrenIdentifier(loadChildren, ngModule);
|
32039 | if (resolvedTo !== null) {
|
32040 | routes.push({
|
32041 | loadChildren,
|
32042 | from,
|
32043 | resolvedTo,
|
32044 | });
|
32045 | }
|
32046 | }
|
32047 | return routes;
|
32048 | }
|
32049 | function scanForProviders(expr, evaluator) {
|
32050 | const loadChildrenIdentifiers = [];
|
32051 | const providers = evaluator.evaluate(expr);
|
32052 | function recursivelyAddProviders(provider) {
|
32053 | if (Array.isArray(provider)) {
|
32054 | for (const entry of provider) {
|
32055 | recursivelyAddProviders(entry);
|
32056 | }
|
32057 | }
|
32058 | else if (provider instanceof Map) {
|
32059 | if (provider.has('provide') && provider.has('useValue')) {
|
32060 | const provide = provider.get('provide');
|
32061 | const useValue = provider.get('useValue');
|
32062 | if (isRouteToken(provide) && Array.isArray(useValue)) {
|
32063 | loadChildrenIdentifiers.push(...scanForLazyRoutes(useValue));
|
32064 | }
|
32065 | }
|
32066 | }
|
32067 | }
|
32068 | recursivelyAddProviders(providers);
|
32069 | return loadChildrenIdentifiers;
|
32070 | }
|
32071 | function scanForRouterModuleUsage(expr, evaluator) {
|
32072 | const loadChildrenIdentifiers = [];
|
32073 | const imports = evaluator.evaluate(expr, routerModuleFFR);
|
32074 | function recursivelyAddRoutes(imp) {
|
32075 | if (Array.isArray(imp)) {
|
32076 | for (const entry of imp) {
|
32077 | recursivelyAddRoutes(entry);
|
32078 | }
|
32079 | }
|
32080 | else if (imp instanceof Map) {
|
32081 | if (imp.has(ROUTES_MARKER) && imp.has('routes')) {
|
32082 | const routes = imp.get('routes');
|
32083 | if (Array.isArray(routes)) {
|
32084 | loadChildrenIdentifiers.push(...scanForLazyRoutes(routes));
|
32085 | }
|
32086 | }
|
32087 | }
|
32088 | }
|
32089 | recursivelyAddRoutes(imports);
|
32090 | return loadChildrenIdentifiers;
|
32091 | }
|
32092 | function scanForLazyRoutes(routes) {
|
32093 | const loadChildrenIdentifiers = [];
|
32094 | function recursivelyScanRoutes(routes) {
|
32095 | for (let route of routes) {
|
32096 | if (!(route instanceof Map)) {
|
32097 | continue;
|
32098 | }
|
32099 | if (route.has('loadChildren')) {
|
32100 | const loadChildren = route.get('loadChildren');
|
32101 | if (typeof loadChildren === 'string') {
|
32102 | loadChildrenIdentifiers.push(loadChildren);
|
32103 | }
|
32104 | }
|
32105 | else if (route.has('children')) {
|
32106 | const children = route.get('children');
|
32107 | if (Array.isArray(children)) {
|
32108 | recursivelyScanRoutes(children);
|
32109 | }
|
32110 | }
|
32111 | }
|
32112 | }
|
32113 | recursivelyScanRoutes(routes);
|
32114 | return loadChildrenIdentifiers;
|
32115 | }
|
32116 | /**
|
32117 | * A foreign function resolver that converts `RouterModule.forRoot/forChild(X)` to a special object
|
32118 | * of the form `{__ngRoutesMarker__: true, routes: X}`.
|
32119 | *
|
32120 | * These objects are then recognizable inside the larger set of imports/exports.
|
32121 | */
|
32122 | const routerModuleFFR = function routerModuleFFR(ref, args) {
|
32123 | if (!isMethodNodeReference(ref) || !ts$1.isClassDeclaration(ref.node.parent)) {
|
32124 | return null;
|
32125 | }
|
32126 | else if (ref.bestGuessOwningModule === null ||
|
32127 | ref.bestGuessOwningModule.specifier !== '@angular/router') {
|
32128 | return null;
|
32129 | }
|
32130 | else if (ref.node.parent.name === undefined || ref.node.parent.name.text !== 'RouterModule') {
|
32131 | return null;
|
32132 | }
|
32133 | else if (!ts$1.isIdentifier(ref.node.name) ||
|
32134 | (ref.node.name.text !== 'forRoot' && ref.node.name.text !== 'forChild')) {
|
32135 | return null;
|
32136 | }
|
32137 | const routes = args[0];
|
32138 | return ts$1.createObjectLiteral([
|
32139 | ts$1.createPropertyAssignment(ROUTES_MARKER, ts$1.createTrue()),
|
32140 | ts$1.createPropertyAssignment('routes', routes),
|
32141 | ]);
|
32142 | };
|
32143 | function hasIdentifier(node) {
|
32144 | const node_ = node;
|
32145 | return (node_.name !== undefined) && ts$1.isIdentifier(node_.name);
|
32146 | }
|
32147 | function isMethodNodeReference(ref) {
|
32148 | return ts$1.isMethodDeclaration(ref.node);
|
32149 | }
|
32150 | function isRouteToken(ref) {
|
32151 | return ref instanceof Reference$1 && ref.bestGuessOwningModule !== null &&
|
32152 | ref.bestGuessOwningModule.specifier === '@angular/router' && ref.debugName === 'ROUTES';
|
32153 | }
|
32154 |
|
32155 | /**
|
32156 | * @license
|
32157 | * Copyright Google LLC All Rights Reserved.
|
32158 | *
|
32159 | * Use of this source code is governed by an MIT-style license that can be
|
32160 | * found in the LICENSE file at https://angular.io/license
|
32161 | */
|
32162 | class NgModuleRouteAnalyzer {
|
32163 | constructor(moduleResolver, evaluator) {
|
32164 | this.evaluator = evaluator;
|
32165 | this.modules = new Map();
|
32166 | this.entryPointManager = new RouterEntryPointManager(moduleResolver);
|
32167 | }
|
32168 | add(sourceFile, moduleName, imports, exports, providers) {
|
32169 | const key = entryPointKeyFor(sourceFile.fileName, moduleName);
|
32170 | if (this.modules.has(key)) {
|
32171 | throw new Error(`Double route analyzing for '${key}'.`);
|
32172 | }
|
32173 | this.modules.set(key, {
|
32174 | sourceFile,
|
32175 | moduleName,
|
32176 | imports,
|
32177 | exports,
|
32178 | providers,
|
32179 | });
|
32180 | }
|
32181 | listLazyRoutes(entryModuleKey) {
|
32182 | if ((entryModuleKey !== undefined) && !this.modules.has(entryModuleKey)) {
|
32183 | throw new Error(`Failed to list lazy routes: Unknown module '${entryModuleKey}'.`);
|
32184 | }
|
32185 | const routes = [];
|
32186 | const scannedModuleKeys = new Set();
|
32187 | const pendingModuleKeys = entryModuleKey ? [entryModuleKey] : Array.from(this.modules.keys());
|
32188 | // When listing lazy routes for a specific entry module, we need to recursively extract
|
32189 | // "transitive" routes from imported/exported modules. This is not necessary when listing all
|
32190 | // lazy routes, because all analyzed modules will be scanned anyway.
|
32191 | const scanRecursively = entryModuleKey !== undefined;
|
32192 | while (pendingModuleKeys.length > 0) {
|
32193 | const key = pendingModuleKeys.pop();
|
32194 | if (scannedModuleKeys.has(key)) {
|
32195 | continue;
|
32196 | }
|
32197 | else {
|
32198 | scannedModuleKeys.add(key);
|
32199 | }
|
32200 | const data = this.modules.get(key);
|
32201 | const entryPoints = scanForRouteEntryPoints(data.sourceFile, data.moduleName, data, this.entryPointManager, this.evaluator);
|
32202 | routes.push(...entryPoints.map(entryPoint => ({
|
32203 | route: entryPoint.loadChildren,
|
32204 | module: entryPoint.from,
|
32205 | referencedModule: entryPoint.resolvedTo,
|
32206 | })));
|
32207 | if (scanRecursively) {
|
32208 | pendingModuleKeys.push(...[
|
32209 | // Scan the retrieved lazy route entry points.
|
32210 | ...entryPoints.map(({ resolvedTo }) => entryPointKeyFor(resolvedTo.filePath, resolvedTo.moduleName)),
|
32211 | // Scan the current module's imported modules.
|
32212 | ...scanForCandidateTransitiveModules(data.imports, this.evaluator),
|
32213 | // Scan the current module's exported modules.
|
32214 | ...scanForCandidateTransitiveModules(data.exports, this.evaluator),
|
32215 | ].filter(key => this.modules.has(key)));
|
32216 | }
|
32217 | }
|
32218 | return routes;
|
32219 | }
|
32220 | }
|
32221 |
|
32222 | /**
|
32223 | * @license
|
32224 | * Copyright Google LLC All Rights Reserved.
|
32225 | *
|
32226 | * Use of this source code is governed by an MIT-style license that can be
|
32227 | * found in the LICENSE file at https://angular.io/license
|
32228 | */
|
32229 | /**
|
32230 | * Reads Angular metadata from classes declared in .d.ts files and computes an `ExportScope`.
|
32231 | *
|
32232 | * Given an NgModule declared in a .d.ts file, this resolver can produce a transitive `ExportScope`
|
32233 | * of all of the directives/pipes it exports. It does this by reading metadata off of Ivy static
|
32234 | * fields on directives, components, pipes, and NgModules.
|
32235 | */
|
32236 | class MetadataDtsModuleScopeResolver {
|
32237 | /**
|
32238 | * @param dtsMetaReader a `MetadataReader` which can read metadata from `.d.ts` files.
|
32239 | */
|
32240 | constructor(dtsMetaReader, aliasingHost) {
|
32241 | this.dtsMetaReader = dtsMetaReader;
|
32242 | this.aliasingHost = aliasingHost;
|
32243 | /**
|
32244 | * Cache which holds fully resolved scopes for NgModule classes from .d.ts files.
|
32245 | */
|
32246 | this.cache = new Map();
|
32247 | }
|
32248 | /**
|
32249 | * Resolve a `Reference`'d NgModule from a .d.ts file and produce a transitive `ExportScope`
|
32250 | * listing the directives and pipes which that NgModule exports to others.
|
32251 | *
|
32252 | * This operation relies on a `Reference` instead of a direct TypeScrpt node as the `Reference`s
|
32253 | * produced depend on how the original NgModule was imported.
|
32254 | */
|
32255 | resolve(ref) {
|
32256 | const clazz = ref.node;
|
32257 | const sourceFile = clazz.getSourceFile();
|
32258 | if (!sourceFile.isDeclarationFile) {
|
32259 | throw new Error(`Debug error: DtsModuleScopeResolver.read(${ref.debugName} from ${sourceFile.fileName}), but not a .d.ts file`);
|
32260 | }
|
32261 | if (this.cache.has(clazz)) {
|
32262 | return this.cache.get(clazz);
|
32263 | }
|
32264 | // Build up the export scope - those directives and pipes made visible by this module.
|
32265 | const directives = [];
|
32266 | const pipes = [];
|
32267 | const ngModules = new Set([clazz]);
|
32268 | const meta = this.dtsMetaReader.getNgModuleMetadata(ref);
|
32269 | if (meta === null) {
|
32270 | this.cache.set(clazz, null);
|
32271 | return null;
|
32272 | }
|
32273 | const declarations = new Set();
|
32274 | for (const declRef of meta.declarations) {
|
32275 | declarations.add(declRef.node);
|
32276 | }
|
32277 | // Only the 'exports' field of the NgModule's metadata is important. Imports and declarations
|
32278 | // don't affect the export scope.
|
32279 | for (const exportRef of meta.exports) {
|
32280 | // Attempt to process the export as a directive.
|
32281 | const directive = this.dtsMetaReader.getDirectiveMetadata(exportRef);
|
32282 | if (directive !== null) {
|
32283 | const isReExport = !declarations.has(exportRef.node);
|
32284 | directives.push(this.maybeAlias(directive, sourceFile, isReExport));
|
32285 | continue;
|
32286 | }
|
32287 | // Attempt to process the export as a pipe.
|
32288 | const pipe = this.dtsMetaReader.getPipeMetadata(exportRef);
|
32289 | if (pipe !== null) {
|
32290 | const isReExport = !declarations.has(exportRef.node);
|
32291 | pipes.push(this.maybeAlias(pipe, sourceFile, isReExport));
|
32292 | continue;
|
32293 | }
|
32294 | // Attempt to process the export as a module.
|
32295 | const exportScope = this.resolve(exportRef);
|
32296 | if (exportScope !== null) {
|
32297 | // It is a module. Add exported directives and pipes to the current scope. This might
|
32298 | // involve rewriting the `Reference`s to those types to have an alias expression if one is
|
32299 | // required.
|
32300 | if (this.aliasingHost === null) {
|
32301 | // Fast path when aliases aren't required.
|
32302 | directives.push(...exportScope.exported.directives);
|
32303 | pipes.push(...exportScope.exported.pipes);
|
32304 | }
|
32305 | else {
|
32306 | // It's necessary to rewrite the `Reference`s to add alias expressions. This way, imports
|
32307 | // generated to these directives and pipes will use a shallow import to `sourceFile`
|
32308 | // instead of a deep import directly to the directive or pipe class.
|
32309 | //
|
32310 | // One important check here is whether the directive/pipe is declared in the same
|
32311 | // source file as the re-exporting NgModule. This can happen if both a directive, its
|
32312 | // NgModule, and the re-exporting NgModule are all in the same file. In this case,
|
32313 | // no import alias is needed as it would go to the same file anyway.
|
32314 | for (const directive of exportScope.exported.directives) {
|
32315 | directives.push(this.maybeAlias(directive, sourceFile, /* isReExport */ true));
|
32316 | }
|
32317 | for (const pipe of exportScope.exported.pipes) {
|
32318 | pipes.push(this.maybeAlias(pipe, sourceFile, /* isReExport */ true));
|
32319 | }
|
32320 | for (const ngModule of exportScope.exported.ngModules) {
|
32321 | ngModules.add(ngModule);
|
32322 | }
|
32323 | }
|
32324 | }
|
32325 | continue;
|
32326 | // The export was not a directive, a pipe, or a module. This is an error.
|
32327 | // TODO(alxhub): produce a ts.Diagnostic
|
32328 | }
|
32329 | const exportScope = {
|
32330 | exported: {
|
32331 | directives,
|
32332 | pipes,
|
32333 | ngModules: Array.from(ngModules),
|
32334 | isPoisoned: false,
|
32335 | },
|
32336 | };
|
32337 | this.cache.set(clazz, exportScope);
|
32338 | return exportScope;
|
32339 | }
|
32340 | maybeAlias(dirOrPipe, maybeAliasFrom, isReExport) {
|
32341 | const ref = dirOrPipe.ref;
|
32342 | if (this.aliasingHost === null || ref.node.getSourceFile() === maybeAliasFrom) {
|
32343 | return dirOrPipe;
|
32344 | }
|
32345 | const alias = this.aliasingHost.getAliasIn(ref.node, maybeAliasFrom, isReExport);
|
32346 | if (alias === null) {
|
32347 | return dirOrPipe;
|
32348 | }
|
32349 | return Object.assign(Object.assign({}, dirOrPipe), { ref: ref.cloneWithAlias(alias) });
|
32350 | }
|
32351 | }
|
32352 |
|
32353 | /**
|
32354 | * @license
|
32355 | * Copyright Google LLC All Rights Reserved.
|
32356 | *
|
32357 | * Use of this source code is governed by an MIT-style license that can be
|
32358 | * found in the LICENSE file at https://angular.io/license
|
32359 | */
|
32360 | /**
|
32361 | * A registry which collects information about NgModules, Directives, Components, and Pipes which
|
32362 | * are local (declared in the ts.Program being compiled), and can produce `LocalModuleScope`s
|
32363 | * which summarize the compilation scope of a component.
|
32364 | *
|
32365 | * This class implements the logic of NgModule declarations, imports, and exports and can produce,
|
32366 | * for a given component, the set of directives and pipes which are "visible" in that component's
|
32367 | * template.
|
32368 | *
|
32369 | * The `LocalModuleScopeRegistry` has two "modes" of operation. During analysis, data for each
|
32370 | * individual NgModule, Directive, Component, and Pipe is added to the registry. No attempt is made
|
32371 | * to traverse or validate the NgModule graph (imports, exports, etc). After analysis, one of
|
32372 | * `getScopeOfModule` or `getScopeForComponent` can be called, which traverses the NgModule graph
|
32373 | * and applies the NgModule logic to generate a `LocalModuleScope`, the full scope for the given
|
32374 | * module or component.
|
32375 | *
|
32376 | * The `LocalModuleScopeRegistry` is also capable of producing `ts.Diagnostic` errors when Angular
|
32377 | * semantics are violated.
|
32378 | */
|
32379 | class LocalModuleScopeRegistry {
|
32380 | constructor(localReader, dependencyScopeReader, refEmitter, aliasingHost) {
|
32381 | this.localReader = localReader;
|
32382 | this.dependencyScopeReader = dependencyScopeReader;
|
32383 | this.refEmitter = refEmitter;
|
32384 | this.aliasingHost = aliasingHost;
|
32385 | /**
|
32386 | * Tracks whether the registry has been asked to produce scopes for a module or component. Once
|
32387 | * this is true, the registry cannot accept registrations of new directives/pipes/modules as it
|
32388 | * would invalidate the cached scope data.
|
32389 | */
|
32390 | this.sealed = false;
|
32391 | /**
|
32392 | * A map of components from the current compilation unit to the NgModule which declared them.
|
32393 | *
|
32394 | * As components and directives are not distinguished at the NgModule level, this map may also
|
32395 | * contain directives. This doesn't cause any problems but isn't useful as there is no concept of
|
32396 | * a directive's compilation scope.
|
32397 | */
|
32398 | this.declarationToModule = new Map();
|
32399 | /**
|
32400 | * This maps from the directive/pipe class to a map of data for each NgModule that declares the
|
32401 | * directive/pipe. This data is needed to produce an error for the given class.
|
32402 | */
|
32403 | this.duplicateDeclarations = new Map();
|
32404 | this.moduleToRef = new Map();
|
32405 | /**
|
32406 | * A cache of calculated `LocalModuleScope`s for each NgModule declared in the current program.
|
32407 |
|
32408 | */
|
32409 | this.cache = new Map();
|
32410 | /**
|
32411 | * Tracks the `RemoteScope` for components requiring "remote scoping".
|
32412 | *
|
32413 | * Remote scoping is when the set of directives which apply to a given component is set in the
|
32414 | * NgModule's file instead of directly on the component def (which is sometimes needed to get
|
32415 | * around cyclic import issues). This is not used in calculation of `LocalModuleScope`s, but is
|
32416 | * tracked here for convenience.
|
32417 | */
|
32418 | this.remoteScoping = new Map();
|
32419 | /**
|
32420 | * Tracks errors accumulated in the processing of scopes for each module declaration.
|
32421 | */
|
32422 | this.scopeErrors = new Map();
|
32423 | /**
|
32424 | * Tracks which NgModules have directives/pipes that are declared in more than one module.
|
32425 | */
|
32426 | this.modulesWithStructuralErrors = new Set();
|
32427 | }
|
32428 | /**
|
32429 | * Add an NgModule's data to the registry.
|
32430 | */
|
32431 | registerNgModuleMetadata(data) {
|
32432 | this.assertCollecting();
|
32433 | const ngModule = data.ref.node;
|
32434 | this.moduleToRef.set(data.ref.node, data.ref);
|
32435 | // Iterate over the module's declarations, and add them to declarationToModule. If duplicates
|
32436 | // are found, they're instead tracked in duplicateDeclarations.
|
32437 | for (const decl of data.declarations) {
|
32438 | this.registerDeclarationOfModule(ngModule, decl, data.rawDeclarations);
|
32439 | }
|
32440 | }
|
32441 | registerDirectiveMetadata(directive) { }
|
32442 | registerPipeMetadata(pipe) { }
|
32443 | getScopeForComponent(clazz) {
|
32444 | const scope = !this.declarationToModule.has(clazz) ?
|
32445 | null :
|
32446 | this.getScopeOfModule(this.declarationToModule.get(clazz).ngModule);
|
32447 | return scope;
|
32448 | }
|
32449 | /**
|
32450 | * If `node` is declared in more than one NgModule (duplicate declaration), then get the
|
32451 | * `DeclarationData` for each offending declaration.
|
32452 | *
|
32453 | * Ordinarily a class is only declared in one NgModule, in which case this function returns
|
32454 | * `null`.
|
32455 | */
|
32456 | getDuplicateDeclarations(node) {
|
32457 | if (!this.duplicateDeclarations.has(node)) {
|
32458 | return null;
|
32459 | }
|
32460 | return Array.from(this.duplicateDeclarations.get(node).values());
|
32461 | }
|
32462 | /**
|
32463 | * Collects registered data for a module and its directives/pipes and convert it into a full
|
32464 | * `LocalModuleScope`.
|
32465 | *
|
32466 | * This method implements the logic of NgModule imports and exports. It returns the
|
32467 | * `LocalModuleScope` for the given NgModule if one can be produced, `null` if no scope was ever
|
32468 | * defined, or the string `'error'` if the scope contained errors.
|
32469 | */
|
32470 | getScopeOfModule(clazz) {
|
32471 | return this.moduleToRef.has(clazz) ?
|
32472 | this.getScopeOfModuleReference(this.moduleToRef.get(clazz)) :
|
32473 | null;
|
32474 | }
|
32475 | /**
|
32476 | * Retrieves any `ts.Diagnostic`s produced during the calculation of the `LocalModuleScope` for
|
32477 | * the given NgModule, or `null` if no errors were present.
|
32478 | */
|
32479 | getDiagnosticsOfModule(clazz) {
|
32480 | // Required to ensure the errors are populated for the given class. If it has been processed
|
32481 | // before, this will be a no-op due to the scope cache.
|
32482 | this.getScopeOfModule(clazz);
|
32483 | if (this.scopeErrors.has(clazz)) {
|
32484 | return this.scopeErrors.get(clazz);
|
32485 | }
|
32486 | else {
|
32487 | return null;
|
32488 | }
|
32489 | }
|
32490 | /**
|
32491 | * Returns a collection of the compilation scope for each registered declaration.
|
32492 | */
|
32493 | getCompilationScopes() {
|
32494 | const scopes = [];
|
32495 | this.declarationToModule.forEach((declData, declaration) => {
|
32496 | const scope = this.getScopeOfModule(declData.ngModule);
|
32497 | if (scope !== null) {
|
32498 | scopes.push(Object.assign({ declaration, ngModule: declData.ngModule }, scope.compilation));
|
32499 | }
|
32500 | });
|
32501 | return scopes;
|
32502 | }
|
32503 | registerDeclarationOfModule(ngModule, decl, rawDeclarations) {
|
32504 | const declData = {
|
32505 | ngModule,
|
32506 | ref: decl,
|
32507 | rawDeclarations,
|
32508 | };
|
32509 | // First, check for duplicate declarations of the same directive/pipe.
|
32510 | if (this.duplicateDeclarations.has(decl.node)) {
|
32511 | // This directive/pipe has already been identified as being duplicated. Add this module to the
|
32512 | // map of modules for which a duplicate declaration exists.
|
32513 | this.duplicateDeclarations.get(decl.node).set(ngModule, declData);
|
32514 | }
|
32515 | else if (this.declarationToModule.has(decl.node) &&
|
32516 | this.declarationToModule.get(decl.node).ngModule !== ngModule) {
|
32517 | // This directive/pipe is already registered as declared in another module. Mark it as a
|
32518 | // duplicate instead.
|
32519 | const duplicateDeclMap = new Map();
|
32520 | const firstDeclData = this.declarationToModule.get(decl.node);
|
32521 | // Mark both modules as having duplicate declarations.
|
32522 | this.modulesWithStructuralErrors.add(firstDeclData.ngModule);
|
32523 | this.modulesWithStructuralErrors.add(ngModule);
|
32524 | // Being detected as a duplicate means there are two NgModules (for now) which declare this
|
32525 | // directive/pipe. Add both of them to the duplicate tracking map.
|
32526 | duplicateDeclMap.set(firstDeclData.ngModule, firstDeclData);
|
32527 | duplicateDeclMap.set(ngModule, declData);
|
32528 | this.duplicateDeclarations.set(decl.node, duplicateDeclMap);
|
32529 | // Remove the directive/pipe from `declarationToModule` as it's a duplicate declaration, and
|
32530 | // therefore not valid.
|
32531 | this.declarationToModule.delete(decl.node);
|
32532 | }
|
32533 | else {
|
32534 | // This is the first declaration of this directive/pipe, so map it.
|
32535 | this.declarationToModule.set(decl.node, declData);
|
32536 | }
|
32537 | }
|
32538 | /**
|
32539 | * Implementation of `getScopeOfModule` which accepts a reference to a class.
|
32540 | */
|
32541 | getScopeOfModuleReference(ref) {
|
32542 | if (this.cache.has(ref.node)) {
|
32543 | return this.cache.get(ref.node);
|
32544 | }
|
32545 | // Seal the registry to protect the integrity of the `LocalModuleScope` cache.
|
32546 | this.sealed = true;
|
32547 | // `ref` should be an NgModule previously added to the registry. If not, a scope for it
|
32548 | // cannot be produced.
|
32549 | const ngModule = this.localReader.getNgModuleMetadata(ref);
|
32550 | if (ngModule === null) {
|
32551 | this.cache.set(ref.node, null);
|
32552 | return null;
|
32553 | }
|
32554 | // Modules which contributed to the compilation scope of this module.
|
32555 | const compilationModules = new Set([ngModule.ref.node]);
|
32556 | // Modules which contributed to the export scope of this module.
|
32557 | const exportedModules = new Set([ngModule.ref.node]);
|
32558 | // Errors produced during computation of the scope are recorded here. At the end, if this array
|
32559 | // isn't empty then `undefined` will be cached and returned to indicate this scope is invalid.
|
32560 | const diagnostics = [];
|
32561 | // At this point, the goal is to produce two distinct transitive sets:
|
32562 | // - the directives and pipes which are visible to components declared in the NgModule.
|
32563 | // - the directives and pipes which are exported to any NgModules which import this one.
|
32564 | // Directives and pipes in the compilation scope.
|
32565 | const compilationDirectives = new Map();
|
32566 | const compilationPipes = new Map();
|
32567 | const declared = new Set();
|
32568 | // Directives and pipes exported to any importing NgModules.
|
32569 | const exportDirectives = new Map();
|
32570 | const exportPipes = new Map();
|
32571 | // The algorithm is as follows:
|
32572 | // 1) Add all of the directives/pipes from each NgModule imported into the current one to the
|
32573 | // compilation scope.
|
32574 | // 2) Add directives/pipes declared in the NgModule to the compilation scope. At this point, the
|
32575 | // compilation scope is complete.
|
32576 | // 3) For each entry in the NgModule's exports:
|
32577 | // a) Attempt to resolve it as an NgModule with its own exported directives/pipes. If it is
|
32578 | // one, add them to the export scope of this NgModule.
|
32579 | // b) Otherwise, it should be a class in the compilation scope of this NgModule. If it is,
|
32580 | // add it to the export scope.
|
32581 | // c) If it's neither an NgModule nor a directive/pipe in the compilation scope, then this
|
32582 | // is an error.
|
32583 | //
|
32584 | let isPoisoned = false;
|
32585 | if (this.modulesWithStructuralErrors.has(ngModule.ref.node)) {
|
32586 | // If the module contains declarations that are duplicates, then it's considered poisoned.
|
32587 | isPoisoned = true;
|
32588 | }
|
32589 | // 1) process imports.
|
32590 | for (const decl of ngModule.imports) {
|
32591 | const importScope = this.getExportedScope(decl, diagnostics, ref.node, 'import');
|
32592 | if (importScope === null) {
|
32593 | // An import wasn't an NgModule, so record an error.
|
32594 | diagnostics.push(invalidRef(ref.node, decl, 'import'));
|
32595 | isPoisoned = true;
|
32596 | continue;
|
32597 | }
|
32598 | else if (importScope === 'invalid' || importScope.exported.isPoisoned) {
|
32599 | // An import was an NgModule but contained errors of its own. Record this as an error too,
|
32600 | // because this scope is always going to be incorrect if one of its imports could not be
|
32601 | // read.
|
32602 | diagnostics.push(invalidTransitiveNgModuleRef(ref.node, decl, 'import'));
|
32603 | isPoisoned = true;
|
32604 | if (importScope === 'invalid') {
|
32605 | continue;
|
32606 | }
|
32607 | }
|
32608 | for (const directive of importScope.exported.directives) {
|
32609 | compilationDirectives.set(directive.ref.node, directive);
|
32610 | }
|
32611 | for (const pipe of importScope.exported.pipes) {
|
32612 | compilationPipes.set(pipe.ref.node, pipe);
|
32613 | }
|
32614 | for (const importedModule of importScope.exported.ngModules) {
|
32615 | compilationModules.add(importedModule);
|
32616 | }
|
32617 | }
|
32618 | // 2) add declarations.
|
32619 | for (const decl of ngModule.declarations) {
|
32620 | const directive = this.localReader.getDirectiveMetadata(decl);
|
32621 | const pipe = this.localReader.getPipeMetadata(decl);
|
32622 | if (directive !== null) {
|
32623 | compilationDirectives.set(decl.node, Object.assign(Object.assign({}, directive), { ref: decl }));
|
32624 | if (directive.isPoisoned) {
|
32625 | isPoisoned = true;
|
32626 | }
|
32627 | }
|
32628 | else if (pipe !== null) {
|
32629 | compilationPipes.set(decl.node, Object.assign(Object.assign({}, pipe), { ref: decl }));
|
32630 | }
|
32631 | else {
|
32632 | const errorNode = decl.getOriginForDiagnostics(ngModule.rawDeclarations);
|
32633 | diagnostics.push(makeDiagnostic(ErrorCode.NGMODULE_INVALID_DECLARATION, errorNode, `The class '${decl.node.name.text}' is listed in the declarations ` +
|
32634 | `of the NgModule '${ngModule.ref.node.name
|
32635 | .text}', but is not a directive, a component, or a pipe. ` +
|
32636 | `Either remove it from the NgModule's declarations, or add an appropriate Angular decorator.`, [makeRelatedInformation(decl.node.name, `'${decl.node.name.text}' is declared here.`)]));
|
32637 | isPoisoned = true;
|
32638 | continue;
|
32639 | }
|
32640 | declared.add(decl.node);
|
32641 | }
|
32642 | // 3) process exports.
|
32643 | // Exports can contain modules, components, or directives. They're processed differently.
|
32644 | // Modules are straightforward. Directives and pipes from exported modules are added to the
|
32645 | // export maps. Directives/pipes are different - they might be exports of declared types or
|
32646 | // imported types.
|
32647 | for (const decl of ngModule.exports) {
|
32648 | // Attempt to resolve decl as an NgModule.
|
32649 | const exportScope = this.getExportedScope(decl, diagnostics, ref.node, 'export');
|
32650 | if (exportScope === 'invalid' || (exportScope !== null && exportScope.exported.isPoisoned)) {
|
32651 | // An export was an NgModule but contained errors of its own. Record this as an error too,
|
32652 | // because this scope is always going to be incorrect if one of its exports could not be
|
32653 | // read.
|
32654 | diagnostics.push(invalidTransitiveNgModuleRef(ref.node, decl, 'export'));
|
32655 | isPoisoned = true;
|
32656 | if (exportScope === 'invalid') {
|
32657 | continue;
|
32658 | }
|
32659 | }
|
32660 | else if (exportScope !== null) {
|
32661 | // decl is an NgModule.
|
32662 | for (const directive of exportScope.exported.directives) {
|
32663 | exportDirectives.set(directive.ref.node, directive);
|
32664 | }
|
32665 | for (const pipe of exportScope.exported.pipes) {
|
32666 | exportPipes.set(pipe.ref.node, pipe);
|
32667 | }
|
32668 | for (const exportedModule of exportScope.exported.ngModules) {
|
32669 | exportedModules.add(exportedModule);
|
32670 | }
|
32671 | }
|
32672 | else if (compilationDirectives.has(decl.node)) {
|
32673 | // decl is a directive or component in the compilation scope of this NgModule.
|
32674 | const directive = compilationDirectives.get(decl.node);
|
32675 | exportDirectives.set(decl.node, directive);
|
32676 | }
|
32677 | else if (compilationPipes.has(decl.node)) {
|
32678 | // decl is a pipe in the compilation scope of this NgModule.
|
32679 | const pipe = compilationPipes.get(decl.node);
|
32680 | exportPipes.set(decl.node, pipe);
|
32681 | }
|
32682 | else {
|
32683 | // decl is an unknown export.
|
32684 | if (this.localReader.getDirectiveMetadata(decl) !== null ||
|
32685 | this.localReader.getPipeMetadata(decl) !== null) {
|
32686 | diagnostics.push(invalidReexport(ref.node, decl));
|
32687 | }
|
32688 | else {
|
32689 | diagnostics.push(invalidRef(ref.node, decl, 'export'));
|
32690 | }
|
32691 | isPoisoned = true;
|
32692 | continue;
|
32693 | }
|
32694 | }
|
32695 | const exported = {
|
32696 | directives: Array.from(exportDirectives.values()),
|
32697 | pipes: Array.from(exportPipes.values()),
|
32698 | ngModules: Array.from(exportedModules),
|
32699 | isPoisoned,
|
32700 | };
|
32701 | const reexports = this.getReexports(ngModule, ref, declared, exported, diagnostics);
|
32702 | // Finally, produce the `LocalModuleScope` with both the compilation and export scopes.
|
32703 | const scope = {
|
32704 | ngModule: ngModule.ref.node,
|
32705 | compilation: {
|
32706 | directives: Array.from(compilationDirectives.values()),
|
32707 | pipes: Array.from(compilationPipes.values()),
|
32708 | ngModules: Array.from(compilationModules),
|
32709 | isPoisoned,
|
32710 | },
|
32711 | exported,
|
32712 | reexports,
|
32713 | schemas: ngModule.schemas,
|
32714 | };
|
32715 | // Check if this scope had any errors during production.
|
32716 | if (diagnostics.length > 0) {
|
32717 | // Save the errors for retrieval.
|
32718 | this.scopeErrors.set(ref.node, diagnostics);
|
32719 | // Mark this module as being tainted.
|
32720 | this.modulesWithStructuralErrors.add(ref.node);
|
32721 | }
|
32722 | this.cache.set(ref.node, scope);
|
32723 | return scope;
|
32724 | }
|
32725 | /**
|
32726 | * Check whether a component requires remote scoping.
|
32727 | */
|
32728 | getRemoteScope(node) {
|
32729 | return this.remoteScoping.has(node) ? this.remoteScoping.get(node) : null;
|
32730 | }
|
32731 | /**
|
32732 | * Set a component as requiring remote scoping, with the given directives and pipes to be
|
32733 | * registered remotely.
|
32734 | */
|
32735 | setComponentRemoteScope(node, directives, pipes) {
|
32736 | this.remoteScoping.set(node, { directives, pipes });
|
32737 | }
|
32738 | /**
|
32739 | * Look up the `ExportScope` of a given `Reference` to an NgModule.
|
32740 | *
|
32741 | * The NgModule in question may be declared locally in the current ts.Program, or it may be
|
32742 | * declared in a .d.ts file.
|
32743 | *
|
32744 | * @returns `null` if no scope could be found, or `'invalid'` if the `Reference` is not a valid
|
32745 | * NgModule.
|
32746 | *
|
32747 | * May also contribute diagnostics of its own by adding to the given `diagnostics`
|
32748 | * array parameter.
|
32749 | */
|
32750 | getExportedScope(ref, diagnostics, ownerForErrors, type) {
|
32751 | if (ref.node.getSourceFile().isDeclarationFile) {
|
32752 | // The NgModule is declared in a .d.ts file. Resolve it with the `DependencyScopeReader`.
|
32753 | if (!ts$1.isClassDeclaration(ref.node)) {
|
32754 | // The NgModule is in a .d.ts file but is not declared as a ts.ClassDeclaration. This is an
|
32755 | // error in the .d.ts metadata.
|
32756 | const code = type === 'import' ? ErrorCode.NGMODULE_INVALID_IMPORT :
|
32757 | ErrorCode.NGMODULE_INVALID_EXPORT;
|
32758 | diagnostics.push(makeDiagnostic(code, identifierOfNode(ref.node) || ref.node, `Appears in the NgModule.${type}s of ${nodeNameForError(ownerForErrors)}, but could not be resolved to an NgModule`));
|
32759 | return 'invalid';
|
32760 | }
|
32761 | return this.dependencyScopeReader.resolve(ref);
|
32762 | }
|
32763 | else {
|
32764 | // The NgModule is declared locally in the current program. Resolve it from the registry.
|
32765 | return this.getScopeOfModuleReference(ref);
|
32766 | }
|
32767 | }
|
32768 | getReexports(ngModule, ref, declared, exported, diagnostics) {
|
32769 | let reexports = null;
|
32770 | const sourceFile = ref.node.getSourceFile();
|
32771 | if (this.aliasingHost === null) {
|
32772 | return null;
|
32773 | }
|
32774 | reexports = [];
|
32775 | // Track re-exports by symbol name, to produce diagnostics if two alias re-exports would share
|
32776 | // the same name.
|
32777 | const reexportMap = new Map();
|
32778 | // Alias ngModuleRef added for readability below.
|
32779 | const ngModuleRef = ref;
|
32780 | const addReexport = (exportRef) => {
|
32781 | if (exportRef.node.getSourceFile() === sourceFile) {
|
32782 | return;
|
32783 | }
|
32784 | const isReExport = !declared.has(exportRef.node);
|
32785 | const exportName = this.aliasingHost.maybeAliasSymbolAs(exportRef, sourceFile, ngModule.ref.node.name.text, isReExport);
|
32786 | if (exportName === null) {
|
32787 | return;
|
32788 | }
|
32789 | if (!reexportMap.has(exportName)) {
|
32790 | if (exportRef.alias && exportRef.alias instanceof ExternalExpr) {
|
32791 | reexports.push({
|
32792 | fromModule: exportRef.alias.value.moduleName,
|
32793 | symbolName: exportRef.alias.value.name,
|
32794 | asAlias: exportName,
|
32795 | });
|
32796 | }
|
32797 | else {
|
32798 | const expr = this.refEmitter.emit(exportRef.cloneWithNoIdentifiers(), sourceFile);
|
32799 | if (!(expr instanceof ExternalExpr) || expr.value.moduleName === null ||
|
32800 | expr.value.name === null) {
|
32801 | throw new Error('Expected ExternalExpr');
|
32802 | }
|
32803 | reexports.push({
|
32804 | fromModule: expr.value.moduleName,
|
32805 | symbolName: expr.value.name,
|
32806 | asAlias: exportName,
|
32807 | });
|
32808 | }
|
32809 | reexportMap.set(exportName, exportRef);
|
32810 | }
|
32811 | else {
|
32812 | // Another re-export already used this name. Produce a diagnostic.
|
32813 | const prevRef = reexportMap.get(exportName);
|
32814 | diagnostics.push(reexportCollision(ngModuleRef.node, prevRef, exportRef));
|
32815 | }
|
32816 | };
|
32817 | for (const { ref } of exported.directives) {
|
32818 | addReexport(ref);
|
32819 | }
|
32820 | for (const { ref } of exported.pipes) {
|
32821 | addReexport(ref);
|
32822 | }
|
32823 | return reexports;
|
32824 | }
|
32825 | assertCollecting() {
|
32826 | if (this.sealed) {
|
32827 | throw new Error(`Assertion: LocalModuleScopeRegistry is not COLLECTING`);
|
32828 | }
|
32829 | }
|
32830 | }
|
32831 | /**
|
32832 | * Produce a `ts.Diagnostic` for an invalid import or export from an NgModule.
|
32833 | */
|
32834 | function invalidRef(clazz, decl, type) {
|
32835 | const code = type === 'import' ? ErrorCode.NGMODULE_INVALID_IMPORT : ErrorCode.NGMODULE_INVALID_EXPORT;
|
32836 | const resolveTarget = type === 'import' ? 'NgModule' : 'NgModule, Component, Directive, or Pipe';
|
32837 | let message = `Appears in the NgModule.${type}s of ${nodeNameForError(clazz)}, but could not be resolved to an ${resolveTarget} class.` +
|
32838 | '\n\n';
|
32839 | const library = decl.ownedByModuleGuess !== null ? ` (${decl.ownedByModuleGuess})` : '';
|
32840 | const sf = decl.node.getSourceFile();
|
32841 | // Provide extra context to the error for the user.
|
32842 | if (!sf.isDeclarationFile) {
|
32843 | // This is a file in the user's program.
|
32844 | const annotationType = type === 'import' ? '@NgModule' : 'Angular';
|
32845 | message += `Is it missing an ${annotationType} annotation?`;
|
32846 | }
|
32847 | else if (sf.fileName.indexOf('node_modules') !== -1) {
|
32848 | // This file comes from a third-party library in node_modules.
|
32849 | message +=
|
32850 | `This likely means that the library${library} which declares ${decl.debugName} has not ` +
|
32851 | 'been processed correctly by ngcc, or is not compatible with Angular Ivy. Check if a ' +
|
32852 | 'newer version of the library is available, and update if so. Also consider checking ' +
|
32853 | 'with the library\'s authors to see if the library is expected to be compatible with Ivy.';
|
32854 | }
|
32855 | else {
|
32856 | // This is a monorepo style local dependency. Unfortunately these are too different to really
|
32857 | // offer much more advice than this.
|
32858 | message += `This likely means that the dependency${library} which declares ${decl.debugName} has not been processed correctly by ngcc.`;
|
32859 | }
|
32860 | return makeDiagnostic(code, identifierOfNode(decl.node) || decl.node, message);
|
32861 | }
|
32862 | /**
|
32863 | * Produce a `ts.Diagnostic` for an import or export which itself has errors.
|
32864 | */
|
32865 | function invalidTransitiveNgModuleRef(clazz, decl, type) {
|
32866 | const code = type === 'import' ? ErrorCode.NGMODULE_INVALID_IMPORT : ErrorCode.NGMODULE_INVALID_EXPORT;
|
32867 | return makeDiagnostic(code, identifierOfNode(decl.node) || decl.node, `Appears in the NgModule.${type}s of ${nodeNameForError(clazz)}, but itself has errors`);
|
32868 | }
|
32869 | /**
|
32870 | * Produce a `ts.Diagnostic` for an exported directive or pipe which was not declared or imported
|
32871 | * by the NgModule in question.
|
32872 | */
|
32873 | function invalidReexport(clazz, decl) {
|
32874 | return makeDiagnostic(ErrorCode.NGMODULE_INVALID_REEXPORT, identifierOfNode(decl.node) || decl.node, `Present in the NgModule.exports of ${nodeNameForError(clazz)} but neither declared nor imported`);
|
32875 | }
|
32876 | /**
|
32877 | * Produce a `ts.Diagnostic` for a collision in re-export names between two directives/pipes.
|
32878 | */
|
32879 | function reexportCollision(module, refA, refB) {
|
32880 | const childMessageText = `This directive/pipe is part of the exports of '${module.name.text}' and shares the same name as another exported directive/pipe.`;
|
32881 | return makeDiagnostic(ErrorCode.NGMODULE_REEXPORT_NAME_COLLISION, module.name, `
|
32882 | There was a name collision between two classes named '${refA.node.name.text}', which are both part of the exports of '${module.name.text}'.
|
32883 |
|
32884 | Angular generates re-exports of an NgModule's exported directives/pipes from the module's source file in certain cases, using the declared name of the class. If two classes of the same name are exported, this automatic naming does not work.
|
32885 |
|
32886 | To fix this problem please re-export one or both classes directly from this file.
|
32887 | `.trim(), [
|
32888 | makeRelatedInformation(refA.node.name, childMessageText),
|
32889 | makeRelatedInformation(refB.node.name, childMessageText),
|
32890 | ]);
|
32891 | }
|
32892 |
|
32893 | /**
|
32894 | * @license
|
32895 | * Copyright Google LLC All Rights Reserved.
|
32896 | *
|
32897 | * Use of this source code is governed by an MIT-style license that can be
|
32898 | * found in the LICENSE file at https://angular.io/license
|
32899 | */
|
32900 | /**
|
32901 | * Computes scope information to be used in template type checking.
|
32902 | */
|
32903 | class TypeCheckScopeRegistry {
|
32904 | constructor(scopeReader, metaReader) {
|
32905 | this.scopeReader = scopeReader;
|
32906 | this.metaReader = metaReader;
|
32907 | /**
|
32908 | * Cache of flattened directive metadata. Because flattened metadata is scope-invariant it's
|
32909 | * cached individually, such that all scopes refer to the same flattened metadata.
|
32910 | */
|
32911 | this.flattenedDirectiveMetaCache = new Map();
|
32912 | /**
|
32913 | * Cache of the computed type check scope per NgModule declaration.
|
32914 | */
|
32915 | this.scopeCache = new Map();
|
32916 | }
|
32917 | /**
|
32918 | * Computes the type-check scope information for the component declaration. If the NgModule
|
32919 | * contains an error, then 'error' is returned. If the component is not declared in any NgModule,
|
32920 | * an empty type-check scope is returned.
|
32921 | */
|
32922 | getTypeCheckScope(node) {
|
32923 | const matcher = new SelectorMatcher();
|
32924 | const directives = [];
|
32925 | const pipes = new Map();
|
32926 | const scope = this.scopeReader.getScopeForComponent(node);
|
32927 | if (scope === null) {
|
32928 | return {
|
32929 | matcher,
|
32930 | directives,
|
32931 | pipes,
|
32932 | schemas: [],
|
32933 | isPoisoned: false,
|
32934 | };
|
32935 | }
|
32936 | if (this.scopeCache.has(scope.ngModule)) {
|
32937 | return this.scopeCache.get(scope.ngModule);
|
32938 | }
|
32939 | for (const meta of scope.compilation.directives) {
|
32940 | if (meta.selector !== null) {
|
32941 | const extMeta = this.getTypeCheckDirectiveMetadata(meta.ref);
|
32942 | matcher.addSelectables(CssSelector.parse(meta.selector), extMeta);
|
32943 | directives.push(extMeta);
|
32944 | }
|
32945 | }
|
32946 | for (const { name, ref } of scope.compilation.pipes) {
|
32947 | if (!ts$1.isClassDeclaration(ref.node)) {
|
32948 | throw new Error(`Unexpected non-class declaration ${ts$1.SyntaxKind[ref.node.kind]} for pipe ${ref.debugName}`);
|
32949 | }
|
32950 | pipes.set(name, ref);
|
32951 | }
|
32952 | const typeCheckScope = {
|
32953 | matcher,
|
32954 | directives,
|
32955 | pipes,
|
32956 | schemas: scope.schemas,
|
32957 | isPoisoned: scope.compilation.isPoisoned || scope.exported.isPoisoned,
|
32958 | };
|
32959 | this.scopeCache.set(scope.ngModule, typeCheckScope);
|
32960 | return typeCheckScope;
|
32961 | }
|
32962 | getTypeCheckDirectiveMetadata(ref) {
|
32963 | const clazz = ref.node;
|
32964 | if (this.flattenedDirectiveMetaCache.has(clazz)) {
|
32965 | return this.flattenedDirectiveMetaCache.get(clazz);
|
32966 | }
|
32967 | const meta = flattenInheritedDirectiveMetadata(this.metaReader, ref);
|
32968 | this.flattenedDirectiveMetaCache.set(clazz, meta);
|
32969 | return meta;
|
32970 | }
|
32971 | }
|
32972 |
|
32973 | /**
|
32974 | * @license
|
32975 | * Copyright Google LLC All Rights Reserved.
|
32976 | *
|
32977 | * Use of this source code is governed by an MIT-style license that can be
|
32978 | * found in the LICENSE file at https://angular.io/license
|
32979 | */
|
32980 | /**
|
32981 | * A `Symbol` which is used to patch extension data onto `ts.SourceFile`s.
|
32982 | */
|
32983 | const NgExtension = Symbol('NgExtension');
|
32984 | /**
|
32985 | * Narrows a `ts.SourceFile` if it has an `NgExtension` property.
|
32986 | */
|
32987 | function isExtended(sf) {
|
32988 | return sf[NgExtension] !== undefined;
|
32989 | }
|
32990 | /**
|
32991 | * Check whether `sf` is a shim `ts.SourceFile` (either a per-file shim or a top-level shim).
|
32992 | */
|
32993 | function isShim(sf) {
|
32994 | return isExtended(sf) && (sf[NgExtension].fileShim !== null || sf[NgExtension].isTopLevelShim);
|
32995 | }
|
32996 |
|
32997 | /**
|
32998 | * @license
|
32999 | * Copyright Google LLC All Rights Reserved.
|
33000 | *
|
33001 | * Use of this source code is governed by an MIT-style license that can be
|
33002 | * found in the LICENSE file at https://angular.io/license
|
33003 | */
|
33004 | const STRIP_NG_FACTORY = /(.*)NgFactory$/;
|
33005 | function generatedFactoryTransform(factoryMap, importRewriter) {
|
33006 | return (context) => {
|
33007 | return (file) => {
|
33008 | return transformFactorySourceFile(factoryMap, context, importRewriter, file);
|
33009 | };
|
33010 | };
|
33011 | }
|
33012 | function transformFactorySourceFile(factoryMap, context, importRewriter, file) {
|
33013 | // If this is not a generated file, it won't have factory info associated with it.
|
33014 | if (!factoryMap.has(file.fileName)) {
|
33015 | // Don't transform non-generated code.
|
33016 | return file;
|
33017 | }
|
33018 | const { moduleSymbols, sourceFilePath } = factoryMap.get(file.fileName);
|
33019 | // Not every exported factory statement is valid. They were generated before the program was
|
33020 | // analyzed, and before ngtsc knew which symbols were actually NgModules. factoryMap contains
|
33021 | // that knowledge now, so this transform filters the statement list and removes exported factories
|
33022 | // that aren't actually factories.
|
33023 | //
|
33024 | // This could leave the generated factory file empty. To prevent this (it causes issues with
|
33025 | // closure compiler) a 'ɵNonEmptyModule' export was added when the factory shim was created.
|
33026 | // Preserve that export if needed, and remove it otherwise.
|
33027 | //
|
33028 | // Additionally, an import to @angular/core is generated, but the current compilation unit could
|
33029 | // actually be @angular/core, in which case such an import is invalid and should be replaced with
|
33030 | // the proper path to access Ivy symbols in core.
|
33031 | // The filtered set of statements.
|
33032 | const transformedStatements = [];
|
33033 | // The statement identified as the ɵNonEmptyModule export.
|
33034 | let nonEmptyExport = null;
|
33035 | // Extracted identifiers which refer to import statements from @angular/core.
|
33036 | const coreImportIdentifiers = new Set();
|
33037 | // Consider all the statements.
|
33038 | for (const stmt of file.statements) {
|
33039 | // Look for imports to @angular/core.
|
33040 | if (ts$1.isImportDeclaration(stmt) && ts$1.isStringLiteral(stmt.moduleSpecifier) &&
|
33041 | stmt.moduleSpecifier.text === '@angular/core') {
|
33042 | // Update the import path to point to the correct file using the ImportRewriter.
|
33043 | const rewrittenModuleSpecifier = importRewriter.rewriteSpecifier('@angular/core', sourceFilePath);
|
33044 | if (rewrittenModuleSpecifier !== stmt.moduleSpecifier.text) {
|
33045 | transformedStatements.push(ts$1.updateImportDeclaration(stmt, stmt.decorators, stmt.modifiers, stmt.importClause, ts$1.createStringLiteral(rewrittenModuleSpecifier)));
|
33046 | // Record the identifier by which this imported module goes, so references to its symbols
|
33047 | // can be discovered later.
|
33048 | if (stmt.importClause !== undefined && stmt.importClause.namedBindings !== undefined &&
|
33049 | ts$1.isNamespaceImport(stmt.importClause.namedBindings)) {
|
33050 | coreImportIdentifiers.add(stmt.importClause.namedBindings.name.text);
|
33051 | }
|
33052 | }
|
33053 | else {
|
33054 | transformedStatements.push(stmt);
|
33055 | }
|
33056 | }
|
33057 | else if (ts$1.isVariableStatement(stmt) && stmt.declarationList.declarations.length === 1) {
|
33058 | const decl = stmt.declarationList.declarations[0];
|
33059 | // If this is the ɵNonEmptyModule export, then save it for later.
|
33060 | if (ts$1.isIdentifier(decl.name)) {
|
33061 | if (decl.name.text === 'ɵNonEmptyModule') {
|
33062 | nonEmptyExport = stmt;
|
33063 | continue;
|
33064 | }
|
33065 | // Otherwise, check if this export is a factory for a known NgModule, and retain it if so.
|
33066 | const match = STRIP_NG_FACTORY.exec(decl.name.text);
|
33067 | const module = match ? moduleSymbols.get(match[1]) : null;
|
33068 | if (module) {
|
33069 | // If the module can be tree shaken, then the factory should be wrapped in a
|
33070 | // `noSideEffects()` call which tells Closure to treat the expression as pure, allowing
|
33071 | // it to be removed if the result is not used.
|
33072 | //
|
33073 | // `NgModule`s with an `id` property will be lazy loaded. Google-internal lazy loading
|
33074 | // infra relies on a side effect from the `new NgModuleFactory()` call, which registers
|
33075 | // the module globally. Because of this, we **cannot** tree shake any module which has
|
33076 | // an `id` property. Doing so would cause lazy loaded modules to never be registered.
|
33077 | const moduleIsTreeShakable = !module.hasId;
|
33078 | const newStmt = !moduleIsTreeShakable ?
|
33079 | stmt :
|
33080 | updateInitializers(stmt, (init) => init ? wrapInNoSideEffects(init) : undefined);
|
33081 | transformedStatements.push(newStmt);
|
33082 | }
|
33083 | }
|
33084 | else {
|
33085 | // Leave the statement alone, as it can't be understood.
|
33086 | transformedStatements.push(stmt);
|
33087 | }
|
33088 | }
|
33089 | else {
|
33090 | // Include non-variable statements (imports, etc).
|
33091 | transformedStatements.push(stmt);
|
33092 | }
|
33093 | }
|
33094 | // Check whether the empty module export is still needed.
|
33095 | if (!transformedStatements.some(ts$1.isVariableStatement) && nonEmptyExport !== null) {
|
33096 | // If the resulting file has no factories, include an empty export to
|
33097 | // satisfy closure compiler.
|
33098 | transformedStatements.push(nonEmptyExport);
|
33099 | }
|
33100 | file = ts$1.updateSourceFileNode(file, transformedStatements);
|
33101 | // If any imports to @angular/core were detected and rewritten (which happens when compiling
|
33102 | // @angular/core), go through the SourceFile and rewrite references to symbols imported from core.
|
33103 | if (coreImportIdentifiers.size > 0) {
|
33104 | const visit = (node) => {
|
33105 | node = ts$1.visitEachChild(node, child => visit(child), context);
|
33106 | // Look for expressions of the form "i.s" where 'i' is a detected name for an @angular/core
|
33107 | // import that was changed above. Rewrite 's' using the ImportResolver.
|
33108 | if (ts$1.isPropertyAccessExpression(node) && ts$1.isIdentifier(node.expression) &&
|
33109 | coreImportIdentifiers.has(node.expression.text)) {
|
33110 | // This is an import of a symbol from @angular/core. Transform it with the importRewriter.
|
33111 | const rewrittenSymbol = importRewriter.rewriteSymbol(node.name.text, '@angular/core');
|
33112 | if (rewrittenSymbol !== node.name.text) {
|
33113 | const updated = ts$1.updatePropertyAccess(node, node.expression, ts$1.createIdentifier(rewrittenSymbol));
|
33114 | node = updated;
|
33115 | }
|
33116 | }
|
33117 | return node;
|
33118 | };
|
33119 | file = visit(file);
|
33120 | }
|
33121 | return file;
|
33122 | }
|
33123 | /**
|
33124 | * Wraps the given expression in a call to `ɵnoSideEffects()`, which tells
|
33125 | * Closure we don't care about the side effects of this expression and it should
|
33126 | * be treated as "pure". Closure is free to tree shake this expression if its
|
33127 | * result is not used.
|
33128 | *
|
33129 | * Example: Takes `1 + 2` and returns `i0.ɵnoSideEffects(() => 1 + 2)`.
|
33130 | */
|
33131 | function wrapInNoSideEffects(expr) {
|
33132 | const noSideEffects = ts$1.createPropertyAccess(ts$1.createIdentifier('i0'), 'ɵnoSideEffects');
|
33133 | return ts$1.createCall(noSideEffects,
|
33134 | /* typeArguments */ [],
|
33135 | /* arguments */
|
33136 | [
|
33137 | ts$1.createFunctionExpression(
|
33138 | /* modifiers */ [],
|
33139 | /* asteriskToken */ undefined,
|
33140 | /* name */ undefined,
|
33141 | /* typeParameters */ [],
|
33142 | /* parameters */ [],
|
33143 | /* type */ undefined,
|
33144 | /* body */ ts$1.createBlock([
|
33145 | ts$1.createReturn(expr),
|
33146 | ])),
|
33147 | ]);
|
33148 | }
|
33149 | /**
|
33150 | * Clones and updates the initializers for a given statement to use the new
|
33151 | * expression provided. Does not mutate the input statement.
|
33152 | */
|
33153 | function updateInitializers(stmt, update) {
|
33154 | return ts$1.updateVariableStatement(stmt, stmt.modifiers, ts$1.updateVariableDeclarationList(stmt.declarationList, stmt.declarationList.declarations.map((decl) => ts$1.updateVariableDeclaration(decl, decl.name, decl.type, update(decl.initializer)))));
|
33155 | }
|
33156 |
|
33157 | /**
|
33158 | * @license
|
33159 | * Copyright Google LLC All Rights Reserved.
|
33160 | *
|
33161 | * Use of this source code is governed by an MIT-style license that can be
|
33162 | * found in the LICENSE file at https://angular.io/license
|
33163 | */
|
33164 | const IVY_SWITCH_PRE_SUFFIX = '__PRE_R3__';
|
33165 | const IVY_SWITCH_POST_SUFFIX = '__POST_R3__';
|
33166 | function ivySwitchTransform(_) {
|
33167 | return flipIvySwitchInFile;
|
33168 | }
|
33169 | function flipIvySwitchInFile(sf) {
|
33170 | // To replace the statements array, it must be copied. This only needs to happen if a statement
|
33171 | // must actually be replaced within the array, so the newStatements array is lazily initialized.
|
33172 | let newStatements = undefined;
|
33173 | // Iterate over the statements in the file.
|
33174 | for (let i = 0; i < sf.statements.length; i++) {
|
33175 | const statement = sf.statements[i];
|
33176 | // Skip over everything that isn't a variable statement.
|
33177 | if (!ts$1.isVariableStatement(statement) || !hasIvySwitches(statement)) {
|
33178 | continue;
|
33179 | }
|
33180 | // This statement needs to be replaced. Check if the newStatements array needs to be lazily
|
33181 | // initialized to a copy of the original statements.
|
33182 | if (newStatements === undefined) {
|
33183 | newStatements = [...sf.statements];
|
33184 | }
|
33185 | // Flip any switches in the VariableStatement. If there were any, a new statement will be
|
33186 | // returned; otherwise the old statement will be.
|
33187 | newStatements[i] = flipIvySwitchesInVariableStatement(statement, sf.statements);
|
33188 | }
|
33189 | // Only update the statements in the SourceFile if any have changed.
|
33190 | if (newStatements !== undefined) {
|
33191 | return ts$1.updateSourceFileNode(sf, newStatements);
|
33192 | }
|
33193 | return sf;
|
33194 | }
|
33195 | /**
|
33196 | * Look for the ts.Identifier of a ts.Declaration with this name.
|
33197 | *
|
33198 | * The real identifier is needed (rather than fabricating one) as TypeScript decides how to
|
33199 | * reference this identifier based on information stored against its node in the AST, which a
|
33200 | * synthetic node would not have. In particular, since the post-switch variable is often exported,
|
33201 | * TypeScript needs to know this so it can write `exports.VAR` instead of just `VAR` when emitting
|
33202 | * code.
|
33203 | *
|
33204 | * Only variable, function, and class declarations are currently searched.
|
33205 | */
|
33206 | function findPostSwitchIdentifier(statements, name) {
|
33207 | for (const stmt of statements) {
|
33208 | if (ts$1.isVariableStatement(stmt)) {
|
33209 | const decl = stmt.declarationList.declarations.find(decl => ts$1.isIdentifier(decl.name) && decl.name.text === name);
|
33210 | if (decl !== undefined) {
|
33211 | return decl.name;
|
33212 | }
|
33213 | }
|
33214 | else if (ts$1.isFunctionDeclaration(stmt) || ts$1.isClassDeclaration(stmt)) {
|
33215 | if (stmt.name !== undefined && ts$1.isIdentifier(stmt.name) && stmt.name.text === name) {
|
33216 | return stmt.name;
|
33217 | }
|
33218 | }
|
33219 | }
|
33220 | return null;
|
33221 | }
|
33222 | /**
|
33223 | * Flip any Ivy switches which are discovered in the given ts.VariableStatement.
|
33224 | */
|
33225 | function flipIvySwitchesInVariableStatement(stmt, statements) {
|
33226 | // Build a new list of variable declarations. Specific declarations that are initialized to a
|
33227 | // pre-switch identifier will be replaced with a declaration initialized to the post-switch
|
33228 | // identifier.
|
33229 | const newDeclarations = [...stmt.declarationList.declarations];
|
33230 | for (let i = 0; i < newDeclarations.length; i++) {
|
33231 | const decl = newDeclarations[i];
|
33232 | // Skip declarations that aren't initialized to an identifier.
|
33233 | if (decl.initializer === undefined || !ts$1.isIdentifier(decl.initializer)) {
|
33234 | continue;
|
33235 | }
|
33236 | // Skip declarations that aren't Ivy switches.
|
33237 | if (!decl.initializer.text.endsWith(IVY_SWITCH_PRE_SUFFIX)) {
|
33238 | continue;
|
33239 | }
|
33240 | // Determine the name of the post-switch variable.
|
33241 | const postSwitchName = decl.initializer.text.replace(IVY_SWITCH_PRE_SUFFIX, IVY_SWITCH_POST_SUFFIX);
|
33242 | // Find the post-switch variable identifier. If one can't be found, it's an error. This is
|
33243 | // reported as a thrown error and not a diagnostic as transformers cannot output diagnostics.
|
33244 | const newIdentifier = findPostSwitchIdentifier(statements, postSwitchName);
|
33245 | if (newIdentifier === null) {
|
33246 | throw new Error(`Unable to find identifier ${postSwitchName} in ${stmt.getSourceFile().fileName} for the Ivy switch.`);
|
33247 | }
|
33248 | newDeclarations[i] = ts$1.updateVariableDeclaration(
|
33249 | /* node */ decl,
|
33250 | /* name */ decl.name,
|
33251 | /* type */ decl.type,
|
33252 | /* initializer */ newIdentifier);
|
33253 | }
|
33254 | const newDeclList = ts$1.updateVariableDeclarationList(
|
33255 | /* declarationList */ stmt.declarationList,
|
33256 | /* declarations */ newDeclarations);
|
33257 | const newStmt = ts$1.updateVariableStatement(
|
33258 | /* statement */ stmt,
|
33259 | /* modifiers */ stmt.modifiers,
|
33260 | /* declarationList */ newDeclList);
|
33261 | return newStmt;
|
33262 | }
|
33263 | /**
|
33264 | * Check whether the given VariableStatement has any Ivy switch variables.
|
33265 | */
|
33266 | function hasIvySwitches(stmt) {
|
33267 | return stmt.declarationList.declarations.some(decl => decl.initializer !== undefined && ts$1.isIdentifier(decl.initializer) &&
|
33268 | decl.initializer.text.endsWith(IVY_SWITCH_PRE_SUFFIX));
|
33269 | }
|
33270 |
|
33271 | /**
|
33272 | * @license
|
33273 | * Copyright Google LLC All Rights Reserved.
|
33274 | *
|
33275 | * Use of this source code is governed by an MIT-style license that can be
|
33276 | * found in the LICENSE file at https://angular.io/license
|
33277 | */
|
33278 | var UpdateMode;
|
33279 | (function (UpdateMode) {
|
33280 | /**
|
33281 | * A complete update creates a completely new overlay of type-checking code on top of the user's
|
33282 | * original program, which doesn't include type-checking code from previous calls to
|
33283 | * `updateFiles`.
|
33284 | */
|
33285 | UpdateMode[UpdateMode["Complete"] = 0] = "Complete";
|
33286 | /**
|
33287 | * An incremental update changes the contents of some files in the type-checking program without
|
33288 | * reverting any prior changes.
|
33289 | */
|
33290 | UpdateMode[UpdateMode["Incremental"] = 1] = "Incremental";
|
33291 | })(UpdateMode || (UpdateMode = {}));
|
33292 |
|
33293 | /**
|
33294 | * @license
|
33295 | * Copyright Google LLC All Rights Reserved.
|
33296 | *
|
33297 | * Use of this source code is governed by an MIT-style license that can be
|
33298 | * found in the LICENSE file at https://angular.io/license
|
33299 | */
|
33300 | /**
|
33301 | * Describes the scope of the caller's interest in template type-checking results.
|
33302 | */
|
33303 | var OptimizeFor;
|
33304 | (function (OptimizeFor) {
|
33305 | /**
|
33306 | * Indicates that a consumer of a `TemplateTypeChecker` is only interested in results for a given
|
33307 | * file, and wants them as fast as possible.
|
33308 | *
|
33309 | * Calling `TemplateTypeChecker` methods successively for multiple files while specifying
|
33310 | * `OptimizeFor.SingleFile` can result in significant unnecessary overhead overall.
|
33311 | */
|
33312 | OptimizeFor[OptimizeFor["SingleFile"] = 0] = "SingleFile";
|
33313 | /**
|
33314 | * Indicates that a consumer of a `TemplateTypeChecker` intends to query for results pertaining to
|
33315 | * the entire user program, and so the type-checker should internally optimize for this case.
|
33316 | *
|
33317 | * Initial calls to retrieve type-checking information may take longer, but repeated calls to
|
33318 | * gather information for the whole user program will be significantly faster with this mode of
|
33319 | * optimization.
|
33320 | */
|
33321 | OptimizeFor[OptimizeFor["WholeProgram"] = 1] = "WholeProgram";
|
33322 | })(OptimizeFor || (OptimizeFor = {}));
|
33323 |
|
33324 | /**
|
33325 | * @license
|
33326 | * Copyright Google LLC All Rights Reserved.
|
33327 | *
|
33328 | * Use of this source code is governed by an MIT-style license that can be
|
33329 | * found in the LICENSE file at https://angular.io/license
|
33330 | */
|
33331 | /**
|
33332 | * Discriminant of an autocompletion source (a `Completion`).
|
33333 | */
|
33334 | var CompletionKind;
|
33335 | (function (CompletionKind) {
|
33336 | CompletionKind[CompletionKind["Reference"] = 0] = "Reference";
|
33337 | CompletionKind[CompletionKind["Variable"] = 1] = "Variable";
|
33338 | })(CompletionKind || (CompletionKind = {}));
|
33339 |
|
33340 | /**
|
33341 | * @license
|
33342 | * Copyright Google LLC All Rights Reserved.
|
33343 | *
|
33344 | * Use of this source code is governed by an MIT-style license that can be
|
33345 | * found in the LICENSE file at https://angular.io/license
|
33346 | */
|
33347 | var SymbolKind;
|
33348 | (function (SymbolKind) {
|
33349 | SymbolKind[SymbolKind["Input"] = 0] = "Input";
|
33350 | SymbolKind[SymbolKind["Output"] = 1] = "Output";
|
33351 | SymbolKind[SymbolKind["Binding"] = 2] = "Binding";
|
33352 | SymbolKind[SymbolKind["Reference"] = 3] = "Reference";
|
33353 | SymbolKind[SymbolKind["Variable"] = 4] = "Variable";
|
33354 | SymbolKind[SymbolKind["Directive"] = 5] = "Directive";
|
33355 | SymbolKind[SymbolKind["Element"] = 6] = "Element";
|
33356 | SymbolKind[SymbolKind["Template"] = 7] = "Template";
|
33357 | SymbolKind[SymbolKind["Expression"] = 8] = "Expression";
|
33358 | SymbolKind[SymbolKind["DomBinding"] = 9] = "DomBinding";
|
33359 | SymbolKind[SymbolKind["Pipe"] = 10] = "Pipe";
|
33360 | })(SymbolKind || (SymbolKind = {}));
|
33361 |
|
33362 | /**
|
33363 | * @license
|
33364 | * Copyright Google LLC All Rights Reserved.
|
33365 | *
|
33366 | * Use of this source code is governed by an MIT-style license that can be
|
33367 | * found in the LICENSE file at https://angular.io/license
|
33368 | */
|
33369 | /**
|
33370 | * A `ShimGenerator` which adds type-checking files to the `ts.Program`.
|
33371 | *
|
33372 | * This is a requirement for performant template type-checking, as TypeScript will only reuse
|
33373 | * information in the main program when creating the type-checking program if the set of files in
|
33374 | * each are exactly the same. Thus, the main program also needs the synthetic type-checking files.
|
33375 | */
|
33376 | class TypeCheckShimGenerator {
|
33377 | constructor() {
|
33378 | this.extensionPrefix = 'ngtypecheck';
|
33379 | this.shouldEmit = false;
|
33380 | }
|
33381 | generateShimForFile(sf, genFilePath, priorShimSf) {
|
33382 | if (priorShimSf !== null) {
|
33383 | // If this shim existed in the previous program, reuse it now. It might not be correct, but
|
33384 | // reusing it in the main program allows the shape of its imports to potentially remain the
|
33385 | // same and TS can then use the fastest path for incremental program creation. Later during
|
33386 | // the type-checking phase it's going to either be reused, or replaced anyways. Thus there's
|
33387 | // no harm in reuse here even if it's out of date.
|
33388 | return priorShimSf;
|
33389 | }
|
33390 | return ts$1.createSourceFile(genFilePath, 'export const USED_FOR_NG_TYPE_CHECKING = true;', ts$1.ScriptTarget.Latest, true, ts$1.ScriptKind.TS);
|
33391 | }
|
33392 | static shimFor(fileName) {
|
33393 | return absoluteFrom(fileName.replace(/\.tsx?$/, '.ngtypecheck.ts'));
|
33394 | }
|
33395 | }
|
33396 |
|
33397 | /**
|
33398 | * @license
|
33399 | * Copyright Google LLC All Rights Reserved.
|
33400 | *
|
33401 | * Use of this source code is governed by an MIT-style license that can be
|
33402 | * found in the LICENSE file at https://angular.io/license
|
33403 | */
|
33404 | const parseSpanComment = /^(\d+),(\d+)$/;
|
33405 | /**
|
33406 | * Reads the trailing comments and finds the first match which is a span comment (i.e. 4,10) on a
|
33407 | * node and returns it as an `AbsoluteSourceSpan`.
|
33408 | *
|
33409 | * Will return `null` if no trailing comments on the node match the expected form of a source span.
|
33410 | */
|
33411 | function readSpanComment(node, sourceFile = node.getSourceFile()) {
|
33412 | return ts$1.forEachTrailingCommentRange(sourceFile.text, node.getEnd(), (pos, end, kind) => {
|
33413 | if (kind !== ts$1.SyntaxKind.MultiLineCommentTrivia) {
|
33414 | return null;
|
33415 | }
|
33416 | const commentText = sourceFile.text.substring(pos + 2, end - 2);
|
33417 | const match = commentText.match(parseSpanComment);
|
33418 | if (match === null) {
|
33419 | return null;
|
33420 | }
|
33421 | return new AbsoluteSourceSpan(+match[1], +match[2]);
|
33422 | }) || null;
|
33423 | }
|
33424 | /** Used to identify what type the comment is. */
|
33425 | var CommentTriviaType;
|
33426 | (function (CommentTriviaType) {
|
33427 | CommentTriviaType["DIAGNOSTIC"] = "D";
|
33428 | CommentTriviaType["EXPRESSION_TYPE_IDENTIFIER"] = "T";
|
33429 | })(CommentTriviaType || (CommentTriviaType = {}));
|
33430 | /** Identifies what the TCB expression is for (for example, a directive declaration). */
|
33431 | var ExpressionIdentifier;
|
33432 | (function (ExpressionIdentifier) {
|
33433 | ExpressionIdentifier["DIRECTIVE"] = "DIR";
|
33434 | ExpressionIdentifier["COMPONENT_COMPLETION"] = "COMPCOMP";
|
33435 | ExpressionIdentifier["EVENT_PARAMETER"] = "EP";
|
33436 | })(ExpressionIdentifier || (ExpressionIdentifier = {}));
|
33437 | /** Tags the node with the given expression identifier. */
|
33438 | function addExpressionIdentifier(node, identifier) {
|
33439 | ts$1.addSyntheticTrailingComment(node, ts$1.SyntaxKind.MultiLineCommentTrivia, `${CommentTriviaType.EXPRESSION_TYPE_IDENTIFIER}:${identifier}`,
|
33440 | /* hasTrailingNewLine */ false);
|
33441 | }
|
33442 | const IGNORE_FOR_DIAGNOSTICS_MARKER = `${CommentTriviaType.DIAGNOSTIC}:ignore`;
|
33443 | /**
|
33444 | * Tag the `ts.Node` with an indication that any errors arising from the evaluation of the node
|
33445 | * should be ignored.
|
33446 | */
|
33447 | function markIgnoreDiagnostics(node) {
|
33448 | ts$1.addSyntheticTrailingComment(node, ts$1.SyntaxKind.MultiLineCommentTrivia, IGNORE_FOR_DIAGNOSTICS_MARKER,
|
33449 | /* hasTrailingNewLine */ false);
|
33450 | }
|
33451 | /** Returns true if the node has a marker that indicates diagnostics errors should be ignored. */
|
33452 | function hasIgnoreForDiagnosticsMarker(node, sourceFile) {
|
33453 | return ts$1.forEachTrailingCommentRange(sourceFile.text, node.getEnd(), (pos, end, kind) => {
|
33454 | if (kind !== ts$1.SyntaxKind.MultiLineCommentTrivia) {
|
33455 | return null;
|
33456 | }
|
33457 | const commentText = sourceFile.text.substring(pos + 2, end - 2);
|
33458 | return commentText === IGNORE_FOR_DIAGNOSTICS_MARKER;
|
33459 | }) === true;
|
33460 | }
|
33461 | function makeRecursiveVisitor(visitor) {
|
33462 | function recursiveVisitor(node) {
|
33463 | const res = visitor(node);
|
33464 | return res !== null ? res : node.forEachChild(recursiveVisitor);
|
33465 | }
|
33466 | return recursiveVisitor;
|
33467 | }
|
33468 | function getSpanFromOptions(opts) {
|
33469 | let withSpan = null;
|
33470 | if (opts.withSpan !== undefined) {
|
33471 | if (opts.withSpan instanceof AbsoluteSourceSpan) {
|
33472 | withSpan = opts.withSpan;
|
33473 | }
|
33474 | else {
|
33475 | withSpan = { start: opts.withSpan.start.offset, end: opts.withSpan.end.offset };
|
33476 | }
|
33477 | }
|
33478 | return withSpan;
|
33479 | }
|
33480 | /**
|
33481 | * Given a `ts.Node` with finds the first node whose matching the criteria specified
|
33482 | * by the `FindOptions`.
|
33483 | *
|
33484 | * Returns `null` when no `ts.Node` matches the given conditions.
|
33485 | */
|
33486 | function findFirstMatchingNode(tcb, opts) {
|
33487 | var _a;
|
33488 | const withSpan = getSpanFromOptions(opts);
|
33489 | const withExpressionIdentifier = opts.withExpressionIdentifier;
|
33490 | const sf = tcb.getSourceFile();
|
33491 | const visitor = makeRecursiveVisitor(node => {
|
33492 | if (!opts.filter(node)) {
|
33493 | return null;
|
33494 | }
|
33495 | if (withSpan !== null) {
|
33496 | const comment = readSpanComment(node, sf);
|
33497 | if (comment === null || withSpan.start !== comment.start || withSpan.end !== comment.end) {
|
33498 | return null;
|
33499 | }
|
33500 | }
|
33501 | if (withExpressionIdentifier !== undefined &&
|
33502 | !hasExpressionIdentifier(sf, node, withExpressionIdentifier)) {
|
33503 | return null;
|
33504 | }
|
33505 | return node;
|
33506 | });
|
33507 | return (_a = tcb.forEachChild(visitor)) !== null && _a !== void 0 ? _a : null;
|
33508 | }
|
33509 | /**
|
33510 | * Given a `ts.Node` with source span comments, finds the first node whose source span comment
|
33511 | * matches the given `sourceSpan`. Additionally, the `filter` function allows matching only
|
33512 | * `ts.Nodes` of a given type, which provides the ability to select only matches of a given type
|
33513 | * when there may be more than one.
|
33514 | *
|
33515 | * Returns `null` when no `ts.Node` matches the given conditions.
|
33516 | */
|
33517 | function findAllMatchingNodes(tcb, opts) {
|
33518 | const withSpan = getSpanFromOptions(opts);
|
33519 | const withExpressionIdentifier = opts.withExpressionIdentifier;
|
33520 | const results = [];
|
33521 | const stack = [tcb];
|
33522 | const sf = tcb.getSourceFile();
|
33523 | while (stack.length > 0) {
|
33524 | const node = stack.pop();
|
33525 | if (!opts.filter(node)) {
|
33526 | stack.push(...node.getChildren());
|
33527 | continue;
|
33528 | }
|
33529 | if (withSpan !== null) {
|
33530 | const comment = readSpanComment(node, sf);
|
33531 | if (comment === null || withSpan.start !== comment.start || withSpan.end !== comment.end) {
|
33532 | stack.push(...node.getChildren());
|
33533 | continue;
|
33534 | }
|
33535 | }
|
33536 | if (withExpressionIdentifier !== undefined &&
|
33537 | !hasExpressionIdentifier(sf, node, withExpressionIdentifier)) {
|
33538 | continue;
|
33539 | }
|
33540 | results.push(node);
|
33541 | }
|
33542 | return results;
|
33543 | }
|
33544 | function hasExpressionIdentifier(sourceFile, node, identifier) {
|
33545 | return ts$1.forEachTrailingCommentRange(sourceFile.text, node.getEnd(), (pos, end, kind) => {
|
33546 | if (kind !== ts$1.SyntaxKind.MultiLineCommentTrivia) {
|
33547 | return false;
|
33548 | }
|
33549 | const commentText = sourceFile.text.substring(pos + 2, end - 2);
|
33550 | return commentText === `${CommentTriviaType.EXPRESSION_TYPE_IDENTIFIER}:${identifier}`;
|
33551 | }) || false;
|
33552 | }
|
33553 |
|
33554 | /**
|
33555 | * @license
|
33556 | * Copyright Google LLC All Rights Reserved.
|
33557 | *
|
33558 | * Use of this source code is governed by an MIT-style license that can be
|
33559 | * found in the LICENSE file at https://angular.io/license
|
33560 | */
|
33561 | /**
|
33562 | * Powers autocompletion for a specific component.
|
33563 | *
|
33564 | * Internally caches autocompletion results, and must be discarded if the component template or
|
33565 | * surrounding TS program have changed.
|
33566 | */
|
33567 | class CompletionEngine {
|
33568 | constructor(tcb, data, shimPath) {
|
33569 | this.tcb = tcb;
|
33570 | this.data = data;
|
33571 | this.shimPath = shimPath;
|
33572 | /**
|
33573 | * Cache of `GlobalCompletion`s for various levels of the template, including the root template
|
33574 | * (`null`).
|
33575 | */
|
33576 | this.globalCompletionCache = new Map();
|
33577 | this.expressionCompletionCache = new Map();
|
33578 | }
|
33579 | /**
|
33580 | * Get global completions within the given template context - either a `TmplAstTemplate` embedded
|
33581 | * view, or `null` for the root template context.
|
33582 | */
|
33583 | getGlobalCompletions(context) {
|
33584 | if (this.globalCompletionCache.has(context)) {
|
33585 | return this.globalCompletionCache.get(context);
|
33586 | }
|
33587 | // Find the component completion expression within the TCB. This looks like: `ctx. /* ... */;`
|
33588 | const globalRead = findFirstMatchingNode(this.tcb, {
|
33589 | filter: ts$1.isPropertyAccessExpression,
|
33590 | withExpressionIdentifier: ExpressionIdentifier.COMPONENT_COMPLETION
|
33591 | });
|
33592 | if (globalRead === null) {
|
33593 | return null;
|
33594 | }
|
33595 | const completion = {
|
33596 | componentContext: {
|
33597 | shimPath: this.shimPath,
|
33598 | // `globalRead.name` is an empty `ts.Identifier`, so its start position immediately follows
|
33599 | // the `.` in `ctx.`. TS autocompletion APIs can then be used to access completion results
|
33600 | // for the component context.
|
33601 | positionInShimFile: globalRead.name.getStart(),
|
33602 | },
|
33603 | templateContext: new Map(),
|
33604 | };
|
33605 | // The bound template already has details about the references and variables in scope in the
|
33606 | // `context` template - they just need to be converted to `Completion`s.
|
33607 | for (const node of this.data.boundTarget.getEntitiesInTemplateScope(context)) {
|
33608 | if (node instanceof Reference) {
|
33609 | completion.templateContext.set(node.name, {
|
33610 | kind: CompletionKind.Reference,
|
33611 | node,
|
33612 | });
|
33613 | }
|
33614 | else {
|
33615 | completion.templateContext.set(node.name, {
|
33616 | kind: CompletionKind.Variable,
|
33617 | node,
|
33618 | });
|
33619 | }
|
33620 | }
|
33621 | this.globalCompletionCache.set(context, completion);
|
33622 | return completion;
|
33623 | }
|
33624 | getExpressionCompletionLocation(expr) {
|
33625 | if (this.expressionCompletionCache.has(expr)) {
|
33626 | return this.expressionCompletionCache.get(expr);
|
33627 | }
|
33628 | // Completion works inside property reads and method calls.
|
33629 | let tsExpr = null;
|
33630 | if (expr instanceof PropertyRead || expr instanceof MethodCall ||
|
33631 | expr instanceof PropertyWrite) {
|
33632 | // Non-safe navigation operations are trivial: `foo.bar` or `foo.bar()`
|
33633 | tsExpr = findFirstMatchingNode(this.tcb, {
|
33634 | filter: ts$1.isPropertyAccessExpression,
|
33635 | withSpan: expr.nameSpan,
|
33636 | });
|
33637 | }
|
33638 | else if (expr instanceof SafePropertyRead || expr instanceof SafeMethodCall) {
|
33639 | // Safe navigation operations are a little more complex, and involve a ternary. Completion
|
33640 | // happens in the "true" case of the ternary.
|
33641 | const ternaryExpr = findFirstMatchingNode(this.tcb, {
|
33642 | filter: ts$1.isParenthesizedExpression,
|
33643 | withSpan: expr.sourceSpan,
|
33644 | });
|
33645 | if (ternaryExpr === null || !ts$1.isConditionalExpression(ternaryExpr.expression)) {
|
33646 | return null;
|
33647 | }
|
33648 | const whenTrue = ternaryExpr.expression.whenTrue;
|
33649 | if (expr instanceof SafePropertyRead && ts$1.isPropertyAccessExpression(whenTrue)) {
|
33650 | tsExpr = whenTrue;
|
33651 | }
|
33652 | else if (expr instanceof SafeMethodCall && ts$1.isCallExpression(whenTrue) &&
|
33653 | ts$1.isPropertyAccessExpression(whenTrue.expression)) {
|
33654 | tsExpr = whenTrue.expression;
|
33655 | }
|
33656 | }
|
33657 | if (tsExpr === null) {
|
33658 | return null;
|
33659 | }
|
33660 | const res = {
|
33661 | shimPath: this.shimPath,
|
33662 | positionInShimFile: tsExpr.name.getEnd(),
|
33663 | };
|
33664 | this.expressionCompletionCache.set(expr, res);
|
33665 | return res;
|
33666 | }
|
33667 | }
|
33668 |
|
33669 | /**
|
33670 | * @license
|
33671 | * Copyright Google LLC All Rights Reserved.
|
33672 | *
|
33673 | * Use of this source code is governed by an MIT-style license that can be
|
33674 | * found in the LICENSE file at https://angular.io/license
|
33675 | */
|
33676 | /**
|
33677 | * Constructs a `ts.Diagnostic` for a given `ParseSourceSpan` within a template.
|
33678 | */
|
33679 | function makeTemplateDiagnostic(templateId, mapping, span, category, code, messageText, relatedMessage) {
|
33680 | if (mapping.type === 'direct') {
|
33681 | let relatedInformation = undefined;
|
33682 | if (relatedMessage !== undefined) {
|
33683 | relatedInformation = [{
|
33684 | category: ts$1.DiagnosticCategory.Message,
|
33685 | code: 0,
|
33686 | file: mapping.node.getSourceFile(),
|
33687 | start: relatedMessage.span.start.offset,
|
33688 | length: relatedMessage.span.end.offset - relatedMessage.span.start.offset,
|
33689 | messageText: relatedMessage.text,
|
33690 | }];
|
33691 | }
|
33692 | // For direct mappings, the error is shown inline as ngtsc was able to pinpoint a string
|
33693 | // constant within the `@Component` decorator for the template. This allows us to map the error
|
33694 | // directly into the bytes of the source file.
|
33695 | return {
|
33696 | source: 'ngtsc',
|
33697 | code,
|
33698 | category,
|
33699 | messageText,
|
33700 | file: mapping.node.getSourceFile(),
|
33701 | componentFile: mapping.node.getSourceFile(),
|
33702 | templateId,
|
33703 | start: span.start.offset,
|
33704 | length: span.end.offset - span.start.offset,
|
33705 | relatedInformation,
|
33706 | };
|
33707 | }
|
33708 | else if (mapping.type === 'indirect' || mapping.type === 'external') {
|
33709 | // For indirect mappings (template was declared inline, but ngtsc couldn't map it directly
|
33710 | // to a string constant in the decorator), the component's file name is given with a suffix
|
33711 | // indicating it's not the TS file being displayed, but a template.
|
33712 | // For external temoplates, the HTML filename is used.
|
33713 | const componentSf = mapping.componentClass.getSourceFile();
|
33714 | const componentName = mapping.componentClass.name.text;
|
33715 | // TODO(alxhub): remove cast when TS in g3 supports this narrowing.
|
33716 | const fileName = mapping.type === 'indirect' ?
|
33717 | `${componentSf.fileName} (${componentName} template)` :
|
33718 | mapping.templateUrl;
|
33719 | // TODO(alxhub): investigate creating a fake `ts.SourceFile` here instead of invoking the TS
|
33720 | // parser against the template (HTML is just really syntactically invalid TypeScript code ;).
|
33721 | // Also investigate caching the file to avoid running the parser multiple times.
|
33722 | const sf = ts$1.createSourceFile(fileName, mapping.template, ts$1.ScriptTarget.Latest, false, ts$1.ScriptKind.JSX);
|
33723 | let relatedInformation = [];
|
33724 | if (relatedMessage !== undefined) {
|
33725 | relatedInformation.push({
|
33726 | category: ts$1.DiagnosticCategory.Message,
|
33727 | code: 0,
|
33728 | file: sf,
|
33729 | start: relatedMessage.span.start.offset,
|
33730 | length: relatedMessage.span.end.offset - relatedMessage.span.start.offset,
|
33731 | messageText: relatedMessage.text,
|
33732 | });
|
33733 | }
|
33734 | relatedInformation.push({
|
33735 | category: ts$1.DiagnosticCategory.Message,
|
33736 | code: 0,
|
33737 | file: componentSf,
|
33738 | // mapping.node represents either the 'template' or 'templateUrl' expression. getStart()
|
33739 | // and getEnd() are used because they don't include surrounding whitespace.
|
33740 | start: mapping.node.getStart(),
|
33741 | length: mapping.node.getEnd() - mapping.node.getStart(),
|
33742 | messageText: `Error occurs in the template of component ${componentName}.`,
|
33743 | });
|
33744 | return {
|
33745 | source: 'ngtsc',
|
33746 | category,
|
33747 | code,
|
33748 | messageText,
|
33749 | file: sf,
|
33750 | componentFile: componentSf,
|
33751 | templateId,
|
33752 | start: span.start.offset,
|
33753 | length: span.end.offset - span.start.offset,
|
33754 | // Show a secondary message indicating the component whose template contains the error.
|
33755 | relatedInformation,
|
33756 | };
|
33757 | }
|
33758 | else {
|
33759 | throw new Error(`Unexpected source mapping type: ${mapping.type}`);
|
33760 | }
|
33761 | }
|
33762 |
|
33763 | /**
|
33764 | * @license
|
33765 | * Copyright Google LLC All Rights Reserved.
|
33766 | *
|
33767 | * Use of this source code is governed by an MIT-style license that can be
|
33768 | * found in the LICENSE file at https://angular.io/license
|
33769 | */
|
33770 | const TEMPLATE_ID = Symbol('ngTemplateId');
|
33771 | const NEXT_TEMPLATE_ID = Symbol('ngNextTemplateId');
|
33772 | function getTemplateId(clazz) {
|
33773 | const node = clazz;
|
33774 | if (node[TEMPLATE_ID] === undefined) {
|
33775 | node[TEMPLATE_ID] = allocateTemplateId(node.getSourceFile());
|
33776 | }
|
33777 | return node[TEMPLATE_ID];
|
33778 | }
|
33779 | function allocateTemplateId(sf) {
|
33780 | if (sf[NEXT_TEMPLATE_ID] === undefined) {
|
33781 | sf[NEXT_TEMPLATE_ID] = 1;
|
33782 | }
|
33783 | return (`tcb${sf[NEXT_TEMPLATE_ID]++}`);
|
33784 | }
|
33785 |
|
33786 | /**
|
33787 | * @license
|
33788 | * Copyright Google LLC All Rights Reserved.
|
33789 | *
|
33790 | * Use of this source code is governed by an MIT-style license that can be
|
33791 | * found in the LICENSE file at https://angular.io/license
|
33792 | */
|
33793 | const REGISTRY = new DomElementSchemaRegistry();
|
33794 | const REMOVE_XHTML_REGEX = /^:xhtml:/;
|
33795 | /**
|
33796 | * Checks non-Angular elements and properties against the `DomElementSchemaRegistry`, a schema
|
33797 | * maintained by the Angular team via extraction from a browser IDL.
|
33798 | */
|
33799 | class RegistryDomSchemaChecker {
|
33800 | constructor(resolver) {
|
33801 | this.resolver = resolver;
|
33802 | this._diagnostics = [];
|
33803 | }
|
33804 | get diagnostics() {
|
33805 | return this._diagnostics;
|
33806 | }
|
33807 | checkElement(id, element, schemas) {
|
33808 | // HTML elements inside an SVG `foreignObject` are declared in the `xhtml` namespace.
|
33809 | // We need to strip it before handing it over to the registry because all HTML tag names
|
33810 | // in the registry are without a namespace.
|
33811 | const name = element.name.replace(REMOVE_XHTML_REGEX, '');
|
33812 | if (!REGISTRY.hasElement(name, schemas)) {
|
33813 | const mapping = this.resolver.getSourceMapping(id);
|
33814 | let errorMsg = `'${name}' is not a known element:\n`;
|
33815 | errorMsg +=
|
33816 | `1. If '${name}' is an Angular component, then verify that it is part of this module.\n`;
|
33817 | if (name.indexOf('-') > -1) {
|
33818 | errorMsg += `2. If '${name}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.`;
|
33819 | }
|
33820 | else {
|
33821 | errorMsg +=
|
33822 | `2. To allow any element add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component.`;
|
33823 | }
|
33824 | const diag = makeTemplateDiagnostic(id, mapping, element.startSourceSpan, ts$1.DiagnosticCategory.Error, ngErrorCode(ErrorCode.SCHEMA_INVALID_ELEMENT), errorMsg);
|
33825 | this._diagnostics.push(diag);
|
33826 | }
|
33827 | }
|
33828 | checkProperty(id, element, name, span, schemas) {
|
33829 | if (!REGISTRY.hasProperty(element.name, name, schemas)) {
|
33830 | const mapping = this.resolver.getSourceMapping(id);
|
33831 | let errorMsg = `Can't bind to '${name}' since it isn't a known property of '${element.name}'.`;
|
33832 | if (element.name.startsWith('ng-')) {
|
33833 | errorMsg +=
|
33834 | `\n1. If '${name}' is an Angular directive, then add 'CommonModule' to the '@NgModule.imports' of this component.` +
|
33835 | `\n2. To allow any property add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component.`;
|
33836 | }
|
33837 | else if (element.name.indexOf('-') > -1) {
|
33838 | errorMsg +=
|
33839 | `\n1. If '${element.name}' is an Angular component and it has '${name}' input, then verify that it is part of this module.` +
|
33840 | `\n2. If '${element
|
33841 | .name}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.` +
|
33842 | `\n3. To allow any property add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component.`;
|
33843 | }
|
33844 | const diag = makeTemplateDiagnostic(id, mapping, span, ts$1.DiagnosticCategory.Error, ngErrorCode(ErrorCode.SCHEMA_INVALID_ATTRIBUTE), errorMsg);
|
33845 | this._diagnostics.push(diag);
|
33846 | }
|
33847 | }
|
33848 | }
|
33849 |
|
33850 | /**
|
33851 | * @license
|
33852 | * Copyright Google LLC All Rights Reserved.
|
33853 | *
|
33854 | * Use of this source code is governed by an MIT-style license that can be
|
33855 | * found in the LICENSE file at https://angular.io/license
|
33856 | */
|
33857 | /**
|
33858 | * A `Set` of `ts.SyntaxKind`s of `ts.Expression` which are safe to wrap in a `ts.AsExpression`
|
33859 | * without needing to be wrapped in parentheses.
|
33860 | *
|
33861 | * For example, `foo.bar()` is a `ts.CallExpression`, and can be safely cast to `any` with
|
33862 | * `foo.bar() as any`. however, `foo !== bar` is a `ts.BinaryExpression`, and attempting to cast
|
33863 | * without the parentheses yields the expression `foo !== bar as any`. This is semantically
|
33864 | * equivalent to `foo !== (bar as any)`, which is not what was intended. Thus,
|
33865 | * `ts.BinaryExpression`s need to be wrapped in parentheses before casting.
|
33866 | */
|
33867 | //
|
33868 | const SAFE_TO_CAST_WITHOUT_PARENS = new Set([
|
33869 | // Expressions which are already parenthesized can be cast without further wrapping.
|
33870 | ts$1.SyntaxKind.ParenthesizedExpression,
|
33871 | // Expressions which form a single lexical unit leave no room for precedence issues with the cast.
|
33872 | ts$1.SyntaxKind.Identifier,
|
33873 | ts$1.SyntaxKind.CallExpression,
|
33874 | ts$1.SyntaxKind.NonNullExpression,
|
33875 | ts$1.SyntaxKind.ElementAccessExpression,
|
33876 | ts$1.SyntaxKind.PropertyAccessExpression,
|
33877 | ts$1.SyntaxKind.ArrayLiteralExpression,
|
33878 | ts$1.SyntaxKind.ObjectLiteralExpression,
|
33879 | // The same goes for various literals.
|
33880 | ts$1.SyntaxKind.StringLiteral,
|
33881 | ts$1.SyntaxKind.NumericLiteral,
|
33882 | ts$1.SyntaxKind.TrueKeyword,
|
33883 | ts$1.SyntaxKind.FalseKeyword,
|
33884 | ts$1.SyntaxKind.NullKeyword,
|
33885 | ts$1.SyntaxKind.UndefinedKeyword,
|
33886 | ]);
|
33887 | function tsCastToAny(expr) {
|
33888 | // Wrap `expr` in parentheses if needed (see `SAFE_TO_CAST_WITHOUT_PARENS` above).
|
33889 | if (!SAFE_TO_CAST_WITHOUT_PARENS.has(expr.kind)) {
|
33890 | expr = ts$1.createParen(expr);
|
33891 | }
|
33892 | // The outer expression is always wrapped in parentheses.
|
33893 | return ts$1.createParen(ts$1.createAsExpression(expr, ts$1.createKeywordTypeNode(ts$1.SyntaxKind.AnyKeyword)));
|
33894 | }
|
33895 | /**
|
33896 | * Create an expression which instantiates an element by its HTML tagName.
|
33897 | *
|
33898 | * Thanks to narrowing of `document.createElement()`, this expression will have its type inferred
|
33899 | * based on the tag name, including for custom elements that have appropriate .d.ts definitions.
|
33900 | */
|
33901 | function tsCreateElement(tagName) {
|
33902 | const createElement = ts$1.createPropertyAccess(
|
33903 | /* expression */ ts$1.createIdentifier('document'), 'createElement');
|
33904 | return ts$1.createCall(
|
33905 | /* expression */ createElement,
|
33906 | /* typeArguments */ undefined,
|
33907 | /* argumentsArray */ [ts$1.createLiteral(tagName)]);
|
33908 | }
|
33909 | /**
|
33910 | * Create a `ts.VariableStatement` which declares a variable without explicit initialization.
|
33911 | *
|
33912 | * The initializer `null!` is used to bypass strict variable initialization checks.
|
33913 | *
|
33914 | * Unlike with `tsCreateVariable`, the type of the variable is explicitly specified.
|
33915 | */
|
33916 | function tsDeclareVariable(id, type) {
|
33917 | const decl = ts$1.createVariableDeclaration(
|
33918 | /* name */ id,
|
33919 | /* type */ type,
|
33920 | /* initializer */ ts$1.createNonNullExpression(ts$1.createNull()));
|
33921 | return ts$1.createVariableStatement(
|
33922 | /* modifiers */ undefined,
|
33923 | /* declarationList */ [decl]);
|
33924 | }
|
33925 | /**
|
33926 | * Creates a `ts.TypeQueryNode` for a coerced input.
|
33927 | *
|
33928 | * For example: `typeof MatInput.ngAcceptInputType_value`, where MatInput is `typeName` and `value`
|
33929 | * is the `coercedInputName`.
|
33930 | *
|
33931 | * @param typeName The `EntityName` of the Directive where the static coerced input is defined.
|
33932 | * @param coercedInputName The field name of the coerced input.
|
33933 | */
|
33934 | function tsCreateTypeQueryForCoercedInput(typeName, coercedInputName) {
|
33935 | return ts$1.createTypeQueryNode(ts$1.createQualifiedName(typeName, `ngAcceptInputType_${coercedInputName}`));
|
33936 | }
|
33937 | /**
|
33938 | * Create a `ts.VariableStatement` that initializes a variable with a given expression.
|
33939 | *
|
33940 | * Unlike with `tsDeclareVariable`, the type of the variable is inferred from the initializer
|
33941 | * expression.
|
33942 | */
|
33943 | function tsCreateVariable(id, initializer) {
|
33944 | const decl = ts$1.createVariableDeclaration(
|
33945 | /* name */ id,
|
33946 | /* type */ undefined,
|
33947 | /* initializer */ initializer);
|
33948 | return ts$1.createVariableStatement(
|
33949 | /* modifiers */ undefined,
|
33950 | /* declarationList */ [decl]);
|
33951 | }
|
33952 | /**
|
33953 | * Construct a `ts.CallExpression` that calls a method on a receiver.
|
33954 | */
|
33955 | function tsCallMethod(receiver, methodName, args = []) {
|
33956 | const methodAccess = ts$1.createPropertyAccess(receiver, methodName);
|
33957 | return ts$1.createCall(
|
33958 | /* expression */ methodAccess,
|
33959 | /* typeArguments */ undefined,
|
33960 | /* argumentsArray */ args);
|
33961 | }
|
33962 | function checkIfClassIsExported(node) {
|
33963 | // A class is exported if one of two conditions is met:
|
33964 | // 1) it has the 'export' modifier.
|
33965 | // 2) it's declared at the top level, and there is an export statement for the class.
|
33966 | if (node.modifiers !== undefined &&
|
33967 | node.modifiers.some(mod => mod.kind === ts$1.SyntaxKind.ExportKeyword)) {
|
33968 | // Condition 1 is true, the class has an 'export' keyword attached.
|
33969 | return true;
|
33970 | }
|
33971 | else if (node.parent !== undefined && ts$1.isSourceFile(node.parent) &&
|
33972 | checkIfFileHasExport(node.parent, node.name.text)) {
|
33973 | // Condition 2 is true, the class is exported via an 'export {}' statement.
|
33974 | return true;
|
33975 | }
|
33976 | return false;
|
33977 | }
|
33978 | function checkIfFileHasExport(sf, name) {
|
33979 | for (const stmt of sf.statements) {
|
33980 | if (ts$1.isExportDeclaration(stmt) && stmt.exportClause !== undefined &&
|
33981 | ts$1.isNamedExports(stmt.exportClause)) {
|
33982 | for (const element of stmt.exportClause.elements) {
|
33983 | if (element.propertyName === undefined && element.name.text === name) {
|
33984 | // The named declaration is directly exported.
|
33985 | return true;
|
33986 | }
|
33987 | else if (element.propertyName !== undefined && element.propertyName.text == name) {
|
33988 | // The named declaration is exported via an alias.
|
33989 | return true;
|
33990 | }
|
33991 | }
|
33992 | }
|
33993 | }
|
33994 | return false;
|
33995 | }
|
33996 | function checkIfGenericTypesAreUnbound(node) {
|
33997 | if (node.typeParameters === undefined) {
|
33998 | return true;
|
33999 | }
|
34000 | return node.typeParameters.every(param => param.constraint === undefined);
|
34001 | }
|
34002 | function isAccessExpression(node) {
|
34003 | return ts$1.isPropertyAccessExpression(node) || ts$1.isElementAccessExpression(node);
|
34004 | }
|
34005 |
|
34006 | /**
|
34007 | * @license
|
34008 | * Copyright Google LLC All Rights Reserved.
|
34009 | *
|
34010 | * Use of this source code is governed by an MIT-style license that can be
|
34011 | * found in the LICENSE file at https://angular.io/license
|
34012 | */
|
34013 | /**
|
34014 | * Determines whether the provided type can be emitted, which means that it can be safely emitted
|
34015 | * into a different location.
|
34016 | *
|
34017 | * If this function returns true, a `TypeEmitter` should be able to succeed. Vice versa, if this
|
34018 | * function returns false, then using the `TypeEmitter` should not be attempted as it is known to
|
34019 | * fail.
|
34020 | */
|
34021 | function canEmitType(type, resolver) {
|
34022 | return canEmitTypeWorker(type);
|
34023 | function canEmitTypeWorker(type) {
|
34024 | return visitTypeNode(type, {
|
34025 | visitTypeReferenceNode: type => canEmitTypeReference(type),
|
34026 | visitArrayTypeNode: type => canEmitTypeWorker(type.elementType),
|
34027 | visitKeywordType: () => true,
|
34028 | visitLiteralType: () => true,
|
34029 | visitOtherType: () => false,
|
34030 | });
|
34031 | }
|
34032 | function canEmitTypeReference(type) {
|
34033 | const reference = resolver(type);
|
34034 | // If the type could not be resolved, it can not be emitted.
|
34035 | if (reference === null) {
|
34036 | return false;
|
34037 | }
|
34038 | // If the type is a reference without a owning module, consider the type not to be eligible for
|
34039 | // emitting.
|
34040 | if (reference instanceof Reference$1 && !reference.hasOwningModuleGuess) {
|
34041 | return false;
|
34042 | }
|
34043 | // The type can be emitted if either it does not have any type arguments, or all of them can be
|
34044 | // emitted.
|
34045 | return type.typeArguments === undefined || type.typeArguments.every(canEmitTypeWorker);
|
34046 | }
|
34047 | }
|
34048 | /**
|
34049 | * Given a `ts.TypeNode`, this class derives an equivalent `ts.TypeNode` that has been emitted into
|
34050 | * a different context.
|
34051 | *
|
34052 | * For example, consider the following code:
|
34053 | *
|
34054 | * ```
|
34055 | * import {NgIterable} from '@angular/core';
|
34056 | *
|
34057 | * class NgForOf<T, U extends NgIterable<T>> {}
|
34058 | * ```
|
34059 | *
|
34060 | * Here, the generic type parameters `T` and `U` can be emitted into a different context, as the
|
34061 | * type reference to `NgIterable` originates from an absolute module import so that it can be
|
34062 | * emitted anywhere, using that same module import. The process of emitting translates the
|
34063 | * `NgIterable` type reference to a type reference that is valid in the context in which it is
|
34064 | * emitted, for example:
|
34065 | *
|
34066 | * ```
|
34067 | * import * as i0 from '@angular/core';
|
34068 | * import * as i1 from '@angular/common';
|
34069 | *
|
34070 | * const _ctor1: <T, U extends i0.NgIterable<T>>(o: Pick<i1.NgForOf<T, U>, 'ngForOf'>):
|
34071 | * i1.NgForOf<T, U>;
|
34072 | * ```
|
34073 | *
|
34074 | * Notice how the type reference for `NgIterable` has been translated into a qualified name,
|
34075 | * referring to the namespace import that was created.
|
34076 | */
|
34077 | class TypeEmitter {
|
34078 | constructor(resolver, emitReference) {
|
34079 | this.resolver = resolver;
|
34080 | this.emitReference = emitReference;
|
34081 | }
|
34082 | emitType(type) {
|
34083 | return visitTypeNode(type, {
|
34084 | visitTypeReferenceNode: type => this.emitTypeReference(type),
|
34085 | visitArrayTypeNode: type => ts$1.updateArrayTypeNode(type, this.emitType(type.elementType)),
|
34086 | visitKeywordType: type => type,
|
34087 | visitLiteralType: type => type,
|
34088 | visitOtherType: () => {
|
34089 | throw new Error('Unable to emit a complex type');
|
34090 | },
|
34091 | });
|
34092 | }
|
34093 | emitTypeReference(type) {
|
34094 | // Determine the reference that the type corresponds with.
|
34095 | const reference = this.resolver(type);
|
34096 | if (reference === null) {
|
34097 | throw new Error('Unable to emit an unresolved reference');
|
34098 | }
|
34099 | // Emit the type arguments, if any.
|
34100 | let typeArguments = undefined;
|
34101 | if (type.typeArguments !== undefined) {
|
34102 | typeArguments = ts$1.createNodeArray(type.typeArguments.map(typeArg => this.emitType(typeArg)));
|
34103 | }
|
34104 | // Emit the type name.
|
34105 | let typeName = type.typeName;
|
34106 | if (reference instanceof Reference$1) {
|
34107 | if (!reference.hasOwningModuleGuess) {
|
34108 | throw new Error('A type reference to emit must be imported from an absolute module');
|
34109 | }
|
34110 | const emittedType = this.emitReference(reference);
|
34111 | if (!ts$1.isTypeReferenceNode(emittedType)) {
|
34112 | throw new Error(`Expected TypeReferenceNode for emitted reference, got ${ts$1.SyntaxKind[emittedType.kind]}`);
|
34113 | }
|
34114 | typeName = emittedType.typeName;
|
34115 | }
|
34116 | return ts$1.updateTypeReferenceNode(type, typeName, typeArguments);
|
34117 | }
|
34118 | }
|
34119 | function visitTypeNode(type, visitor) {
|
34120 | if (ts$1.isTypeReferenceNode(type)) {
|
34121 | return visitor.visitTypeReferenceNode(type);
|
34122 | }
|
34123 | else if (ts$1.isArrayTypeNode(type)) {
|
34124 | return visitor.visitArrayTypeNode(type);
|
34125 | }
|
34126 | else if (ts$1.isLiteralTypeNode(type)) {
|
34127 | return visitor.visitLiteralType(type);
|
34128 | }
|
34129 | switch (type.kind) {
|
34130 | case ts$1.SyntaxKind.AnyKeyword:
|
34131 | case ts$1.SyntaxKind.UnknownKeyword:
|
34132 | case ts$1.SyntaxKind.NumberKeyword:
|
34133 | case ts$1.SyntaxKind.ObjectKeyword:
|
34134 | case ts$1.SyntaxKind.BooleanKeyword:
|
34135 | case ts$1.SyntaxKind.StringKeyword:
|
34136 | case ts$1.SyntaxKind.UndefinedKeyword:
|
34137 | case ts$1.SyntaxKind.NullKeyword:
|
34138 | return visitor.visitKeywordType(type);
|
34139 | default:
|
34140 | return visitor.visitOtherType(type);
|
34141 | }
|
34142 | }
|
34143 |
|
34144 | /**
|
34145 | * @license
|
34146 | * Copyright Google LLC All Rights Reserved.
|
34147 | *
|
34148 | * Use of this source code is governed by an MIT-style license that can be
|
34149 | * found in the LICENSE file at https://angular.io/license
|
34150 | */
|
34151 | /**
|
34152 | * See `TypeEmitter` for more information on the emitting process.
|
34153 | */
|
34154 | class TypeParameterEmitter {
|
34155 | constructor(typeParameters, reflector) {
|
34156 | this.typeParameters = typeParameters;
|
34157 | this.reflector = reflector;
|
34158 | }
|
34159 | /**
|
34160 | * Determines whether the type parameters can be emitted. If this returns true, then a call to
|
34161 | * `emit` is known to succeed. Vice versa, if false is returned then `emit` should not be
|
34162 | * called, as it would fail.
|
34163 | */
|
34164 | canEmit() {
|
34165 | if (this.typeParameters === undefined) {
|
34166 | return true;
|
34167 | }
|
34168 | return this.typeParameters.every(typeParam => {
|
34169 | if (typeParam.constraint === undefined) {
|
34170 | return true;
|
34171 | }
|
34172 | return canEmitType(typeParam.constraint, type => this.resolveTypeReference(type));
|
34173 | });
|
34174 | }
|
34175 | /**
|
34176 | * Emits the type parameters using the provided emitter function for `Reference`s.
|
34177 | */
|
34178 | emit(emitReference) {
|
34179 | if (this.typeParameters === undefined) {
|
34180 | return undefined;
|
34181 | }
|
34182 | const emitter = new TypeEmitter(type => this.resolveTypeReference(type), emitReference);
|
34183 | return this.typeParameters.map(typeParam => {
|
34184 | const constraint = typeParam.constraint !== undefined ? emitter.emitType(typeParam.constraint) : undefined;
|
34185 | return ts$1.updateTypeParameterDeclaration(
|
34186 | /* node */ typeParam,
|
34187 | /* name */ typeParam.name,
|
34188 | /* constraint */ constraint,
|
34189 | /* defaultType */ typeParam.default);
|
34190 | });
|
34191 | }
|
34192 | resolveTypeReference(type) {
|
34193 | const target = ts$1.isIdentifier(type.typeName) ? type.typeName : type.typeName.right;
|
34194 | const declaration = this.reflector.getDeclarationOfIdentifier(target);
|
34195 | // If no declaration could be resolved or does not have a `ts.Declaration`, the type cannot be
|
34196 | // resolved.
|
34197 | if (declaration === null || declaration.node === null) {
|
34198 | return null;
|
34199 | }
|
34200 | // If the declaration corresponds with a local type parameter, the type reference can be used
|
34201 | // as is.
|
34202 | if (this.isLocalTypeParameter(declaration.node)) {
|
34203 | return type;
|
34204 | }
|
34205 | let owningModule = null;
|
34206 | if (declaration.viaModule !== null) {
|
34207 | owningModule = {
|
34208 | specifier: declaration.viaModule,
|
34209 | resolutionContext: type.getSourceFile().fileName,
|
34210 | };
|
34211 | }
|
34212 | return new Reference$1(declaration.node, owningModule);
|
34213 | }
|
34214 | isLocalTypeParameter(decl) {
|
34215 | // Checking for local type parameters only occurs during resolution of type parameters, so it is
|
34216 | // guaranteed that type parameters are present.
|
34217 | return this.typeParameters.some(param => param === decl);
|
34218 | }
|
34219 | }
|
34220 |
|
34221 | /**
|
34222 | * @license
|
34223 | * Copyright Google LLC All Rights Reserved.
|
34224 | *
|
34225 | * Use of this source code is governed by an MIT-style license that can be
|
34226 | * found in the LICENSE file at https://angular.io/license
|
34227 | */
|
34228 | function generateTypeCtorDeclarationFn(node, meta, nodeTypeRef, typeParams, reflector) {
|
34229 | if (requiresInlineTypeCtor(node, reflector)) {
|
34230 | throw new Error(`${node.name.text} requires an inline type constructor`);
|
34231 | }
|
34232 | const rawTypeArgs = typeParams !== undefined ? generateGenericArgs(typeParams) : undefined;
|
34233 | const rawType = ts$1.createTypeReferenceNode(nodeTypeRef, rawTypeArgs);
|
34234 | const initParam = constructTypeCtorParameter(node, meta, rawType);
|
34235 | const typeParameters = typeParametersWithDefaultTypes(typeParams);
|
34236 | if (meta.body) {
|
34237 | const fnType = ts$1.createFunctionTypeNode(
|
34238 | /* typeParameters */ typeParameters,
|
34239 | /* parameters */ [initParam],
|
34240 | /* type */ rawType);
|
34241 | const decl = ts$1.createVariableDeclaration(
|
34242 | /* name */ meta.fnName,
|
34243 | /* type */ fnType,
|
34244 | /* body */ ts$1.createNonNullExpression(ts$1.createNull()));
|
34245 | const declList = ts$1.createVariableDeclarationList([decl], ts$1.NodeFlags.Const);
|
34246 | return ts$1.createVariableStatement(
|
34247 | /* modifiers */ undefined,
|
34248 | /* declarationList */ declList);
|
34249 | }
|
34250 | else {
|
34251 | return ts$1.createFunctionDeclaration(
|
34252 | /* decorators */ undefined,
|
34253 | /* modifiers */ [ts$1.createModifier(ts$1.SyntaxKind.DeclareKeyword)],
|
34254 | /* asteriskToken */ undefined,
|
34255 | /* name */ meta.fnName,
|
34256 | /* typeParameters */ typeParameters,
|
34257 | /* parameters */ [initParam],
|
34258 | /* type */ rawType,
|
34259 | /* body */ undefined);
|
34260 | }
|
34261 | }
|
34262 | /**
|
34263 | * Generate an inline type constructor for the given class and metadata.
|
34264 | *
|
34265 | * An inline type constructor is a specially shaped TypeScript static method, intended to be placed
|
34266 | * within a directive class itself, that permits type inference of any generic type parameters of
|
34267 | * the class from the types of expressions bound to inputs or outputs, and the types of elements
|
34268 | * that match queries performed by the directive. It also catches any errors in the types of these
|
34269 | * expressions. This method is never called at runtime, but is used in type-check blocks to
|
34270 | * construct directive types.
|
34271 | *
|
34272 | * An inline type constructor for NgFor looks like:
|
34273 | *
|
34274 | * static ngTypeCtor<T>(init: Pick<NgForOf<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>):
|
34275 | * NgForOf<T>;
|
34276 | *
|
34277 | * A typical constructor would be:
|
34278 | *
|
34279 | * NgForOf.ngTypeCtor(init: {
|
34280 | * ngForOf: ['foo', 'bar'],
|
34281 | * ngForTrackBy: null as any,
|
34282 | * ngForTemplate: null as any,
|
34283 | * }); // Infers a type of NgForOf<string>.
|
34284 | *
|
34285 | * Any inputs declared on the type for which no property binding is present are assigned a value of
|
34286 | * type `any`, to avoid producing any type errors for unset inputs.
|
34287 | *
|
34288 | * Inline type constructors are used when the type being created has bounded generic types which
|
34289 | * make writing a declared type constructor (via `generateTypeCtorDeclarationFn`) difficult or
|
34290 | * impossible.
|
34291 | *
|
34292 | * @param node the `ClassDeclaration<ts.ClassDeclaration>` for which a type constructor will be
|
34293 | * generated.
|
34294 | * @param meta additional metadata required to generate the type constructor.
|
34295 | * @returns a `ts.MethodDeclaration` for the type constructor.
|
34296 | */
|
34297 | function generateInlineTypeCtor(node, meta) {
|
34298 | // Build rawType, a `ts.TypeNode` of the class with its generic parameters passed through from
|
34299 | // the definition without any type bounds. For example, if the class is
|
34300 | // `FooDirective<T extends Bar>`, its rawType would be `FooDirective<T>`.
|
34301 | const rawTypeArgs = node.typeParameters !== undefined ? generateGenericArgs(node.typeParameters) : undefined;
|
34302 | const rawType = ts$1.createTypeReferenceNode(node.name, rawTypeArgs);
|
34303 | const initParam = constructTypeCtorParameter(node, meta, rawType);
|
34304 | // If this constructor is being generated into a .ts file, then it needs a fake body. The body
|
34305 | // is set to a return of `null!`. If the type constructor is being generated into a .d.ts file,
|
34306 | // it needs no body.
|
34307 | let body = undefined;
|
34308 | if (meta.body) {
|
34309 | body = ts$1.createBlock([
|
34310 | ts$1.createReturn(ts$1.createNonNullExpression(ts$1.createNull())),
|
34311 | ]);
|
34312 | }
|
34313 | // Create the type constructor method declaration.
|
34314 | return ts$1.createMethod(
|
34315 | /* decorators */ undefined,
|
34316 | /* modifiers */ [ts$1.createModifier(ts$1.SyntaxKind.StaticKeyword)],
|
34317 | /* asteriskToken */ undefined,
|
34318 | /* name */ meta.fnName,
|
34319 | /* questionToken */ undefined,
|
34320 | /* typeParameters */ typeParametersWithDefaultTypes(node.typeParameters),
|
34321 | /* parameters */ [initParam],
|
34322 | /* type */ rawType,
|
34323 | /* body */ body);
|
34324 | }
|
34325 | function constructTypeCtorParameter(node, meta, rawType) {
|
34326 | // initType is the type of 'init', the single argument to the type constructor method.
|
34327 | // If the Directive has any inputs, its initType will be:
|
34328 | //
|
34329 | // Pick<rawType, 'inputA'|'inputB'>
|
34330 | //
|
34331 | // Pick here is used to select only those fields from which the generic type parameters of the
|
34332 | // directive will be inferred.
|
34333 | //
|
34334 | // In the special case there are no inputs, initType is set to {}.
|
34335 | let initType = null;
|
34336 | const keys = meta.fields.inputs;
|
34337 | const plainKeys = [];
|
34338 | const coercedKeys = [];
|
34339 | for (const key of keys) {
|
34340 | if (!meta.coercedInputFields.has(key)) {
|
34341 | plainKeys.push(ts$1.createLiteralTypeNode(ts$1.createStringLiteral(key)));
|
34342 | }
|
34343 | else {
|
34344 | coercedKeys.push(ts$1.createPropertySignature(
|
34345 | /* modifiers */ undefined,
|
34346 | /* name */ key,
|
34347 | /* questionToken */ undefined,
|
34348 | /* type */ tsCreateTypeQueryForCoercedInput(rawType.typeName, key),
|
34349 | /* initializer */ undefined));
|
34350 | }
|
34351 | }
|
34352 | if (plainKeys.length > 0) {
|
34353 | // Construct a union of all the field names.
|
34354 | const keyTypeUnion = ts$1.createUnionTypeNode(plainKeys);
|
34355 | // Construct the Pick<rawType, keyTypeUnion>.
|
34356 | initType = ts$1.createTypeReferenceNode('Pick', [rawType, keyTypeUnion]);
|
34357 | }
|
34358 | if (coercedKeys.length > 0) {
|
34359 | const coercedLiteral = ts$1.createTypeLiteralNode(coercedKeys);
|
34360 | initType = initType !== null ? ts$1.createIntersectionTypeNode([initType, coercedLiteral]) :
|
34361 | coercedLiteral;
|
34362 | }
|
34363 | if (initType === null) {
|
34364 | // Special case - no inputs, outputs, or other fields which could influence the result type.
|
34365 | initType = ts$1.createTypeLiteralNode([]);
|
34366 | }
|
34367 | // Create the 'init' parameter itself.
|
34368 | return ts$1.createParameter(
|
34369 | /* decorators */ undefined,
|
34370 | /* modifiers */ undefined,
|
34371 | /* dotDotDotToken */ undefined,
|
34372 | /* name */ 'init',
|
34373 | /* questionToken */ undefined,
|
34374 | /* type */ initType,
|
34375 | /* initializer */ undefined);
|
34376 | }
|
34377 | function generateGenericArgs(params) {
|
34378 | return params.map(param => ts$1.createTypeReferenceNode(param.name, undefined));
|
34379 | }
|
34380 | function requiresInlineTypeCtor(node, host) {
|
34381 | // The class requires an inline type constructor if it has generic type bounds that can not be
|
34382 | // emitted into a different context.
|
34383 | return !checkIfGenericTypeBoundsAreContextFree(node, host);
|
34384 | }
|
34385 | function checkIfGenericTypeBoundsAreContextFree(node, reflector) {
|
34386 | // Generic type parameters are considered context free if they can be emitted into any context.
|
34387 | return new TypeParameterEmitter(node.typeParameters, reflector).canEmit();
|
34388 | }
|
34389 | /**
|
34390 | * Add a default `= any` to type parameters that don't have a default value already.
|
34391 | *
|
34392 | * TypeScript uses the default type of a type parameter whenever inference of that parameter fails.
|
34393 | * This can happen when inferring a complex type from 'any'. For example, if `NgFor`'s inference is
|
34394 | * done with the TCB code:
|
34395 | *
|
34396 | * ```
|
34397 | * class NgFor<T> {
|
34398 | * ngForOf: T[];
|
34399 | * }
|
34400 | *
|
34401 | * declare function ctor<T>(o: Pick<NgFor<T>, 'ngForOf'|'ngForTrackBy'|'ngForTemplate'>): NgFor<T>;
|
34402 | * ```
|
34403 | *
|
34404 | * An invocation looks like:
|
34405 | *
|
34406 | * ```
|
34407 | * var _t1 = ctor({ngForOf: [1, 2], ngForTrackBy: null as any, ngForTemplate: null as any});
|
34408 | * ```
|
34409 | *
|
34410 | * This correctly infers the type `NgFor<number>` for `_t1`, since `T` is inferred from the
|
34411 | * assignment of type `number[]` to `ngForOf`'s type `T[]`. However, if `any` is passed instead:
|
34412 | *
|
34413 | * ```
|
34414 | * var _t2 = ctor({ngForOf: [1, 2] as any, ngForTrackBy: null as any, ngForTemplate: null as any});
|
34415 | * ```
|
34416 | *
|
34417 | * then inference for `T` fails (it cannot be inferred from `T[] = any`). In this case, `T` takes
|
34418 | * the type `{}`, and so `_t2` is inferred as `NgFor<{}>`. This is obviously wrong.
|
34419 | *
|
34420 | * Adding a default type to the generic declaration in the constructor solves this problem, as the
|
34421 | * default type will be used in the event that inference fails.
|
34422 | *
|
34423 | * ```
|
34424 | * declare function ctor<T = any>(o: Pick<NgFor<T>, 'ngForOf'>): NgFor<T>;
|
34425 | *
|
34426 | * var _t3 = ctor({ngForOf: [1, 2] as any});
|
34427 | * ```
|
34428 | *
|
34429 | * This correctly infers `T` as `any`, and therefore `_t3` as `NgFor<any>`.
|
34430 | */
|
34431 | function typeParametersWithDefaultTypes(params) {
|
34432 | if (params === undefined) {
|
34433 | return undefined;
|
34434 | }
|
34435 | return params.map(param => {
|
34436 | if (param.default === undefined) {
|
34437 | return ts$1.updateTypeParameterDeclaration(
|
34438 | /* node */ param,
|
34439 | /* name */ param.name,
|
34440 | /* constraint */ param.constraint,
|
34441 | /* defaultType */ ts$1.createKeywordTypeNode(ts$1.SyntaxKind.AnyKeyword));
|
34442 | }
|
34443 | else {
|
34444 | return param;
|
34445 | }
|
34446 | });
|
34447 | }
|
34448 |
|
34449 | /**
|
34450 | * @license
|
34451 | * Copyright Google LLC All Rights Reserved.
|
34452 | *
|
34453 | * Use of this source code is governed by an MIT-style license that can be
|
34454 | * found in the LICENSE file at https://angular.io/license
|
34455 | */
|
34456 | /**
|
34457 | * A context which hosts one or more Type Check Blocks (TCBs).
|
34458 | *
|
34459 | * An `Environment` supports the generation of TCBs by tracking necessary imports, declarations of
|
34460 | * type constructors, and other statements beyond the type-checking code within the TCB itself.
|
34461 | * Through method calls on `Environment`, the TCB generator can request `ts.Expression`s which
|
34462 | * reference declarations in the `Environment` for these artifacts`.
|
34463 | *
|
34464 | * `Environment` can be used in a standalone fashion, or can be extended to support more specialized
|
34465 | * usage.
|
34466 | */
|
34467 | class Environment {
|
34468 | constructor(config, importManager, refEmitter, reflector, contextFile) {
|
34469 | this.config = config;
|
34470 | this.importManager = importManager;
|
34471 | this.refEmitter = refEmitter;
|
34472 | this.reflector = reflector;
|
34473 | this.contextFile = contextFile;
|
34474 | this.nextIds = {
|
34475 | pipeInst: 1,
|
34476 | typeCtor: 1,
|
34477 | };
|
34478 | this.typeCtors = new Map();
|
34479 | this.typeCtorStatements = [];
|
34480 | this.pipeInsts = new Map();
|
34481 | this.pipeInstStatements = [];
|
34482 | }
|
34483 | /**
|
34484 | * Get an expression referring to a type constructor for the given directive.
|
34485 | *
|
34486 | * Depending on the shape of the directive itself, this could be either a reference to a declared
|
34487 | * type constructor, or to an inline type constructor.
|
34488 | */
|
34489 | typeCtorFor(dir) {
|
34490 | const dirRef = dir.ref;
|
34491 | const node = dirRef.node;
|
34492 | if (this.typeCtors.has(node)) {
|
34493 | return this.typeCtors.get(node);
|
34494 | }
|
34495 | if (requiresInlineTypeCtor(node, this.reflector)) {
|
34496 | // The constructor has already been created inline, we just need to construct a reference to
|
34497 | // it.
|
34498 | const ref = this.reference(dirRef);
|
34499 | const typeCtorExpr = ts$1.createPropertyAccess(ref, 'ngTypeCtor');
|
34500 | this.typeCtors.set(node, typeCtorExpr);
|
34501 | return typeCtorExpr;
|
34502 | }
|
34503 | else {
|
34504 | const fnName = `_ctor${this.nextIds.typeCtor++}`;
|
34505 | const nodeTypeRef = this.referenceType(dirRef);
|
34506 | if (!ts$1.isTypeReferenceNode(nodeTypeRef)) {
|
34507 | throw new Error(`Expected TypeReferenceNode from reference to ${dirRef.debugName}`);
|
34508 | }
|
34509 | const meta = {
|
34510 | fnName,
|
34511 | body: true,
|
34512 | fields: {
|
34513 | inputs: dir.inputs.classPropertyNames,
|
34514 | outputs: dir.outputs.classPropertyNames,
|
34515 | // TODO: support queries
|
34516 | queries: dir.queries,
|
34517 | },
|
34518 | coercedInputFields: dir.coercedInputFields,
|
34519 | };
|
34520 | const typeParams = this.emitTypeParameters(node);
|
34521 | const typeCtor = generateTypeCtorDeclarationFn(node, meta, nodeTypeRef.typeName, typeParams, this.reflector);
|
34522 | this.typeCtorStatements.push(typeCtor);
|
34523 | const fnId = ts$1.createIdentifier(fnName);
|
34524 | this.typeCtors.set(node, fnId);
|
34525 | return fnId;
|
34526 | }
|
34527 | }
|
34528 | /*
|
34529 | * Get an expression referring to an instance of the given pipe.
|
34530 | */
|
34531 | pipeInst(ref) {
|
34532 | if (this.pipeInsts.has(ref.node)) {
|
34533 | return this.pipeInsts.get(ref.node);
|
34534 | }
|
34535 | const pipeType = this.referenceType(ref);
|
34536 | const pipeInstId = ts$1.createIdentifier(`_pipe${this.nextIds.pipeInst++}`);
|
34537 | this.pipeInstStatements.push(tsDeclareVariable(pipeInstId, pipeType));
|
34538 | this.pipeInsts.set(ref.node, pipeInstId);
|
34539 | return pipeInstId;
|
34540 | }
|
34541 | /**
|
34542 | * Generate a `ts.Expression` that references the given node.
|
34543 | *
|
34544 | * This may involve importing the node into the file if it's not declared there already.
|
34545 | */
|
34546 | reference(ref) {
|
34547 | // Disable aliasing for imports generated in a template type-checking context, as there is no
|
34548 | // guarantee that any alias re-exports exist in the .d.ts files. It's safe to use direct imports
|
34549 | // in these cases as there is no strict dependency checking during the template type-checking
|
34550 | // pass.
|
34551 | const ngExpr = this.refEmitter.emit(ref, this.contextFile, ImportFlags.NoAliasing);
|
34552 | // Use `translateExpression` to convert the `Expression` into a `ts.Expression`.
|
34553 | return translateExpression(ngExpr, this.importManager);
|
34554 | }
|
34555 | /**
|
34556 | * Generate a `ts.TypeNode` that references the given node as a type.
|
34557 | *
|
34558 | * This may involve importing the node into the file if it's not declared there already.
|
34559 | */
|
34560 | referenceType(ref) {
|
34561 | const ngExpr = this.refEmitter.emit(ref, this.contextFile, ImportFlags.NoAliasing | ImportFlags.AllowTypeImports);
|
34562 | // Create an `ExpressionType` from the `Expression` and translate it via `translateType`.
|
34563 | // TODO(alxhub): support references to types with generic arguments in a clean way.
|
34564 | return translateType(new ExpressionType(ngExpr), this.importManager);
|
34565 | }
|
34566 | emitTypeParameters(declaration) {
|
34567 | const emitter = new TypeParameterEmitter(declaration.typeParameters, this.reflector);
|
34568 | return emitter.emit(ref => this.referenceType(ref));
|
34569 | }
|
34570 | /**
|
34571 | * Generate a `ts.TypeNode` that references a given type from the provided module.
|
34572 | *
|
34573 | * This will involve importing the type into the file, and will also add type parameters if
|
34574 | * provided.
|
34575 | */
|
34576 | referenceExternalType(moduleName, name, typeParams) {
|
34577 | const external = new ExternalExpr({ moduleName, name });
|
34578 | return translateType(new ExpressionType(external, [ /* modifiers */], typeParams), this.importManager);
|
34579 | }
|
34580 | getPreludeStatements() {
|
34581 | return [
|
34582 | ...this.pipeInstStatements,
|
34583 | ...this.typeCtorStatements,
|
34584 | ];
|
34585 | }
|
34586 | }
|
34587 |
|
34588 | /**
|
34589 | * @license
|
34590 | * Copyright Google LLC All Rights Reserved.
|
34591 | *
|
34592 | * Use of this source code is governed by an MIT-style license that can be
|
34593 | * found in the LICENSE file at https://angular.io/license
|
34594 | */
|
34595 | class OutOfBandDiagnosticRecorderImpl {
|
34596 | constructor(resolver) {
|
34597 | this.resolver = resolver;
|
34598 | this._diagnostics = [];
|
34599 | /**
|
34600 | * Tracks which `BindingPipe` nodes have already been recorded as invalid, so only one diagnostic
|
34601 | * is ever produced per node.
|
34602 | */
|
34603 | this.recordedPipes = new Set();
|
34604 | }
|
34605 | get diagnostics() {
|
34606 | return this._diagnostics;
|
34607 | }
|
34608 | missingReferenceTarget(templateId, ref) {
|
34609 | const mapping = this.resolver.getSourceMapping(templateId);
|
34610 | const value = ref.value.trim();
|
34611 | const errorMsg = `No directive found with exportAs '${value}'.`;
|
34612 | this._diagnostics.push(makeTemplateDiagnostic(templateId, mapping, ref.valueSpan || ref.sourceSpan, ts$1.DiagnosticCategory.Error, ngErrorCode(ErrorCode.MISSING_REFERENCE_TARGET), errorMsg));
|
34613 | }
|
34614 | missingPipe(templateId, ast) {
|
34615 | if (this.recordedPipes.has(ast)) {
|
34616 | return;
|
34617 | }
|
34618 | const mapping = this.resolver.getSourceMapping(templateId);
|
34619 | const errorMsg = `No pipe found with name '${ast.name}'.`;
|
34620 | const sourceSpan = this.resolver.toParseSourceSpan(templateId, ast.nameSpan);
|
34621 | if (sourceSpan === null) {
|
34622 | throw new Error(`Assertion failure: no SourceLocation found for usage of pipe '${ast.name}'.`);
|
34623 | }
|
34624 | this._diagnostics.push(makeTemplateDiagnostic(templateId, mapping, sourceSpan, ts$1.DiagnosticCategory.Error, ngErrorCode(ErrorCode.MISSING_PIPE), errorMsg));
|
34625 | this.recordedPipes.add(ast);
|
34626 | }
|
34627 | illegalAssignmentToTemplateVar(templateId, assignment, target) {
|
34628 | const mapping = this.resolver.getSourceMapping(templateId);
|
34629 | const errorMsg = `Cannot use variable '${assignment
|
34630 | .name}' as the left-hand side of an assignment expression. Template variables are read-only.`;
|
34631 | const sourceSpan = this.resolver.toParseSourceSpan(templateId, assignment.sourceSpan);
|
34632 | if (sourceSpan === null) {
|
34633 | throw new Error(`Assertion failure: no SourceLocation found for property binding.`);
|
34634 | }
|
34635 | this._diagnostics.push(makeTemplateDiagnostic(templateId, mapping, sourceSpan, ts$1.DiagnosticCategory.Error, ngErrorCode(ErrorCode.WRITE_TO_READ_ONLY_VARIABLE), errorMsg, {
|
34636 | text: `The variable ${assignment.name} is declared here.`,
|
34637 | span: target.valueSpan || target.sourceSpan,
|
34638 | }));
|
34639 | }
|
34640 | duplicateTemplateVar(templateId, variable, firstDecl) {
|
34641 | const mapping = this.resolver.getSourceMapping(templateId);
|
34642 | const errorMsg = `Cannot redeclare variable '${variable.name}' as it was previously declared elsewhere for the same template.`;
|
34643 | // The allocation of the error here is pretty useless for variables declared in microsyntax,
|
34644 | // since the sourceSpan refers to the entire microsyntax property, not a span for the specific
|
34645 | // variable in question.
|
34646 | //
|
34647 | // TODO(alxhub): allocate to a tighter span once one is available.
|
34648 | this._diagnostics.push(makeTemplateDiagnostic(templateId, mapping, variable.sourceSpan, ts$1.DiagnosticCategory.Error, ngErrorCode(ErrorCode.DUPLICATE_VARIABLE_DECLARATION), errorMsg, {
|
34649 | text: `The variable '${firstDecl.name}' was first declared here.`,
|
34650 | span: firstDecl.sourceSpan,
|
34651 | }));
|
34652 | }
|
34653 | requiresInlineTcb(templateId, node) {
|
34654 | this._diagnostics.push(makeInlineDiagnostic(templateId, ErrorCode.INLINE_TCB_REQUIRED, node.name, `This component requires inline template type-checking, which is not supported by the current environment.`));
|
34655 | }
|
34656 | requiresInlineTypeConstructors(templateId, node, directives) {
|
34657 | let message;
|
34658 | if (directives.length > 1) {
|
34659 | message =
|
34660 | `This component uses directives which require inline type constructors, which are not supported by the current environment.`;
|
34661 | }
|
34662 | else {
|
34663 | message =
|
34664 | `This component uses a directive which requires an inline type constructor, which is not supported by the current environment.`;
|
34665 | }
|
34666 | this._diagnostics.push(makeInlineDiagnostic(templateId, ErrorCode.INLINE_TYPE_CTOR_REQUIRED, node.name, message, directives.map(dir => makeRelatedInformation(dir.name, `Requires an inline type constructor.`))));
|
34667 | }
|
34668 | }
|
34669 | function makeInlineDiagnostic(templateId, code, node, messageText, relatedInformation) {
|
34670 | return Object.assign(Object.assign({}, makeDiagnostic(code, node, messageText, relatedInformation)), { componentFile: node.getSourceFile(), templateId });
|
34671 | }
|
34672 |
|
34673 | /**
|
34674 | * @license
|
34675 | * Copyright Google LLC All Rights Reserved.
|
34676 | *
|
34677 | * Use of this source code is governed by an MIT-style license that can be
|
34678 | * found in the LICENSE file at https://angular.io/license
|
34679 | */
|
34680 | function requiresInlineTypeCheckBlock(node, usedPipes) {
|
34681 | // In order to qualify for a declared TCB (not inline) two conditions must be met:
|
34682 | // 1) the class must be exported
|
34683 | // 2) it must not have constrained generic types
|
34684 | if (!checkIfClassIsExported(node)) {
|
34685 | // Condition 1 is false, the class is not exported.
|
34686 | return true;
|
34687 | }
|
34688 | else if (!checkIfGenericTypesAreUnbound(node)) {
|
34689 | // Condition 2 is false, the class has constrained generic types
|
34690 | return true;
|
34691 | }
|
34692 | else if (Array.from(usedPipes.values())
|
34693 | .some(pipeRef => !checkIfClassIsExported(pipeRef.node))) {
|
34694 | // If one of the pipes used by the component is not exported, a non-inline TCB will not be able
|
34695 | // to import it, so this requires an inline TCB.
|
34696 | return true;
|
34697 | }
|
34698 | else {
|
34699 | return false;
|
34700 | }
|
34701 | }
|
34702 | /** Maps a shim position back to a template location. */
|
34703 | function getTemplateMapping(shimSf, position, resolver, isDiagnosticRequest) {
|
34704 | const node = getTokenAtPosition(shimSf, position);
|
34705 | const sourceLocation = findSourceLocation(node, shimSf, isDiagnosticRequest);
|
34706 | if (sourceLocation === null) {
|
34707 | return null;
|
34708 | }
|
34709 | const mapping = resolver.getSourceMapping(sourceLocation.id);
|
34710 | const span = resolver.toParseSourceSpan(sourceLocation.id, sourceLocation.span);
|
34711 | if (span === null) {
|
34712 | return null;
|
34713 | }
|
34714 | // TODO(atscott): Consider adding a context span by walking up from `node` until we get a
|
34715 | // different span.
|
34716 | return { sourceLocation, templateSourceMapping: mapping, span };
|
34717 | }
|
34718 | function findTypeCheckBlock(file, id, isDiagnosticRequest) {
|
34719 | for (const stmt of file.statements) {
|
34720 | if (ts$1.isFunctionDeclaration(stmt) && getTemplateId$1(stmt, file, isDiagnosticRequest) === id) {
|
34721 | return stmt;
|
34722 | }
|
34723 | }
|
34724 | return null;
|
34725 | }
|
34726 | /**
|
34727 | * Traverses up the AST starting from the given node to extract the source location from comments
|
34728 | * that have been emitted into the TCB. If the node does not exist within a TCB, or if an ignore
|
34729 | * marker comment is found up the tree (and this is part of a diagnostic request), this function
|
34730 | * returns null.
|
34731 | */
|
34732 | function findSourceLocation(node, sourceFile, isDiagnosticsRequest) {
|
34733 | // Search for comments until the TCB's function declaration is encountered.
|
34734 | while (node !== undefined && !ts$1.isFunctionDeclaration(node)) {
|
34735 | if (hasIgnoreForDiagnosticsMarker(node, sourceFile) && isDiagnosticsRequest) {
|
34736 | // There's an ignore marker on this node, so the diagnostic should not be reported.
|
34737 | return null;
|
34738 | }
|
34739 | const span = readSpanComment(node, sourceFile);
|
34740 | if (span !== null) {
|
34741 | // Once the positional information has been extracted, search further up the TCB to extract
|
34742 | // the unique id that is attached with the TCB's function declaration.
|
34743 | const id = getTemplateId$1(node, sourceFile, isDiagnosticsRequest);
|
34744 | if (id === null) {
|
34745 | return null;
|
34746 | }
|
34747 | return { id, span };
|
34748 | }
|
34749 | node = node.parent;
|
34750 | }
|
34751 | return null;
|
34752 | }
|
34753 | function getTemplateId$1(node, sourceFile, isDiagnosticRequest) {
|
34754 | // Walk up to the function declaration of the TCB, the file information is attached there.
|
34755 | while (!ts$1.isFunctionDeclaration(node)) {
|
34756 | if (hasIgnoreForDiagnosticsMarker(node, sourceFile) && isDiagnosticRequest) {
|
34757 | // There's an ignore marker on this node, so the diagnostic should not be reported.
|
34758 | return null;
|
34759 | }
|
34760 | node = node.parent;
|
34761 | // Bail once we have reached the root.
|
34762 | if (node === undefined) {
|
34763 | return null;
|
34764 | }
|
34765 | }
|
34766 | const start = node.getFullStart();
|
34767 | return ts$1.forEachLeadingCommentRange(sourceFile.text, start, (pos, end, kind) => {
|
34768 | if (kind !== ts$1.SyntaxKind.MultiLineCommentTrivia) {
|
34769 | return null;
|
34770 | }
|
34771 | const commentText = sourceFile.text.substring(pos + 2, end - 2);
|
34772 | return commentText;
|
34773 | }) || null;
|
34774 | }
|
34775 |
|
34776 | /**
|
34777 | * @license
|
34778 | * Copyright Google LLC All Rights Reserved.
|
34779 | *
|
34780 | * Use of this source code is governed by an MIT-style license that can be
|
34781 | * found in the LICENSE file at https://angular.io/license
|
34782 | */
|
34783 | /**
|
34784 | * Wraps the node in parenthesis such that inserted span comments become attached to the proper
|
34785 | * node. This is an alias for `ts.createParen` with the benefit that it signifies that the
|
34786 | * inserted parenthesis are for diagnostic purposes, not for correctness of the rendered TCB code.
|
34787 | *
|
34788 | * Note that it is important that nodes and its attached comment are not wrapped into parenthesis
|
34789 | * by default, as it prevents correct translation of e.g. diagnostics produced for incorrect method
|
34790 | * arguments. Such diagnostics would then be produced for the parenthesised node whereas the
|
34791 | * positional comment would be located within that node, resulting in a mismatch.
|
34792 | */
|
34793 | function wrapForDiagnostics(expr) {
|
34794 | return ts$1.createParen(expr);
|
34795 | }
|
34796 | /**
|
34797 | * Wraps the node in parenthesis such that inserted span comments become attached to the proper
|
34798 | * node. This is an alias for `ts.createParen` with the benefit that it signifies that the
|
34799 | * inserted parenthesis are for use by the type checker, not for correctness of the rendered TCB
|
34800 | * code.
|
34801 | */
|
34802 | function wrapForTypeChecker(expr) {
|
34803 | return ts$1.createParen(expr);
|
34804 | }
|
34805 | /**
|
34806 | * Adds a synthetic comment to the expression that represents the parse span of the provided node.
|
34807 | * This comment can later be retrieved as trivia of a node to recover original source locations.
|
34808 | */
|
34809 | function addParseSpanInfo(node, span) {
|
34810 | let commentText;
|
34811 | if (span instanceof AbsoluteSourceSpan) {
|
34812 | commentText = `${span.start},${span.end}`;
|
34813 | }
|
34814 | else {
|
34815 | commentText = `${span.start.offset},${span.end.offset}`;
|
34816 | }
|
34817 | ts$1.addSyntheticTrailingComment(node, ts$1.SyntaxKind.MultiLineCommentTrivia, commentText, /* hasTrailingNewLine */ false);
|
34818 | }
|
34819 | /**
|
34820 | * Adds a synthetic comment to the function declaration that contains the template id
|
34821 | * of the class declaration.
|
34822 | */
|
34823 | function addTemplateId(tcb, id) {
|
34824 | ts$1.addSyntheticLeadingComment(tcb, ts$1.SyntaxKind.MultiLineCommentTrivia, id, true);
|
34825 | }
|
34826 | /**
|
34827 | * Determines if the diagnostic should be reported. Some diagnostics are produced because of the
|
34828 | * way TCBs are generated; those diagnostics should not be reported as type check errors of the
|
34829 | * template.
|
34830 | */
|
34831 | function shouldReportDiagnostic(diagnostic) {
|
34832 | const { code } = diagnostic;
|
34833 | if (code === 6133 /* $var is declared but its value is never read. */) {
|
34834 | return false;
|
34835 | }
|
34836 | else if (code === 6199 /* All variables are unused. */) {
|
34837 | return false;
|
34838 | }
|
34839 | else if (code === 2695 /* Left side of comma operator is unused and has no side effects. */) {
|
34840 | return false;
|
34841 | }
|
34842 | else if (code === 7006 /* Parameter '$event' implicitly has an 'any' type. */) {
|
34843 | return false;
|
34844 | }
|
34845 | return true;
|
34846 | }
|
34847 | /**
|
34848 | * Attempts to translate a TypeScript diagnostic produced during template type-checking to their
|
34849 | * location of origin, based on the comments that are emitted in the TCB code.
|
34850 | *
|
34851 | * If the diagnostic could not be translated, `null` is returned to indicate that the diagnostic
|
34852 | * should not be reported at all. This prevents diagnostics from non-TCB code in a user's source
|
34853 | * file from being reported as type-check errors.
|
34854 | */
|
34855 | function translateDiagnostic(diagnostic, resolver) {
|
34856 | if (diagnostic.file === undefined || diagnostic.start === undefined) {
|
34857 | return null;
|
34858 | }
|
34859 | const fullMapping = getTemplateMapping(diagnostic.file, diagnostic.start, resolver, /*isDiagnosticsRequest*/ true);
|
34860 | if (fullMapping === null) {
|
34861 | return null;
|
34862 | }
|
34863 | const { sourceLocation, templateSourceMapping, span } = fullMapping;
|
34864 | return makeTemplateDiagnostic(sourceLocation.id, templateSourceMapping, span, diagnostic.category, diagnostic.code, diagnostic.messageText);
|
34865 | }
|
34866 |
|
34867 | /**
|
34868 | * @license
|
34869 | * Copyright Google LLC All Rights Reserved.
|
34870 | *
|
34871 | * Use of this source code is governed by an MIT-style license that can be
|
34872 | * found in the LICENSE file at https://angular.io/license
|
34873 | */
|
34874 | const NULL_AS_ANY = ts$1.createAsExpression(ts$1.createNull(), ts$1.createKeywordTypeNode(ts$1.SyntaxKind.AnyKeyword));
|
34875 | const UNDEFINED = ts$1.createIdentifier('undefined');
|
34876 | const UNARY_OPS = new Map([
|
34877 | ['+', ts$1.SyntaxKind.PlusToken],
|
34878 | ['-', ts$1.SyntaxKind.MinusToken],
|
34879 | ]);
|
34880 | const BINARY_OPS = new Map([
|
34881 | ['+', ts$1.SyntaxKind.PlusToken],
|
34882 | ['-', ts$1.SyntaxKind.MinusToken],
|
34883 | ['<', ts$1.SyntaxKind.LessThanToken],
|
34884 | ['>', ts$1.SyntaxKind.GreaterThanToken],
|
34885 | ['<=', ts$1.SyntaxKind.LessThanEqualsToken],
|
34886 | ['>=', ts$1.SyntaxKind.GreaterThanEqualsToken],
|
34887 | ['==', ts$1.SyntaxKind.EqualsEqualsToken],
|
34888 | ['===', ts$1.SyntaxKind.EqualsEqualsEqualsToken],
|
34889 | ['*', ts$1.SyntaxKind.AsteriskToken],
|
34890 | ['/', ts$1.SyntaxKind.SlashToken],
|
34891 | ['%', ts$1.SyntaxKind.PercentToken],
|
34892 | ['!=', ts$1.SyntaxKind.ExclamationEqualsToken],
|
34893 | ['!==', ts$1.SyntaxKind.ExclamationEqualsEqualsToken],
|
34894 | ['||', ts$1.SyntaxKind.BarBarToken],
|
34895 | ['&&', ts$1.SyntaxKind.AmpersandAmpersandToken],
|
34896 | ['&', ts$1.SyntaxKind.AmpersandToken],
|
34897 | ['|', ts$1.SyntaxKind.BarToken],
|
34898 | ]);
|
34899 | /**
|
34900 | * Convert an `AST` to TypeScript code directly, without going through an intermediate `Expression`
|
34901 | * AST.
|
34902 | */
|
34903 | function astToTypescript(ast, maybeResolve, config) {
|
34904 | const translator = new AstTranslator(maybeResolve, config);
|
34905 | return translator.translate(ast);
|
34906 | }
|
34907 | class AstTranslator {
|
34908 | constructor(maybeResolve, config) {
|
34909 | this.maybeResolve = maybeResolve;
|
34910 | this.config = config;
|
34911 | }
|
34912 | translate(ast) {
|
34913 | // Skip over an `ASTWithSource` as its `visit` method calls directly into its ast's `visit`,
|
34914 | // which would prevent any custom resolution through `maybeResolve` for that node.
|
34915 | if (ast instanceof ASTWithSource) {
|
34916 | ast = ast.ast;
|
34917 | }
|
34918 | // The `EmptyExpr` doesn't have a dedicated method on `AstVisitor`, so it's special cased here.
|
34919 | if (ast instanceof EmptyExpr) {
|
34920 | return UNDEFINED;
|
34921 | }
|
34922 | // First attempt to let any custom resolution logic provide a translation for the given node.
|
34923 | const resolved = this.maybeResolve(ast);
|
34924 | if (resolved !== null) {
|
34925 | return resolved;
|
34926 | }
|
34927 | return ast.visit(this);
|
34928 | }
|
34929 | visitUnary(ast) {
|
34930 | const expr = this.translate(ast.expr);
|
34931 | const op = UNARY_OPS.get(ast.operator);
|
34932 | if (op === undefined) {
|
34933 | throw new Error(`Unsupported Unary.operator: ${ast.operator}`);
|
34934 | }
|
34935 | const node = wrapForDiagnostics(ts$1.createPrefix(op, expr));
|
34936 | addParseSpanInfo(node, ast.sourceSpan);
|
34937 | return node;
|
34938 | }
|
34939 | visitBinary(ast) {
|
34940 | const lhs = wrapForDiagnostics(this.translate(ast.left));
|
34941 | const rhs = wrapForDiagnostics(this.translate(ast.right));
|
34942 | const op = BINARY_OPS.get(ast.operation);
|
34943 | if (op === undefined) {
|
34944 | throw new Error(`Unsupported Binary.operation: ${ast.operation}`);
|
34945 | }
|
34946 | const node = ts$1.createBinary(lhs, op, rhs);
|
34947 | addParseSpanInfo(node, ast.sourceSpan);
|
34948 | return node;
|
34949 | }
|
34950 | visitChain(ast) {
|
34951 | const elements = ast.expressions.map(expr => this.translate(expr));
|
34952 | const node = wrapForDiagnostics(ts$1.createCommaList(elements));
|
34953 | addParseSpanInfo(node, ast.sourceSpan);
|
34954 | return node;
|
34955 | }
|
34956 | visitConditional(ast) {
|
34957 | const condExpr = this.translate(ast.condition);
|
34958 | const trueExpr = this.translate(ast.trueExp);
|
34959 | // Wrap `falseExpr` in parens so that the trailing parse span info is not attributed to the
|
34960 | // whole conditional.
|
34961 | // In the following example, the last source span comment (5,6) could be seen as the
|
34962 | // trailing comment for _either_ the whole conditional expression _or_ just the `falseExpr` that
|
34963 | // is immediately before it:
|
34964 | // `conditional /*1,2*/ ? trueExpr /*3,4*/ : falseExpr /*5,6*/`
|
34965 | // This should be instead be `conditional /*1,2*/ ? trueExpr /*3,4*/ : (falseExpr /*5,6*/)`
|
34966 | const falseExpr = wrapForTypeChecker(this.translate(ast.falseExp));
|
34967 | const node = ts$1.createParen(ts$1.createConditional(condExpr, trueExpr, falseExpr));
|
34968 | addParseSpanInfo(node, ast.sourceSpan);
|
34969 | return node;
|
34970 | }
|
34971 | visitFunctionCall(ast) {
|
34972 | const receiver = wrapForDiagnostics(this.translate(ast.target));
|
34973 | const args = ast.args.map(expr => this.translate(expr));
|
34974 | const node = ts$1.createCall(receiver, undefined, args);
|
34975 | addParseSpanInfo(node, ast.sourceSpan);
|
34976 | return node;
|
34977 | }
|
34978 | visitImplicitReceiver(ast) {
|
34979 | throw new Error('Method not implemented.');
|
34980 | }
|
34981 | visitThisReceiver(ast) {
|
34982 | throw new Error('Method not implemented.');
|
34983 | }
|
34984 | visitInterpolation(ast) {
|
34985 | // Build up a chain of binary + operations to simulate the string concatenation of the
|
34986 | // interpolation's expressions. The chain is started using an actual string literal to ensure
|
34987 | // the type is inferred as 'string'.
|
34988 | return ast.expressions.reduce((lhs, ast) => ts$1.createBinary(lhs, ts$1.SyntaxKind.PlusToken, wrapForTypeChecker(this.translate(ast))), ts$1.createLiteral(''));
|
34989 | }
|
34990 | visitKeyedRead(ast) {
|
34991 | const receiver = wrapForDiagnostics(this.translate(ast.obj));
|
34992 | const key = this.translate(ast.key);
|
34993 | const node = ts$1.createElementAccess(receiver, key);
|
34994 | addParseSpanInfo(node, ast.sourceSpan);
|
34995 | return node;
|
34996 | }
|
34997 | visitKeyedWrite(ast) {
|
34998 | const receiver = wrapForDiagnostics(this.translate(ast.obj));
|
34999 | const left = ts$1.createElementAccess(receiver, this.translate(ast.key));
|
35000 | // TODO(joost): annotate `left` with the span of the element access, which is not currently
|
35001 | // available on `ast`.
|
35002 | const right = wrapForTypeChecker(this.translate(ast.value));
|
35003 | const node = wrapForDiagnostics(ts$1.createBinary(left, ts$1.SyntaxKind.EqualsToken, right));
|
35004 | addParseSpanInfo(node, ast.sourceSpan);
|
35005 | return node;
|
35006 | }
|
35007 | visitLiteralArray(ast) {
|
35008 | const elements = ast.expressions.map(expr => this.translate(expr));
|
35009 | const literal = ts$1.createArrayLiteral(elements);
|
35010 | // If strictLiteralTypes is disabled, array literals are cast to `any`.
|
35011 | const node = this.config.strictLiteralTypes ? literal : tsCastToAny(literal);
|
35012 | addParseSpanInfo(node, ast.sourceSpan);
|
35013 | return node;
|
35014 | }
|
35015 | visitLiteralMap(ast) {
|
35016 | const properties = ast.keys.map(({ key }, idx) => {
|
35017 | const value = this.translate(ast.values[idx]);
|
35018 | return ts$1.createPropertyAssignment(ts$1.createStringLiteral(key), value);
|
35019 | });
|
35020 | const literal = ts$1.createObjectLiteral(properties, true);
|
35021 | // If strictLiteralTypes is disabled, object literals are cast to `any`.
|
35022 | const node = this.config.strictLiteralTypes ? literal : tsCastToAny(literal);
|
35023 | addParseSpanInfo(node, ast.sourceSpan);
|
35024 | return node;
|
35025 | }
|
35026 | visitLiteralPrimitive(ast) {
|
35027 | let node;
|
35028 | if (ast.value === undefined) {
|
35029 | node = ts$1.createIdentifier('undefined');
|
35030 | }
|
35031 | else if (ast.value === null) {
|
35032 | node = ts$1.createNull();
|
35033 | }
|
35034 | else {
|
35035 | node = ts$1.createLiteral(ast.value);
|
35036 | }
|
35037 | addParseSpanInfo(node, ast.sourceSpan);
|
35038 | return node;
|
35039 | }
|
35040 | visitMethodCall(ast) {
|
35041 | const receiver = wrapForDiagnostics(this.translate(ast.receiver));
|
35042 | const method = ts$1.createPropertyAccess(receiver, ast.name);
|
35043 | addParseSpanInfo(method, ast.nameSpan);
|
35044 | const args = ast.args.map(expr => this.translate(expr));
|
35045 | const node = ts$1.createCall(method, undefined, args);
|
35046 | addParseSpanInfo(node, ast.sourceSpan);
|
35047 | return node;
|
35048 | }
|
35049 | visitNonNullAssert(ast) {
|
35050 | const expr = wrapForDiagnostics(this.translate(ast.expression));
|
35051 | const node = ts$1.createNonNullExpression(expr);
|
35052 | addParseSpanInfo(node, ast.sourceSpan);
|
35053 | return node;
|
35054 | }
|
35055 | visitPipe(ast) {
|
35056 | throw new Error('Method not implemented.');
|
35057 | }
|
35058 | visitPrefixNot(ast) {
|
35059 | const expression = wrapForDiagnostics(this.translate(ast.expression));
|
35060 | const node = ts$1.createLogicalNot(expression);
|
35061 | addParseSpanInfo(node, ast.sourceSpan);
|
35062 | return node;
|
35063 | }
|
35064 | visitPropertyRead(ast) {
|
35065 | // This is a normal property read - convert the receiver to an expression and emit the correct
|
35066 | // TypeScript expression to read the property.
|
35067 | const receiver = wrapForDiagnostics(this.translate(ast.receiver));
|
35068 | const name = ts$1.createPropertyAccess(receiver, ast.name);
|
35069 | addParseSpanInfo(name, ast.nameSpan);
|
35070 | const node = wrapForDiagnostics(name);
|
35071 | addParseSpanInfo(node, ast.sourceSpan);
|
35072 | return node;
|
35073 | }
|
35074 | visitPropertyWrite(ast) {
|
35075 | const receiver = wrapForDiagnostics(this.translate(ast.receiver));
|
35076 | const left = ts$1.createPropertyAccess(receiver, ast.name);
|
35077 | addParseSpanInfo(left, ast.nameSpan);
|
35078 | // TypeScript reports assignment errors on the entire lvalue expression. Annotate the lvalue of
|
35079 | // the assignment with the sourceSpan, which includes receivers, rather than nameSpan for
|
35080 | // consistency of the diagnostic location.
|
35081 | // a.b.c = 1
|
35082 | // ^^^^^^^^^ sourceSpan
|
35083 | // ^ nameSpan
|
35084 | const leftWithPath = wrapForDiagnostics(left);
|
35085 | addParseSpanInfo(leftWithPath, ast.sourceSpan);
|
35086 | // The right needs to be wrapped in parens as well or we cannot accurately match its
|
35087 | // span to just the RHS. For example, the span in `e = $event /*0,10*/` is ambiguous.
|
35088 | // It could refer to either the whole binary expression or just the RHS.
|
35089 | // We should instead generate `e = ($event /*0,10*/)` so we know the span 0,10 matches RHS.
|
35090 | const right = wrapForTypeChecker(this.translate(ast.value));
|
35091 | const node = wrapForDiagnostics(ts$1.createBinary(leftWithPath, ts$1.SyntaxKind.EqualsToken, right));
|
35092 | addParseSpanInfo(node, ast.sourceSpan);
|
35093 | return node;
|
35094 | }
|
35095 | visitQuote(ast) {
|
35096 | return NULL_AS_ANY;
|
35097 | }
|
35098 | visitSafeMethodCall(ast) {
|
35099 | // See the comments in SafePropertyRead above for an explanation of the cases here.
|
35100 | let node;
|
35101 | const receiver = wrapForDiagnostics(this.translate(ast.receiver));
|
35102 | const args = ast.args.map(expr => this.translate(expr));
|
35103 | if (this.config.strictSafeNavigationTypes) {
|
35104 | // "a?.method(...)" becomes (null as any ? a!.method(...) : undefined)
|
35105 | const method = ts$1.createPropertyAccess(ts$1.createNonNullExpression(receiver), ast.name);
|
35106 | addParseSpanInfo(method, ast.nameSpan);
|
35107 | const call = ts$1.createCall(method, undefined, args);
|
35108 | node = ts$1.createParen(ts$1.createConditional(NULL_AS_ANY, call, UNDEFINED));
|
35109 | }
|
35110 | else if (VeSafeLhsInferenceBugDetector.veWillInferAnyFor(ast)) {
|
35111 | // "a?.method(...)" becomes (a as any).method(...)
|
35112 | const method = ts$1.createPropertyAccess(tsCastToAny(receiver), ast.name);
|
35113 | addParseSpanInfo(method, ast.nameSpan);
|
35114 | node = ts$1.createCall(method, undefined, args);
|
35115 | }
|
35116 | else {
|
35117 | // "a?.method(...)" becomes (a!.method(...) as any)
|
35118 | const method = ts$1.createPropertyAccess(ts$1.createNonNullExpression(receiver), ast.name);
|
35119 | addParseSpanInfo(method, ast.nameSpan);
|
35120 | node = tsCastToAny(ts$1.createCall(method, undefined, args));
|
35121 | }
|
35122 | addParseSpanInfo(node, ast.sourceSpan);
|
35123 | return node;
|
35124 | }
|
35125 | visitSafePropertyRead(ast) {
|
35126 | let node;
|
35127 | const receiver = wrapForDiagnostics(this.translate(ast.receiver));
|
35128 | // The form of safe property reads depends on whether strictness is in use.
|
35129 | if (this.config.strictSafeNavigationTypes) {
|
35130 | // Basically, the return here is either the type of the complete expression with a null-safe
|
35131 | // property read, or `undefined`. So a ternary is used to create an "or" type:
|
35132 | // "a?.b" becomes (null as any ? a!.b : undefined)
|
35133 | // The type of this expression is (typeof a!.b) | undefined, which is exactly as desired.
|
35134 | const expr = ts$1.createPropertyAccess(ts$1.createNonNullExpression(receiver), ast.name);
|
35135 | addParseSpanInfo(expr, ast.nameSpan);
|
35136 | node = ts$1.createParen(ts$1.createConditional(NULL_AS_ANY, expr, UNDEFINED));
|
35137 | }
|
35138 | else if (VeSafeLhsInferenceBugDetector.veWillInferAnyFor(ast)) {
|
35139 | // Emulate a View Engine bug where 'any' is inferred for the left-hand side of the safe
|
35140 | // navigation operation. With this bug, the type of the left-hand side is regarded as any.
|
35141 | // Therefore, the left-hand side only needs repeating in the output (to validate it), and then
|
35142 | // 'any' is used for the rest of the expression. This is done using a comma operator:
|
35143 | // "a?.b" becomes (a as any).b, which will of course have type 'any'.
|
35144 | node = ts$1.createPropertyAccess(tsCastToAny(receiver), ast.name);
|
35145 | }
|
35146 | else {
|
35147 | // The View Engine bug isn't active, so check the entire type of the expression, but the final
|
35148 | // result is still inferred as `any`.
|
35149 | // "a?.b" becomes (a!.b as any)
|
35150 | const expr = ts$1.createPropertyAccess(ts$1.createNonNullExpression(receiver), ast.name);
|
35151 | addParseSpanInfo(expr, ast.nameSpan);
|
35152 | node = tsCastToAny(expr);
|
35153 | }
|
35154 | addParseSpanInfo(node, ast.sourceSpan);
|
35155 | return node;
|
35156 | }
|
35157 | }
|
35158 | /**
|
35159 | * Checks whether View Engine will infer a type of 'any' for the left-hand side of a safe navigation
|
35160 | * operation.
|
35161 | *
|
35162 | * In View Engine's template type-checker, certain receivers of safe navigation operations will
|
35163 | * cause a temporary variable to be allocated as part of the checking expression, to save the value
|
35164 | * of the receiver and use it more than once in the expression. This temporary variable has type
|
35165 | * 'any'. In practice, this means certain receivers cause View Engine to not check the full
|
35166 | * expression, and other receivers will receive more complete checking.
|
35167 | *
|
35168 | * For compatibility, this logic is adapted from View Engine's expression_converter.ts so that the
|
35169 | * Ivy checker can emulate this bug when needed.
|
35170 | */
|
35171 | class VeSafeLhsInferenceBugDetector {
|
35172 | static veWillInferAnyFor(ast) {
|
35173 | return ast.receiver.visit(VeSafeLhsInferenceBugDetector.SINGLETON);
|
35174 | }
|
35175 | visitUnary(ast) {
|
35176 | return ast.expr.visit(this);
|
35177 | }
|
35178 | visitBinary(ast) {
|
35179 | return ast.left.visit(this) || ast.right.visit(this);
|
35180 | }
|
35181 | visitChain(ast) {
|
35182 | return false;
|
35183 | }
|
35184 | visitConditional(ast) {
|
35185 | return ast.condition.visit(this) || ast.trueExp.visit(this) || ast.falseExp.visit(this);
|
35186 | }
|
35187 | visitFunctionCall(ast) {
|
35188 | return true;
|
35189 | }
|
35190 | visitImplicitReceiver(ast) {
|
35191 | return false;
|
35192 | }
|
35193 | visitThisReceiver(ast) {
|
35194 | return false;
|
35195 | }
|
35196 | visitInterpolation(ast) {
|
35197 | return ast.expressions.some(exp => exp.visit(this));
|
35198 | }
|
35199 | visitKeyedRead(ast) {
|
35200 | return false;
|
35201 | }
|
35202 | visitKeyedWrite(ast) {
|
35203 | return false;
|
35204 | }
|
35205 | visitLiteralArray(ast) {
|
35206 | return true;
|
35207 | }
|
35208 | visitLiteralMap(ast) {
|
35209 | return true;
|
35210 | }
|
35211 | visitLiteralPrimitive(ast) {
|
35212 | return false;
|
35213 | }
|
35214 | visitMethodCall(ast) {
|
35215 | return true;
|
35216 | }
|
35217 | visitPipe(ast) {
|
35218 | return true;
|
35219 | }
|
35220 | visitPrefixNot(ast) {
|
35221 | return ast.expression.visit(this);
|
35222 | }
|
35223 | visitNonNullAssert(ast) {
|
35224 | return ast.expression.visit(this);
|
35225 | }
|
35226 | visitPropertyRead(ast) {
|
35227 | return false;
|
35228 | }
|
35229 | visitPropertyWrite(ast) {
|
35230 | return false;
|
35231 | }
|
35232 | visitQuote(ast) {
|
35233 | return false;
|
35234 | }
|
35235 | visitSafeMethodCall(ast) {
|
35236 | return true;
|
35237 | }
|
35238 | visitSafePropertyRead(ast) {
|
35239 | return false;
|
35240 | }
|
35241 | }
|
35242 | VeSafeLhsInferenceBugDetector.SINGLETON = new VeSafeLhsInferenceBugDetector();
|
35243 |
|
35244 | /**
|
35245 | * @license
|
35246 | * Copyright Google LLC All Rights Reserved.
|
35247 | *
|
35248 | * Use of this source code is governed by an MIT-style license that can be
|
35249 | * found in the LICENSE file at https://angular.io/license
|
35250 | */
|
35251 | /**
|
35252 | * Visits a template and records any semantic errors within its expressions.
|
35253 | */
|
35254 | class ExpressionSemanticVisitor extends RecursiveAstVisitor {
|
35255 | constructor(templateId, boundTarget, oob) {
|
35256 | super();
|
35257 | this.templateId = templateId;
|
35258 | this.boundTarget = boundTarget;
|
35259 | this.oob = oob;
|
35260 | }
|
35261 | visitPropertyWrite(ast, context) {
|
35262 | super.visitPropertyWrite(ast, context);
|
35263 | if (!(ast.receiver instanceof ImplicitReceiver)) {
|
35264 | return;
|
35265 | }
|
35266 | const target = this.boundTarget.getExpressionTarget(ast);
|
35267 | if (target instanceof Variable) {
|
35268 | // Template variables are read-only.
|
35269 | this.oob.illegalAssignmentToTemplateVar(this.templateId, ast, target);
|
35270 | }
|
35271 | }
|
35272 | static visit(ast, id, boundTarget, oob) {
|
35273 | ast.visit(new ExpressionSemanticVisitor(id, boundTarget, oob));
|
35274 | }
|
35275 | }
|
35276 |
|
35277 | /**
|
35278 | * @license
|
35279 | * Copyright Google LLC All Rights Reserved.
|
35280 | *
|
35281 | * Use of this source code is governed by an MIT-style license that can be
|
35282 | * found in the LICENSE file at https://angular.io/license
|
35283 | */
|
35284 | /**
|
35285 | * Given a `ts.ClassDeclaration` for a component, and metadata regarding that component, compose a
|
35286 | * "type check block" function.
|
35287 | *
|
35288 | * When passed through TypeScript's TypeChecker, type errors that arise within the type check block
|
35289 | * function indicate issues in the template itself.
|
35290 | *
|
35291 | * As a side effect of generating a TCB for the component, `ts.Diagnostic`s may also be produced
|
35292 | * directly for issues within the template which are identified during generation. These issues are
|
35293 | * recorded in either the `domSchemaChecker` (which checks usage of DOM elements and bindings) as
|
35294 | * well as the `oobRecorder` (which records errors when the type-checking code generator is unable
|
35295 | * to sufficiently understand a template).
|
35296 | *
|
35297 | * @param env an `Environment` into which type-checking code will be generated.
|
35298 | * @param ref a `Reference` to the component class which should be type-checked.
|
35299 | * @param name a `ts.Identifier` to use for the generated `ts.FunctionDeclaration`.
|
35300 | * @param meta metadata about the component's template and the function being generated.
|
35301 | * @param domSchemaChecker used to check and record errors regarding improper usage of DOM elements
|
35302 | * and bindings.
|
35303 | * @param oobRecorder used to record errors regarding template elements which could not be correctly
|
35304 | * translated into types during TCB generation.
|
35305 | */
|
35306 | function generateTypeCheckBlock(env, ref, name, meta, domSchemaChecker, oobRecorder) {
|
35307 | const tcb = new Context$1(env, domSchemaChecker, oobRecorder, meta.id, meta.boundTarget, meta.pipes, meta.schemas);
|
35308 | const scope = Scope$1.forNodes(tcb, null, tcb.boundTarget.target.template, /* guard */ null);
|
35309 | const ctxRawType = env.referenceType(ref);
|
35310 | if (!ts$1.isTypeReferenceNode(ctxRawType)) {
|
35311 | throw new Error(`Expected TypeReferenceNode when referencing the ctx param for ${ref.debugName}`);
|
35312 | }
|
35313 | const paramList = [tcbCtxParam(ref.node, ctxRawType.typeName, env.config.useContextGenericType)];
|
35314 | const scopeStatements = scope.render();
|
35315 | const innerBody = ts$1.createBlock([
|
35316 | ...env.getPreludeStatements(),
|
35317 | ...scopeStatements,
|
35318 | ]);
|
35319 | // Wrap the body in an "if (true)" expression. This is unnecessary but has the effect of causing
|
35320 | // the `ts.Printer` to format the type-check block nicely.
|
35321 | const body = ts$1.createBlock([ts$1.createIf(ts$1.createTrue(), innerBody, undefined)]);
|
35322 | const fnDecl = ts$1.createFunctionDeclaration(
|
35323 | /* decorators */ undefined,
|
35324 | /* modifiers */ undefined,
|
35325 | /* asteriskToken */ undefined,
|
35326 | /* name */ name,
|
35327 | /* typeParameters */ env.config.useContextGenericType ? ref.node.typeParameters : undefined,
|
35328 | /* parameters */ paramList,
|
35329 | /* type */ undefined,
|
35330 | /* body */ body);
|
35331 | addTemplateId(fnDecl, meta.id);
|
35332 | return fnDecl;
|
35333 | }
|
35334 | /**
|
35335 | * A code generation operation that's involved in the construction of a Type Check Block.
|
35336 | *
|
35337 | * The generation of a TCB is non-linear. Bindings within a template may result in the need to
|
35338 | * construct certain types earlier than they otherwise would be constructed. That is, if the
|
35339 | * generation of a TCB for a template is broken down into specific operations (constructing a
|
35340 | * directive, extracting a variable from a let- operation, etc), then it's possible for operations
|
35341 | * earlier in the sequence to depend on operations which occur later in the sequence.
|
35342 | *
|
35343 | * `TcbOp` abstracts the different types of operations which are required to convert a template into
|
35344 | * a TCB. This allows for two phases of processing for the template, where 1) a linear sequence of
|
35345 | * `TcbOp`s is generated, and then 2) these operations are executed, not necessarily in linear
|
35346 | * order.
|
35347 | *
|
35348 | * Each `TcbOp` may insert statements into the body of the TCB, and also optionally return a
|
35349 | * `ts.Expression` which can be used to reference the operation's result.
|
35350 | */
|
35351 | class TcbOp {
|
35352 | /**
|
35353 | * Replacement value or operation used while this `TcbOp` is executing (i.e. to resolve circular
|
35354 | * references during its execution).
|
35355 | *
|
35356 | * This is usually a `null!` expression (which asks TS to infer an appropriate type), but another
|
35357 | * `TcbOp` can be returned in cases where additional code generation is necessary to deal with
|
35358 | * circular references.
|
35359 | */
|
35360 | circularFallback() {
|
35361 | return INFER_TYPE_FOR_CIRCULAR_OP_EXPR;
|
35362 | }
|
35363 | }
|
35364 | /**
|
35365 | * A `TcbOp` which creates an expression for a native DOM element (or web component) from a
|
35366 | * `TmplAstElement`.
|
35367 | *
|
35368 | * Executing this operation returns a reference to the element variable.
|
35369 | */
|
35370 | class TcbElementOp extends TcbOp {
|
35371 | constructor(tcb, scope, element) {
|
35372 | super();
|
35373 | this.tcb = tcb;
|
35374 | this.scope = scope;
|
35375 | this.element = element;
|
35376 | }
|
35377 | get optional() {
|
35378 | // The statement generated by this operation is only used for type-inference of the DOM
|
35379 | // element's type and won't report diagnostics by itself, so the operation is marked as optional
|
35380 | // to avoid generating statements for DOM elements that are never referenced.
|
35381 | return true;
|
35382 | }
|
35383 | execute() {
|
35384 | const id = this.tcb.allocateId();
|
35385 | // Add the declaration of the element using document.createElement.
|
35386 | const initializer = tsCreateElement(this.element.name);
|
35387 | addParseSpanInfo(initializer, this.element.startSourceSpan || this.element.sourceSpan);
|
35388 | this.scope.addStatement(tsCreateVariable(id, initializer));
|
35389 | return id;
|
35390 | }
|
35391 | }
|
35392 | /**
|
35393 | * A `TcbOp` which creates an expression for particular let- `TmplAstVariable` on a
|
35394 | * `TmplAstTemplate`'s context.
|
35395 | *
|
35396 | * Executing this operation returns a reference to the variable variable (lol).
|
35397 | */
|
35398 | class TcbVariableOp extends TcbOp {
|
35399 | constructor(tcb, scope, template, variable) {
|
35400 | super();
|
35401 | this.tcb = tcb;
|
35402 | this.scope = scope;
|
35403 | this.template = template;
|
35404 | this.variable = variable;
|
35405 | }
|
35406 | get optional() {
|
35407 | return false;
|
35408 | }
|
35409 | execute() {
|
35410 | // Look for a context variable for the template.
|
35411 | const ctx = this.scope.resolve(this.template);
|
35412 | // Allocate an identifier for the TmplAstVariable, and initialize it to a read of the variable
|
35413 | // on the template context.
|
35414 | const id = this.tcb.allocateId();
|
35415 | const initializer = ts$1.createPropertyAccess(
|
35416 | /* expression */ ctx,
|
35417 | /* name */ this.variable.value || '$implicit');
|
35418 | addParseSpanInfo(id, this.variable.keySpan);
|
35419 | // Declare the variable, and return its identifier.
|
35420 | let variable;
|
35421 | if (this.variable.valueSpan !== undefined) {
|
35422 | addParseSpanInfo(initializer, this.variable.valueSpan);
|
35423 | variable = tsCreateVariable(id, wrapForTypeChecker(initializer));
|
35424 | }
|
35425 | else {
|
35426 | variable = tsCreateVariable(id, initializer);
|
35427 | }
|
35428 | addParseSpanInfo(variable.declarationList.declarations[0], this.variable.sourceSpan);
|
35429 | this.scope.addStatement(variable);
|
35430 | return id;
|
35431 | }
|
35432 | }
|
35433 | /**
|
35434 | * A `TcbOp` which generates a variable for a `TmplAstTemplate`'s context.
|
35435 | *
|
35436 | * Executing this operation returns a reference to the template's context variable.
|
35437 | */
|
35438 | class TcbTemplateContextOp extends TcbOp {
|
35439 | constructor(tcb, scope) {
|
35440 | super();
|
35441 | this.tcb = tcb;
|
35442 | this.scope = scope;
|
35443 | // The declaration of the context variable is only needed when the context is actually referenced.
|
35444 | this.optional = true;
|
35445 | }
|
35446 | execute() {
|
35447 | // Allocate a template ctx variable and declare it with an 'any' type. The type of this variable
|
35448 | // may be narrowed as a result of template guard conditions.
|
35449 | const ctx = this.tcb.allocateId();
|
35450 | const type = ts$1.createKeywordTypeNode(ts$1.SyntaxKind.AnyKeyword);
|
35451 | this.scope.addStatement(tsDeclareVariable(ctx, type));
|
35452 | return ctx;
|
35453 | }
|
35454 | }
|
35455 | /**
|
35456 | * A `TcbOp` which descends into a `TmplAstTemplate`'s children and generates type-checking code for
|
35457 | * them.
|
35458 | *
|
35459 | * This operation wraps the children's type-checking code in an `if` block, which may include one
|
35460 | * or more type guard conditions that narrow types within the template body.
|
35461 | */
|
35462 | class TcbTemplateBodyOp extends TcbOp {
|
35463 | constructor(tcb, scope, template) {
|
35464 | super();
|
35465 | this.tcb = tcb;
|
35466 | this.scope = scope;
|
35467 | this.template = template;
|
35468 | }
|
35469 | get optional() {
|
35470 | return false;
|
35471 | }
|
35472 | execute() {
|
35473 | // An `if` will be constructed, within which the template's children will be type checked. The
|
35474 | // `if` is used for two reasons: it creates a new syntactic scope, isolating variables declared
|
35475 | // in the template's TCB from the outer context, and it allows any directives on the templates
|
35476 | // to perform type narrowing of either expressions or the template's context.
|
35477 | //
|
35478 | // The guard is the `if` block's condition. It's usually set to `true` but directives that exist
|
35479 | // on the template can trigger extra guard expressions that serve to narrow types within the
|
35480 | // `if`. `guard` is calculated by starting with `true` and adding other conditions as needed.
|
35481 | // Collect these into `guards` by processing the directives.
|
35482 | const directiveGuards = [];
|
35483 | const directives = this.tcb.boundTarget.getDirectivesOfNode(this.template);
|
35484 | if (directives !== null) {
|
35485 | for (const dir of directives) {
|
35486 | const dirInstId = this.scope.resolve(this.template, dir);
|
35487 | const dirId = this.tcb.env.reference(dir.ref);
|
35488 | // There are two kinds of guards. Template guards (ngTemplateGuards) allow type narrowing of
|
35489 | // the expression passed to an @Input of the directive. Scan the directive to see if it has
|
35490 | // any template guards, and generate them if needed.
|
35491 | dir.ngTemplateGuards.forEach(guard => {
|
35492 | // For each template guard function on the directive, look for a binding to that input.
|
35493 | const boundInput = this.template.inputs.find(i => i.name === guard.inputName) ||
|
35494 | this.template.templateAttrs.find((i) => i instanceof BoundAttribute && i.name === guard.inputName);
|
35495 | if (boundInput !== undefined) {
|
35496 | // If there is such a binding, generate an expression for it.
|
35497 | const expr = tcbExpression(boundInput.value, this.tcb, this.scope);
|
35498 | // The expression has already been checked in the type constructor invocation, so
|
35499 | // it should be ignored when used within a template guard.
|
35500 | markIgnoreDiagnostics(expr);
|
35501 | if (guard.type === 'binding') {
|
35502 | // Use the binding expression itself as guard.
|
35503 | directiveGuards.push(expr);
|
35504 | }
|
35505 | else {
|
35506 | // Call the guard function on the directive with the directive instance and that
|
35507 | // expression.
|
35508 | const guardInvoke = tsCallMethod(dirId, `ngTemplateGuard_${guard.inputName}`, [
|
35509 | dirInstId,
|
35510 | expr,
|
35511 | ]);
|
35512 | addParseSpanInfo(guardInvoke, boundInput.value.sourceSpan);
|
35513 | directiveGuards.push(guardInvoke);
|
35514 | }
|
35515 | }
|
35516 | });
|
35517 | // The second kind of guard is a template context guard. This guard narrows the template
|
35518 | // rendering context variable `ctx`.
|
35519 | if (dir.hasNgTemplateContextGuard && this.tcb.env.config.applyTemplateContextGuards) {
|
35520 | const ctx = this.scope.resolve(this.template);
|
35521 | const guardInvoke = tsCallMethod(dirId, 'ngTemplateContextGuard', [dirInstId, ctx]);
|
35522 | addParseSpanInfo(guardInvoke, this.template.sourceSpan);
|
35523 | directiveGuards.push(guardInvoke);
|
35524 | }
|
35525 | }
|
35526 | }
|
35527 | // By default the guard is simply `true`.
|
35528 | let guard = null;
|
35529 | // If there are any guards from directives, use them instead.
|
35530 | if (directiveGuards.length > 0) {
|
35531 | // Pop the first value and use it as the initializer to reduce(). This way, a single guard
|
35532 | // will be used on its own, but two or more will be combined into binary AND expressions.
|
35533 | guard = directiveGuards.reduce((expr, dirGuard) => ts$1.createBinary(expr, ts$1.SyntaxKind.AmpersandAmpersandToken, dirGuard), directiveGuards.pop());
|
35534 | }
|
35535 | // Create a new Scope for the template. This constructs the list of operations for the template
|
35536 | // children, as well as tracks bindings within the template.
|
35537 | const tmplScope = Scope$1.forNodes(this.tcb, this.scope, this.template, guard);
|
35538 | // Render the template's `Scope` into its statements.
|
35539 | const statements = tmplScope.render();
|
35540 | if (statements.length === 0) {
|
35541 | // As an optimization, don't generate the scope's block if it has no statements. This is
|
35542 | // beneficial for templates that contain for example `<span *ngIf="first"></span>`, in which
|
35543 | // case there's no need to render the `NgIf` guard expression. This seems like a minor
|
35544 | // improvement, however it reduces the number of flow-node antecedents that TypeScript needs
|
35545 | // to keep into account for such cases, resulting in an overall reduction of
|
35546 | // type-checking time.
|
35547 | return null;
|
35548 | }
|
35549 | let tmplBlock = ts$1.createBlock(statements);
|
35550 | if (guard !== null) {
|
35551 | // The scope has a guard that needs to be applied, so wrap the template block into an `if`
|
35552 | // statement containing the guard expression.
|
35553 | tmplBlock = ts$1.createIf(/* expression */ guard, /* thenStatement */ tmplBlock);
|
35554 | }
|
35555 | this.scope.addStatement(tmplBlock);
|
35556 | return null;
|
35557 | }
|
35558 | }
|
35559 | /**
|
35560 | * A `TcbOp` which renders a text binding (interpolation) into the TCB.
|
35561 | *
|
35562 | * Executing this operation returns nothing.
|
35563 | */
|
35564 | class TcbTextInterpolationOp extends TcbOp {
|
35565 | constructor(tcb, scope, binding) {
|
35566 | super();
|
35567 | this.tcb = tcb;
|
35568 | this.scope = scope;
|
35569 | this.binding = binding;
|
35570 | }
|
35571 | get optional() {
|
35572 | return false;
|
35573 | }
|
35574 | execute() {
|
35575 | const expr = tcbExpression(this.binding.value, this.tcb, this.scope);
|
35576 | this.scope.addStatement(ts$1.createExpressionStatement(expr));
|
35577 | return null;
|
35578 | }
|
35579 | }
|
35580 | /**
|
35581 | * A `TcbOp` which constructs an instance of a directive _without_ setting any of its inputs. Inputs
|
35582 | * are later set in the `TcbDirectiveInputsOp`. Type checking was found to be faster when done in
|
35583 | * this way as opposed to `TcbDirectiveCtorOp` which is only necessary when the directive is
|
35584 | * generic.
|
35585 | *
|
35586 | * Executing this operation returns a reference to the directive instance variable with its inferred
|
35587 | * type.
|
35588 | */
|
35589 | class TcbDirectiveTypeOp extends TcbOp {
|
35590 | constructor(tcb, scope, node, dir) {
|
35591 | super();
|
35592 | this.tcb = tcb;
|
35593 | this.scope = scope;
|
35594 | this.node = node;
|
35595 | this.dir = dir;
|
35596 | }
|
35597 | get optional() {
|
35598 | // The statement generated by this operation is only used to declare the directive's type and
|
35599 | // won't report diagnostics by itself, so the operation is marked as optional to avoid
|
35600 | // generating declarations for directives that don't have any inputs/outputs.
|
35601 | return true;
|
35602 | }
|
35603 | execute() {
|
35604 | const id = this.tcb.allocateId();
|
35605 | const type = this.tcb.env.referenceType(this.dir.ref);
|
35606 | addExpressionIdentifier(type, ExpressionIdentifier.DIRECTIVE);
|
35607 | addParseSpanInfo(type, this.node.startSourceSpan || this.node.sourceSpan);
|
35608 | this.scope.addStatement(tsDeclareVariable(id, type));
|
35609 | return id;
|
35610 | }
|
35611 | }
|
35612 | /**
|
35613 | * A `TcbOp` which creates a variable for a local ref in a template.
|
35614 | * The initializer for the variable is the variable expression for the directive, template, or
|
35615 | * element the ref refers to. When the reference is used in the template, those TCB statements will
|
35616 | * access this variable as well. For example:
|
35617 | * ```
|
35618 | * var _t1 = document.createElement('div');
|
35619 | * var _t2 = _t1;
|
35620 | * _t2.value
|
35621 | * ```
|
35622 | * This operation supports more fluent lookups for the `TemplateTypeChecker` when getting a symbol
|
35623 | * for a reference. In most cases, this isn't essential; that is, the information for the symbol
|
35624 | * could be gathered without this operation using the `BoundTarget`. However, for the case of
|
35625 | * ng-template references, we will need this reference variable to not only provide a location in
|
35626 | * the shim file, but also to narrow the variable to the correct `TemplateRef<T>` type rather than
|
35627 | * `TemplateRef<any>` (this work is still TODO).
|
35628 | *
|
35629 | * Executing this operation returns a reference to the directive instance variable with its inferred
|
35630 | * type.
|
35631 | */
|
35632 | class TcbReferenceOp extends TcbOp {
|
35633 | constructor(tcb, scope, node, host, target) {
|
35634 | super();
|
35635 | this.tcb = tcb;
|
35636 | this.scope = scope;
|
35637 | this.node = node;
|
35638 | this.host = host;
|
35639 | this.target = target;
|
35640 | // The statement generated by this operation is only used to for the Type Checker
|
35641 | // so it can map a reference variable in the template directly to a node in the TCB.
|
35642 | this.optional = true;
|
35643 | }
|
35644 | execute() {
|
35645 | const id = this.tcb.allocateId();
|
35646 | let initializer = this.target instanceof Template || this.target instanceof Element ?
|
35647 | this.scope.resolve(this.target) :
|
35648 | this.scope.resolve(this.host, this.target);
|
35649 | // The reference is either to an element, an <ng-template> node, or to a directive on an
|
35650 | // element or template.
|
35651 | if ((this.target instanceof Element && !this.tcb.env.config.checkTypeOfDomReferences) ||
|
35652 | !this.tcb.env.config.checkTypeOfNonDomReferences) {
|
35653 | // References to DOM nodes are pinned to 'any' when `checkTypeOfDomReferences` is `false`.
|
35654 | // References to `TemplateRef`s and directives are pinned to 'any' when
|
35655 | // `checkTypeOfNonDomReferences` is `false`.
|
35656 | initializer =
|
35657 | ts$1.createAsExpression(initializer, ts$1.createKeywordTypeNode(ts$1.SyntaxKind.AnyKeyword));
|
35658 | }
|
35659 | else if (this.target instanceof Template) {
|
35660 | // Direct references to an <ng-template> node simply require a value of type
|
35661 | // `TemplateRef<any>`. To get this, an expression of the form
|
35662 | // `(_t1 as any as TemplateRef<any>)` is constructed.
|
35663 | initializer =
|
35664 | ts$1.createAsExpression(initializer, ts$1.createKeywordTypeNode(ts$1.SyntaxKind.AnyKeyword));
|
35665 | initializer = ts$1.createAsExpression(initializer, this.tcb.env.referenceExternalType('@angular/core', 'TemplateRef', [DYNAMIC_TYPE]));
|
35666 | initializer = ts$1.createParen(initializer);
|
35667 | }
|
35668 | addParseSpanInfo(initializer, this.node.sourceSpan);
|
35669 | addParseSpanInfo(id, this.node.keySpan);
|
35670 | this.scope.addStatement(tsCreateVariable(id, initializer));
|
35671 | return id;
|
35672 | }
|
35673 | }
|
35674 | /**
|
35675 | * A `TcbOp` which is used when the target of a reference is missing. This operation generates a
|
35676 | * variable of type any for usages of the invalid reference to resolve to. The invalid reference
|
35677 | * itself is recorded out-of-band.
|
35678 | */
|
35679 | class TcbInvalidReferenceOp extends TcbOp {
|
35680 | constructor(tcb, scope) {
|
35681 | super();
|
35682 | this.tcb = tcb;
|
35683 | this.scope = scope;
|
35684 | // The declaration of a missing reference is only needed when the reference is resolved.
|
35685 | this.optional = true;
|
35686 | }
|
35687 | execute() {
|
35688 | const id = this.tcb.allocateId();
|
35689 | this.scope.addStatement(tsCreateVariable(id, NULL_AS_ANY));
|
35690 | return id;
|
35691 | }
|
35692 | }
|
35693 | /**
|
35694 | * A `TcbOp` which constructs an instance of a directive with types inferred from its inputs. The
|
35695 | * inputs themselves are not checked here; checking of inputs is achieved in `TcbDirectiveInputsOp`.
|
35696 | * Any errors reported in this statement are ignored, as the type constructor call is only present
|
35697 | * for type-inference.
|
35698 | *
|
35699 | * When a Directive is generic, it is required that the TCB generates the instance using this method
|
35700 | * in order to infer the type information correctly.
|
35701 | *
|
35702 | * Executing this operation returns a reference to the directive instance variable with its inferred
|
35703 | * type.
|
35704 | */
|
35705 | class TcbDirectiveCtorOp extends TcbOp {
|
35706 | constructor(tcb, scope, node, dir) {
|
35707 | super();
|
35708 | this.tcb = tcb;
|
35709 | this.scope = scope;
|
35710 | this.node = node;
|
35711 | this.dir = dir;
|
35712 | }
|
35713 | get optional() {
|
35714 | // The statement generated by this operation is only used to infer the directive's type and
|
35715 | // won't report diagnostics by itself, so the operation is marked as optional.
|
35716 | return true;
|
35717 | }
|
35718 | execute() {
|
35719 | const id = this.tcb.allocateId();
|
35720 | addExpressionIdentifier(id, ExpressionIdentifier.DIRECTIVE);
|
35721 | addParseSpanInfo(id, this.node.startSourceSpan || this.node.sourceSpan);
|
35722 | const genericInputs = new Map();
|
35723 | const inputs = getBoundInputs(this.dir, this.node, this.tcb);
|
35724 | for (const input of inputs) {
|
35725 | // Skip text attributes if configured to do so.
|
35726 | if (!this.tcb.env.config.checkTypeOfAttributes &&
|
35727 | input.attribute instanceof TextAttribute) {
|
35728 | continue;
|
35729 | }
|
35730 | for (const fieldName of input.fieldNames) {
|
35731 | // Skip the field if an attribute has already been bound to it; we can't have a duplicate
|
35732 | // key in the type constructor call.
|
35733 | if (genericInputs.has(fieldName)) {
|
35734 | continue;
|
35735 | }
|
35736 | const expression = translateInput(input.attribute, this.tcb, this.scope);
|
35737 | genericInputs.set(fieldName, {
|
35738 | type: 'binding',
|
35739 | field: fieldName,
|
35740 | expression,
|
35741 | sourceSpan: input.attribute.sourceSpan
|
35742 | });
|
35743 | }
|
35744 | }
|
35745 | // Add unset directive inputs for each of the remaining unset fields.
|
35746 | for (const [fieldName] of this.dir.inputs) {
|
35747 | if (!genericInputs.has(fieldName)) {
|
35748 | genericInputs.set(fieldName, { type: 'unset', field: fieldName });
|
35749 | }
|
35750 | }
|
35751 | // Call the type constructor of the directive to infer a type, and assign the directive
|
35752 | // instance.
|
35753 | const typeCtor = tcbCallTypeCtor(this.dir, this.tcb, Array.from(genericInputs.values()));
|
35754 | markIgnoreDiagnostics(typeCtor);
|
35755 | this.scope.addStatement(tsCreateVariable(id, typeCtor));
|
35756 | return id;
|
35757 | }
|
35758 | circularFallback() {
|
35759 | return new TcbDirectiveCtorCircularFallbackOp(this.tcb, this.scope, this.node, this.dir);
|
35760 | }
|
35761 | }
|
35762 | /**
|
35763 | * A `TcbOp` which generates code to check input bindings on an element that correspond with the
|
35764 | * members of a directive.
|
35765 | *
|
35766 | * Executing this operation returns nothing.
|
35767 | */
|
35768 | class TcbDirectiveInputsOp extends TcbOp {
|
35769 | constructor(tcb, scope, node, dir) {
|
35770 | super();
|
35771 | this.tcb = tcb;
|
35772 | this.scope = scope;
|
35773 | this.node = node;
|
35774 | this.dir = dir;
|
35775 | }
|
35776 | get optional() {
|
35777 | return false;
|
35778 | }
|
35779 | execute() {
|
35780 | let dirId = null;
|
35781 | // TODO(joost): report duplicate properties
|
35782 | const inputs = getBoundInputs(this.dir, this.node, this.tcb);
|
35783 | for (const input of inputs) {
|
35784 | // For bound inputs, the property is assigned the binding expression.
|
35785 | let expr = translateInput(input.attribute, this.tcb, this.scope);
|
35786 | if (!this.tcb.env.config.checkTypeOfInputBindings) {
|
35787 | // If checking the type of bindings is disabled, cast the resulting expression to 'any'
|
35788 | // before the assignment.
|
35789 | expr = tsCastToAny(expr);
|
35790 | }
|
35791 | else if (!this.tcb.env.config.strictNullInputBindings) {
|
35792 | // If strict null checks are disabled, erase `null` and `undefined` from the type by
|
35793 | // wrapping the expression in a non-null assertion.
|
35794 | expr = ts$1.createNonNullExpression(expr);
|
35795 | }
|
35796 | let assignment = wrapForDiagnostics(expr);
|
35797 | for (const fieldName of input.fieldNames) {
|
35798 | let target;
|
35799 | if (this.dir.coercedInputFields.has(fieldName)) {
|
35800 | // The input has a coercion declaration which should be used instead of assigning the
|
35801 | // expression into the input field directly. To achieve this, a variable is declared
|
35802 | // with a type of `typeof Directive.ngAcceptInputType_fieldName` which is then used as
|
35803 | // target of the assignment.
|
35804 | const dirTypeRef = this.tcb.env.referenceType(this.dir.ref);
|
35805 | if (!ts$1.isTypeReferenceNode(dirTypeRef)) {
|
35806 | throw new Error(`Expected TypeReferenceNode from reference to ${this.dir.ref.debugName}`);
|
35807 | }
|
35808 | const id = this.tcb.allocateId();
|
35809 | const type = tsCreateTypeQueryForCoercedInput(dirTypeRef.typeName, fieldName);
|
35810 | this.scope.addStatement(tsDeclareVariable(id, type));
|
35811 | target = id;
|
35812 | }
|
35813 | else if (this.dir.undeclaredInputFields.has(fieldName)) {
|
35814 | // If no coercion declaration is present nor is the field declared (i.e. the input is
|
35815 | // declared in a `@Directive` or `@Component` decorator's `inputs` property) there is no
|
35816 | // assignment target available, so this field is skipped.
|
35817 | continue;
|
35818 | }
|
35819 | else if (!this.tcb.env.config.honorAccessModifiersForInputBindings &&
|
35820 | this.dir.restrictedInputFields.has(fieldName)) {
|
35821 | // If strict checking of access modifiers is disabled and the field is restricted
|
35822 | // (i.e. private/protected/readonly), generate an assignment into a temporary variable
|
35823 | // that has the type of the field. This achieves type-checking but circumvents the access
|
35824 | // modifiers.
|
35825 | if (dirId === null) {
|
35826 | dirId = this.scope.resolve(this.node, this.dir);
|
35827 | }
|
35828 | const id = this.tcb.allocateId();
|
35829 | const dirTypeRef = this.tcb.env.referenceType(this.dir.ref);
|
35830 | if (!ts$1.isTypeReferenceNode(dirTypeRef)) {
|
35831 | throw new Error(`Expected TypeReferenceNode from reference to ${this.dir.ref.debugName}`);
|
35832 | }
|
35833 | const type = ts$1.createIndexedAccessTypeNode(ts$1.createTypeQueryNode(dirId), ts$1.createLiteralTypeNode(ts$1.createStringLiteral(fieldName)));
|
35834 | const temp = tsDeclareVariable(id, type);
|
35835 | this.scope.addStatement(temp);
|
35836 | target = id;
|
35837 | }
|
35838 | else {
|
35839 | if (dirId === null) {
|
35840 | dirId = this.scope.resolve(this.node, this.dir);
|
35841 | }
|
35842 | // To get errors assign directly to the fields on the instance, using property access
|
35843 | // when possible. String literal fields may not be valid JS identifiers so we use
|
35844 | // literal element access instead for those cases.
|
35845 | target = this.dir.stringLiteralInputFields.has(fieldName) ?
|
35846 | ts$1.createElementAccess(dirId, ts$1.createStringLiteral(fieldName)) :
|
35847 | ts$1.createPropertyAccess(dirId, ts$1.createIdentifier(fieldName));
|
35848 | }
|
35849 | if (input.attribute.keySpan !== undefined) {
|
35850 | addParseSpanInfo(target, input.attribute.keySpan);
|
35851 | }
|
35852 | // Finally the assignment is extended by assigning it into the target expression.
|
35853 | assignment = ts$1.createBinary(target, ts$1.SyntaxKind.EqualsToken, assignment);
|
35854 | }
|
35855 | addParseSpanInfo(assignment, input.attribute.sourceSpan);
|
35856 | // Ignore diagnostics for text attributes if configured to do so.
|
35857 | if (!this.tcb.env.config.checkTypeOfAttributes &&
|
35858 | input.attribute instanceof TextAttribute) {
|
35859 | markIgnoreDiagnostics(assignment);
|
35860 | }
|
35861 | this.scope.addStatement(ts$1.createExpressionStatement(assignment));
|
35862 | }
|
35863 | return null;
|
35864 | }
|
35865 | }
|
35866 | /**
|
35867 | * A `TcbOp` which is used to generate a fallback expression if the inference of a directive type
|
35868 | * via `TcbDirectiveCtorOp` requires a reference to its own type. This can happen using a template
|
35869 | * reference:
|
35870 | *
|
35871 | * ```html
|
35872 | * <some-cmp #ref [prop]="ref.foo"></some-cmp>
|
35873 | * ```
|
35874 | *
|
35875 | * In this case, `TcbDirectiveCtorCircularFallbackOp` will add a second inference of the directive
|
35876 | * type to the type-check block, this time calling the directive's type constructor without any
|
35877 | * input expressions. This infers the widest possible supertype for the directive, which is used to
|
35878 | * resolve any recursive references required to infer the real type.
|
35879 | */
|
35880 | class TcbDirectiveCtorCircularFallbackOp extends TcbOp {
|
35881 | constructor(tcb, scope, node, dir) {
|
35882 | super();
|
35883 | this.tcb = tcb;
|
35884 | this.scope = scope;
|
35885 | this.node = node;
|
35886 | this.dir = dir;
|
35887 | }
|
35888 | get optional() {
|
35889 | return false;
|
35890 | }
|
35891 | execute() {
|
35892 | const id = this.tcb.allocateId();
|
35893 | const typeCtor = this.tcb.env.typeCtorFor(this.dir);
|
35894 | const circularPlaceholder = ts$1.createCall(typeCtor, /* typeArguments */ undefined, [ts$1.createNonNullExpression(ts$1.createNull())]);
|
35895 | this.scope.addStatement(tsCreateVariable(id, circularPlaceholder));
|
35896 | return id;
|
35897 | }
|
35898 | }
|
35899 | /**
|
35900 | * A `TcbOp` which feeds elements and unclaimed properties to the `DomSchemaChecker`.
|
35901 | *
|
35902 | * The DOM schema is not checked via TCB code generation. Instead, the `DomSchemaChecker` ingests
|
35903 | * elements and property bindings and accumulates synthetic `ts.Diagnostic`s out-of-band. These are
|
35904 | * later merged with the diagnostics generated from the TCB.
|
35905 | *
|
35906 | * For convenience, the TCB iteration of the template is used to drive the `DomSchemaChecker` via
|
35907 | * the `TcbDomSchemaCheckerOp`.
|
35908 | */
|
35909 | class TcbDomSchemaCheckerOp extends TcbOp {
|
35910 | constructor(tcb, element, checkElement, claimedInputs) {
|
35911 | super();
|
35912 | this.tcb = tcb;
|
35913 | this.element = element;
|
35914 | this.checkElement = checkElement;
|
35915 | this.claimedInputs = claimedInputs;
|
35916 | }
|
35917 | get optional() {
|
35918 | return false;
|
35919 | }
|
35920 | execute() {
|
35921 | if (this.checkElement) {
|
35922 | this.tcb.domSchemaChecker.checkElement(this.tcb.id, this.element, this.tcb.schemas);
|
35923 | }
|
35924 | // TODO(alxhub): this could be more efficient.
|
35925 | for (const binding of this.element.inputs) {
|
35926 | if (binding.type === 0 /* Property */ && this.claimedInputs.has(binding.name)) {
|
35927 | // Skip this binding as it was claimed by a directive.
|
35928 | continue;
|
35929 | }
|
35930 | if (binding.type === 0 /* Property */) {
|
35931 | if (binding.name !== 'style' && binding.name !== 'class') {
|
35932 | // A direct binding to a property.
|
35933 | const propertyName = ATTR_TO_PROP[binding.name] || binding.name;
|
35934 | this.tcb.domSchemaChecker.checkProperty(this.tcb.id, this.element, propertyName, binding.sourceSpan, this.tcb.schemas);
|
35935 | }
|
35936 | }
|
35937 | }
|
35938 | return null;
|
35939 | }
|
35940 | }
|
35941 | /**
|
35942 | * Mapping between attributes names that don't correspond to their element property names.
|
35943 | * Note: this mapping has to be kept in sync with the equally named mapping in the runtime.
|
35944 | */
|
35945 | const ATTR_TO_PROP = {
|
35946 | 'class': 'className',
|
35947 | 'for': 'htmlFor',
|
35948 | 'formaction': 'formAction',
|
35949 | 'innerHtml': 'innerHTML',
|
35950 | 'readonly': 'readOnly',
|
35951 | 'tabindex': 'tabIndex',
|
35952 | };
|
35953 | /**
|
35954 | * A `TcbOp` which generates code to check "unclaimed inputs" - bindings on an element which were
|
35955 | * not attributed to any directive or component, and are instead processed against the HTML element
|
35956 | * itself.
|
35957 | *
|
35958 | * Currently, only the expressions of these bindings are checked. The targets of the bindings are
|
35959 | * checked against the DOM schema via a `TcbDomSchemaCheckerOp`.
|
35960 | *
|
35961 | * Executing this operation returns nothing.
|
35962 | */
|
35963 | class TcbUnclaimedInputsOp extends TcbOp {
|
35964 | constructor(tcb, scope, element, claimedInputs) {
|
35965 | super();
|
35966 | this.tcb = tcb;
|
35967 | this.scope = scope;
|
35968 | this.element = element;
|
35969 | this.claimedInputs = claimedInputs;
|
35970 | }
|
35971 | get optional() {
|
35972 | return false;
|
35973 | }
|
35974 | execute() {
|
35975 | // `this.inputs` contains only those bindings not matched by any directive. These bindings go to
|
35976 | // the element itself.
|
35977 | let elId = null;
|
35978 | // TODO(alxhub): this could be more efficient.
|
35979 | for (const binding of this.element.inputs) {
|
35980 | if (binding.type === 0 /* Property */ && this.claimedInputs.has(binding.name)) {
|
35981 | // Skip this binding as it was claimed by a directive.
|
35982 | continue;
|
35983 | }
|
35984 | let expr = tcbExpression(binding.value, this.tcb, this.scope);
|
35985 | if (!this.tcb.env.config.checkTypeOfInputBindings) {
|
35986 | // If checking the type of bindings is disabled, cast the resulting expression to 'any'
|
35987 | // before the assignment.
|
35988 | expr = tsCastToAny(expr);
|
35989 | }
|
35990 | else if (!this.tcb.env.config.strictNullInputBindings) {
|
35991 | // If strict null checks are disabled, erase `null` and `undefined` from the type by
|
35992 | // wrapping the expression in a non-null assertion.
|
35993 | expr = ts$1.createNonNullExpression(expr);
|
35994 | }
|
35995 | if (this.tcb.env.config.checkTypeOfDomBindings && binding.type === 0 /* Property */) {
|
35996 | if (binding.name !== 'style' && binding.name !== 'class') {
|
35997 | if (elId === null) {
|
35998 | elId = this.scope.resolve(this.element);
|
35999 | }
|
36000 | // A direct binding to a property.
|
36001 | const propertyName = ATTR_TO_PROP[binding.name] || binding.name;
|
36002 | const prop = ts$1.createElementAccess(elId, ts$1.createStringLiteral(propertyName));
|
36003 | const stmt = ts$1.createBinary(prop, ts$1.SyntaxKind.EqualsToken, wrapForDiagnostics(expr));
|
36004 | addParseSpanInfo(stmt, binding.sourceSpan);
|
36005 | this.scope.addStatement(ts$1.createExpressionStatement(stmt));
|
36006 | }
|
36007 | else {
|
36008 | this.scope.addStatement(ts$1.createExpressionStatement(expr));
|
36009 | }
|
36010 | }
|
36011 | else {
|
36012 | // A binding to an animation, attribute, class or style. For now, only validate the right-
|
36013 | // hand side of the expression.
|
36014 | // TODO: properly check class and style bindings.
|
36015 | this.scope.addStatement(ts$1.createExpressionStatement(expr));
|
36016 | }
|
36017 | }
|
36018 | return null;
|
36019 | }
|
36020 | }
|
36021 | /**
|
36022 | * A `TcbOp` which generates code to check event bindings on an element that correspond with the
|
36023 | * outputs of a directive.
|
36024 | *
|
36025 | * Executing this operation returns nothing.
|
36026 | */
|
36027 | class TcbDirectiveOutputsOp extends TcbOp {
|
36028 | constructor(tcb, scope, node, dir) {
|
36029 | super();
|
36030 | this.tcb = tcb;
|
36031 | this.scope = scope;
|
36032 | this.node = node;
|
36033 | this.dir = dir;
|
36034 | }
|
36035 | get optional() {
|
36036 | return false;
|
36037 | }
|
36038 | execute() {
|
36039 | let dirId = null;
|
36040 | const outputs = this.dir.outputs;
|
36041 | for (const output of this.node.outputs) {
|
36042 | if (output.type !== 0 /* Regular */ || !outputs.hasBindingPropertyName(output.name)) {
|
36043 | continue;
|
36044 | }
|
36045 | // TODO(alxhub): consider supporting multiple fields with the same property name for outputs.
|
36046 | const field = outputs.getByBindingPropertyName(output.name)[0].classPropertyName;
|
36047 | if (dirId === null) {
|
36048 | dirId = this.scope.resolve(this.node, this.dir);
|
36049 | }
|
36050 | const outputField = ts$1.createElementAccess(dirId, ts$1.createStringLiteral(field));
|
36051 | addParseSpanInfo(outputField, output.keySpan);
|
36052 | if (this.tcb.env.config.checkTypeOfOutputEvents) {
|
36053 | // For strict checking of directive events, generate a call to the `subscribe` method
|
36054 | // on the directive's output field to let type information flow into the handler function's
|
36055 | // `$event` parameter.
|
36056 | const handler = tcbCreateEventHandler(output, this.tcb, this.scope, 0 /* Infer */);
|
36057 | const subscribeFn = ts$1.createPropertyAccess(outputField, 'subscribe');
|
36058 | const call = ts$1.createCall(subscribeFn, /* typeArguments */ undefined, [handler]);
|
36059 | addParseSpanInfo(call, output.sourceSpan);
|
36060 | this.scope.addStatement(ts$1.createExpressionStatement(call));
|
36061 | }
|
36062 | else {
|
36063 | // If strict checking of directive events is disabled:
|
36064 | //
|
36065 | // * We still generate the access to the output field as a statement in the TCB so consumers
|
36066 | // of the `TemplateTypeChecker` can still find the node for the class member for the
|
36067 | // output.
|
36068 | // * Emit a handler function where the `$event` parameter has an explicit `any` type.
|
36069 | this.scope.addStatement(ts$1.createExpressionStatement(outputField));
|
36070 | const handler = tcbCreateEventHandler(output, this.tcb, this.scope, 1 /* Any */);
|
36071 | this.scope.addStatement(ts$1.createExpressionStatement(handler));
|
36072 | }
|
36073 | ExpressionSemanticVisitor.visit(output.handler, this.tcb.id, this.tcb.boundTarget, this.tcb.oobRecorder);
|
36074 | }
|
36075 | return null;
|
36076 | }
|
36077 | }
|
36078 | /**
|
36079 | * A `TcbOp` which generates code to check "unclaimed outputs" - event bindings on an element which
|
36080 | * were not attributed to any directive or component, and are instead processed against the HTML
|
36081 | * element itself.
|
36082 | *
|
36083 | * Executing this operation returns nothing.
|
36084 | */
|
36085 | class TcbUnclaimedOutputsOp extends TcbOp {
|
36086 | constructor(tcb, scope, element, claimedOutputs) {
|
36087 | super();
|
36088 | this.tcb = tcb;
|
36089 | this.scope = scope;
|
36090 | this.element = element;
|
36091 | this.claimedOutputs = claimedOutputs;
|
36092 | }
|
36093 | get optional() {
|
36094 | return false;
|
36095 | }
|
36096 | execute() {
|
36097 | let elId = null;
|
36098 | // TODO(alxhub): this could be more efficient.
|
36099 | for (const output of this.element.outputs) {
|
36100 | if (this.claimedOutputs.has(output.name)) {
|
36101 | // Skip this event handler as it was claimed by a directive.
|
36102 | continue;
|
36103 | }
|
36104 | if (output.type === 1 /* Animation */) {
|
36105 | // Animation output bindings always have an `$event` parameter of type `AnimationEvent`.
|
36106 | const eventType = this.tcb.env.config.checkTypeOfAnimationEvents ?
|
36107 | this.tcb.env.referenceExternalType('@angular/animations', 'AnimationEvent') :
|
36108 | 1 /* Any */;
|
36109 | const handler = tcbCreateEventHandler(output, this.tcb, this.scope, eventType);
|
36110 | this.scope.addStatement(ts$1.createExpressionStatement(handler));
|
36111 | }
|
36112 | else if (this.tcb.env.config.checkTypeOfDomEvents) {
|
36113 | // If strict checking of DOM events is enabled, generate a call to `addEventListener` on
|
36114 | // the element instance so that TypeScript's type inference for
|
36115 | // `HTMLElement.addEventListener` using `HTMLElementEventMap` to infer an accurate type for
|
36116 | // `$event` depending on the event name. For unknown event names, TypeScript resorts to the
|
36117 | // base `Event` type.
|
36118 | const handler = tcbCreateEventHandler(output, this.tcb, this.scope, 0 /* Infer */);
|
36119 | if (elId === null) {
|
36120 | elId = this.scope.resolve(this.element);
|
36121 | }
|
36122 | const propertyAccess = ts$1.createPropertyAccess(elId, 'addEventListener');
|
36123 | addParseSpanInfo(propertyAccess, output.keySpan);
|
36124 | const call = ts$1.createCall(
|
36125 | /* expression */ propertyAccess,
|
36126 | /* typeArguments */ undefined,
|
36127 | /* arguments */ [ts$1.createStringLiteral(output.name), handler]);
|
36128 | addParseSpanInfo(call, output.sourceSpan);
|
36129 | this.scope.addStatement(ts$1.createExpressionStatement(call));
|
36130 | }
|
36131 | else {
|
36132 | // If strict checking of DOM inputs is disabled, emit a handler function where the `$event`
|
36133 | // parameter has an explicit `any` type.
|
36134 | const handler = tcbCreateEventHandler(output, this.tcb, this.scope, 1 /* Any */);
|
36135 | this.scope.addStatement(ts$1.createExpressionStatement(handler));
|
36136 | }
|
36137 | ExpressionSemanticVisitor.visit(output.handler, this.tcb.id, this.tcb.boundTarget, this.tcb.oobRecorder);
|
36138 | }
|
36139 | return null;
|
36140 | }
|
36141 | }
|
36142 | /**
|
36143 | * A `TcbOp` which generates a completion point for the component context.
|
36144 | *
|
36145 | * This completion point looks like `ctx. ;` in the TCB output, and does not produce diagnostics.
|
36146 | * TypeScript autocompletion APIs can be used at this completion point (after the '.') to produce
|
36147 | * autocompletion results of properties and methods from the template's component context.
|
36148 | */
|
36149 | class TcbComponentContextCompletionOp extends TcbOp {
|
36150 | constructor(scope) {
|
36151 | super();
|
36152 | this.scope = scope;
|
36153 | this.optional = false;
|
36154 | }
|
36155 | execute() {
|
36156 | const ctx = ts$1.createIdentifier('ctx');
|
36157 | const ctxDot = ts$1.createPropertyAccess(ctx, '');
|
36158 | markIgnoreDiagnostics(ctxDot);
|
36159 | addExpressionIdentifier(ctxDot, ExpressionIdentifier.COMPONENT_COMPLETION);
|
36160 | this.scope.addStatement(ts$1.createExpressionStatement(ctxDot));
|
36161 | return null;
|
36162 | }
|
36163 | }
|
36164 | /**
|
36165 | * Value used to break a circular reference between `TcbOp`s.
|
36166 | *
|
36167 | * This value is returned whenever `TcbOp`s have a circular dependency. The expression is a non-null
|
36168 | * assertion of the null value (in TypeScript, the expression `null!`). This construction will infer
|
36169 | * the least narrow type for whatever it's assigned to.
|
36170 | */
|
36171 | const INFER_TYPE_FOR_CIRCULAR_OP_EXPR = ts$1.createNonNullExpression(ts$1.createNull());
|
36172 | /**
|
36173 | * Overall generation context for the type check block.
|
36174 | *
|
36175 | * `Context` handles operations during code generation which are global with respect to the whole
|
36176 | * block. It's responsible for variable name allocation and management of any imports needed. It
|
36177 | * also contains the template metadata itself.
|
36178 | */
|
36179 | class Context$1 {
|
36180 | constructor(env, domSchemaChecker, oobRecorder, id, boundTarget, pipes, schemas) {
|
36181 | this.env = env;
|
36182 | this.domSchemaChecker = domSchemaChecker;
|
36183 | this.oobRecorder = oobRecorder;
|
36184 | this.id = id;
|
36185 | this.boundTarget = boundTarget;
|
36186 | this.pipes = pipes;
|
36187 | this.schemas = schemas;
|
36188 | this.nextId = 1;
|
36189 | }
|
36190 | /**
|
36191 | * Allocate a new variable name for use within the `Context`.
|
36192 | *
|
36193 | * Currently this uses a monotonically increasing counter, but in the future the variable name
|
36194 | * might change depending on the type of data being stored.
|
36195 | */
|
36196 | allocateId() {
|
36197 | return ts$1.createIdentifier(`_t${this.nextId++}`);
|
36198 | }
|
36199 | getPipeByName(name) {
|
36200 | if (!this.pipes.has(name)) {
|
36201 | return null;
|
36202 | }
|
36203 | return this.pipes.get(name);
|
36204 | }
|
36205 | }
|
36206 | /**
|
36207 | * Local scope within the type check block for a particular template.
|
36208 | *
|
36209 | * The top-level template and each nested `<ng-template>` have their own `Scope`, which exist in a
|
36210 | * hierarchy. The structure of this hierarchy mirrors the syntactic scopes in the generated type
|
36211 | * check block, where each nested template is encased in an `if` structure.
|
36212 | *
|
36213 | * As a template's `TcbOp`s are executed in a given `Scope`, statements are added via
|
36214 | * `addStatement()`. When this processing is complete, the `Scope` can be turned into a `ts.Block`
|
36215 | * via `renderToBlock()`.
|
36216 | *
|
36217 | * If a `TcbOp` requires the output of another, it can call `resolve()`.
|
36218 | */
|
36219 | class Scope$1 {
|
36220 | constructor(tcb, parent = null, guard = null) {
|
36221 | this.tcb = tcb;
|
36222 | this.parent = parent;
|
36223 | this.guard = guard;
|
36224 | /**
|
36225 | * A queue of operations which need to be performed to generate the TCB code for this scope.
|
36226 | *
|
36227 | * This array can contain either a `TcbOp` which has yet to be executed, or a `ts.Expression|null`
|
36228 | * representing the memoized result of executing the operation. As operations are executed, their
|
36229 | * results are written into the `opQueue`, overwriting the original operation.
|
36230 | *
|
36231 | * If an operation is in the process of being executed, it is temporarily overwritten here with
|
36232 | * `INFER_TYPE_FOR_CIRCULAR_OP_EXPR`. This way, if a cycle is encountered where an operation
|
36233 | * depends transitively on its own result, the inner operation will infer the least narrow type
|
36234 | * that fits instead. This has the same semantics as TypeScript itself when types are referenced
|
36235 | * circularly.
|
36236 | */
|
36237 | this.opQueue = [];
|
36238 | /**
|
36239 | * A map of `TmplAstElement`s to the index of their `TcbElementOp` in the `opQueue`
|
36240 | */
|
36241 | this.elementOpMap = new Map();
|
36242 | /**
|
36243 | * A map of maps which tracks the index of `TcbDirectiveCtorOp`s in the `opQueue` for each
|
36244 | * directive on a `TmplAstElement` or `TmplAstTemplate` node.
|
36245 | */
|
36246 | this.directiveOpMap = new Map();
|
36247 | /**
|
36248 | * A map of `TmplAstReference`s to the index of their `TcbReferenceOp` in the `opQueue`
|
36249 | */
|
36250 | this.referenceOpMap = new Map();
|
36251 | /**
|
36252 | * Map of immediately nested <ng-template>s (within this `Scope`) represented by `TmplAstTemplate`
|
36253 | * nodes to the index of their `TcbTemplateContextOp`s in the `opQueue`.
|
36254 | */
|
36255 | this.templateCtxOpMap = new Map();
|
36256 | /**
|
36257 | * Map of variables declared on the template that created this `Scope` (represented by
|
36258 | * `TmplAstVariable` nodes) to the index of their `TcbVariableOp`s in the `opQueue`.
|
36259 | */
|
36260 | this.varMap = new Map();
|
36261 | /**
|
36262 | * Statements for this template.
|
36263 | *
|
36264 | * Executing the `TcbOp`s in the `opQueue` populates this array.
|
36265 | */
|
36266 | this.statements = [];
|
36267 | }
|
36268 | /**
|
36269 | * Constructs a `Scope` given either a `TmplAstTemplate` or a list of `TmplAstNode`s.
|
36270 | *
|
36271 | * @param tcb the overall context of TCB generation.
|
36272 | * @param parent the `Scope` of the parent template (if any) or `null` if this is the root
|
36273 | * `Scope`.
|
36274 | * @param templateOrNodes either a `TmplAstTemplate` representing the template for which to
|
36275 | * calculate the `Scope`, or a list of nodes if no outer template object is available.
|
36276 | * @param guard an expression that is applied to this scope for type narrowing purposes.
|
36277 | */
|
36278 | static forNodes(tcb, parent, templateOrNodes, guard) {
|
36279 | const scope = new Scope$1(tcb, parent, guard);
|
36280 | if (parent === null && tcb.env.config.enableTemplateTypeChecker) {
|
36281 | // Add an autocompletion point for the component context.
|
36282 | scope.opQueue.push(new TcbComponentContextCompletionOp(scope));
|
36283 | }
|
36284 | let children;
|
36285 | // If given an actual `TmplAstTemplate` instance, then process any additional information it
|
36286 | // has.
|
36287 | if (templateOrNodes instanceof Template) {
|
36288 | // The template's variable declarations need to be added as `TcbVariableOp`s.
|
36289 | const varMap = new Map();
|
36290 | for (const v of templateOrNodes.variables) {
|
36291 | // Validate that variables on the `TmplAstTemplate` are only declared once.
|
36292 | if (!varMap.has(v.name)) {
|
36293 | varMap.set(v.name, v);
|
36294 | }
|
36295 | else {
|
36296 | const firstDecl = varMap.get(v.name);
|
36297 | tcb.oobRecorder.duplicateTemplateVar(tcb.id, v, firstDecl);
|
36298 | }
|
36299 | const opIndex = scope.opQueue.push(new TcbVariableOp(tcb, scope, templateOrNodes, v)) - 1;
|
36300 | scope.varMap.set(v, opIndex);
|
36301 | }
|
36302 | children = templateOrNodes.children;
|
36303 | }
|
36304 | else {
|
36305 | children = templateOrNodes;
|
36306 | }
|
36307 | for (const node of children) {
|
36308 | scope.appendNode(node);
|
36309 | }
|
36310 | return scope;
|
36311 | }
|
36312 | /**
|
36313 | * Look up a `ts.Expression` representing the value of some operation in the current `Scope`,
|
36314 | * including any parent scope(s). This method always returns a mutable clone of the
|
36315 | * `ts.Expression` with the comments cleared.
|
36316 | *
|
36317 | * @param node a `TmplAstNode` of the operation in question. The lookup performed will depend on
|
36318 | * the type of this node:
|
36319 | *
|
36320 | * Assuming `directive` is not present, then `resolve` will return:
|
36321 | *
|
36322 | * * `TmplAstElement` - retrieve the expression for the element DOM node
|
36323 | * * `TmplAstTemplate` - retrieve the template context variable
|
36324 | * * `TmplAstVariable` - retrieve a template let- variable
|
36325 | * * `TmplAstReference` - retrieve variable created for the local ref
|
36326 | *
|
36327 | * @param directive if present, a directive type on a `TmplAstElement` or `TmplAstTemplate` to
|
36328 | * look up instead of the default for an element or template node.
|
36329 | */
|
36330 | resolve(node, directive) {
|
36331 | // Attempt to resolve the operation locally.
|
36332 | const res = this.resolveLocal(node, directive);
|
36333 | if (res !== null) {
|
36334 | // We want to get a clone of the resolved expression and clear the trailing comments
|
36335 | // so they don't continue to appear in every place the expression is used.
|
36336 | // As an example, this would otherwise produce:
|
36337 | // var _t1 /**T:DIR*/ /*1,2*/ = _ctor1();
|
36338 | // _t1 /**T:DIR*/ /*1,2*/.input = 'value';
|
36339 | //
|
36340 | // In addition, returning a clone prevents the consumer of `Scope#resolve` from
|
36341 | // attaching comments at the declaration site.
|
36342 | const clone = ts$1.getMutableClone(res);
|
36343 | ts$1.setSyntheticTrailingComments(clone, []);
|
36344 | return clone;
|
36345 | }
|
36346 | else if (this.parent !== null) {
|
36347 | // Check with the parent.
|
36348 | return this.parent.resolve(node, directive);
|
36349 | }
|
36350 | else {
|
36351 | throw new Error(`Could not resolve ${node} / ${directive}`);
|
36352 | }
|
36353 | }
|
36354 | /**
|
36355 | * Add a statement to this scope.
|
36356 | */
|
36357 | addStatement(stmt) {
|
36358 | this.statements.push(stmt);
|
36359 | }
|
36360 | /**
|
36361 | * Get the statements.
|
36362 | */
|
36363 | render() {
|
36364 | for (let i = 0; i < this.opQueue.length; i++) {
|
36365 | // Optional statements cannot be skipped when we are generating the TCB for use
|
36366 | // by the TemplateTypeChecker.
|
36367 | const skipOptional = !this.tcb.env.config.enableTemplateTypeChecker;
|
36368 | this.executeOp(i, skipOptional);
|
36369 | }
|
36370 | return this.statements;
|
36371 | }
|
36372 | /**
|
36373 | * Returns an expression of all template guards that apply to this scope, including those of
|
36374 | * parent scopes. If no guards have been applied, null is returned.
|
36375 | */
|
36376 | guards() {
|
36377 | let parentGuards = null;
|
36378 | if (this.parent !== null) {
|
36379 | // Start with the guards from the parent scope, if present.
|
36380 | parentGuards = this.parent.guards();
|
36381 | }
|
36382 | if (this.guard === null) {
|
36383 | // This scope does not have a guard, so return the parent's guards as is.
|
36384 | return parentGuards;
|
36385 | }
|
36386 | else if (parentGuards === null) {
|
36387 | // There's no guards from the parent scope, so this scope's guard represents all available
|
36388 | // guards.
|
36389 | return this.guard;
|
36390 | }
|
36391 | else {
|
36392 | // Both the parent scope and this scope provide a guard, so create a combination of the two.
|
36393 | // It is important that the parent guard is used as left operand, given that it may provide
|
36394 | // narrowing that is required for this scope's guard to be valid.
|
36395 | return ts$1.createBinary(parentGuards, ts$1.SyntaxKind.AmpersandAmpersandToken, this.guard);
|
36396 | }
|
36397 | }
|
36398 | resolveLocal(ref, directive) {
|
36399 | if (ref instanceof Reference && this.referenceOpMap.has(ref)) {
|
36400 | return this.resolveOp(this.referenceOpMap.get(ref));
|
36401 | }
|
36402 | else if (ref instanceof Variable && this.varMap.has(ref)) {
|
36403 | // Resolving a context variable for this template.
|
36404 | // Execute the `TcbVariableOp` associated with the `TmplAstVariable`.
|
36405 | return this.resolveOp(this.varMap.get(ref));
|
36406 | }
|
36407 | else if (ref instanceof Template && directive === undefined &&
|
36408 | this.templateCtxOpMap.has(ref)) {
|
36409 | // Resolving the context of the given sub-template.
|
36410 | // Execute the `TcbTemplateContextOp` for the template.
|
36411 | return this.resolveOp(this.templateCtxOpMap.get(ref));
|
36412 | }
|
36413 | else if ((ref instanceof Element || ref instanceof Template) &&
|
36414 | directive !== undefined && this.directiveOpMap.has(ref)) {
|
36415 | // Resolving a directive on an element or sub-template.
|
36416 | const dirMap = this.directiveOpMap.get(ref);
|
36417 | if (dirMap.has(directive)) {
|
36418 | return this.resolveOp(dirMap.get(directive));
|
36419 | }
|
36420 | else {
|
36421 | return null;
|
36422 | }
|
36423 | }
|
36424 | else if (ref instanceof Element && this.elementOpMap.has(ref)) {
|
36425 | // Resolving the DOM node of an element in this template.
|
36426 | return this.resolveOp(this.elementOpMap.get(ref));
|
36427 | }
|
36428 | else {
|
36429 | return null;
|
36430 | }
|
36431 | }
|
36432 | /**
|
36433 | * Like `executeOp`, but assert that the operation actually returned `ts.Expression`.
|
36434 | */
|
36435 | resolveOp(opIndex) {
|
36436 | const res = this.executeOp(opIndex, /* skipOptional */ false);
|
36437 | if (res === null) {
|
36438 | throw new Error(`Error resolving operation, got null`);
|
36439 | }
|
36440 | return res;
|
36441 | }
|
36442 | /**
|
36443 | * Execute a particular `TcbOp` in the `opQueue`.
|
36444 | *
|
36445 | * This method replaces the operation in the `opQueue` with the result of execution (once done)
|
36446 | * and also protects against a circular dependency from the operation to itself by temporarily
|
36447 | * setting the operation's result to a special expression.
|
36448 | */
|
36449 | executeOp(opIndex, skipOptional) {
|
36450 | const op = this.opQueue[opIndex];
|
36451 | if (!(op instanceof TcbOp)) {
|
36452 | return op;
|
36453 | }
|
36454 | if (skipOptional && op.optional) {
|
36455 | return null;
|
36456 | }
|
36457 | // Set the result of the operation in the queue to its circular fallback. If executing this
|
36458 | // operation results in a circular dependency, this will prevent an infinite loop and allow for
|
36459 | // the resolution of such cycles.
|
36460 | this.opQueue[opIndex] = op.circularFallback();
|
36461 | const res = op.execute();
|
36462 | // Once the operation has finished executing, it's safe to cache the real result.
|
36463 | this.opQueue[opIndex] = res;
|
36464 | return res;
|
36465 | }
|
36466 | appendNode(node) {
|
36467 | if (node instanceof Element) {
|
36468 | const opIndex = this.opQueue.push(new TcbElementOp(this.tcb, this, node)) - 1;
|
36469 | this.elementOpMap.set(node, opIndex);
|
36470 | this.appendDirectivesAndInputsOfNode(node);
|
36471 | this.appendOutputsOfNode(node);
|
36472 | for (const child of node.children) {
|
36473 | this.appendNode(child);
|
36474 | }
|
36475 | this.checkAndAppendReferencesOfNode(node);
|
36476 | }
|
36477 | else if (node instanceof Template) {
|
36478 | // Template children are rendered in a child scope.
|
36479 | this.appendDirectivesAndInputsOfNode(node);
|
36480 | this.appendOutputsOfNode(node);
|
36481 | const ctxIndex = this.opQueue.push(new TcbTemplateContextOp(this.tcb, this)) - 1;
|
36482 | this.templateCtxOpMap.set(node, ctxIndex);
|
36483 | if (this.tcb.env.config.checkTemplateBodies) {
|
36484 | this.opQueue.push(new TcbTemplateBodyOp(this.tcb, this, node));
|
36485 | }
|
36486 | else if (this.tcb.env.config.alwaysCheckSchemaInTemplateBodies) {
|
36487 | this.appendDeepSchemaChecks(node.children);
|
36488 | }
|
36489 | this.checkAndAppendReferencesOfNode(node);
|
36490 | }
|
36491 | else if (node instanceof BoundText) {
|
36492 | this.opQueue.push(new TcbTextInterpolationOp(this.tcb, this, node));
|
36493 | }
|
36494 | else if (node instanceof Icu) {
|
36495 | this.appendIcuExpressions(node);
|
36496 | }
|
36497 | }
|
36498 | checkAndAppendReferencesOfNode(node) {
|
36499 | for (const ref of node.references) {
|
36500 | const target = this.tcb.boundTarget.getReferenceTarget(ref);
|
36501 | let ctxIndex;
|
36502 | if (target === null) {
|
36503 | // The reference is invalid if it doesn't have a target, so report it as an error.
|
36504 | this.tcb.oobRecorder.missingReferenceTarget(this.tcb.id, ref);
|
36505 | // Any usages of the invalid reference will be resolved to a variable of type any.
|
36506 | ctxIndex = this.opQueue.push(new TcbInvalidReferenceOp(this.tcb, this)) - 1;
|
36507 | }
|
36508 | else if (target instanceof Template || target instanceof Element) {
|
36509 | ctxIndex = this.opQueue.push(new TcbReferenceOp(this.tcb, this, ref, node, target)) - 1;
|
36510 | }
|
36511 | else {
|
36512 | ctxIndex =
|
36513 | this.opQueue.push(new TcbReferenceOp(this.tcb, this, ref, node, target.directive)) - 1;
|
36514 | }
|
36515 | this.referenceOpMap.set(ref, ctxIndex);
|
36516 | }
|
36517 | }
|
36518 | appendDirectivesAndInputsOfNode(node) {
|
36519 | // Collect all the inputs on the element.
|
36520 | const claimedInputs = new Set();
|
36521 | const directives = this.tcb.boundTarget.getDirectivesOfNode(node);
|
36522 | if (directives === null || directives.length === 0) {
|
36523 | // If there are no directives, then all inputs are unclaimed inputs, so queue an operation
|
36524 | // to add them if needed.
|
36525 | if (node instanceof Element) {
|
36526 | this.opQueue.push(new TcbUnclaimedInputsOp(this.tcb, this, node, claimedInputs));
|
36527 | this.opQueue.push(new TcbDomSchemaCheckerOp(this.tcb, node, /* checkElement */ true, claimedInputs));
|
36528 | }
|
36529 | return;
|
36530 | }
|
36531 | const dirMap = new Map();
|
36532 | for (const dir of directives) {
|
36533 | const directiveOp = dir.isGeneric ? new TcbDirectiveCtorOp(this.tcb, this, node, dir) :
|
36534 | new TcbDirectiveTypeOp(this.tcb, this, node, dir);
|
36535 | const dirIndex = this.opQueue.push(directiveOp) - 1;
|
36536 | dirMap.set(dir, dirIndex);
|
36537 | this.opQueue.push(new TcbDirectiveInputsOp(this.tcb, this, node, dir));
|
36538 | }
|
36539 | this.directiveOpMap.set(node, dirMap);
|
36540 | // After expanding the directives, we might need to queue an operation to check any unclaimed
|
36541 | // inputs.
|
36542 | if (node instanceof Element) {
|
36543 | // Go through the directives and remove any inputs that it claims from `elementInputs`.
|
36544 | for (const dir of directives) {
|
36545 | for (const propertyName of dir.inputs.propertyNames) {
|
36546 | claimedInputs.add(propertyName);
|
36547 | }
|
36548 | }
|
36549 | this.opQueue.push(new TcbUnclaimedInputsOp(this.tcb, this, node, claimedInputs));
|
36550 | // If there are no directives which match this element, then it's a "plain" DOM element (or a
|
36551 | // web component), and should be checked against the DOM schema. If any directives match,
|
36552 | // we must assume that the element could be custom (either a component, or a directive like
|
36553 | // <router-outlet>) and shouldn't validate the element name itself.
|
36554 | const checkElement = directives.length === 0;
|
36555 | this.opQueue.push(new TcbDomSchemaCheckerOp(this.tcb, node, checkElement, claimedInputs));
|
36556 | }
|
36557 | }
|
36558 | appendOutputsOfNode(node) {
|
36559 | // Collect all the outputs on the element.
|
36560 | const claimedOutputs = new Set();
|
36561 | const directives = this.tcb.boundTarget.getDirectivesOfNode(node);
|
36562 | if (directives === null || directives.length === 0) {
|
36563 | // If there are no directives, then all outputs are unclaimed outputs, so queue an operation
|
36564 | // to add them if needed.
|
36565 | if (node instanceof Element) {
|
36566 | this.opQueue.push(new TcbUnclaimedOutputsOp(this.tcb, this, node, claimedOutputs));
|
36567 | }
|
36568 | return;
|
36569 | }
|
36570 | // Queue operations for all directives to check the relevant outputs for a directive.
|
36571 | for (const dir of directives) {
|
36572 | this.opQueue.push(new TcbDirectiveOutputsOp(this.tcb, this, node, dir));
|
36573 | }
|
36574 | // After expanding the directives, we might need to queue an operation to check any unclaimed
|
36575 | // outputs.
|
36576 | if (node instanceof Element) {
|
36577 | // Go through the directives and register any outputs that it claims in `claimedOutputs`.
|
36578 | for (const dir of directives) {
|
36579 | for (const outputProperty of dir.outputs.propertyNames) {
|
36580 | claimedOutputs.add(outputProperty);
|
36581 | }
|
36582 | }
|
36583 | this.opQueue.push(new TcbUnclaimedOutputsOp(this.tcb, this, node, claimedOutputs));
|
36584 | }
|
36585 | }
|
36586 | appendDeepSchemaChecks(nodes) {
|
36587 | for (const node of nodes) {
|
36588 | if (!(node instanceof Element || node instanceof Template)) {
|
36589 | continue;
|
36590 | }
|
36591 | if (node instanceof Element) {
|
36592 | const claimedInputs = new Set();
|
36593 | const directives = this.tcb.boundTarget.getDirectivesOfNode(node);
|
36594 | let hasDirectives;
|
36595 | if (directives === null || directives.length === 0) {
|
36596 | hasDirectives = false;
|
36597 | }
|
36598 | else {
|
36599 | hasDirectives = true;
|
36600 | for (const dir of directives) {
|
36601 | for (const propertyName of dir.inputs.propertyNames) {
|
36602 | claimedInputs.add(propertyName);
|
36603 | }
|
36604 | }
|
36605 | }
|
36606 | this.opQueue.push(new TcbDomSchemaCheckerOp(this.tcb, node, !hasDirectives, claimedInputs));
|
36607 | }
|
36608 | this.appendDeepSchemaChecks(node.children);
|
36609 | }
|
36610 | }
|
36611 | appendIcuExpressions(node) {
|
36612 | for (const variable of Object.values(node.vars)) {
|
36613 | this.opQueue.push(new TcbTextInterpolationOp(this.tcb, this, variable));
|
36614 | }
|
36615 | for (const placeholder of Object.values(node.placeholders)) {
|
36616 | if (placeholder instanceof BoundText) {
|
36617 | this.opQueue.push(new TcbTextInterpolationOp(this.tcb, this, placeholder));
|
36618 | }
|
36619 | }
|
36620 | }
|
36621 | }
|
36622 | /**
|
36623 | * Create the `ctx` parameter to the top-level TCB function.
|
36624 | *
|
36625 | * This is a parameter with a type equivalent to the component type, with all generic type
|
36626 | * parameters listed (without their generic bounds).
|
36627 | */
|
36628 | function tcbCtxParam(node, name, useGenericType) {
|
36629 | let typeArguments = undefined;
|
36630 | // Check if the component is generic, and pass generic type parameters if so.
|
36631 | if (node.typeParameters !== undefined) {
|
36632 | if (useGenericType) {
|
36633 | typeArguments =
|
36634 | node.typeParameters.map(param => ts$1.createTypeReferenceNode(param.name, undefined));
|
36635 | }
|
36636 | else {
|
36637 | typeArguments =
|
36638 | node.typeParameters.map(() => ts$1.createKeywordTypeNode(ts$1.SyntaxKind.AnyKeyword));
|
36639 | }
|
36640 | }
|
36641 | const type = ts$1.createTypeReferenceNode(name, typeArguments);
|
36642 | return ts$1.createParameter(
|
36643 | /* decorators */ undefined,
|
36644 | /* modifiers */ undefined,
|
36645 | /* dotDotDotToken */ undefined,
|
36646 | /* name */ 'ctx',
|
36647 | /* questionToken */ undefined,
|
36648 | /* type */ type,
|
36649 | /* initializer */ undefined);
|
36650 | }
|
36651 | /**
|
36652 | * Process an `AST` expression and convert it into a `ts.Expression`, generating references to the
|
36653 | * correct identifiers in the current scope.
|
36654 | */
|
36655 | function tcbExpression(ast, tcb, scope) {
|
36656 | const translator = new TcbExpressionTranslator(tcb, scope);
|
36657 | return translator.translate(ast);
|
36658 | }
|
36659 | class TcbExpressionTranslator {
|
36660 | constructor(tcb, scope) {
|
36661 | this.tcb = tcb;
|
36662 | this.scope = scope;
|
36663 | }
|
36664 | translate(ast) {
|
36665 | // `astToTypescript` actually does the conversion. A special resolver `tcbResolve` is passed
|
36666 | // which interprets specific expression nodes that interact with the `ImplicitReceiver`. These
|
36667 | // nodes actually refer to identifiers within the current scope.
|
36668 | return astToTypescript(ast, ast => this.resolve(ast), this.tcb.env.config);
|
36669 | }
|
36670 | /**
|
36671 | * Resolve an `AST` expression within the given scope.
|
36672 | *
|
36673 | * Some `AST` expressions refer to top-level concepts (references, variables, the component
|
36674 | * context). This method assists in resolving those.
|
36675 | */
|
36676 | resolve(ast) {
|
36677 | if (ast instanceof PropertyRead && ast.receiver instanceof ImplicitReceiver) {
|
36678 | // Try to resolve a bound target for this expression. If no such target is available, then
|
36679 | // the expression is referencing the top-level component context. In that case, `null` is
|
36680 | // returned here to let it fall through resolution so it will be caught when the
|
36681 | // `ImplicitReceiver` is resolved in the branch below.
|
36682 | return this.resolveTarget(ast);
|
36683 | }
|
36684 | else if (ast instanceof PropertyWrite && ast.receiver instanceof ImplicitReceiver) {
|
36685 | const target = this.resolveTarget(ast);
|
36686 | if (target === null) {
|
36687 | return null;
|
36688 | }
|
36689 | const expr = this.translate(ast.value);
|
36690 | const result = ts$1.createParen(ts$1.createBinary(target, ts$1.SyntaxKind.EqualsToken, expr));
|
36691 | addParseSpanInfo(result, ast.sourceSpan);
|
36692 | return result;
|
36693 | }
|
36694 | else if (ast instanceof ImplicitReceiver) {
|
36695 | // AST instances representing variables and references look very similar to property reads
|
36696 | // or method calls from the component context: both have the shape
|
36697 | // PropertyRead(ImplicitReceiver, 'propName') or MethodCall(ImplicitReceiver, 'methodName').
|
36698 | //
|
36699 | // `translate` will first try to `resolve` the outer PropertyRead/MethodCall. If this works,
|
36700 | // it's because the `BoundTarget` found an expression target for the whole expression, and
|
36701 | // therefore `translate` will never attempt to `resolve` the ImplicitReceiver of that
|
36702 | // PropertyRead/MethodCall.
|
36703 | //
|
36704 | // Therefore if `resolve` is called on an `ImplicitReceiver`, it's because no outer
|
36705 | // PropertyRead/MethodCall resolved to a variable or reference, and therefore this is a
|
36706 | // property read or method call on the component context itself.
|
36707 | return ts$1.createIdentifier('ctx');
|
36708 | }
|
36709 | else if (ast instanceof BindingPipe) {
|
36710 | const expr = this.translate(ast.exp);
|
36711 | const pipeRef = this.tcb.getPipeByName(ast.name);
|
36712 | let pipe;
|
36713 | if (pipeRef === null) {
|
36714 | // No pipe by that name exists in scope. Record this as an error.
|
36715 | this.tcb.oobRecorder.missingPipe(this.tcb.id, ast);
|
36716 | // Use an 'any' value to at least allow the rest of the expression to be checked.
|
36717 | pipe = NULL_AS_ANY;
|
36718 | }
|
36719 | else if (this.tcb.env.config.checkTypeOfPipes) {
|
36720 | // Use a variable declared as the pipe's type.
|
36721 | pipe = this.tcb.env.pipeInst(pipeRef);
|
36722 | }
|
36723 | else {
|
36724 | // Use an 'any' value when not checking the type of the pipe.
|
36725 | pipe = ts$1.createAsExpression(this.tcb.env.pipeInst(pipeRef), ts$1.createKeywordTypeNode(ts$1.SyntaxKind.AnyKeyword));
|
36726 | }
|
36727 | const args = ast.args.map(arg => this.translate(arg));
|
36728 | const methodAccess = ts$1.createPropertyAccess(pipe, 'transform');
|
36729 | addParseSpanInfo(methodAccess, ast.nameSpan);
|
36730 | const result = ts$1.createCall(
|
36731 | /* expression */ methodAccess,
|
36732 | /* typeArguments */ undefined,
|
36733 | /* argumentsArray */ [expr, ...args]);
|
36734 | addParseSpanInfo(result, ast.sourceSpan);
|
36735 | return result;
|
36736 | }
|
36737 | else if (ast instanceof MethodCall && ast.receiver instanceof ImplicitReceiver &&
|
36738 | !(ast.receiver instanceof ThisReceiver)) {
|
36739 | // Resolve the special `$any(expr)` syntax to insert a cast of the argument to type `any`.
|
36740 | // `$any(expr)` -> `expr as any`
|
36741 | if (ast.name === '$any' && ast.args.length === 1) {
|
36742 | const expr = this.translate(ast.args[0]);
|
36743 | const exprAsAny = ts$1.createAsExpression(expr, ts$1.createKeywordTypeNode(ts$1.SyntaxKind.AnyKeyword));
|
36744 | const result = ts$1.createParen(exprAsAny);
|
36745 | addParseSpanInfo(result, ast.sourceSpan);
|
36746 | return result;
|
36747 | }
|
36748 | // Attempt to resolve a bound target for the method, and generate the method call if a target
|
36749 | // could be resolved. If no target is available, then the method is referencing the top-level
|
36750 | // component context, in which case `null` is returned to let the `ImplicitReceiver` being
|
36751 | // resolved to the component context.
|
36752 | const receiver = this.resolveTarget(ast);
|
36753 | if (receiver === null) {
|
36754 | return null;
|
36755 | }
|
36756 | const method = wrapForDiagnostics(receiver);
|
36757 | addParseSpanInfo(method, ast.nameSpan);
|
36758 | const args = ast.args.map(arg => this.translate(arg));
|
36759 | const node = ts$1.createCall(method, undefined, args);
|
36760 | addParseSpanInfo(node, ast.sourceSpan);
|
36761 | return node;
|
36762 | }
|
36763 | else {
|
36764 | // This AST isn't special after all.
|
36765 | return null;
|
36766 | }
|
36767 | }
|
36768 | /**
|
36769 | * Attempts to resolve a bound target for a given expression, and translates it into the
|
36770 | * appropriate `ts.Expression` that represents the bound target. If no target is available,
|
36771 | * `null` is returned.
|
36772 | */
|
36773 | resolveTarget(ast) {
|
36774 | const binding = this.tcb.boundTarget.getExpressionTarget(ast);
|
36775 | if (binding === null) {
|
36776 | return null;
|
36777 | }
|
36778 | const expr = this.scope.resolve(binding);
|
36779 | addParseSpanInfo(expr, ast.sourceSpan);
|
36780 | return expr;
|
36781 | }
|
36782 | }
|
36783 | /**
|
36784 | * Call the type constructor of a directive instance on a given template node, inferring a type for
|
36785 | * the directive instance from any bound inputs.
|
36786 | */
|
36787 | function tcbCallTypeCtor(dir, tcb, inputs) {
|
36788 | const typeCtor = tcb.env.typeCtorFor(dir);
|
36789 | // Construct an array of `ts.PropertyAssignment`s for each of the directive's inputs.
|
36790 | const members = inputs.map(input => {
|
36791 | const propertyName = ts$1.createStringLiteral(input.field);
|
36792 | if (input.type === 'binding') {
|
36793 | // For bound inputs, the property is assigned the binding expression.
|
36794 | let expr = input.expression;
|
36795 | if (!tcb.env.config.checkTypeOfInputBindings) {
|
36796 | // If checking the type of bindings is disabled, cast the resulting expression to 'any'
|
36797 | // before the assignment.
|
36798 | expr = tsCastToAny(expr);
|
36799 | }
|
36800 | else if (!tcb.env.config.strictNullInputBindings) {
|
36801 | // If strict null checks are disabled, erase `null` and `undefined` from the type by
|
36802 | // wrapping the expression in a non-null assertion.
|
36803 | expr = ts$1.createNonNullExpression(expr);
|
36804 | }
|
36805 | const assignment = ts$1.createPropertyAssignment(propertyName, wrapForDiagnostics(expr));
|
36806 | addParseSpanInfo(assignment, input.sourceSpan);
|
36807 | return assignment;
|
36808 | }
|
36809 | else {
|
36810 | // A type constructor is required to be called with all input properties, so any unset
|
36811 | // inputs are simply assigned a value of type `any` to ignore them.
|
36812 | return ts$1.createPropertyAssignment(propertyName, NULL_AS_ANY);
|
36813 | }
|
36814 | });
|
36815 | // Call the `ngTypeCtor` method on the directive class, with an object literal argument created
|
36816 | // from the matched inputs.
|
36817 | return ts$1.createCall(
|
36818 | /* expression */ typeCtor,
|
36819 | /* typeArguments */ undefined,
|
36820 | /* argumentsArray */ [ts$1.createObjectLiteral(members)]);
|
36821 | }
|
36822 | function getBoundInputs(directive, node, tcb) {
|
36823 | const boundInputs = [];
|
36824 | const processAttribute = (attr) => {
|
36825 | // Skip non-property bindings.
|
36826 | if (attr instanceof BoundAttribute && attr.type !== 0 /* Property */) {
|
36827 | return;
|
36828 | }
|
36829 | // Skip the attribute if the directive does not have an input for it.
|
36830 | const inputs = directive.inputs.getByBindingPropertyName(attr.name);
|
36831 | if (inputs === null) {
|
36832 | return;
|
36833 | }
|
36834 | const fieldNames = inputs.map(input => input.classPropertyName);
|
36835 | boundInputs.push({ attribute: attr, fieldNames });
|
36836 | };
|
36837 | node.inputs.forEach(processAttribute);
|
36838 | node.attributes.forEach(processAttribute);
|
36839 | if (node instanceof Template) {
|
36840 | node.templateAttrs.forEach(processAttribute);
|
36841 | }
|
36842 | return boundInputs;
|
36843 | }
|
36844 | /**
|
36845 | * Translates the given attribute binding to a `ts.Expression`.
|
36846 | */
|
36847 | function translateInput(attr, tcb, scope) {
|
36848 | if (attr instanceof BoundAttribute) {
|
36849 | // Produce an expression representing the value of the binding.
|
36850 | return tcbExpression(attr.value, tcb, scope);
|
36851 | }
|
36852 | else {
|
36853 | // For regular attributes with a static string value, use the represented string literal.
|
36854 | return ts$1.createStringLiteral(attr.value);
|
36855 | }
|
36856 | }
|
36857 | const EVENT_PARAMETER = '$event';
|
36858 | /**
|
36859 | * Creates an arrow function to be used as handler function for event bindings. The handler
|
36860 | * function has a single parameter `$event` and the bound event's handler `AST` represented as a
|
36861 | * TypeScript expression as its body.
|
36862 | *
|
36863 | * When `eventType` is set to `Infer`, the `$event` parameter will not have an explicit type. This
|
36864 | * allows for the created handler function to have its `$event` parameter's type inferred based on
|
36865 | * how it's used, to enable strict type checking of event bindings. When set to `Any`, the `$event`
|
36866 | * parameter will have an explicit `any` type, effectively disabling strict type checking of event
|
36867 | * bindings. Alternatively, an explicit type can be passed for the `$event` parameter.
|
36868 | */
|
36869 | function tcbCreateEventHandler(event, tcb, scope, eventType) {
|
36870 | const handler = tcbEventHandlerExpression(event.handler, tcb, scope);
|
36871 | let eventParamType;
|
36872 | if (eventType === 0 /* Infer */) {
|
36873 | eventParamType = undefined;
|
36874 | }
|
36875 | else if (eventType === 1 /* Any */) {
|
36876 | eventParamType = ts$1.createKeywordTypeNode(ts$1.SyntaxKind.AnyKeyword);
|
36877 | }
|
36878 | else {
|
36879 | eventParamType = eventType;
|
36880 | }
|
36881 | // Obtain all guards that have been applied to the scope and its parents, as they have to be
|
36882 | // repeated within the handler function for their narrowing to be in effect within the handler.
|
36883 | const guards = scope.guards();
|
36884 | let body = ts$1.createExpressionStatement(handler);
|
36885 | if (guards !== null) {
|
36886 | // Wrap the body in an `if` statement containing all guards that have to be applied.
|
36887 | body = ts$1.createIf(guards, body);
|
36888 | }
|
36889 | const eventParam = ts$1.createParameter(
|
36890 | /* decorators */ undefined,
|
36891 | /* modifiers */ undefined,
|
36892 | /* dotDotDotToken */ undefined,
|
36893 | /* name */ EVENT_PARAMETER,
|
36894 | /* questionToken */ undefined,
|
36895 | /* type */ eventParamType);
|
36896 | addExpressionIdentifier(eventParam, ExpressionIdentifier.EVENT_PARAMETER);
|
36897 | return ts$1.createFunctionExpression(
|
36898 | /* modifier */ undefined,
|
36899 | /* asteriskToken */ undefined,
|
36900 | /* name */ undefined,
|
36901 | /* typeParameters */ undefined,
|
36902 | /* parameters */ [eventParam],
|
36903 | /* type */ ts$1.createKeywordTypeNode(ts$1.SyntaxKind.AnyKeyword),
|
36904 | /* body */ ts$1.createBlock([body]));
|
36905 | }
|
36906 | /**
|
36907 | * Similar to `tcbExpression`, this function converts the provided `AST` expression into a
|
36908 | * `ts.Expression`, with special handling of the `$event` variable that can be used within event
|
36909 | * bindings.
|
36910 | */
|
36911 | function tcbEventHandlerExpression(ast, tcb, scope) {
|
36912 | const translator = new TcbEventHandlerTranslator(tcb, scope);
|
36913 | return translator.translate(ast);
|
36914 | }
|
36915 | class TcbEventHandlerTranslator extends TcbExpressionTranslator {
|
36916 | resolve(ast) {
|
36917 | // Recognize a property read on the implicit receiver corresponding with the event parameter
|
36918 | // that is available in event bindings. Since this variable is a parameter of the handler
|
36919 | // function that the converted expression becomes a child of, just create a reference to the
|
36920 | // parameter by its name.
|
36921 | if (ast instanceof PropertyRead && ast.receiver instanceof ImplicitReceiver &&
|
36922 | !(ast.receiver instanceof ThisReceiver) && ast.name === EVENT_PARAMETER) {
|
36923 | const event = ts$1.createIdentifier(EVENT_PARAMETER);
|
36924 | addParseSpanInfo(event, ast.nameSpan);
|
36925 | return event;
|
36926 | }
|
36927 | return super.resolve(ast);
|
36928 | }
|
36929 | }
|
36930 |
|
36931 | /**
|
36932 | * @license
|
36933 | * Copyright Google LLC All Rights Reserved.
|
36934 | *
|
36935 | * Use of this source code is governed by an MIT-style license that can be
|
36936 | * found in the LICENSE file at https://angular.io/license
|
36937 | */
|
36938 | /**
|
36939 | * An `Environment` representing the single type-checking file into which most (if not all) Type
|
36940 | * Check Blocks (TCBs) will be generated.
|
36941 | *
|
36942 | * The `TypeCheckFile` hosts multiple TCBs and allows the sharing of declarations (e.g. type
|
36943 | * constructors) between them. Rather than return such declarations via `getPreludeStatements()`, it
|
36944 | * hoists them to the top of the generated `ts.SourceFile`.
|
36945 | */
|
36946 | class TypeCheckFile extends Environment {
|
36947 | constructor(fileName, config, refEmitter, reflector, compilerHost) {
|
36948 | super(config, new ImportManager(new NoopImportRewriter(), 'i'), refEmitter, reflector, ts$1.createSourceFile(compilerHost.getCanonicalFileName(fileName), '', ts$1.ScriptTarget.Latest, true));
|
36949 | this.fileName = fileName;
|
36950 | this.nextTcbId = 1;
|
36951 | this.tcbStatements = [];
|
36952 | }
|
36953 | addTypeCheckBlock(ref, meta, domSchemaChecker, oobRecorder) {
|
36954 | const fnId = ts$1.createIdentifier(`_tcb${this.nextTcbId++}`);
|
36955 | const fn = generateTypeCheckBlock(this, ref, fnId, meta, domSchemaChecker, oobRecorder);
|
36956 | this.tcbStatements.push(fn);
|
36957 | }
|
36958 | render() {
|
36959 | let source = this.importManager.getAllImports(this.contextFile.fileName)
|
36960 | .map(i => `import * as ${i.qualifier.text} from '${i.specifier}';`)
|
36961 | .join('\n') +
|
36962 | '\n\n';
|
36963 | const printer = ts$1.createPrinter();
|
36964 | source += '\n';
|
36965 | for (const stmt of this.pipeInstStatements) {
|
36966 | source += printer.printNode(ts$1.EmitHint.Unspecified, stmt, this.contextFile) + '\n';
|
36967 | }
|
36968 | for (const stmt of this.typeCtorStatements) {
|
36969 | source += printer.printNode(ts$1.EmitHint.Unspecified, stmt, this.contextFile) + '\n';
|
36970 | }
|
36971 | source += '\n';
|
36972 | for (const stmt of this.tcbStatements) {
|
36973 | source += printer.printNode(ts$1.EmitHint.Unspecified, stmt, this.contextFile) + '\n';
|
36974 | }
|
36975 | // Ensure the template type-checking file is an ES module. Otherwise, it's interpreted as some
|
36976 | // kind of global namespace in TS, which forces a full re-typecheck of the user's program that
|
36977 | // is somehow more expensive than the initial parse.
|
36978 | source += '\nexport const IS_A_MODULE = true;\n';
|
36979 | return source;
|
36980 | }
|
36981 | getPreludeStatements() {
|
36982 | return [];
|
36983 | }
|
36984 | }
|
36985 |
|
36986 | /**
|
36987 | * @license
|
36988 | * Copyright Google LLC All Rights Reserved.
|
36989 | *
|
36990 | * Use of this source code is governed by an MIT-style license that can be
|
36991 | * found in the LICENSE file at https://angular.io/license
|
36992 | */
|
36993 | /**
|
36994 | * How a type-checking context should handle operations which would require inlining.
|
36995 | */
|
36996 | var InliningMode;
|
36997 | (function (InliningMode) {
|
36998 | /**
|
36999 | * Use inlining operations when required.
|
37000 | */
|
37001 | InliningMode[InliningMode["InlineOps"] = 0] = "InlineOps";
|
37002 | /**
|
37003 | * Produce diagnostics if an operation would require inlining.
|
37004 | */
|
37005 | InliningMode[InliningMode["Error"] = 1] = "Error";
|
37006 | })(InliningMode || (InliningMode = {}));
|
37007 | /**
|
37008 | * A template type checking context for a program.
|
37009 | *
|
37010 | * The `TypeCheckContext` allows registration of components and their templates which need to be
|
37011 | * type checked.
|
37012 | */
|
37013 | class TypeCheckContextImpl {
|
37014 | constructor(config, compilerHost, componentMappingStrategy, refEmitter, reflector, host, inlining) {
|
37015 | this.config = config;
|
37016 | this.compilerHost = compilerHost;
|
37017 | this.componentMappingStrategy = componentMappingStrategy;
|
37018 | this.refEmitter = refEmitter;
|
37019 | this.reflector = reflector;
|
37020 | this.host = host;
|
37021 | this.inlining = inlining;
|
37022 | this.fileMap = new Map();
|
37023 | /**
|
37024 | * A `Map` of `ts.SourceFile`s that the context has seen to the operations (additions of methods
|
37025 | * or type-check blocks) that need to be eventually performed on that file.
|
37026 | */
|
37027 | this.opMap = new Map();
|
37028 | /**
|
37029 | * Tracks when an a particular class has a pending type constructor patching operation already
|
37030 | * queued.
|
37031 | */
|
37032 | this.typeCtorPending = new Set();
|
37033 | }
|
37034 | /**
|
37035 | * Register a template to potentially be type-checked.
|
37036 | *
|
37037 | * Implements `TypeCheckContext.addTemplate`.
|
37038 | */
|
37039 | addTemplate(ref, binder, template, pipes, schemas, sourceMapping, file, parseErrors) {
|
37040 | if (!this.host.shouldCheckComponent(ref.node)) {
|
37041 | return;
|
37042 | }
|
37043 | const fileData = this.dataForFile(ref.node.getSourceFile());
|
37044 | const shimData = this.pendingShimForComponent(ref.node);
|
37045 | const templateId = fileData.sourceManager.getTemplateId(ref.node);
|
37046 | const templateDiagnostics = [];
|
37047 | if (parseErrors !== null) {
|
37048 | templateDiagnostics.push(...this.getTemplateDiagnostics(parseErrors, templateId, sourceMapping));
|
37049 | }
|
37050 | // Accumulate a list of any directives which could not have type constructors generated due to
|
37051 | // unsupported inlining operations.
|
37052 | let missingInlines = [];
|
37053 | const boundTarget = binder.bind({ template });
|
37054 | // Get all of the directives used in the template and record type constructors for all of them.
|
37055 | for (const dir of boundTarget.getUsedDirectives()) {
|
37056 | const dirRef = dir.ref;
|
37057 | const dirNode = dirRef.node;
|
37058 | if (dir.isGeneric && requiresInlineTypeCtor(dirNode, this.reflector)) {
|
37059 | if (this.inlining === InliningMode.Error) {
|
37060 | missingInlines.push(dirNode);
|
37061 | continue;
|
37062 | }
|
37063 | // Add a type constructor operation for the directive.
|
37064 | this.addInlineTypeCtor(fileData, dirNode.getSourceFile(), dirRef, {
|
37065 | fnName: 'ngTypeCtor',
|
37066 | // The constructor should have a body if the directive comes from a .ts file, but not if
|
37067 | // it comes from a .d.ts file. .d.ts declarations don't have bodies.
|
37068 | body: !dirNode.getSourceFile().isDeclarationFile,
|
37069 | fields: {
|
37070 | inputs: dir.inputs.classPropertyNames,
|
37071 | outputs: dir.outputs.classPropertyNames,
|
37072 | // TODO(alxhub): support queries
|
37073 | queries: dir.queries,
|
37074 | },
|
37075 | coercedInputFields: dir.coercedInputFields,
|
37076 | });
|
37077 | }
|
37078 | }
|
37079 | shimData.templates.set(templateId, {
|
37080 | template,
|
37081 | boundTarget,
|
37082 | templateDiagnostics,
|
37083 | });
|
37084 | const tcbRequiresInline = requiresInlineTypeCheckBlock(ref.node, pipes);
|
37085 | // If inlining is not supported, but is required for either the TCB or one of its directive
|
37086 | // dependencies, then exit here with an error.
|
37087 | if (this.inlining === InliningMode.Error && (tcbRequiresInline || missingInlines.length > 0)) {
|
37088 | // This template cannot be supported because the underlying strategy does not support inlining
|
37089 | // and inlining would be required.
|
37090 | // Record diagnostics to indicate the issues with this template.
|
37091 | if (tcbRequiresInline) {
|
37092 | shimData.oobRecorder.requiresInlineTcb(templateId, ref.node);
|
37093 | }
|
37094 | if (missingInlines.length > 0) {
|
37095 | shimData.oobRecorder.requiresInlineTypeConstructors(templateId, ref.node, missingInlines);
|
37096 | }
|
37097 | // Checking this template would be unsupported, so don't try.
|
37098 | return;
|
37099 | }
|
37100 | const meta = {
|
37101 | id: fileData.sourceManager.captureSource(ref.node, sourceMapping, file),
|
37102 | boundTarget,
|
37103 | pipes,
|
37104 | schemas,
|
37105 | };
|
37106 | if (tcbRequiresInline) {
|
37107 | // This class didn't meet the requirements for external type checking, so generate an inline
|
37108 | // TCB for the class.
|
37109 | this.addInlineTypeCheckBlock(fileData, shimData, ref, meta);
|
37110 | }
|
37111 | else {
|
37112 | // The class can be type-checked externally as normal.
|
37113 | shimData.file.addTypeCheckBlock(ref, meta, shimData.domSchemaChecker, shimData.oobRecorder);
|
37114 | }
|
37115 | }
|
37116 | /**
|
37117 | * Record a type constructor for the given `node` with the given `ctorMetadata`.
|
37118 | */
|
37119 | addInlineTypeCtor(fileData, sf, ref, ctorMeta) {
|
37120 | if (this.typeCtorPending.has(ref.node)) {
|
37121 | return;
|
37122 | }
|
37123 | this.typeCtorPending.add(ref.node);
|
37124 | // Lazily construct the operation map.
|
37125 | if (!this.opMap.has(sf)) {
|
37126 | this.opMap.set(sf, []);
|
37127 | }
|
37128 | const ops = this.opMap.get(sf);
|
37129 | // Push a `TypeCtorOp` into the operation queue for the source file.
|
37130 | ops.push(new TypeCtorOp(ref, ctorMeta));
|
37131 | fileData.hasInlines = true;
|
37132 | }
|
37133 | /**
|
37134 | * Transform a `ts.SourceFile` into a version that includes type checking code.
|
37135 | *
|
37136 | * If this particular `ts.SourceFile` requires changes, the text representing its new contents
|
37137 | * will be returned. Otherwise, a `null` return indicates no changes were necessary.
|
37138 | */
|
37139 | transform(sf) {
|
37140 | // If there are no operations pending for this particular file, return `null` to indicate no
|
37141 | // changes.
|
37142 | if (!this.opMap.has(sf)) {
|
37143 | return null;
|
37144 | }
|
37145 | // Imports may need to be added to the file to support type-checking of directives used in the
|
37146 | // template within it.
|
37147 | const importManager = new ImportManager(new NoopImportRewriter(), '_i');
|
37148 | // Each Op has a splitPoint index into the text where it needs to be inserted. Split the
|
37149 | // original source text into chunks at these split points, where code will be inserted between
|
37150 | // the chunks.
|
37151 | const ops = this.opMap.get(sf).sort(orderOps);
|
37152 | const textParts = splitStringAtPoints(sf.text, ops.map(op => op.splitPoint));
|
37153 | // Use a `ts.Printer` to generate source code.
|
37154 | const printer = ts$1.createPrinter({ omitTrailingSemicolon: true });
|
37155 | // Begin with the intial section of the code text.
|
37156 | let code = textParts[0];
|
37157 | // Process each operation and use the printer to generate source code for it, inserting it into
|
37158 | // the source code in between the original chunks.
|
37159 | ops.forEach((op, idx) => {
|
37160 | const text = op.execute(importManager, sf, this.refEmitter, printer);
|
37161 | code += '\n\n' + text + textParts[idx + 1];
|
37162 | });
|
37163 | // Write out the imports that need to be added to the beginning of the file.
|
37164 | let imports = importManager.getAllImports(sf.fileName)
|
37165 | .map(i => `import * as ${i.qualifier.text} from '${i.specifier}';`)
|
37166 | .join('\n');
|
37167 | code = imports + '\n' + code;
|
37168 | return code;
|
37169 | }
|
37170 | finalize() {
|
37171 | // First, build the map of updates to source files.
|
37172 | const updates = new Map();
|
37173 | for (const originalSf of this.opMap.keys()) {
|
37174 | const newText = this.transform(originalSf);
|
37175 | if (newText !== null) {
|
37176 | updates.set(absoluteFromSourceFile(originalSf), newText);
|
37177 | }
|
37178 | }
|
37179 | // Then go through each input file that has pending code generation operations.
|
37180 | for (const [sfPath, pendingFileData] of this.fileMap) {
|
37181 | // For each input file, consider generation operations for each of its shims.
|
37182 | for (const pendingShimData of pendingFileData.shimData.values()) {
|
37183 | this.host.recordShimData(sfPath, {
|
37184 | genesisDiagnostics: [
|
37185 | ...pendingShimData.domSchemaChecker.diagnostics,
|
37186 | ...pendingShimData.oobRecorder.diagnostics,
|
37187 | ],
|
37188 | hasInlines: pendingFileData.hasInlines,
|
37189 | path: pendingShimData.file.fileName,
|
37190 | templates: pendingShimData.templates,
|
37191 | });
|
37192 | updates.set(pendingShimData.file.fileName, pendingShimData.file.render());
|
37193 | }
|
37194 | }
|
37195 | return updates;
|
37196 | }
|
37197 | addInlineTypeCheckBlock(fileData, shimData, ref, tcbMeta) {
|
37198 | const sf = ref.node.getSourceFile();
|
37199 | if (!this.opMap.has(sf)) {
|
37200 | this.opMap.set(sf, []);
|
37201 | }
|
37202 | const ops = this.opMap.get(sf);
|
37203 | ops.push(new TcbOp$1(ref, tcbMeta, this.config, this.reflector, shimData.domSchemaChecker, shimData.oobRecorder));
|
37204 | fileData.hasInlines = true;
|
37205 | }
|
37206 | pendingShimForComponent(node) {
|
37207 | const fileData = this.dataForFile(node.getSourceFile());
|
37208 | const shimPath = this.componentMappingStrategy.shimPathForComponent(node);
|
37209 | if (!fileData.shimData.has(shimPath)) {
|
37210 | fileData.shimData.set(shimPath, {
|
37211 | domSchemaChecker: new RegistryDomSchemaChecker(fileData.sourceManager),
|
37212 | oobRecorder: new OutOfBandDiagnosticRecorderImpl(fileData.sourceManager),
|
37213 | file: new TypeCheckFile(shimPath, this.config, this.refEmitter, this.reflector, this.compilerHost),
|
37214 | templates: new Map(),
|
37215 | });
|
37216 | }
|
37217 | return fileData.shimData.get(shimPath);
|
37218 | }
|
37219 | dataForFile(sf) {
|
37220 | const sfPath = absoluteFromSourceFile(sf);
|
37221 | if (!this.fileMap.has(sfPath)) {
|
37222 | const data = {
|
37223 | hasInlines: false,
|
37224 | sourceManager: this.host.getSourceManager(sfPath),
|
37225 | shimData: new Map(),
|
37226 | };
|
37227 | this.fileMap.set(sfPath, data);
|
37228 | }
|
37229 | return this.fileMap.get(sfPath);
|
37230 | }
|
37231 | getTemplateDiagnostics(parseErrors, templateId, sourceMapping) {
|
37232 | return parseErrors.map(error => {
|
37233 | const span = error.span;
|
37234 | if (span.start.offset === span.end.offset) {
|
37235 | // Template errors can contain zero-length spans, if the error occurs at a single point.
|
37236 | // However, TypeScript does not handle displaying a zero-length diagnostic very well, so
|
37237 | // increase the ending offset by 1 for such errors, to ensure the position is shown in the
|
37238 | // diagnostic.
|
37239 | span.end.offset++;
|
37240 | }
|
37241 | return makeTemplateDiagnostic(templateId, sourceMapping, span, ts$1.DiagnosticCategory.Error, ngErrorCode(ErrorCode.TEMPLATE_PARSE_ERROR), error.msg);
|
37242 | });
|
37243 | }
|
37244 | }
|
37245 | /**
|
37246 | * A type check block operation which produces type check code for a particular component.
|
37247 | */
|
37248 | class TcbOp$1 {
|
37249 | constructor(ref, meta, config, reflector, domSchemaChecker, oobRecorder) {
|
37250 | this.ref = ref;
|
37251 | this.meta = meta;
|
37252 | this.config = config;
|
37253 | this.reflector = reflector;
|
37254 | this.domSchemaChecker = domSchemaChecker;
|
37255 | this.oobRecorder = oobRecorder;
|
37256 | }
|
37257 | /**
|
37258 | * Type check blocks are inserted immediately after the end of the component class.
|
37259 | */
|
37260 | get splitPoint() {
|
37261 | return this.ref.node.end + 1;
|
37262 | }
|
37263 | execute(im, sf, refEmitter, printer) {
|
37264 | const env = new Environment(this.config, im, refEmitter, this.reflector, sf);
|
37265 | const fnName = ts$1.createIdentifier(`_tcb_${this.ref.node.pos}`);
|
37266 | const fn = generateTypeCheckBlock(env, this.ref, fnName, this.meta, this.domSchemaChecker, this.oobRecorder);
|
37267 | return printer.printNode(ts$1.EmitHint.Unspecified, fn, sf);
|
37268 | }
|
37269 | }
|
37270 | /**
|
37271 | * A type constructor operation which produces type constructor code for a particular directive.
|
37272 | */
|
37273 | class TypeCtorOp {
|
37274 | constructor(ref, meta) {
|
37275 | this.ref = ref;
|
37276 | this.meta = meta;
|
37277 | }
|
37278 | /**
|
37279 | * Type constructor operations are inserted immediately before the end of the directive class.
|
37280 | */
|
37281 | get splitPoint() {
|
37282 | return this.ref.node.end - 1;
|
37283 | }
|
37284 | execute(im, sf, refEmitter, printer) {
|
37285 | const tcb = generateInlineTypeCtor(this.ref.node, this.meta);
|
37286 | return printer.printNode(ts$1.EmitHint.Unspecified, tcb, sf);
|
37287 | }
|
37288 | }
|
37289 | /**
|
37290 | * Compare two operations and return their split point ordering.
|
37291 | */
|
37292 | function orderOps(op1, op2) {
|
37293 | return op1.splitPoint - op2.splitPoint;
|
37294 | }
|
37295 | /**
|
37296 | * Split a string into chunks at any number of split points.
|
37297 | */
|
37298 | function splitStringAtPoints(str, points) {
|
37299 | const splits = [];
|
37300 | let start = 0;
|
37301 | for (let i = 0; i < points.length; i++) {
|
37302 | const point = points[i];
|
37303 | splits.push(str.substring(start, point));
|
37304 | start = point;
|
37305 | }
|
37306 | splits.push(str.substring(start));
|
37307 | return splits;
|
37308 | }
|
37309 |
|
37310 | /**
|
37311 | * @license
|
37312 | * Copyright Google LLC All Rights Reserved.
|
37313 | *
|
37314 | * Use of this source code is governed by an MIT-style license that can be
|
37315 | * found in the LICENSE file at https://angular.io/license
|
37316 | */
|
37317 | const LF_CHAR = 10;
|
37318 | const CR_CHAR = 13;
|
37319 | const LINE_SEP_CHAR = 8232;
|
37320 | const PARAGRAPH_CHAR = 8233;
|
37321 | /** Gets the line and character for the given position from the line starts map. */
|
37322 | function getLineAndCharacterFromPosition(lineStartsMap, position) {
|
37323 | const lineIndex = findClosestLineStartPosition(lineStartsMap, position);
|
37324 | return { character: position - lineStartsMap[lineIndex], line: lineIndex };
|
37325 | }
|
37326 | /**
|
37327 | * Computes the line start map of the given text. This can be used in order to
|
37328 | * retrieve the line and character of a given text position index.
|
37329 | */
|
37330 | function computeLineStartsMap(text) {
|
37331 | const result = [0];
|
37332 | let pos = 0;
|
37333 | while (pos < text.length) {
|
37334 | const char = text.charCodeAt(pos++);
|
37335 | // Handles the "CRLF" line break. In that case we peek the character
|
37336 | // after the "CR" and check if it is a line feed.
|
37337 | if (char === CR_CHAR) {
|
37338 | if (text.charCodeAt(pos) === LF_CHAR) {
|
37339 | pos++;
|
37340 | }
|
37341 | result.push(pos);
|
37342 | }
|
37343 | else if (char === LF_CHAR || char === LINE_SEP_CHAR || char === PARAGRAPH_CHAR) {
|
37344 | result.push(pos);
|
37345 | }
|
37346 | }
|
37347 | result.push(pos);
|
37348 | return result;
|
37349 | }
|
37350 | /** Finds the closest line start for the given position. */
|
37351 | function findClosestLineStartPosition(linesMap, position, low = 0, high = linesMap.length - 1) {
|
37352 | while (low <= high) {
|
37353 | const pivotIdx = Math.floor((low + high) / 2);
|
37354 | const pivotEl = linesMap[pivotIdx];
|
37355 | if (pivotEl === position) {
|
37356 | return pivotIdx;
|
37357 | }
|
37358 | else if (position > pivotEl) {
|
37359 | low = pivotIdx + 1;
|
37360 | }
|
37361 | else {
|
37362 | high = pivotIdx - 1;
|
37363 | }
|
37364 | }
|
37365 | // In case there was no exact match, return the closest "lower" line index. We also
|
37366 | // subtract the index by one because want the index of the previous line start.
|
37367 | return low - 1;
|
37368 | }
|
37369 |
|
37370 | /**
|
37371 | * @license
|
37372 | * Copyright Google LLC All Rights Reserved.
|
37373 | *
|
37374 | * Use of this source code is governed by an MIT-style license that can be
|
37375 | * found in the LICENSE file at https://angular.io/license
|
37376 | */
|
37377 | /**
|
37378 | * Represents the source of a template that was processed during type-checking. This information is
|
37379 | * used when translating parse offsets in diagnostics back to their original line/column location.
|
37380 | */
|
37381 | class TemplateSource {
|
37382 | constructor(mapping, file) {
|
37383 | this.mapping = mapping;
|
37384 | this.file = file;
|
37385 | this.lineStarts = null;
|
37386 | }
|
37387 | toParseSourceSpan(start, end) {
|
37388 | const startLoc = this.toParseLocation(start);
|
37389 | const endLoc = this.toParseLocation(end);
|
37390 | return new ParseSourceSpan(startLoc, endLoc);
|
37391 | }
|
37392 | toParseLocation(position) {
|
37393 | const lineStarts = this.acquireLineStarts();
|
37394 | const { line, character } = getLineAndCharacterFromPosition(lineStarts, position);
|
37395 | return new ParseLocation(this.file, position, line, character);
|
37396 | }
|
37397 | acquireLineStarts() {
|
37398 | if (this.lineStarts === null) {
|
37399 | this.lineStarts = computeLineStartsMap(this.file.content);
|
37400 | }
|
37401 | return this.lineStarts;
|
37402 | }
|
37403 | }
|
37404 | /**
|
37405 | * Assigns IDs to templates and keeps track of their origins.
|
37406 | *
|
37407 | * Implements `TemplateSourceResolver` to resolve the source of a template based on these IDs.
|
37408 | */
|
37409 | class TemplateSourceManager {
|
37410 | constructor() {
|
37411 | /**
|
37412 | * This map keeps track of all template sources that have been type-checked by the id that is
|
37413 | * attached to a TCB's function declaration as leading trivia. This enables translation of
|
37414 | * diagnostics produced for TCB code to their source location in the template.
|
37415 | */
|
37416 | this.templateSources = new Map();
|
37417 | }
|
37418 | getTemplateId(node) {
|
37419 | return getTemplateId(node);
|
37420 | }
|
37421 | captureSource(node, mapping, file) {
|
37422 | const id = getTemplateId(node);
|
37423 | this.templateSources.set(id, new TemplateSource(mapping, file));
|
37424 | return id;
|
37425 | }
|
37426 | getSourceMapping(id) {
|
37427 | if (!this.templateSources.has(id)) {
|
37428 | throw new Error(`Unexpected unknown template ID: ${id}`);
|
37429 | }
|
37430 | return this.templateSources.get(id).mapping;
|
37431 | }
|
37432 | toParseSourceSpan(id, span) {
|
37433 | if (!this.templateSources.has(id)) {
|
37434 | return null;
|
37435 | }
|
37436 | const templateSource = this.templateSources.get(id);
|
37437 | return templateSource.toParseSourceSpan(span.start, span.end);
|
37438 | }
|
37439 | }
|
37440 |
|
37441 | /**
|
37442 | * @license
|
37443 | * Copyright Google LLC All Rights Reserved.
|
37444 | *
|
37445 | * Use of this source code is governed by an MIT-style license that can be
|
37446 | * found in the LICENSE file at https://angular.io/license
|
37447 | */
|
37448 | /**
|
37449 | * Generates and caches `Symbol`s for various template structures for a given component.
|
37450 | *
|
37451 | * The `SymbolBuilder` internally caches the `Symbol`s it creates, and must be destroyed and
|
37452 | * replaced if the component's template changes.
|
37453 | */
|
37454 | class SymbolBuilder {
|
37455 | constructor(shimPath, typeCheckBlock, templateData, componentScopeReader,
|
37456 | // The `ts.TypeChecker` depends on the current type-checking program, and so must be requested
|
37457 | // on-demand instead of cached.
|
37458 | getTypeChecker) {
|
37459 | this.shimPath = shimPath;
|
37460 | this.typeCheckBlock = typeCheckBlock;
|
37461 | this.templateData = templateData;
|
37462 | this.componentScopeReader = componentScopeReader;
|
37463 | this.getTypeChecker = getTypeChecker;
|
37464 | this.symbolCache = new Map();
|
37465 | }
|
37466 | getSymbol(node) {
|
37467 | if (this.symbolCache.has(node)) {
|
37468 | return this.symbolCache.get(node);
|
37469 | }
|
37470 | let symbol = null;
|
37471 | if (node instanceof BoundAttribute || node instanceof TextAttribute) {
|
37472 | // TODO(atscott): input and output bindings only return the first directive match but should
|
37473 | // return a list of bindings for all of them.
|
37474 | symbol = this.getSymbolOfInputBinding(node);
|
37475 | }
|
37476 | else if (node instanceof BoundEvent) {
|
37477 | symbol = this.getSymbolOfBoundEvent(node);
|
37478 | }
|
37479 | else if (node instanceof Element) {
|
37480 | symbol = this.getSymbolOfElement(node);
|
37481 | }
|
37482 | else if (node instanceof Template) {
|
37483 | symbol = this.getSymbolOfAstTemplate(node);
|
37484 | }
|
37485 | else if (node instanceof Variable) {
|
37486 | symbol = this.getSymbolOfVariable(node);
|
37487 | }
|
37488 | else if (node instanceof Reference) {
|
37489 | symbol = this.getSymbolOfReference(node);
|
37490 | }
|
37491 | else if (node instanceof BindingPipe) {
|
37492 | symbol = this.getSymbolOfPipe(node);
|
37493 | }
|
37494 | else if (node instanceof AST) {
|
37495 | symbol = this.getSymbolOfTemplateExpression(node);
|
37496 | }
|
37497 | this.symbolCache.set(node, symbol);
|
37498 | return symbol;
|
37499 | }
|
37500 | getSymbolOfAstTemplate(template) {
|
37501 | const directives = this.getDirectivesOfNode(template);
|
37502 | return { kind: SymbolKind.Template, directives, templateNode: template };
|
37503 | }
|
37504 | getSymbolOfElement(element) {
|
37505 | var _a;
|
37506 | const elementSourceSpan = (_a = element.startSourceSpan) !== null && _a !== void 0 ? _a : element.sourceSpan;
|
37507 | const node = findFirstMatchingNode(this.typeCheckBlock, { withSpan: elementSourceSpan, filter: ts$1.isVariableDeclaration });
|
37508 | if (node === null) {
|
37509 | return null;
|
37510 | }
|
37511 | const symbolFromDeclaration = this.getSymbolOfTsNode(node);
|
37512 | if (symbolFromDeclaration === null || symbolFromDeclaration.tsSymbol === null) {
|
37513 | return null;
|
37514 | }
|
37515 | const directives = this.getDirectivesOfNode(element);
|
37516 | // All statements in the TCB are `Expression`s that optionally include more information.
|
37517 | // An `ElementSymbol` uses the information returned for the variable declaration expression,
|
37518 | // adds the directives for the element, and updates the `kind` to be `SymbolKind.Element`.
|
37519 | return Object.assign(Object.assign({}, symbolFromDeclaration), { kind: SymbolKind.Element, directives, templateNode: element });
|
37520 | }
|
37521 | getDirectivesOfNode(element) {
|
37522 | var _a;
|
37523 | const elementSourceSpan = (_a = element.startSourceSpan) !== null && _a !== void 0 ? _a : element.sourceSpan;
|
37524 | const tcbSourceFile = this.typeCheckBlock.getSourceFile();
|
37525 | // directives could be either:
|
37526 | // - var _t1: TestDir /*T:D*/ = (null!);
|
37527 | // - var _t1 /*T:D*/ = _ctor1({});
|
37528 | const isDirectiveDeclaration = (node) => (ts$1.isTypeNode(node) || ts$1.isIdentifier(node)) && ts$1.isVariableDeclaration(node.parent) &&
|
37529 | hasExpressionIdentifier(tcbSourceFile, node, ExpressionIdentifier.DIRECTIVE);
|
37530 | const nodes = findAllMatchingNodes(this.typeCheckBlock, { withSpan: elementSourceSpan, filter: isDirectiveDeclaration });
|
37531 | return nodes
|
37532 | .map(node => {
|
37533 | var _a;
|
37534 | const symbol = this.getSymbolOfTsNode(node.parent);
|
37535 | if (symbol === null || symbol.tsSymbol === null ||
|
37536 | symbol.tsSymbol.valueDeclaration === undefined ||
|
37537 | !ts$1.isClassDeclaration(symbol.tsSymbol.valueDeclaration)) {
|
37538 | return null;
|
37539 | }
|
37540 | const meta = this.getDirectiveMeta(element, symbol.tsSymbol.valueDeclaration);
|
37541 | if (meta === null) {
|
37542 | return null;
|
37543 | }
|
37544 | const ngModule = this.getDirectiveModule(symbol.tsSymbol.valueDeclaration);
|
37545 | if (meta.selector === null) {
|
37546 | return null;
|
37547 | }
|
37548 | const isComponent = (_a = meta.isComponent) !== null && _a !== void 0 ? _a : null;
|
37549 | const directiveSymbol = Object.assign(Object.assign({}, symbol), { tsSymbol: symbol.tsSymbol, selector: meta.selector, isComponent,
|
37550 | ngModule, kind: SymbolKind.Directive, isStructural: meta.isStructural });
|
37551 | return directiveSymbol;
|
37552 | })
|
37553 | .filter((d) => d !== null);
|
37554 | }
|
37555 | getDirectiveMeta(host, directiveDeclaration) {
|
37556 | var _a;
|
37557 | const directives = this.templateData.boundTarget.getDirectivesOfNode(host);
|
37558 | if (directives === null) {
|
37559 | return null;
|
37560 | }
|
37561 | return (_a = directives.find(m => m.ref.node === directiveDeclaration)) !== null && _a !== void 0 ? _a : null;
|
37562 | }
|
37563 | getDirectiveModule(declaration) {
|
37564 | const scope = this.componentScopeReader.getScopeForComponent(declaration);
|
37565 | if (scope === null) {
|
37566 | return null;
|
37567 | }
|
37568 | return scope.ngModule;
|
37569 | }
|
37570 | getSymbolOfBoundEvent(eventBinding) {
|
37571 | const consumer = this.templateData.boundTarget.getConsumerOfBinding(eventBinding);
|
37572 | if (consumer === null) {
|
37573 | return null;
|
37574 | }
|
37575 | // Outputs in the TCB look like one of the two:
|
37576 | // * _t1["outputField"].subscribe(handler);
|
37577 | // * _t1.addEventListener(handler);
|
37578 | // Even with strict null checks disabled, we still produce the access as a separate statement
|
37579 | // so that it can be found here.
|
37580 | let expectedAccess;
|
37581 | if (consumer instanceof Template || consumer instanceof Element) {
|
37582 | expectedAccess = 'addEventListener';
|
37583 | }
|
37584 | else {
|
37585 | const bindingPropertyNames = consumer.outputs.getByBindingPropertyName(eventBinding.name);
|
37586 | if (bindingPropertyNames === null || bindingPropertyNames.length === 0) {
|
37587 | return null;
|
37588 | }
|
37589 | // Note that we only get the expectedAccess text from a single consumer of the binding. If
|
37590 | // there are multiple consumers (not supported in the `boundTarget` API) and one of them has
|
37591 | // an alias, it will not get matched here.
|
37592 | expectedAccess = bindingPropertyNames[0].classPropertyName;
|
37593 | }
|
37594 | function filter(n) {
|
37595 | if (!isAccessExpression(n)) {
|
37596 | return false;
|
37597 | }
|
37598 | if (ts$1.isPropertyAccessExpression(n)) {
|
37599 | return n.name.getText() === expectedAccess;
|
37600 | }
|
37601 | else {
|
37602 | return ts$1.isStringLiteral(n.argumentExpression) &&
|
37603 | n.argumentExpression.text === expectedAccess;
|
37604 | }
|
37605 | }
|
37606 | const outputFieldAccesses = findAllMatchingNodes(this.typeCheckBlock, { withSpan: eventBinding.keySpan, filter });
|
37607 | const bindings = [];
|
37608 | for (const outputFieldAccess of outputFieldAccesses) {
|
37609 | if (consumer instanceof Template || consumer instanceof Element) {
|
37610 | if (!ts$1.isPropertyAccessExpression(outputFieldAccess)) {
|
37611 | continue;
|
37612 | }
|
37613 | const addEventListener = outputFieldAccess.name;
|
37614 | const tsSymbol = this.getTypeChecker().getSymbolAtLocation(addEventListener);
|
37615 | const tsType = this.getTypeChecker().getTypeAtLocation(addEventListener);
|
37616 | const positionInShimFile = this.getShimPositionForNode(addEventListener);
|
37617 | const target = this.getSymbol(consumer);
|
37618 | if (target === null || tsSymbol === undefined) {
|
37619 | continue;
|
37620 | }
|
37621 | bindings.push({
|
37622 | kind: SymbolKind.Binding,
|
37623 | tsSymbol,
|
37624 | tsType,
|
37625 | target,
|
37626 | shimLocation: { shimPath: this.shimPath, positionInShimFile },
|
37627 | });
|
37628 | }
|
37629 | else {
|
37630 | if (!ts$1.isElementAccessExpression(outputFieldAccess)) {
|
37631 | continue;
|
37632 | }
|
37633 | const tsSymbol = this.getTypeChecker().getSymbolAtLocation(outputFieldAccess.argumentExpression);
|
37634 | if (tsSymbol === undefined) {
|
37635 | continue;
|
37636 | }
|
37637 | const target = this.getDirectiveSymbolForAccessExpression(outputFieldAccess, consumer);
|
37638 | if (target === null) {
|
37639 | continue;
|
37640 | }
|
37641 | const positionInShimFile = this.getShimPositionForNode(outputFieldAccess);
|
37642 | const tsType = this.getTypeChecker().getTypeAtLocation(outputFieldAccess);
|
37643 | bindings.push({
|
37644 | kind: SymbolKind.Binding,
|
37645 | tsSymbol,
|
37646 | tsType,
|
37647 | target,
|
37648 | shimLocation: { shimPath: this.shimPath, positionInShimFile },
|
37649 | });
|
37650 | }
|
37651 | }
|
37652 | if (bindings.length === 0) {
|
37653 | return null;
|
37654 | }
|
37655 | return { kind: SymbolKind.Output, bindings };
|
37656 | }
|
37657 | getSymbolOfInputBinding(binding) {
|
37658 | const consumer = this.templateData.boundTarget.getConsumerOfBinding(binding);
|
37659 | if (consumer === null) {
|
37660 | return null;
|
37661 | }
|
37662 | if (consumer instanceof Element || consumer instanceof Template) {
|
37663 | const host = this.getSymbol(consumer);
|
37664 | return host !== null ? { kind: SymbolKind.DomBinding, host } : null;
|
37665 | }
|
37666 | const nodes = findAllMatchingNodes(this.typeCheckBlock, { withSpan: binding.sourceSpan, filter: isAssignment });
|
37667 | const bindings = [];
|
37668 | for (const node of nodes) {
|
37669 | if (!isAccessExpression(node.left)) {
|
37670 | continue;
|
37671 | }
|
37672 | const symbolInfo = this.getSymbolOfTsNode(node.left);
|
37673 | if (symbolInfo === null || symbolInfo.tsSymbol === null) {
|
37674 | continue;
|
37675 | }
|
37676 | const target = this.getDirectiveSymbolForAccessExpression(node.left, consumer);
|
37677 | if (target === null) {
|
37678 | continue;
|
37679 | }
|
37680 | bindings.push(Object.assign(Object.assign({}, symbolInfo), { tsSymbol: symbolInfo.tsSymbol, kind: SymbolKind.Binding, target }));
|
37681 | }
|
37682 | if (bindings.length === 0) {
|
37683 | return null;
|
37684 | }
|
37685 | return { kind: SymbolKind.Input, bindings };
|
37686 | }
|
37687 | getDirectiveSymbolForAccessExpression(node, { isComponent, selector, isStructural }) {
|
37688 | var _a;
|
37689 | // In either case, `_t1["index"]` or `_t1.index`, `node.expression` is _t1.
|
37690 | // The retrieved symbol for _t1 will be the variable declaration.
|
37691 | const tsSymbol = this.getTypeChecker().getSymbolAtLocation(node.expression);
|
37692 | if (tsSymbol === undefined || tsSymbol.declarations.length === 0 || selector === null) {
|
37693 | return null;
|
37694 | }
|
37695 | const [declaration] = tsSymbol.declarations;
|
37696 | if (!ts$1.isVariableDeclaration(declaration) ||
|
37697 | !hasExpressionIdentifier(
|
37698 | // The expression identifier could be on the type (for regular directives) or the name
|
37699 | // (for generic directives and the ctor op).
|
37700 | declaration.getSourceFile(), (_a = declaration.type) !== null && _a !== void 0 ? _a : declaration.name, ExpressionIdentifier.DIRECTIVE)) {
|
37701 | return null;
|
37702 | }
|
37703 | const symbol = this.getSymbolOfTsNode(declaration);
|
37704 | if (symbol === null || symbol.tsSymbol === null ||
|
37705 | symbol.tsSymbol.valueDeclaration === undefined ||
|
37706 | !ts$1.isClassDeclaration(symbol.tsSymbol.valueDeclaration)) {
|
37707 | return null;
|
37708 | }
|
37709 | const ngModule = this.getDirectiveModule(symbol.tsSymbol.valueDeclaration);
|
37710 | return {
|
37711 | kind: SymbolKind.Directive,
|
37712 | tsSymbol: symbol.tsSymbol,
|
37713 | tsType: symbol.tsType,
|
37714 | shimLocation: symbol.shimLocation,
|
37715 | isComponent,
|
37716 | isStructural,
|
37717 | selector,
|
37718 | ngModule,
|
37719 | };
|
37720 | }
|
37721 | getSymbolOfVariable(variable) {
|
37722 | const node = findFirstMatchingNode(this.typeCheckBlock, { withSpan: variable.sourceSpan, filter: ts$1.isVariableDeclaration });
|
37723 | if (node === null || node.initializer === undefined) {
|
37724 | return null;
|
37725 | }
|
37726 | const expressionSymbol = this.getSymbolOfTsNode(node.initializer);
|
37727 | if (expressionSymbol === null) {
|
37728 | return null;
|
37729 | }
|
37730 | return {
|
37731 | tsType: expressionSymbol.tsType,
|
37732 | tsSymbol: expressionSymbol.tsSymbol,
|
37733 | initializerLocation: expressionSymbol.shimLocation,
|
37734 | kind: SymbolKind.Variable,
|
37735 | declaration: variable,
|
37736 | localVarLocation: {
|
37737 | shimPath: this.shimPath,
|
37738 | positionInShimFile: this.getShimPositionForNode(node.name),
|
37739 | }
|
37740 | };
|
37741 | }
|
37742 | getSymbolOfReference(ref) {
|
37743 | const target = this.templateData.boundTarget.getReferenceTarget(ref);
|
37744 | // Find the node for the reference declaration, i.e. `var _t2 = _t1;`
|
37745 | let node = findFirstMatchingNode(this.typeCheckBlock, { withSpan: ref.sourceSpan, filter: ts$1.isVariableDeclaration });
|
37746 | if (node === null || target === null || node.initializer === undefined) {
|
37747 | return null;
|
37748 | }
|
37749 | // Get the original declaration for the references variable, with the exception of template refs
|
37750 | // which are of the form var _t3 = (_t2 as any as i2.TemplateRef<any>)
|
37751 | // TODO(atscott): Consider adding an `ExpressionIdentifier` to tag variable declaration
|
37752 | // initializers as invalid for symbol retrieval.
|
37753 | const originalDeclaration = ts$1.isParenthesizedExpression(node.initializer) &&
|
37754 | ts$1.isAsExpression(node.initializer.expression) ?
|
37755 | this.getTypeChecker().getSymbolAtLocation(node.name) :
|
37756 | this.getTypeChecker().getSymbolAtLocation(node.initializer);
|
37757 | if (originalDeclaration === undefined || originalDeclaration.valueDeclaration === undefined) {
|
37758 | return null;
|
37759 | }
|
37760 | const symbol = this.getSymbolOfTsNode(originalDeclaration.valueDeclaration);
|
37761 | if (symbol === null || symbol.tsSymbol === null) {
|
37762 | return null;
|
37763 | }
|
37764 | const referenceVarShimLocation = {
|
37765 | shimPath: this.shimPath,
|
37766 | positionInShimFile: this.getShimPositionForNode(node),
|
37767 | };
|
37768 | if (target instanceof Template || target instanceof Element) {
|
37769 | return {
|
37770 | kind: SymbolKind.Reference,
|
37771 | tsSymbol: symbol.tsSymbol,
|
37772 | tsType: symbol.tsType,
|
37773 | target,
|
37774 | declaration: ref,
|
37775 | targetLocation: symbol.shimLocation,
|
37776 | referenceVarLocation: referenceVarShimLocation,
|
37777 | };
|
37778 | }
|
37779 | else {
|
37780 | if (!ts$1.isClassDeclaration(target.directive.ref.node)) {
|
37781 | return null;
|
37782 | }
|
37783 | return {
|
37784 | kind: SymbolKind.Reference,
|
37785 | tsSymbol: symbol.tsSymbol,
|
37786 | tsType: symbol.tsType,
|
37787 | declaration: ref,
|
37788 | target: target.directive.ref.node,
|
37789 | targetLocation: symbol.shimLocation,
|
37790 | referenceVarLocation: referenceVarShimLocation,
|
37791 | };
|
37792 | }
|
37793 | }
|
37794 | getSymbolOfPipe(expression) {
|
37795 | const node = findFirstMatchingNode(this.typeCheckBlock, { withSpan: expression.sourceSpan, filter: ts$1.isCallExpression });
|
37796 | if (node === null || !ts$1.isPropertyAccessExpression(node.expression)) {
|
37797 | return null;
|
37798 | }
|
37799 | const methodAccess = node.expression;
|
37800 | // Find the node for the pipe variable from the transform property access. This will be one of
|
37801 | // two forms: `_pipe1.transform` or `(_pipe1 as any).transform`.
|
37802 | const pipeVariableNode = ts$1.isParenthesizedExpression(methodAccess.expression) &&
|
37803 | ts$1.isAsExpression(methodAccess.expression.expression) ?
|
37804 | methodAccess.expression.expression.expression :
|
37805 | methodAccess.expression;
|
37806 | const pipeDeclaration = this.getTypeChecker().getSymbolAtLocation(pipeVariableNode);
|
37807 | if (pipeDeclaration === undefined || pipeDeclaration.valueDeclaration === undefined) {
|
37808 | return null;
|
37809 | }
|
37810 | const pipeInstance = this.getSymbolOfTsNode(pipeDeclaration.valueDeclaration);
|
37811 | if (pipeInstance === null || pipeInstance.tsSymbol === null) {
|
37812 | return null;
|
37813 | }
|
37814 | const symbolInfo = this.getSymbolOfTsNode(methodAccess);
|
37815 | if (symbolInfo === null) {
|
37816 | return null;
|
37817 | }
|
37818 | return Object.assign(Object.assign({ kind: SymbolKind.Pipe }, symbolInfo), { classSymbol: Object.assign(Object.assign({}, pipeInstance), { tsSymbol: pipeInstance.tsSymbol }) });
|
37819 | }
|
37820 | getSymbolOfTemplateExpression(expression) {
|
37821 | if (expression instanceof ASTWithSource) {
|
37822 | expression = expression.ast;
|
37823 | }
|
37824 | const expressionTarget = this.templateData.boundTarget.getExpressionTarget(expression);
|
37825 | if (expressionTarget !== null) {
|
37826 | return this.getSymbol(expressionTarget);
|
37827 | }
|
37828 | // The `name` part of a `PropertyWrite` and `MethodCall` does not have its own
|
37829 | // AST so there is no way to retrieve a `Symbol` for just the `name` via a specific node.
|
37830 | const withSpan = (expression instanceof PropertyWrite || expression instanceof MethodCall) ?
|
37831 | expression.nameSpan :
|
37832 | expression.sourceSpan;
|
37833 | let node = findFirstMatchingNode(this.typeCheckBlock, { withSpan, filter: (n) => true });
|
37834 | if (node === null) {
|
37835 | return null;
|
37836 | }
|
37837 | while (ts$1.isParenthesizedExpression(node)) {
|
37838 | node = node.expression;
|
37839 | }
|
37840 | // - If we have safe property read ("a?.b") we want to get the Symbol for b, the `whenTrue`
|
37841 | // expression.
|
37842 | // - If our expression is a pipe binding ("a | test:b:c"), we want the Symbol for the
|
37843 | // `transform` on the pipe.
|
37844 | // - Otherwise, we retrieve the symbol for the node itself with no special considerations
|
37845 | if ((expression instanceof SafePropertyRead || expression instanceof SafeMethodCall) &&
|
37846 | ts$1.isConditionalExpression(node)) {
|
37847 | const whenTrueSymbol = (expression instanceof SafeMethodCall && ts$1.isCallExpression(node.whenTrue)) ?
|
37848 | this.getSymbolOfTsNode(node.whenTrue.expression) :
|
37849 | this.getSymbolOfTsNode(node.whenTrue);
|
37850 | if (whenTrueSymbol === null) {
|
37851 | return null;
|
37852 | }
|
37853 | return Object.assign(Object.assign({}, whenTrueSymbol), { kind: SymbolKind.Expression,
|
37854 | // Rather than using the type of only the `whenTrue` part of the expression, we should
|
37855 | // still get the type of the whole conditional expression to include `|undefined`.
|
37856 | tsType: this.getTypeChecker().getTypeAtLocation(node) });
|
37857 | }
|
37858 | else {
|
37859 | const symbolInfo = this.getSymbolOfTsNode(node);
|
37860 | return symbolInfo === null ? null : Object.assign(Object.assign({}, symbolInfo), { kind: SymbolKind.Expression });
|
37861 | }
|
37862 | }
|
37863 | getSymbolOfTsNode(node) {
|
37864 | var _a;
|
37865 | while (ts$1.isParenthesizedExpression(node)) {
|
37866 | node = node.expression;
|
37867 | }
|
37868 | let tsSymbol;
|
37869 | if (ts$1.isPropertyAccessExpression(node)) {
|
37870 | tsSymbol = this.getTypeChecker().getSymbolAtLocation(node.name);
|
37871 | }
|
37872 | else if (ts$1.isElementAccessExpression(node)) {
|
37873 | tsSymbol = this.getTypeChecker().getSymbolAtLocation(node.argumentExpression);
|
37874 | }
|
37875 | else {
|
37876 | tsSymbol = this.getTypeChecker().getSymbolAtLocation(node);
|
37877 | }
|
37878 | const positionInShimFile = this.getShimPositionForNode(node);
|
37879 | const type = this.getTypeChecker().getTypeAtLocation(node);
|
37880 | return {
|
37881 | // If we could not find a symbol, fall back to the symbol on the type for the node.
|
37882 | // Some nodes won't have a "symbol at location" but will have a symbol for the type.
|
37883 | // Examples of this would be literals and `document.createElement('div')`.
|
37884 | tsSymbol: (_a = tsSymbol !== null && tsSymbol !== void 0 ? tsSymbol : type.symbol) !== null && _a !== void 0 ? _a : null,
|
37885 | tsType: type,
|
37886 | shimLocation: { shimPath: this.shimPath, positionInShimFile },
|
37887 | };
|
37888 | }
|
37889 | getShimPositionForNode(node) {
|
37890 | if (ts$1.isTypeReferenceNode(node)) {
|
37891 | return this.getShimPositionForNode(node.typeName);
|
37892 | }
|
37893 | else if (ts$1.isQualifiedName(node)) {
|
37894 | return node.right.getStart();
|
37895 | }
|
37896 | else if (ts$1.isPropertyAccessExpression(node)) {
|
37897 | return node.name.getStart();
|
37898 | }
|
37899 | else if (ts$1.isElementAccessExpression(node)) {
|
37900 | return node.argumentExpression.getStart();
|
37901 | }
|
37902 | else {
|
37903 | return node.getStart();
|
37904 | }
|
37905 | }
|
37906 | }
|
37907 |
|
37908 | /**
|
37909 | * @license
|
37910 | * Copyright Google LLC All Rights Reserved.
|
37911 | *
|
37912 | * Use of this source code is governed by an MIT-style license that can be
|
37913 | * found in the LICENSE file at https://angular.io/license
|
37914 | */
|
37915 | const REGISTRY$1 = new DomElementSchemaRegistry();
|
37916 | /**
|
37917 | * Primary template type-checking engine, which performs type-checking using a
|
37918 | * `TypeCheckingProgramStrategy` for type-checking program maintenance, and the
|
37919 | * `ProgramTypeCheckAdapter` for generation of template type-checking code.
|
37920 | */
|
37921 | class TemplateTypeCheckerImpl {
|
37922 | constructor(originalProgram, typeCheckingStrategy, typeCheckAdapter, config, refEmitter, reflector, compilerHost, priorBuild, componentScopeReader, typeCheckScopeRegistry) {
|
37923 | this.originalProgram = originalProgram;
|
37924 | this.typeCheckingStrategy = typeCheckingStrategy;
|
37925 | this.typeCheckAdapter = typeCheckAdapter;
|
37926 | this.config = config;
|
37927 | this.refEmitter = refEmitter;
|
37928 | this.reflector = reflector;
|
37929 | this.compilerHost = compilerHost;
|
37930 | this.priorBuild = priorBuild;
|
37931 | this.componentScopeReader = componentScopeReader;
|
37932 | this.typeCheckScopeRegistry = typeCheckScopeRegistry;
|
37933 | this.state = new Map();
|
37934 | /**
|
37935 | * Stores the `CompletionEngine` which powers autocompletion for each component class.
|
37936 | *
|
37937 | * Must be invalidated whenever the component's template or the `ts.Program` changes. Invalidation
|
37938 | * on template changes is performed within this `TemplateTypeCheckerImpl` instance. When the
|
37939 | * `ts.Program` changes, the `TemplateTypeCheckerImpl` as a whole is destroyed and replaced.
|
37940 | */
|
37941 | this.completionCache = new Map();
|
37942 | /**
|
37943 | * Stores the `SymbolBuilder` which creates symbols for each component class.
|
37944 | *
|
37945 | * Must be invalidated whenever the component's template or the `ts.Program` changes. Invalidation
|
37946 | * on template changes is performed within this `TemplateTypeCheckerImpl` instance. When the
|
37947 | * `ts.Program` changes, the `TemplateTypeCheckerImpl` as a whole is destroyed and replaced.
|
37948 | */
|
37949 | this.symbolBuilderCache = new Map();
|
37950 | /**
|
37951 | * Stores directives and pipes that are in scope for each component.
|
37952 | *
|
37953 | * Unlike other caches, the scope of a component is not affected by its template. It will be
|
37954 | * destroyed when the `ts.Program` changes and the `TemplateTypeCheckerImpl` as a whole is
|
37955 | * destroyed and replaced.
|
37956 | */
|
37957 | this.scopeCache = new Map();
|
37958 | /**
|
37959 | * Stores potential element tags for each component (a union of DOM tags as well as directive
|
37960 | * tags).
|
37961 | *
|
37962 | * Unlike other caches, the scope of a component is not affected by its template. It will be
|
37963 | * destroyed when the `ts.Program` changes and the `TemplateTypeCheckerImpl` as a whole is
|
37964 | * destroyed and replaced.
|
37965 | */
|
37966 | this.elementTagCache = new Map();
|
37967 | this.isComplete = false;
|
37968 | }
|
37969 | getTemplate(component) {
|
37970 | const { data } = this.getLatestComponentState(component);
|
37971 | if (data === null) {
|
37972 | return null;
|
37973 | }
|
37974 | return data.template;
|
37975 | }
|
37976 | getLatestComponentState(component) {
|
37977 | this.ensureShimForComponent(component);
|
37978 | const sf = component.getSourceFile();
|
37979 | const sfPath = absoluteFromSourceFile(sf);
|
37980 | const shimPath = this.typeCheckingStrategy.shimPathForComponent(component);
|
37981 | const fileRecord = this.getFileData(sfPath);
|
37982 | if (!fileRecord.shimData.has(shimPath)) {
|
37983 | return { data: null, tcb: null, shimPath };
|
37984 | }
|
37985 | const templateId = fileRecord.sourceManager.getTemplateId(component);
|
37986 | const shimRecord = fileRecord.shimData.get(shimPath);
|
37987 | const id = fileRecord.sourceManager.getTemplateId(component);
|
37988 | const program = this.typeCheckingStrategy.getProgram();
|
37989 | const shimSf = getSourceFileOrNull(program, shimPath);
|
37990 | if (shimSf === null || !fileRecord.shimData.has(shimPath)) {
|
37991 | throw new Error(`Error: no shim file in program: ${shimPath}`);
|
37992 | }
|
37993 | let tcb = findTypeCheckBlock(shimSf, id, /*isDiagnosticsRequest*/ false);
|
37994 | if (tcb === null) {
|
37995 | // Try for an inline block.
|
37996 | const inlineSf = getSourceFileOrError(program, sfPath);
|
37997 | tcb = findTypeCheckBlock(inlineSf, id, /*isDiagnosticsRequest*/ false);
|
37998 | }
|
37999 | let data = null;
|
38000 | if (shimRecord.templates.has(templateId)) {
|
38001 | data = shimRecord.templates.get(templateId);
|
38002 | }
|
38003 | return { data, tcb, shimPath };
|
38004 | }
|
38005 | isTrackedTypeCheckFile(filePath) {
|
38006 | return this.getFileAndShimRecordsForPath(filePath) !== null;
|
38007 | }
|
38008 | getFileAndShimRecordsForPath(shimPath) {
|
38009 | for (const fileRecord of this.state.values()) {
|
38010 | if (fileRecord.shimData.has(shimPath)) {
|
38011 | return { fileRecord, shimRecord: fileRecord.shimData.get(shimPath) };
|
38012 | }
|
38013 | }
|
38014 | return null;
|
38015 | }
|
38016 | getTemplateMappingAtShimLocation({ shimPath, positionInShimFile }) {
|
38017 | const records = this.getFileAndShimRecordsForPath(absoluteFrom(shimPath));
|
38018 | if (records === null) {
|
38019 | return null;
|
38020 | }
|
38021 | const { fileRecord } = records;
|
38022 | const shimSf = this.typeCheckingStrategy.getProgram().getSourceFile(absoluteFrom(shimPath));
|
38023 | if (shimSf === undefined) {
|
38024 | return null;
|
38025 | }
|
38026 | return getTemplateMapping(shimSf, positionInShimFile, fileRecord.sourceManager, /*isDiagnosticsRequest*/ false);
|
38027 | }
|
38028 | generateAllTypeCheckBlocks() {
|
38029 | this.ensureAllShimsForAllFiles();
|
38030 | }
|
38031 | /**
|
38032 | * Retrieve type-checking and template parse diagnostics from the given `ts.SourceFile` using the
|
38033 | * most recent type-checking program.
|
38034 | */
|
38035 | getDiagnosticsForFile(sf, optimizeFor) {
|
38036 | switch (optimizeFor) {
|
38037 | case OptimizeFor.WholeProgram:
|
38038 | this.ensureAllShimsForAllFiles();
|
38039 | break;
|
38040 | case OptimizeFor.SingleFile:
|
38041 | this.ensureAllShimsForOneFile(sf);
|
38042 | break;
|
38043 | }
|
38044 | const sfPath = absoluteFromSourceFile(sf);
|
38045 | const fileRecord = this.state.get(sfPath);
|
38046 | const typeCheckProgram = this.typeCheckingStrategy.getProgram();
|
38047 | const diagnostics = [];
|
38048 | if (fileRecord.hasInlines) {
|
38049 | const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath);
|
38050 | diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf).map(diag => convertDiagnostic(diag, fileRecord.sourceManager)));
|
38051 | }
|
38052 | for (const [shimPath, shimRecord] of fileRecord.shimData) {
|
38053 | const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
|
38054 | diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(shimSf).map(diag => convertDiagnostic(diag, fileRecord.sourceManager)));
|
38055 | diagnostics.push(...shimRecord.genesisDiagnostics);
|
38056 | for (const templateData of shimRecord.templates.values()) {
|
38057 | diagnostics.push(...templateData.templateDiagnostics);
|
38058 | }
|
38059 | }
|
38060 | return diagnostics.filter((diag) => diag !== null);
|
38061 | }
|
38062 | getDiagnosticsForComponent(component) {
|
38063 | this.ensureShimForComponent(component);
|
38064 | const sf = component.getSourceFile();
|
38065 | const sfPath = absoluteFromSourceFile(sf);
|
38066 | const shimPath = this.typeCheckingStrategy.shimPathForComponent(component);
|
38067 | const fileRecord = this.getFileData(sfPath);
|
38068 | if (!fileRecord.shimData.has(shimPath)) {
|
38069 | return [];
|
38070 | }
|
38071 | const templateId = fileRecord.sourceManager.getTemplateId(component);
|
38072 | const shimRecord = fileRecord.shimData.get(shimPath);
|
38073 | const typeCheckProgram = this.typeCheckingStrategy.getProgram();
|
38074 | const diagnostics = [];
|
38075 | if (shimRecord.hasInlines) {
|
38076 | const inlineSf = getSourceFileOrError(typeCheckProgram, sfPath);
|
38077 | diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(inlineSf).map(diag => convertDiagnostic(diag, fileRecord.sourceManager)));
|
38078 | }
|
38079 | const shimSf = getSourceFileOrError(typeCheckProgram, shimPath);
|
38080 | diagnostics.push(...typeCheckProgram.getSemanticDiagnostics(shimSf).map(diag => convertDiagnostic(diag, fileRecord.sourceManager)));
|
38081 | diagnostics.push(...shimRecord.genesisDiagnostics);
|
38082 | for (const templateData of shimRecord.templates.values()) {
|
38083 | diagnostics.push(...templateData.templateDiagnostics);
|
38084 | }
|
38085 | return diagnostics.filter((diag) => diag !== null && diag.templateId === templateId);
|
38086 | }
|
38087 | getTypeCheckBlock(component) {
|
38088 | return this.getLatestComponentState(component).tcb;
|
38089 | }
|
38090 | getGlobalCompletions(context, component) {
|
38091 | const engine = this.getOrCreateCompletionEngine(component);
|
38092 | if (engine === null) {
|
38093 | return null;
|
38094 | }
|
38095 | return engine.getGlobalCompletions(context);
|
38096 | }
|
38097 | getExpressionCompletionLocation(ast, component) {
|
38098 | const engine = this.getOrCreateCompletionEngine(component);
|
38099 | if (engine === null) {
|
38100 | return null;
|
38101 | }
|
38102 | return engine.getExpressionCompletionLocation(ast);
|
38103 | }
|
38104 | invalidateClass(clazz) {
|
38105 | this.completionCache.delete(clazz);
|
38106 | this.symbolBuilderCache.delete(clazz);
|
38107 | this.scopeCache.delete(clazz);
|
38108 | this.elementTagCache.delete(clazz);
|
38109 | const sf = clazz.getSourceFile();
|
38110 | const sfPath = absoluteFromSourceFile(sf);
|
38111 | const shimPath = this.typeCheckingStrategy.shimPathForComponent(clazz);
|
38112 | const fileData = this.getFileData(sfPath);
|
38113 | const templateId = fileData.sourceManager.getTemplateId(clazz);
|
38114 | fileData.shimData.delete(shimPath);
|
38115 | fileData.isComplete = false;
|
38116 | this.isComplete = false;
|
38117 | }
|
38118 | getOrCreateCompletionEngine(component) {
|
38119 | if (this.completionCache.has(component)) {
|
38120 | return this.completionCache.get(component);
|
38121 | }
|
38122 | const { tcb, data, shimPath } = this.getLatestComponentState(component);
|
38123 | if (tcb === null || data === null) {
|
38124 | return null;
|
38125 | }
|
38126 | const engine = new CompletionEngine(tcb, data, shimPath);
|
38127 | this.completionCache.set(component, engine);
|
38128 | return engine;
|
38129 | }
|
38130 | maybeAdoptPriorResultsForFile(sf) {
|
38131 | const sfPath = absoluteFromSourceFile(sf);
|
38132 | if (this.state.has(sfPath)) {
|
38133 | const existingResults = this.state.get(sfPath);
|
38134 | if (existingResults.isComplete) {
|
38135 | // All data for this file has already been generated, so no need to adopt anything.
|
38136 | return;
|
38137 | }
|
38138 | }
|
38139 | const previousResults = this.priorBuild.priorTypeCheckingResultsFor(sf);
|
38140 | if (previousResults === null || !previousResults.isComplete) {
|
38141 | return;
|
38142 | }
|
38143 | this.state.set(sfPath, previousResults);
|
38144 | }
|
38145 | ensureAllShimsForAllFiles() {
|
38146 | if (this.isComplete) {
|
38147 | return;
|
38148 | }
|
38149 | const host = new WholeProgramTypeCheckingHost(this);
|
38150 | const ctx = this.newContext(host);
|
38151 | for (const sf of this.originalProgram.getSourceFiles()) {
|
38152 | if (sf.isDeclarationFile || isShim(sf)) {
|
38153 | continue;
|
38154 | }
|
38155 | this.maybeAdoptPriorResultsForFile(sf);
|
38156 | const sfPath = absoluteFromSourceFile(sf);
|
38157 | const fileData = this.getFileData(sfPath);
|
38158 | if (fileData.isComplete) {
|
38159 | continue;
|
38160 | }
|
38161 | this.typeCheckAdapter.typeCheck(sf, ctx);
|
38162 | fileData.isComplete = true;
|
38163 | }
|
38164 | this.updateFromContext(ctx);
|
38165 | this.isComplete = true;
|
38166 | }
|
38167 | ensureAllShimsForOneFile(sf) {
|
38168 | this.maybeAdoptPriorResultsForFile(sf);
|
38169 | const sfPath = absoluteFromSourceFile(sf);
|
38170 | const fileData = this.getFileData(sfPath);
|
38171 | if (fileData.isComplete) {
|
38172 | // All data for this file is present and accounted for already.
|
38173 | return;
|
38174 | }
|
38175 | const host = new SingleFileTypeCheckingHost(sfPath, fileData, this.typeCheckingStrategy, this);
|
38176 | const ctx = this.newContext(host);
|
38177 | this.typeCheckAdapter.typeCheck(sf, ctx);
|
38178 | fileData.isComplete = true;
|
38179 | this.updateFromContext(ctx);
|
38180 | }
|
38181 | ensureShimForComponent(component) {
|
38182 | const sf = component.getSourceFile();
|
38183 | const sfPath = absoluteFromSourceFile(sf);
|
38184 | this.maybeAdoptPriorResultsForFile(sf);
|
38185 | const fileData = this.getFileData(sfPath);
|
38186 | const shimPath = this.typeCheckingStrategy.shimPathForComponent(component);
|
38187 | if (fileData.shimData.has(shimPath)) {
|
38188 | // All data for this component is available.
|
38189 | return;
|
38190 | }
|
38191 | const host = new SingleShimTypeCheckingHost(sfPath, fileData, this.typeCheckingStrategy, this, shimPath);
|
38192 | const ctx = this.newContext(host);
|
38193 | this.typeCheckAdapter.typeCheck(sf, ctx);
|
38194 | this.updateFromContext(ctx);
|
38195 | }
|
38196 | newContext(host) {
|
38197 | const inlining = this.typeCheckingStrategy.supportsInlineOperations ? InliningMode.InlineOps :
|
38198 | InliningMode.Error;
|
38199 | return new TypeCheckContextImpl(this.config, this.compilerHost, this.typeCheckingStrategy, this.refEmitter, this.reflector, host, inlining);
|
38200 | }
|
38201 | /**
|
38202 | * Remove any shim data that depends on inline operations applied to the type-checking program.
|
38203 | *
|
38204 | * This can be useful if new inlines need to be applied, and it's not possible to guarantee that
|
38205 | * they won't overwrite or corrupt existing inlines that are used by such shims.
|
38206 | */
|
38207 | clearAllShimDataUsingInlines() {
|
38208 | for (const fileData of this.state.values()) {
|
38209 | if (!fileData.hasInlines) {
|
38210 | continue;
|
38211 | }
|
38212 | for (const [shimFile, shimData] of fileData.shimData.entries()) {
|
38213 | if (shimData.hasInlines) {
|
38214 | fileData.shimData.delete(shimFile);
|
38215 | }
|
38216 | }
|
38217 | fileData.hasInlines = false;
|
38218 | fileData.isComplete = false;
|
38219 | this.isComplete = false;
|
38220 | }
|
38221 | }
|
38222 | updateFromContext(ctx) {
|
38223 | const updates = ctx.finalize();
|
38224 | this.typeCheckingStrategy.updateFiles(updates, UpdateMode.Incremental);
|
38225 | this.priorBuild.recordSuccessfulTypeCheck(this.state);
|
38226 | }
|
38227 | getFileData(path) {
|
38228 | if (!this.state.has(path)) {
|
38229 | this.state.set(path, {
|
38230 | hasInlines: false,
|
38231 | sourceManager: new TemplateSourceManager(),
|
38232 | isComplete: false,
|
38233 | shimData: new Map(),
|
38234 | });
|
38235 | }
|
38236 | return this.state.get(path);
|
38237 | }
|
38238 | getSymbolOfNode(node, component) {
|
38239 | const builder = this.getOrCreateSymbolBuilder(component);
|
38240 | if (builder === null) {
|
38241 | return null;
|
38242 | }
|
38243 | return builder.getSymbol(node);
|
38244 | }
|
38245 | getOrCreateSymbolBuilder(component) {
|
38246 | if (this.symbolBuilderCache.has(component)) {
|
38247 | return this.symbolBuilderCache.get(component);
|
38248 | }
|
38249 | const { tcb, data, shimPath } = this.getLatestComponentState(component);
|
38250 | if (tcb === null || data === null) {
|
38251 | return null;
|
38252 | }
|
38253 | const builder = new SymbolBuilder(shimPath, tcb, data, this.componentScopeReader, () => this.typeCheckingStrategy.getProgram().getTypeChecker());
|
38254 | this.symbolBuilderCache.set(component, builder);
|
38255 | return builder;
|
38256 | }
|
38257 | getDirectivesInScope(component) {
|
38258 | const data = this.getScopeData(component);
|
38259 | if (data === null) {
|
38260 | return null;
|
38261 | }
|
38262 | return data.directives;
|
38263 | }
|
38264 | getPipesInScope(component) {
|
38265 | const data = this.getScopeData(component);
|
38266 | if (data === null) {
|
38267 | return null;
|
38268 | }
|
38269 | return data.pipes;
|
38270 | }
|
38271 | getDirectiveMetadata(dir) {
|
38272 | if (!isNamedClassDeclaration(dir)) {
|
38273 | return null;
|
38274 | }
|
38275 | return this.typeCheckScopeRegistry.getTypeCheckDirectiveMetadata(new Reference$1(dir));
|
38276 | }
|
38277 | getPotentialElementTags(component) {
|
38278 | if (this.elementTagCache.has(component)) {
|
38279 | return this.elementTagCache.get(component);
|
38280 | }
|
38281 | const tagMap = new Map();
|
38282 | for (const tag of REGISTRY$1.allKnownElementNames()) {
|
38283 | tagMap.set(tag, null);
|
38284 | }
|
38285 | const scope = this.getScopeData(component);
|
38286 | if (scope !== null) {
|
38287 | for (const directive of scope.directives) {
|
38288 | for (const selector of CssSelector.parse(directive.selector)) {
|
38289 | if (selector.element === null || tagMap.has(selector.element)) {
|
38290 | // Skip this directive if it doesn't match an element tag, or if another directive has
|
38291 | // already been included with the same element name.
|
38292 | continue;
|
38293 | }
|
38294 | tagMap.set(selector.element, directive);
|
38295 | }
|
38296 | }
|
38297 | }
|
38298 | this.elementTagCache.set(component, tagMap);
|
38299 | return tagMap;
|
38300 | }
|
38301 | getPotentialDomBindings(tagName) {
|
38302 | const attributes = REGISTRY$1.allKnownAttributesOfElement(tagName);
|
38303 | return attributes.map(attribute => ({
|
38304 | attribute,
|
38305 | property: REGISTRY$1.getMappedPropName(attribute),
|
38306 | }));
|
38307 | }
|
38308 | getScopeData(component) {
|
38309 | if (this.scopeCache.has(component)) {
|
38310 | return this.scopeCache.get(component);
|
38311 | }
|
38312 | if (!isNamedClassDeclaration(component)) {
|
38313 | throw new Error(`AssertionError: components must have names`);
|
38314 | }
|
38315 | const scope = this.componentScopeReader.getScopeForComponent(component);
|
38316 | if (scope === null) {
|
38317 | return null;
|
38318 | }
|
38319 | const data = {
|
38320 | directives: [],
|
38321 | pipes: [],
|
38322 | isPoisoned: scope.compilation.isPoisoned,
|
38323 | };
|
38324 | const typeChecker = this.typeCheckingStrategy.getProgram().getTypeChecker();
|
38325 | for (const dir of scope.compilation.directives) {
|
38326 | if (dir.selector === null) {
|
38327 | // Skip this directive, it can't be added to a template anyway.
|
38328 | continue;
|
38329 | }
|
38330 | const tsSymbol = typeChecker.getSymbolAtLocation(dir.ref.node.name);
|
38331 | if (tsSymbol === undefined) {
|
38332 | continue;
|
38333 | }
|
38334 | let ngModule = null;
|
38335 | const moduleScopeOfDir = this.componentScopeReader.getScopeForComponent(dir.ref.node);
|
38336 | if (moduleScopeOfDir !== null) {
|
38337 | ngModule = moduleScopeOfDir.ngModule;
|
38338 | }
|
38339 | data.directives.push({
|
38340 | isComponent: dir.isComponent,
|
38341 | isStructural: dir.isStructural,
|
38342 | selector: dir.selector,
|
38343 | tsSymbol,
|
38344 | ngModule,
|
38345 | });
|
38346 | }
|
38347 | for (const pipe of scope.compilation.pipes) {
|
38348 | const tsSymbol = typeChecker.getSymbolAtLocation(pipe.ref.node.name);
|
38349 | if (tsSymbol === undefined) {
|
38350 | continue;
|
38351 | }
|
38352 | data.pipes.push({
|
38353 | name: pipe.name,
|
38354 | tsSymbol,
|
38355 | });
|
38356 | }
|
38357 | this.scopeCache.set(component, data);
|
38358 | return data;
|
38359 | }
|
38360 | }
|
38361 | function convertDiagnostic(diag, sourceResolver) {
|
38362 | if (!shouldReportDiagnostic(diag)) {
|
38363 | return null;
|
38364 | }
|
38365 | return translateDiagnostic(diag, sourceResolver);
|
38366 | }
|
38367 | /**
|
38368 | * Drives a `TypeCheckContext` to generate type-checking code for every component in the program.
|
38369 | */
|
38370 | class WholeProgramTypeCheckingHost {
|
38371 | constructor(impl) {
|
38372 | this.impl = impl;
|
38373 | }
|
38374 | getSourceManager(sfPath) {
|
38375 | return this.impl.getFileData(sfPath).sourceManager;
|
38376 | }
|
38377 | shouldCheckComponent(node) {
|
38378 | const fileData = this.impl.getFileData(absoluteFromSourceFile(node.getSourceFile()));
|
38379 | const shimPath = this.impl.typeCheckingStrategy.shimPathForComponent(node);
|
38380 | // The component needs to be checked unless the shim which would contain it already exists.
|
38381 | return !fileData.shimData.has(shimPath);
|
38382 | }
|
38383 | recordShimData(sfPath, data) {
|
38384 | const fileData = this.impl.getFileData(sfPath);
|
38385 | fileData.shimData.set(data.path, data);
|
38386 | if (data.hasInlines) {
|
38387 | fileData.hasInlines = true;
|
38388 | }
|
38389 | }
|
38390 | recordComplete(sfPath) {
|
38391 | this.impl.getFileData(sfPath).isComplete = true;
|
38392 | }
|
38393 | }
|
38394 | /**
|
38395 | * Drives a `TypeCheckContext` to generate type-checking code efficiently for a single input file.
|
38396 | */
|
38397 | class SingleFileTypeCheckingHost {
|
38398 | constructor(sfPath, fileData, strategy, impl) {
|
38399 | this.sfPath = sfPath;
|
38400 | this.fileData = fileData;
|
38401 | this.strategy = strategy;
|
38402 | this.impl = impl;
|
38403 | this.seenInlines = false;
|
38404 | }
|
38405 | assertPath(sfPath) {
|
38406 | if (this.sfPath !== sfPath) {
|
38407 | throw new Error(`AssertionError: querying TypeCheckingHost outside of assigned file`);
|
38408 | }
|
38409 | }
|
38410 | getSourceManager(sfPath) {
|
38411 | this.assertPath(sfPath);
|
38412 | return this.fileData.sourceManager;
|
38413 | }
|
38414 | shouldCheckComponent(node) {
|
38415 | if (this.sfPath !== absoluteFromSourceFile(node.getSourceFile())) {
|
38416 | return false;
|
38417 | }
|
38418 | const shimPath = this.strategy.shimPathForComponent(node);
|
38419 | // Only need to generate a TCB for the class if no shim exists for it currently.
|
38420 | return !this.fileData.shimData.has(shimPath);
|
38421 | }
|
38422 | recordShimData(sfPath, data) {
|
38423 | this.assertPath(sfPath);
|
38424 | // Previous type-checking state may have required the use of inlines (assuming they were
|
38425 | // supported). If the current operation also requires inlines, this presents a problem:
|
38426 | // generating new inlines may invalidate any old inlines that old state depends on.
|
38427 | //
|
38428 | // Rather than resolve this issue by tracking specific dependencies on inlines, if the new state
|
38429 | // relies on inlines, any old state that relied on them is simply cleared. This happens when the
|
38430 | // first new state that uses inlines is encountered.
|
38431 | if (data.hasInlines && !this.seenInlines) {
|
38432 | this.impl.clearAllShimDataUsingInlines();
|
38433 | this.seenInlines = true;
|
38434 | }
|
38435 | this.fileData.shimData.set(data.path, data);
|
38436 | if (data.hasInlines) {
|
38437 | this.fileData.hasInlines = true;
|
38438 | }
|
38439 | }
|
38440 | recordComplete(sfPath) {
|
38441 | this.assertPath(sfPath);
|
38442 | this.fileData.isComplete = true;
|
38443 | }
|
38444 | }
|
38445 | /**
|
38446 | * Drives a `TypeCheckContext` to generate type-checking code efficiently for only those components
|
38447 | * which map to a single shim of a single input file.
|
38448 | */
|
38449 | class SingleShimTypeCheckingHost extends SingleFileTypeCheckingHost {
|
38450 | constructor(sfPath, fileData, strategy, impl, shimPath) {
|
38451 | super(sfPath, fileData, strategy, impl);
|
38452 | this.shimPath = shimPath;
|
38453 | }
|
38454 | shouldCheckNode(node) {
|
38455 | if (this.sfPath !== absoluteFromSourceFile(node.getSourceFile())) {
|
38456 | return false;
|
38457 | }
|
38458 | // Only generate a TCB for the component if it maps to the requested shim file.
|
38459 | const shimPath = this.strategy.shimPathForComponent(node);
|
38460 | if (shimPath !== this.shimPath) {
|
38461 | return false;
|
38462 | }
|
38463 | // Only need to generate a TCB for the class if no shim exists for it currently.
|
38464 | return !this.fileData.shimData.has(shimPath);
|
38465 | }
|
38466 | }
|
38467 |
|
38468 | /**
|
38469 | * @license
|
38470 | * Copyright Google LLC All Rights Reserved.
|
38471 | *
|
38472 | * Use of this source code is governed by an MIT-style license that can be
|
38473 | * found in the LICENSE file at https://angular.io/license
|
38474 | */
|
38475 | // This file exists as a target for g3 patches which change the Angular compiler's behavior.
|
38476 | // Separating the patched code in a separate file eliminates the possibility of conflicts with the
|
38477 | // patch diffs when making changes to the rest of the compiler codebase.
|
38478 | // In ngtsc we no longer want to compile undecorated classes with Angular features.
|
38479 | // Migrations for these patterns ran as part of `ng update` and we want to ensure
|
38480 | // that projects do not regress. See https://hackmd.io/@alx/ryfYYuvzH for more details.
|
38481 | const compileUndecoratedClassesWithAngularFeatures = false;
|
38482 |
|
38483 | /**
|
38484 | * @license
|
38485 | * Copyright Google LLC All Rights Reserved.
|
38486 | *
|
38487 | * Use of this source code is governed by an MIT-style license that can be
|
38488 | * found in the LICENSE file at https://angular.io/license
|
38489 | */
|
38490 | /**
|
38491 | * Discriminant type for a `CompilationTicket`.
|
38492 | */
|
38493 | var CompilationTicketKind;
|
38494 | (function (CompilationTicketKind) {
|
38495 | CompilationTicketKind[CompilationTicketKind["Fresh"] = 0] = "Fresh";
|
38496 | CompilationTicketKind[CompilationTicketKind["IncrementalTypeScript"] = 1] = "IncrementalTypeScript";
|
38497 | CompilationTicketKind[CompilationTicketKind["IncrementalResource"] = 2] = "IncrementalResource";
|
38498 | })(CompilationTicketKind || (CompilationTicketKind = {}));
|
38499 | /**
|
38500 | * Create a `CompilationTicket` for a brand new compilation, using no prior state.
|
38501 | */
|
38502 | function freshCompilationTicket(tsProgram, options, incrementalBuildStrategy, typeCheckingProgramStrategy, enableTemplateTypeChecker, usePoisonedData) {
|
38503 | return {
|
38504 | kind: CompilationTicketKind.Fresh,
|
38505 | tsProgram,
|
38506 | options,
|
38507 | incrementalBuildStrategy,
|
38508 | typeCheckingProgramStrategy,
|
38509 | enableTemplateTypeChecker,
|
38510 | usePoisonedData,
|
38511 | };
|
38512 | }
|
38513 | /**
|
38514 | * Create a `CompilationTicket` as efficiently as possible, based on a previous `NgCompiler`
|
38515 | * instance and a new `ts.Program`.
|
38516 | */
|
38517 | function incrementalFromCompilerTicket(oldCompiler, newProgram, incrementalBuildStrategy, typeCheckingProgramStrategy, modifiedResourceFiles) {
|
38518 | const oldProgram = oldCompiler.getNextProgram();
|
38519 | const oldDriver = oldCompiler.incrementalStrategy.getIncrementalDriver(oldProgram);
|
38520 | if (oldDriver === null) {
|
38521 | // No incremental step is possible here, since no IncrementalDriver was found for the old
|
38522 | // program.
|
38523 | return freshCompilationTicket(newProgram, oldCompiler.options, incrementalBuildStrategy, typeCheckingProgramStrategy, oldCompiler.enableTemplateTypeChecker, oldCompiler.usePoisonedData);
|
38524 | }
|
38525 | const newDriver = IncrementalDriver.reconcile(oldProgram, oldDriver, newProgram, modifiedResourceFiles);
|
38526 | return {
|
38527 | kind: CompilationTicketKind.IncrementalTypeScript,
|
38528 | enableTemplateTypeChecker: oldCompiler.enableTemplateTypeChecker,
|
38529 | usePoisonedData: oldCompiler.usePoisonedData,
|
38530 | options: oldCompiler.options,
|
38531 | incrementalBuildStrategy,
|
38532 | typeCheckingProgramStrategy,
|
38533 | newDriver,
|
38534 | oldProgram,
|
38535 | newProgram,
|
38536 | };
|
38537 | }
|
38538 | function resourceChangeTicket(compiler, modifiedResourceFiles) {
|
38539 | return {
|
38540 | kind: CompilationTicketKind.IncrementalResource,
|
38541 | compiler,
|
38542 | modifiedResourceFiles,
|
38543 | };
|
38544 | }
|
38545 | /**
|
38546 | * The heart of the Angular Ivy compiler.
|
38547 | *
|
38548 | * The `NgCompiler` provides an API for performing Angular compilation within a custom TypeScript
|
38549 | * compiler. Each instance of `NgCompiler` supports a single compilation, which might be
|
38550 | * incremental.
|
38551 | *
|
38552 | * `NgCompiler` is lazy, and does not perform any of the work of the compilation until one of its
|
38553 | * output methods (e.g. `getDiagnostics`) is called.
|
38554 | *
|
38555 | * See the README.md for more information.
|
38556 | */
|
38557 | class NgCompiler {
|
38558 | constructor(adapter, options, tsProgram, typeCheckingProgramStrategy, incrementalStrategy, incrementalDriver, enableTemplateTypeChecker, usePoisonedData, perfRecorder = NOOP_PERF_RECORDER) {
|
38559 | this.adapter = adapter;
|
38560 | this.options = options;
|
38561 | this.tsProgram = tsProgram;
|
38562 | this.typeCheckingProgramStrategy = typeCheckingProgramStrategy;
|
38563 | this.incrementalStrategy = incrementalStrategy;
|
38564 | this.incrementalDriver = incrementalDriver;
|
38565 | this.enableTemplateTypeChecker = enableTemplateTypeChecker;
|
38566 | this.usePoisonedData = usePoisonedData;
|
38567 | this.perfRecorder = perfRecorder;
|
38568 | /**
|
38569 | * Lazily evaluated state of the compilation.
|
38570 | *
|
38571 | * This is created on demand by calling `ensureAnalyzed`.
|
38572 | */
|
38573 | this.compilation = null;
|
38574 | /**
|
38575 | * Any diagnostics related to the construction of the compilation.
|
38576 | *
|
38577 | * These are diagnostics which arose during setup of the host and/or program.
|
38578 | */
|
38579 | this.constructionDiagnostics = [];
|
38580 | /**
|
38581 | * Non-template diagnostics related to the program itself. Does not include template
|
38582 | * diagnostics because the template type checker memoizes them itself.
|
38583 | *
|
38584 | * This is set by (and memoizes) `getNonTemplateDiagnostics`.
|
38585 | */
|
38586 | this.nonTemplateDiagnostics = null;
|
38587 | this.constructionDiagnostics.push(...this.adapter.constructionDiagnostics);
|
38588 | const incompatibleTypeCheckOptionsDiagnostic = verifyCompatibleTypeCheckOptions(this.options);
|
38589 | if (incompatibleTypeCheckOptionsDiagnostic !== null) {
|
38590 | this.constructionDiagnostics.push(incompatibleTypeCheckOptionsDiagnostic);
|
38591 | }
|
38592 | this.nextProgram = tsProgram;
|
38593 | this.closureCompilerEnabled = !!this.options.annotateForClosureCompiler;
|
38594 | this.entryPoint =
|
38595 | adapter.entryPoint !== null ? getSourceFileOrNull(tsProgram, adapter.entryPoint) : null;
|
38596 | const moduleResolutionCache = ts$1.createModuleResolutionCache(this.adapter.getCurrentDirectory(),
|
38597 | // Note: this used to be an arrow-function closure. However, JS engines like v8 have some
|
38598 | // strange behaviors with retaining the lexical scope of the closure. Even if this function
|
38599 | // doesn't retain a reference to `this`, if other closures in the constructor here reference
|
38600 | // `this` internally then a closure created here would retain them. This can cause major
|
38601 | // memory leak issues since the `moduleResolutionCache` is a long-lived object and finds its
|
38602 | // way into all kinds of places inside TS internal objects.
|
38603 | this.adapter.getCanonicalFileName.bind(this.adapter));
|
38604 | this.moduleResolver =
|
38605 | new ModuleResolver(tsProgram, this.options, this.adapter, moduleResolutionCache);
|
38606 | this.resourceManager = new AdapterResourceLoader(adapter, this.options);
|
38607 | this.cycleAnalyzer = new CycleAnalyzer(new ImportGraph(this.moduleResolver));
|
38608 | this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, tsProgram);
|
38609 | this.ignoreForDiagnostics =
|
38610 | new Set(tsProgram.getSourceFiles().filter(sf => this.adapter.isShim(sf)));
|
38611 | this.ignoreForEmit = this.adapter.ignoreForEmit;
|
38612 | }
|
38613 | /**
|
38614 | * Convert a `CompilationTicket` into an `NgCompiler` instance for the requested compilation.
|
38615 | *
|
38616 | * Depending on the nature of the compilation request, the `NgCompiler` instance may be reused
|
38617 | * from a previous compilation and updated with any changes, it may be a new instance which
|
38618 | * incrementally reuses state from a previous compilation, or it may represent a fresh compilation
|
38619 | * entirely.
|
38620 | */
|
38621 | static fromTicket(ticket, adapter, perfRecorder) {
|
38622 | switch (ticket.kind) {
|
38623 | case CompilationTicketKind.Fresh:
|
38624 | return new NgCompiler(adapter, ticket.options, ticket.tsProgram, ticket.typeCheckingProgramStrategy, ticket.incrementalBuildStrategy, IncrementalDriver.fresh(ticket.tsProgram), ticket.enableTemplateTypeChecker, ticket.usePoisonedData, perfRecorder);
|
38625 | case CompilationTicketKind.IncrementalTypeScript:
|
38626 | return new NgCompiler(adapter, ticket.options, ticket.newProgram, ticket.typeCheckingProgramStrategy, ticket.incrementalBuildStrategy, ticket.newDriver, ticket.enableTemplateTypeChecker, ticket.usePoisonedData, perfRecorder);
|
38627 | case CompilationTicketKind.IncrementalResource:
|
38628 | const compiler = ticket.compiler;
|
38629 | compiler.updateWithChangedResources(ticket.modifiedResourceFiles);
|
38630 | return compiler;
|
38631 | }
|
38632 | }
|
38633 | updateWithChangedResources(changedResources) {
|
38634 | if (this.compilation === null) {
|
38635 | // Analysis hasn't happened yet, so no update is necessary - any changes to resources will be
|
38636 | // captured by the inital analysis pass itself.
|
38637 | return;
|
38638 | }
|
38639 | this.resourceManager.invalidate();
|
38640 | const classesToUpdate = new Set();
|
38641 | for (const resourceFile of changedResources) {
|
38642 | for (const templateClass of this.getComponentsWithTemplateFile(resourceFile)) {
|
38643 | classesToUpdate.add(templateClass);
|
38644 | }
|
38645 | for (const styleClass of this.getComponentsWithStyleFile(resourceFile)) {
|
38646 | classesToUpdate.add(styleClass);
|
38647 | }
|
38648 | }
|
38649 | for (const clazz of classesToUpdate) {
|
38650 | this.compilation.traitCompiler.updateResources(clazz);
|
38651 | if (!ts$1.isClassDeclaration(clazz)) {
|
38652 | continue;
|
38653 | }
|
38654 | this.compilation.templateTypeChecker.invalidateClass(clazz);
|
38655 | }
|
38656 | }
|
38657 | /**
|
38658 | * Get the resource dependencies of a file.
|
38659 | *
|
38660 | * If the file is not part of the compilation, an empty array will be returned.
|
38661 | */
|
38662 | getResourceDependencies(file) {
|
38663 | this.ensureAnalyzed();
|
38664 | return this.incrementalDriver.depGraph.getResourceDependencies(file);
|
38665 | }
|
38666 | /**
|
38667 | * Get all Angular-related diagnostics for this compilation.
|
38668 | */
|
38669 | getDiagnostics() {
|
38670 | return this.addMessageTextDetails([...this.getNonTemplateDiagnostics(), ...this.getTemplateDiagnostics()]);
|
38671 | }
|
38672 | /**
|
38673 | * Get all Angular-related diagnostics for this compilation.
|
38674 | *
|
38675 | * If a `ts.SourceFile` is passed, only diagnostics related to that file are returned.
|
38676 | */
|
38677 | getDiagnosticsForFile(file, optimizeFor) {
|
38678 | return this.addMessageTextDetails([
|
38679 | ...this.getNonTemplateDiagnostics().filter(diag => diag.file === file),
|
38680 | ...this.getTemplateDiagnosticsForFile(file, optimizeFor)
|
38681 | ]);
|
38682 | }
|
38683 | /**
|
38684 | * Add Angular.io error guide links to diagnostics for this compilation.
|
38685 | */
|
38686 | addMessageTextDetails(diagnostics) {
|
38687 | return diagnostics.map(diag => {
|
38688 | if (diag.code && COMPILER_ERRORS_WITH_GUIDES.has(ngErrorCode(diag.code))) {
|
38689 | return Object.assign(Object.assign({}, diag), { messageText: diag.messageText +
|
38690 | `. Find more at ${ERROR_DETAILS_PAGE_BASE_URL}/NG${ngErrorCode(diag.code)}` });
|
38691 | }
|
38692 | return diag;
|
38693 | });
|
38694 | }
|
38695 | /**
|
38696 | * Get all setup-related diagnostics for this compilation.
|
38697 | */
|
38698 | getOptionDiagnostics() {
|
38699 | return this.constructionDiagnostics;
|
38700 | }
|
38701 | /**
|
38702 | * Get the `ts.Program` to use as a starting point when spawning a subsequent incremental
|
38703 | * compilation.
|
38704 | *
|
38705 | * The `NgCompiler` spawns an internal incremental TypeScript compilation (inheriting the
|
38706 | * consumer's `ts.Program` into a new one for the purposes of template type-checking). After this
|
38707 | * operation, the consumer's `ts.Program` is no longer usable for starting a new incremental
|
38708 | * compilation. `getNextProgram` retrieves the `ts.Program` which can be used instead.
|
38709 | */
|
38710 | getNextProgram() {
|
38711 | return this.nextProgram;
|
38712 | }
|
38713 | getTemplateTypeChecker() {
|
38714 | if (!this.enableTemplateTypeChecker) {
|
38715 | throw new Error('The `TemplateTypeChecker` does not work without `enableTemplateTypeChecker`.');
|
38716 | }
|
38717 | return this.ensureAnalyzed().templateTypeChecker;
|
38718 | }
|
38719 | /**
|
38720 | * Retrieves the `ts.Declaration`s for any component(s) which use the given template file.
|
38721 | */
|
38722 | getComponentsWithTemplateFile(templateFilePath) {
|
38723 | const { resourceRegistry } = this.ensureAnalyzed();
|
38724 | return resourceRegistry.getComponentsWithTemplate(resolve(templateFilePath));
|
38725 | }
|
38726 | /**
|
38727 | * Retrieves the `ts.Declaration`s for any component(s) which use the given template file.
|
38728 | */
|
38729 | getComponentsWithStyleFile(styleFilePath) {
|
38730 | const { resourceRegistry } = this.ensureAnalyzed();
|
38731 | return resourceRegistry.getComponentsWithStyle(resolve(styleFilePath));
|
38732 | }
|
38733 | /**
|
38734 | * Retrieves external resources for the given component.
|
38735 | */
|
38736 | getComponentResources(classDecl) {
|
38737 | if (!isNamedClassDeclaration(classDecl)) {
|
38738 | return null;
|
38739 | }
|
38740 | const { resourceRegistry } = this.ensureAnalyzed();
|
38741 | const styles = resourceRegistry.getStyles(classDecl);
|
38742 | const template = resourceRegistry.getTemplate(classDecl);
|
38743 | if (template === null) {
|
38744 | return null;
|
38745 | }
|
38746 | return { styles, template };
|
38747 | }
|
38748 | /**
|
38749 | * Perform Angular's analysis step (as a precursor to `getDiagnostics` or `prepareEmit`)
|
38750 | * asynchronously.
|
38751 | *
|
38752 | * Normally, this operation happens lazily whenever `getDiagnostics` or `prepareEmit` are called.
|
38753 | * However, certain consumers may wish to allow for an asynchronous phase of analysis, where
|
38754 | * resources such as `styleUrls` are resolved asynchonously. In these cases `analyzeAsync` must be
|
38755 | * called first, and its `Promise` awaited prior to calling any other APIs of `NgCompiler`.
|
38756 | */
|
38757 | analyzeAsync() {
|
38758 | return __awaiter(this, void 0, void 0, function* () {
|
38759 | if (this.compilation !== null) {
|
38760 | return;
|
38761 | }
|
38762 | this.compilation = this.makeCompilation();
|
38763 | const analyzeSpan = this.perfRecorder.start('analyze');
|
38764 | const promises = [];
|
38765 | for (const sf of this.tsProgram.getSourceFiles()) {
|
38766 | if (sf.isDeclarationFile) {
|
38767 | continue;
|
38768 | }
|
38769 | const analyzeFileSpan = this.perfRecorder.start('analyzeFile', sf);
|
38770 | let analysisPromise = this.compilation.traitCompiler.analyzeAsync(sf);
|
38771 | this.scanForMwp(sf);
|
38772 | if (analysisPromise === undefined) {
|
38773 | this.perfRecorder.stop(analyzeFileSpan);
|
38774 | }
|
38775 | else if (this.perfRecorder.enabled) {
|
38776 | analysisPromise = analysisPromise.then(() => this.perfRecorder.stop(analyzeFileSpan));
|
38777 | }
|
38778 | if (analysisPromise !== undefined) {
|
38779 | promises.push(analysisPromise);
|
38780 | }
|
38781 | }
|
38782 | yield Promise.all(promises);
|
38783 | this.perfRecorder.stop(analyzeSpan);
|
38784 | this.resolveCompilation(this.compilation.traitCompiler);
|
38785 | });
|
38786 | }
|
38787 | /**
|
38788 | * List lazy routes detected during analysis.
|
38789 | *
|
38790 | * This can be called for one specific route, or to retrieve all top-level routes.
|
38791 | */
|
38792 | listLazyRoutes(entryRoute) {
|
38793 | if (entryRoute) {
|
38794 | // Note:
|
38795 | // This resolution step is here to match the implementation of the old `AotCompilerHost` (see
|
38796 | // https://github.com/angular/angular/blob/50732e156/packages/compiler-cli/src/transformers/compiler_host.ts#L175-L188).
|
38797 | //
|
38798 | // `@angular/cli` will always call this API with an absolute path, so the resolution step is
|
38799 | // not necessary, but keeping it backwards compatible in case someone else is using the API.
|
38800 | // Relative entry paths are disallowed.
|
38801 | if (entryRoute.startsWith('.')) {
|
38802 | throw new Error(`Failed to list lazy routes: Resolution of relative paths (${entryRoute}) is not supported.`);
|
38803 | }
|
38804 | // Non-relative entry paths fall into one of the following categories:
|
38805 | // - Absolute system paths (e.g. `/foo/bar/my-project/my-module`), which are unaffected by the
|
38806 | // logic below.
|
38807 | // - Paths to enternal modules (e.g. `some-lib`).
|
38808 | // - Paths mapped to directories in `tsconfig.json` (e.g. `shared/my-module`).
|
38809 | // (See https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping.)
|
38810 | //
|
38811 | // In all cases above, the `containingFile` argument is ignored, so we can just take the first
|
38812 | // of the root files.
|
38813 | const containingFile = this.tsProgram.getRootFileNames()[0];
|
38814 | const [entryPath, moduleName] = entryRoute.split('#');
|
38815 | const resolvedModule = resolveModuleName(entryPath, containingFile, this.options, this.adapter, null);
|
38816 | if (resolvedModule) {
|
38817 | entryRoute = entryPointKeyFor(resolvedModule.resolvedFileName, moduleName);
|
38818 | }
|
38819 | }
|
38820 | const compilation = this.ensureAnalyzed();
|
38821 | return compilation.routeAnalyzer.listLazyRoutes(entryRoute);
|
38822 | }
|
38823 | /**
|
38824 | * Fetch transformers and other information which is necessary for a consumer to `emit` the
|
38825 | * program with Angular-added definitions.
|
38826 | */
|
38827 | prepareEmit() {
|
38828 | const compilation = this.ensureAnalyzed();
|
38829 | const coreImportsFrom = compilation.isCore ? getR3SymbolsFile(this.tsProgram) : null;
|
38830 | let importRewriter;
|
38831 | if (coreImportsFrom !== null) {
|
38832 | importRewriter = new R3SymbolsImportRewriter(coreImportsFrom.fileName);
|
38833 | }
|
38834 | else {
|
38835 | importRewriter = new NoopImportRewriter();
|
38836 | }
|
38837 | const before = [
|
38838 | ivyTransformFactory(compilation.traitCompiler, compilation.reflector, importRewriter, compilation.defaultImportTracker, compilation.isCore, this.closureCompilerEnabled),
|
38839 | aliasTransformFactory(compilation.traitCompiler.exportStatements),
|
38840 | compilation.defaultImportTracker.importPreservingTransformer(),
|
38841 | ];
|
38842 | const afterDeclarations = [];
|
38843 | if (compilation.dtsTransforms !== null) {
|
38844 | afterDeclarations.push(declarationTransformFactory(compilation.dtsTransforms, importRewriter));
|
38845 | }
|
38846 | // Only add aliasing re-exports to the .d.ts output if the `AliasingHost` requests it.
|
38847 | if (compilation.aliasingHost !== null && compilation.aliasingHost.aliasExportsInDts) {
|
38848 | afterDeclarations.push(aliasTransformFactory(compilation.traitCompiler.exportStatements));
|
38849 | }
|
38850 | if (this.adapter.factoryTracker !== null) {
|
38851 | before.push(generatedFactoryTransform(this.adapter.factoryTracker.sourceInfo, importRewriter));
|
38852 | }
|
38853 | before.push(ivySwitchTransform);
|
38854 | return { transformers: { before, afterDeclarations } };
|
38855 | }
|
38856 | /**
|
38857 | * Run the indexing process and return a `Map` of all indexed components.
|
38858 | *
|
38859 | * See the `indexing` package for more details.
|
38860 | */
|
38861 | getIndexedComponents() {
|
38862 | const compilation = this.ensureAnalyzed();
|
38863 | const context = new IndexingContext();
|
38864 | compilation.traitCompiler.index(context);
|
38865 | return generateAnalysis(context);
|
38866 | }
|
38867 | ensureAnalyzed() {
|
38868 | if (this.compilation === null) {
|
38869 | this.analyzeSync();
|
38870 | }
|
38871 | return this.compilation;
|
38872 | }
|
38873 | analyzeSync() {
|
38874 | const analyzeSpan = this.perfRecorder.start('analyze');
|
38875 | this.compilation = this.makeCompilation();
|
38876 | for (const sf of this.tsProgram.getSourceFiles()) {
|
38877 | if (sf.isDeclarationFile) {
|
38878 | continue;
|
38879 | }
|
38880 | const analyzeFileSpan = this.perfRecorder.start('analyzeFile', sf);
|
38881 | this.compilation.traitCompiler.analyzeSync(sf);
|
38882 | this.scanForMwp(sf);
|
38883 | this.perfRecorder.stop(analyzeFileSpan);
|
38884 | }
|
38885 | this.perfRecorder.stop(analyzeSpan);
|
38886 | this.resolveCompilation(this.compilation.traitCompiler);
|
38887 | }
|
38888 | resolveCompilation(traitCompiler) {
|
38889 | traitCompiler.resolve();
|
38890 | this.recordNgModuleScopeDependencies();
|
38891 | // At this point, analysis is complete and the compiler can now calculate which files need to
|
38892 | // be emitted, so do that.
|
38893 | this.incrementalDriver.recordSuccessfulAnalysis(traitCompiler);
|
38894 | }
|
38895 | get fullTemplateTypeCheck() {
|
38896 | // Determine the strictness level of type checking based on compiler options. As
|
38897 | // `strictTemplates` is a superset of `fullTemplateTypeCheck`, the former implies the latter.
|
38898 | // Also see `verifyCompatibleTypeCheckOptions` where it is verified that `fullTemplateTypeCheck`
|
38899 | // is not disabled when `strictTemplates` is enabled.
|
38900 | const strictTemplates = !!this.options.strictTemplates;
|
38901 | return strictTemplates || !!this.options.fullTemplateTypeCheck;
|
38902 | }
|
38903 | getTypeCheckingConfig() {
|
38904 | // Determine the strictness level of type checking based on compiler options. As
|
38905 | // `strictTemplates` is a superset of `fullTemplateTypeCheck`, the former implies the latter.
|
38906 | // Also see `verifyCompatibleTypeCheckOptions` where it is verified that `fullTemplateTypeCheck`
|
38907 | // is not disabled when `strictTemplates` is enabled.
|
38908 | const strictTemplates = !!this.options.strictTemplates;
|
38909 | // First select a type-checking configuration, based on whether full template type-checking is
|
38910 | // requested.
|
38911 | let typeCheckingConfig;
|
38912 | if (this.fullTemplateTypeCheck) {
|
38913 | typeCheckingConfig = {
|
38914 | applyTemplateContextGuards: strictTemplates,
|
38915 | checkQueries: false,
|
38916 | checkTemplateBodies: true,
|
38917 | alwaysCheckSchemaInTemplateBodies: true,
|
38918 | checkTypeOfInputBindings: strictTemplates,
|
38919 | honorAccessModifiersForInputBindings: false,
|
38920 | strictNullInputBindings: strictTemplates,
|
38921 | checkTypeOfAttributes: strictTemplates,
|
38922 | // Even in full template type-checking mode, DOM binding checks are not quite ready yet.
|
38923 | checkTypeOfDomBindings: false,
|
38924 | checkTypeOfOutputEvents: strictTemplates,
|
38925 | checkTypeOfAnimationEvents: strictTemplates,
|
38926 | // Checking of DOM events currently has an adverse effect on developer experience,
|
38927 | // e.g. for `<input (blur)="update($event.target.value)">` enabling this check results in:
|
38928 | // - error TS2531: Object is possibly 'null'.
|
38929 | // - error TS2339: Property 'value' does not exist on type 'EventTarget'.
|
38930 | checkTypeOfDomEvents: strictTemplates,
|
38931 | checkTypeOfDomReferences: strictTemplates,
|
38932 | // Non-DOM references have the correct type in View Engine so there is no strictness flag.
|
38933 | checkTypeOfNonDomReferences: true,
|
38934 | // Pipes are checked in View Engine so there is no strictness flag.
|
38935 | checkTypeOfPipes: true,
|
38936 | strictSafeNavigationTypes: strictTemplates,
|
38937 | useContextGenericType: strictTemplates,
|
38938 | strictLiteralTypes: true,
|
38939 | enableTemplateTypeChecker: this.enableTemplateTypeChecker,
|
38940 | };
|
38941 | }
|
38942 | else {
|
38943 | typeCheckingConfig = {
|
38944 | applyTemplateContextGuards: false,
|
38945 | checkQueries: false,
|
38946 | checkTemplateBodies: false,
|
38947 | // Enable deep schema checking in "basic" template type-checking mode only if Closure
|
38948 | // compilation is requested, which is a good proxy for "only in google3".
|
38949 | alwaysCheckSchemaInTemplateBodies: this.closureCompilerEnabled,
|
38950 | checkTypeOfInputBindings: false,
|
38951 | strictNullInputBindings: false,
|
38952 | honorAccessModifiersForInputBindings: false,
|
38953 | checkTypeOfAttributes: false,
|
38954 | checkTypeOfDomBindings: false,
|
38955 | checkTypeOfOutputEvents: false,
|
38956 | checkTypeOfAnimationEvents: false,
|
38957 | checkTypeOfDomEvents: false,
|
38958 | checkTypeOfDomReferences: false,
|
38959 | checkTypeOfNonDomReferences: false,
|
38960 | checkTypeOfPipes: false,
|
38961 | strictSafeNavigationTypes: false,
|
38962 | useContextGenericType: false,
|
38963 | strictLiteralTypes: false,
|
38964 | enableTemplateTypeChecker: this.enableTemplateTypeChecker,
|
38965 | };
|
38966 | }
|
38967 | // Apply explicitly configured strictness flags on top of the default configuration
|
38968 | // based on "fullTemplateTypeCheck".
|
38969 | if (this.options.strictInputTypes !== undefined) {
|
38970 | typeCheckingConfig.checkTypeOfInputBindings = this.options.strictInputTypes;
|
38971 | typeCheckingConfig.applyTemplateContextGuards = this.options.strictInputTypes;
|
38972 | }
|
38973 | if (this.options.strictInputAccessModifiers !== undefined) {
|
38974 | typeCheckingConfig.honorAccessModifiersForInputBindings =
|
38975 | this.options.strictInputAccessModifiers;
|
38976 | }
|
38977 | if (this.options.strictNullInputTypes !== undefined) {
|
38978 | typeCheckingConfig.strictNullInputBindings = this.options.strictNullInputTypes;
|
38979 | }
|
38980 | if (this.options.strictOutputEventTypes !== undefined) {
|
38981 | typeCheckingConfig.checkTypeOfOutputEvents = this.options.strictOutputEventTypes;
|
38982 | typeCheckingConfig.checkTypeOfAnimationEvents = this.options.strictOutputEventTypes;
|
38983 | }
|
38984 | if (this.options.strictDomEventTypes !== undefined) {
|
38985 | typeCheckingConfig.checkTypeOfDomEvents = this.options.strictDomEventTypes;
|
38986 | }
|
38987 | if (this.options.strictSafeNavigationTypes !== undefined) {
|
38988 | typeCheckingConfig.strictSafeNavigationTypes = this.options.strictSafeNavigationTypes;
|
38989 | }
|
38990 | if (this.options.strictDomLocalRefTypes !== undefined) {
|
38991 | typeCheckingConfig.checkTypeOfDomReferences = this.options.strictDomLocalRefTypes;
|
38992 | }
|
38993 | if (this.options.strictAttributeTypes !== undefined) {
|
38994 | typeCheckingConfig.checkTypeOfAttributes = this.options.strictAttributeTypes;
|
38995 | }
|
38996 | if (this.options.strictContextGenerics !== undefined) {
|
38997 | typeCheckingConfig.useContextGenericType = this.options.strictContextGenerics;
|
38998 | }
|
38999 | if (this.options.strictLiteralTypes !== undefined) {
|
39000 | typeCheckingConfig.strictLiteralTypes = this.options.strictLiteralTypes;
|
39001 | }
|
39002 | return typeCheckingConfig;
|
39003 | }
|
39004 | getTemplateDiagnostics() {
|
39005 | const compilation = this.ensureAnalyzed();
|
39006 | // Get the diagnostics.
|
39007 | const typeCheckSpan = this.perfRecorder.start('typeCheckDiagnostics');
|
39008 | const diagnostics = [];
|
39009 | for (const sf of this.tsProgram.getSourceFiles()) {
|
39010 | if (sf.isDeclarationFile || this.adapter.isShim(sf)) {
|
39011 | continue;
|
39012 | }
|
39013 | diagnostics.push(...compilation.templateTypeChecker.getDiagnosticsForFile(sf, OptimizeFor.WholeProgram));
|
39014 | }
|
39015 | const program = this.typeCheckingProgramStrategy.getProgram();
|
39016 | this.perfRecorder.stop(typeCheckSpan);
|
39017 | this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program);
|
39018 | this.nextProgram = program;
|
39019 | return diagnostics;
|
39020 | }
|
39021 | getTemplateDiagnosticsForFile(sf, optimizeFor) {
|
39022 | const compilation = this.ensureAnalyzed();
|
39023 | // Get the diagnostics.
|
39024 | const typeCheckSpan = this.perfRecorder.start('typeCheckDiagnostics');
|
39025 | const diagnostics = [];
|
39026 | if (!sf.isDeclarationFile && !this.adapter.isShim(sf)) {
|
39027 | diagnostics.push(...compilation.templateTypeChecker.getDiagnosticsForFile(sf, optimizeFor));
|
39028 | }
|
39029 | const program = this.typeCheckingProgramStrategy.getProgram();
|
39030 | this.perfRecorder.stop(typeCheckSpan);
|
39031 | this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program);
|
39032 | this.nextProgram = program;
|
39033 | return diagnostics;
|
39034 | }
|
39035 | getNonTemplateDiagnostics() {
|
39036 | if (this.nonTemplateDiagnostics === null) {
|
39037 | const compilation = this.ensureAnalyzed();
|
39038 | this.nonTemplateDiagnostics = [...compilation.traitCompiler.diagnostics];
|
39039 | if (this.entryPoint !== null && compilation.exportReferenceGraph !== null) {
|
39040 | this.nonTemplateDiagnostics.push(...checkForPrivateExports(this.entryPoint, this.tsProgram.getTypeChecker(), compilation.exportReferenceGraph));
|
39041 | }
|
39042 | }
|
39043 | return this.nonTemplateDiagnostics;
|
39044 | }
|
39045 | /**
|
39046 | * Reifies the inter-dependencies of NgModules and the components within their compilation scopes
|
39047 | * into the `IncrementalDriver`'s dependency graph.
|
39048 | */
|
39049 | recordNgModuleScopeDependencies() {
|
39050 | const recordSpan = this.perfRecorder.start('recordDependencies');
|
39051 | const depGraph = this.incrementalDriver.depGraph;
|
39052 | for (const scope of this.compilation.scopeRegistry.getCompilationScopes()) {
|
39053 | const file = scope.declaration.getSourceFile();
|
39054 | const ngModuleFile = scope.ngModule.getSourceFile();
|
39055 | // A change to any dependency of the declaration causes the declaration to be invalidated,
|
39056 | // which requires the NgModule to be invalidated as well.
|
39057 | depGraph.addTransitiveDependency(ngModuleFile, file);
|
39058 | // A change to the NgModule file should cause the declaration itself to be invalidated.
|
39059 | depGraph.addDependency(file, ngModuleFile);
|
39060 | const meta = this.compilation.metaReader.getDirectiveMetadata(new Reference$1(scope.declaration));
|
39061 | if (meta !== null && meta.isComponent) {
|
39062 | // If a component's template changes, it might have affected the import graph, and thus the
|
39063 | // remote scoping feature which is activated in the event of potential import cycles. Thus,
|
39064 | // the module depends not only on the transitive dependencies of the component, but on its
|
39065 | // resources as well.
|
39066 | depGraph.addTransitiveResources(ngModuleFile, file);
|
39067 | // A change to any directive/pipe in the compilation scope should cause the component to be
|
39068 | // invalidated.
|
39069 | for (const directive of scope.directives) {
|
39070 | // When a directive in scope is updated, the component needs to be recompiled as e.g. a
|
39071 | // selector may have changed.
|
39072 | depGraph.addTransitiveDependency(file, directive.ref.node.getSourceFile());
|
39073 | }
|
39074 | for (const pipe of scope.pipes) {
|
39075 | // When a pipe in scope is updated, the component needs to be recompiled as e.g. the
|
39076 | // pipe's name may have changed.
|
39077 | depGraph.addTransitiveDependency(file, pipe.ref.node.getSourceFile());
|
39078 | }
|
39079 | // Components depend on the entire export scope. In addition to transitive dependencies on
|
39080 | // all directives/pipes in the export scope, they also depend on every NgModule in the
|
39081 | // scope, as changes to a module may add new directives/pipes to the scope.
|
39082 | for (const depModule of scope.ngModules) {
|
39083 | // There is a correctness issue here. To be correct, this should be a transitive
|
39084 | // dependency on the depModule file, since the depModule's exports might change via one of
|
39085 | // its dependencies, even if depModule's file itself doesn't change. However, doing this
|
39086 | // would also trigger recompilation if a non-exported component or directive changed,
|
39087 | // which causes performance issues for rebuilds.
|
39088 | //
|
39089 | // Given the rebuild issue is an edge case, currently we err on the side of performance
|
39090 | // instead of correctness. A correct and performant design would distinguish between
|
39091 | // changes to the depModule which affect its export scope and changes which do not, and
|
39092 | // only add a dependency for the former. This concept is currently in development.
|
39093 | //
|
39094 | // TODO(alxhub): fix correctness issue by understanding the semantics of the dependency.
|
39095 | depGraph.addDependency(file, depModule.getSourceFile());
|
39096 | }
|
39097 | }
|
39098 | else {
|
39099 | // Directives (not components) and pipes only depend on the NgModule which directly declares
|
39100 | // them.
|
39101 | depGraph.addDependency(file, ngModuleFile);
|
39102 | }
|
39103 | }
|
39104 | this.perfRecorder.stop(recordSpan);
|
39105 | }
|
39106 | scanForMwp(sf) {
|
39107 | this.compilation.mwpScanner.scan(sf, {
|
39108 | addTypeReplacement: (node, type) => {
|
39109 | // Only obtain the return type transform for the source file once there's a type to replace,
|
39110 | // so that no transform is allocated when there's nothing to do.
|
39111 | this.compilation.dtsTransforms.getReturnTypeTransform(sf).addTypeReplacement(node, type);
|
39112 | }
|
39113 | });
|
39114 | }
|
39115 | makeCompilation() {
|
39116 | const checker = this.tsProgram.getTypeChecker();
|
39117 | const reflector = new TypeScriptReflectionHost(checker);
|
39118 | // Construct the ReferenceEmitter.
|
39119 | let refEmitter;
|
39120 | let aliasingHost = null;
|
39121 | if (this.adapter.unifiedModulesHost === null || !this.options._useHostForImportGeneration) {
|
39122 | let localImportStrategy;
|
39123 | // The strategy used for local, in-project imports depends on whether TS has been configured
|
39124 | // with rootDirs. If so, then multiple directories may be mapped in the same "module
|
39125 | // namespace" and the logic of `LogicalProjectStrategy` is required to generate correct
|
39126 | // imports which may cross these multiple directories. Otherwise, plain relative imports are
|
39127 | // sufficient.
|
39128 | if (this.options.rootDir !== undefined ||
|
39129 | (this.options.rootDirs !== undefined && this.options.rootDirs.length > 0)) {
|
39130 | // rootDirs logic is in effect - use the `LogicalProjectStrategy` for in-project relative
|
39131 | // imports.
|
39132 | localImportStrategy = new LogicalProjectStrategy(reflector, new LogicalFileSystem([...this.adapter.rootDirs], this.adapter));
|
39133 | }
|
39134 | else {
|
39135 | // Plain relative imports are all that's needed.
|
39136 | localImportStrategy = new RelativePathStrategy(reflector);
|
39137 | }
|
39138 | // The CompilerHost doesn't have fileNameToModuleName, so build an NPM-centric reference
|
39139 | // resolution strategy.
|
39140 | refEmitter = new ReferenceEmitter([
|
39141 | // First, try to use local identifiers if available.
|
39142 | new LocalIdentifierStrategy(),
|
39143 | // Next, attempt to use an absolute import.
|
39144 | new AbsoluteModuleStrategy(this.tsProgram, checker, this.moduleResolver, reflector),
|
39145 | // Finally, check if the reference is being written into a file within the project's .ts
|
39146 | // sources, and use a relative import if so. If this fails, ReferenceEmitter will throw
|
39147 | // an error.
|
39148 | localImportStrategy,
|
39149 | ]);
|
39150 | // If an entrypoint is present, then all user imports should be directed through the
|
39151 | // entrypoint and private exports are not needed. The compiler will validate that all publicly
|
39152 | // visible directives/pipes are importable via this entrypoint.
|
39153 | if (this.entryPoint === null && this.options.generateDeepReexports === true) {
|
39154 | // No entrypoint is present and deep re-exports were requested, so configure the aliasing
|
39155 | // system to generate them.
|
39156 | aliasingHost = new PrivateExportAliasingHost(reflector);
|
39157 | }
|
39158 | }
|
39159 | else {
|
39160 | // The CompilerHost supports fileNameToModuleName, so use that to emit imports.
|
39161 | refEmitter = new ReferenceEmitter([
|
39162 | // First, try to use local identifiers if available.
|
39163 | new LocalIdentifierStrategy(),
|
39164 | // Then use aliased references (this is a workaround to StrictDeps checks).
|
39165 | new AliasStrategy(),
|
39166 | // Then use fileNameToModuleName to emit imports.
|
39167 | new UnifiedModulesStrategy(reflector, this.adapter.unifiedModulesHost),
|
39168 | ]);
|
39169 | aliasingHost = new UnifiedModulesAliasingHost(this.adapter.unifiedModulesHost);
|
39170 | }
|
39171 | const evaluator = new PartialEvaluator(reflector, checker, this.incrementalDriver.depGraph);
|
39172 | const dtsReader = new DtsMetadataReader(checker, reflector);
|
39173 | const localMetaRegistry = new LocalMetadataRegistry();
|
39174 | const localMetaReader = localMetaRegistry;
|
39175 | const depScopeReader = new MetadataDtsModuleScopeResolver(dtsReader, aliasingHost);
|
39176 | const scopeRegistry = new LocalModuleScopeRegistry(localMetaReader, depScopeReader, refEmitter, aliasingHost);
|
39177 | const scopeReader = scopeRegistry;
|
39178 | const metaRegistry = new CompoundMetadataRegistry([localMetaRegistry, scopeRegistry]);
|
39179 | const injectableRegistry = new InjectableClassRegistry(reflector);
|
39180 | const metaReader = new CompoundMetadataReader([localMetaReader, dtsReader]);
|
39181 | const typeCheckScopeRegistry = new TypeCheckScopeRegistry(scopeReader, metaReader);
|
39182 | // If a flat module entrypoint was specified, then track references via a `ReferenceGraph` in
|
39183 | // order to produce proper diagnostics for incorrectly exported directives/pipes/etc. If there
|
39184 | // is no flat module entrypoint then don't pay the cost of tracking references.
|
39185 | let referencesRegistry;
|
39186 | let exportReferenceGraph = null;
|
39187 | if (this.entryPoint !== null) {
|
39188 | exportReferenceGraph = new ReferenceGraph();
|
39189 | referencesRegistry = new ReferenceGraphAdapter(exportReferenceGraph);
|
39190 | }
|
39191 | else {
|
39192 | referencesRegistry = new NoopReferencesRegistry();
|
39193 | }
|
39194 | const routeAnalyzer = new NgModuleRouteAnalyzer(this.moduleResolver, evaluator);
|
39195 | const dtsTransforms = new DtsTransformRegistry();
|
39196 | const mwpScanner = new ModuleWithProvidersScanner(reflector, evaluator, refEmitter);
|
39197 | const isCore = isAngularCorePackage(this.tsProgram);
|
39198 | const defaultImportTracker = new DefaultImportTracker();
|
39199 | const resourceRegistry = new ResourceRegistry();
|
39200 | const compilationMode = this.options.compilationMode === 'partial' ? CompilationMode.PARTIAL : CompilationMode.FULL;
|
39201 | // Cycles are handled in full compilation mode by "remote scoping".
|
39202 | // "Remote scoping" does not work well with tree shaking for libraries.
|
39203 | // So in partial compilation mode, when building a library, a cycle will cause an error.
|
39204 | const cycleHandlingStrategy = compilationMode === CompilationMode.FULL ?
|
39205 | 0 /* UseRemoteScoping */ :
|
39206 | 1 /* Error */;
|
39207 | // Set up the IvyCompilation, which manages state for the Ivy transformer.
|
39208 | const handlers = [
|
39209 | new ComponentDecoratorHandler(reflector, evaluator, metaRegistry, metaReader, scopeReader, scopeRegistry, typeCheckScopeRegistry, resourceRegistry, isCore, this.resourceManager, this.adapter.rootDirs, this.options.preserveWhitespaces || false, this.options.i18nUseExternalIds !== false, this.options.enableI18nLegacyMessageIdFormat !== false, this.usePoisonedData, this.options.i18nNormalizeLineEndingsInICUs, this.moduleResolver, this.cycleAnalyzer, cycleHandlingStrategy, refEmitter, defaultImportTracker, this.incrementalDriver.depGraph, injectableRegistry, this.closureCompilerEnabled),
|
39210 | // TODO(alxhub): understand why the cast here is necessary (something to do with `null`
|
39211 | // not being assignable to `unknown` when wrapped in `Readonly`).
|
39212 | // clang-format off
|
39213 | new DirectiveDecoratorHandler(reflector, evaluator, metaRegistry, scopeRegistry, metaReader, defaultImportTracker, injectableRegistry, isCore, this.closureCompilerEnabled, compileUndecoratedClassesWithAngularFeatures),
|
39214 | // clang-format on
|
39215 | // Pipe handler must be before injectable handler in list so pipe factories are printed
|
39216 | // before injectable factories (so injectable factories can delegate to them)
|
39217 | new PipeDecoratorHandler(reflector, evaluator, metaRegistry, scopeRegistry, defaultImportTracker, injectableRegistry, isCore),
|
39218 | new InjectableDecoratorHandler(reflector, defaultImportTracker, isCore, this.options.strictInjectionParameters || false, injectableRegistry),
|
39219 | new NgModuleDecoratorHandler(reflector, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry, isCore, routeAnalyzer, refEmitter, this.adapter.factoryTracker, defaultImportTracker, this.closureCompilerEnabled, injectableRegistry, this.options.i18nInLocale),
|
39220 | ];
|
39221 | const traitCompiler = new TraitCompiler(handlers, reflector, this.perfRecorder, this.incrementalDriver, this.options.compileNonExportedClasses !== false, compilationMode, dtsTransforms);
|
39222 | const templateTypeChecker = new TemplateTypeCheckerImpl(this.tsProgram, this.typeCheckingProgramStrategy, traitCompiler, this.getTypeCheckingConfig(), refEmitter, reflector, this.adapter, this.incrementalDriver, scopeRegistry, typeCheckScopeRegistry);
|
39223 | return {
|
39224 | isCore,
|
39225 | traitCompiler,
|
39226 | reflector,
|
39227 | scopeRegistry,
|
39228 | dtsTransforms,
|
39229 | exportReferenceGraph,
|
39230 | routeAnalyzer,
|
39231 | mwpScanner,
|
39232 | metaReader,
|
39233 | typeCheckScopeRegistry,
|
39234 | defaultImportTracker,
|
39235 | aliasingHost,
|
39236 | refEmitter,
|
39237 | templateTypeChecker,
|
39238 | resourceRegistry,
|
39239 | };
|
39240 | }
|
39241 | }
|
39242 | /**
|
39243 | * Determine if the given `Program` is @angular/core.
|
39244 | */
|
39245 | function isAngularCorePackage(program) {
|
39246 | // Look for its_just_angular.ts somewhere in the program.
|
39247 | const r3Symbols = getR3SymbolsFile(program);
|
39248 | if (r3Symbols === null) {
|
39249 | return false;
|
39250 | }
|
39251 | // Look for the constant ITS_JUST_ANGULAR in that file.
|
39252 | return r3Symbols.statements.some(stmt => {
|
39253 | // The statement must be a variable declaration statement.
|
39254 | if (!ts$1.isVariableStatement(stmt)) {
|
39255 | return false;
|
39256 | }
|
39257 | // It must be exported.
|
39258 | if (stmt.modifiers === undefined ||
|
39259 | !stmt.modifiers.some(mod => mod.kind === ts$1.SyntaxKind.ExportKeyword)) {
|
39260 | return false;
|
39261 | }
|
39262 | // It must declare ITS_JUST_ANGULAR.
|
39263 | return stmt.declarationList.declarations.some(decl => {
|
39264 | // The declaration must match the name.
|
39265 | if (!ts$1.isIdentifier(decl.name) || decl.name.text !== 'ITS_JUST_ANGULAR') {
|
39266 | return false;
|
39267 | }
|
39268 | // It must initialize the variable to true.
|
39269 | if (decl.initializer === undefined || decl.initializer.kind !== ts$1.SyntaxKind.TrueKeyword) {
|
39270 | return false;
|
39271 | }
|
39272 | // This definition matches.
|
39273 | return true;
|
39274 | });
|
39275 | });
|
39276 | }
|
39277 | /**
|
39278 | * Find the 'r3_symbols.ts' file in the given `Program`, or return `null` if it wasn't there.
|
39279 | */
|
39280 | function getR3SymbolsFile(program) {
|
39281 | return program.getSourceFiles().find(file => file.fileName.indexOf('r3_symbols.ts') >= 0) || null;
|
39282 | }
|
39283 | /**
|
39284 | * Since "strictTemplates" is a true superset of type checking capabilities compared to
|
39285 | * "fullTemplateTypeCheck", it is required that the latter is not explicitly disabled if the
|
39286 | * former is enabled.
|
39287 | */
|
39288 | function verifyCompatibleTypeCheckOptions(options) {
|
39289 | if (options.fullTemplateTypeCheck === false && options.strictTemplates === true) {
|
39290 | return {
|
39291 | category: ts$1.DiagnosticCategory.Error,
|
39292 | code: ngErrorCode(ErrorCode.CONFIG_STRICT_TEMPLATES_IMPLIES_FULL_TEMPLATE_TYPECHECK),
|
39293 | file: undefined,
|
39294 | start: undefined,
|
39295 | length: undefined,
|
39296 | messageText: `Angular compiler option "strictTemplates" is enabled, however "fullTemplateTypeCheck" is disabled.
|
39297 |
|
39298 | Having the "strictTemplates" flag enabled implies that "fullTemplateTypeCheck" is also enabled, so
|
39299 | the latter can not be explicitly disabled.
|
39300 |
|
39301 | One of the following actions is required:
|
39302 | 1. Remove the "fullTemplateTypeCheck" option.
|
39303 | 2. Remove "strictTemplates" or set it to 'false'.
|
39304 |
|
39305 | More information about the template type checking compiler options can be found in the documentation:
|
39306 | https://v9.angular.io/guide/template-typecheck#template-type-checking`,
|
39307 | };
|
39308 | }
|
39309 | return null;
|
39310 | }
|
39311 | class ReferenceGraphAdapter {
|
39312 | constructor(graph) {
|
39313 | this.graph = graph;
|
39314 | }
|
39315 | add(source, ...references) {
|
39316 | for (const { node } of references) {
|
39317 | let sourceFile = node.getSourceFile();
|
39318 | if (sourceFile === undefined) {
|
39319 | sourceFile = ts$1.getOriginalNode(node).getSourceFile();
|
39320 | }
|
39321 | // Only record local references (not references into .d.ts files).
|
39322 | if (sourceFile === undefined || !isDtsPath(sourceFile.fileName)) {
|
39323 | this.graph.add(source, node);
|
39324 | }
|
39325 | }
|
39326 | }
|
39327 | }
|
39328 |
|
39329 | /**
|
39330 | * @license
|
39331 | * Copyright Google LLC All Rights Reserved.
|
39332 | *
|
39333 | * Use of this source code is governed by an MIT-style license that can be
|
39334 | * found in the LICENSE file at https://angular.io/license
|
39335 | */
|
39336 | function calcProjectFileAndBasePath(project, host = getFileSystem()) {
|
39337 | const absProject = host.resolve(project);
|
39338 | const projectIsDir = host.lstat(absProject).isDirectory();
|
39339 | const projectFile = projectIsDir ? host.join(absProject, 'tsconfig.json') : absProject;
|
39340 | const projectDir = projectIsDir ? absProject : host.dirname(absProject);
|
39341 | const basePath = host.resolve(projectDir);
|
39342 | return { projectFile, basePath };
|
39343 | }
|
39344 | function readConfiguration(project, existingOptions, host = getFileSystem()) {
|
39345 | var _a;
|
39346 | try {
|
39347 | const fs = getFileSystem();
|
39348 | const readConfigFile = (configFile) => ts$1.readConfigFile(configFile, file => host.readFile(host.resolve(file)));
|
39349 | const readAngularCompilerOptions = (configFile, parentOptions = {}) => {
|
39350 | const { config, error } = readConfigFile(configFile);
|
39351 | if (error) {
|
39352 | // Errors are handled later on by 'parseJsonConfigFileContent'
|
39353 | return parentOptions;
|
39354 | }
|
39355 | // we are only interested into merging 'angularCompilerOptions' as
|
39356 | // other options like 'compilerOptions' are merged by TS
|
39357 | const existingNgCompilerOptions = Object.assign(Object.assign({}, config.angularCompilerOptions), parentOptions);
|
39358 | if (config.extends && typeof config.extends === 'string') {
|
39359 | const extendedConfigPath = getExtendedConfigPath(configFile, config.extends, host, fs);
|
39360 | if (extendedConfigPath !== null) {
|
39361 | // Call readAngularCompilerOptions recursively to merge NG Compiler options
|
39362 | return readAngularCompilerOptions(extendedConfigPath, existingNgCompilerOptions);
|
39363 | }
|
39364 | }
|
39365 | return existingNgCompilerOptions;
|
39366 | };
|
39367 | const { projectFile, basePath } = calcProjectFileAndBasePath(project, host);
|
39368 | const configFileName = host.resolve(host.pwd(), projectFile);
|
39369 | const { config, error } = readConfigFile(projectFile);
|
39370 | if (error) {
|
39371 | return {
|
39372 | project,
|
39373 | errors: [error],
|
39374 | rootNames: [],
|
39375 | options: {},
|
39376 | emitFlags: EmitFlags.Default
|
39377 | };
|
39378 | }
|
39379 | const existingCompilerOptions = Object.assign(Object.assign({ genDir: basePath, basePath }, readAngularCompilerOptions(configFileName)), existingOptions);
|
39380 | const parseConfigHost = createParseConfigHost(host, fs);
|
39381 | const { options, errors, fileNames: rootNames, projectReferences } = ts$1.parseJsonConfigFileContent(config, parseConfigHost, basePath, existingCompilerOptions, configFileName);
|
39382 | // Coerce to boolean as `enableIvy` can be `ngtsc|true|false|undefined` here.
|
39383 | options.enableIvy = !!((_a = options.enableIvy) !== null && _a !== void 0 ? _a : true);
|
39384 | let emitFlags = EmitFlags.Default;
|
39385 | if (!(options.skipMetadataEmit || options.flatModuleOutFile)) {
|
39386 | emitFlags |= EmitFlags.Metadata;
|
39387 | }
|
39388 | if (options.skipTemplateCodegen) {
|
39389 | emitFlags = emitFlags & ~EmitFlags.Codegen;
|
39390 | }
|
39391 | return { project: projectFile, rootNames, projectReferences, options, errors, emitFlags };
|
39392 | }
|
39393 | catch (e) {
|
39394 | const errors = [{
|
39395 | category: ts$1.DiagnosticCategory.Error,
|
39396 | messageText: e.stack,
|
39397 | file: undefined,
|
39398 | start: undefined,
|
39399 | length: undefined,
|
39400 | source: 'angular',
|
39401 | code: UNKNOWN_ERROR_CODE,
|
39402 | }];
|
39403 | return { project: '', errors, rootNames: [], options: {}, emitFlags: EmitFlags.Default };
|
39404 | }
|
39405 | }
|
39406 | function createParseConfigHost(host, fs = getFileSystem()) {
|
39407 | return {
|
39408 | fileExists: host.exists.bind(host),
|
39409 | readDirectory: ts$1.sys.readDirectory,
|
39410 | readFile: host.readFile.bind(host),
|
39411 | useCaseSensitiveFileNames: fs.isCaseSensitive(),
|
39412 | };
|
39413 | }
|
39414 | function getExtendedConfigPath(configFile, extendsValue, host, fs) {
|
39415 | let extendedConfigPath = null;
|
39416 | if (extendsValue.startsWith('.') || fs.isRooted(extendsValue)) {
|
39417 | extendedConfigPath = host.resolve(host.dirname(configFile), extendsValue);
|
39418 | extendedConfigPath = host.extname(extendedConfigPath) ?
|
39419 | extendedConfigPath :
|
39420 | absoluteFrom(`${extendedConfigPath}.json`);
|
39421 | }
|
39422 | else {
|
39423 | const parseConfigHost = createParseConfigHost(host, fs);
|
39424 | // Path isn't a rooted or relative path, resolve like a module.
|
39425 | const { resolvedModule, } = ts$1.nodeModuleNameResolver(extendsValue, configFile, { moduleResolution: ts$1.ModuleResolutionKind.NodeJs, resolveJsonModule: true }, parseConfigHost);
|
39426 | if (resolvedModule) {
|
39427 | extendedConfigPath = absoluteFrom(resolvedModule.resolvedFileName);
|
39428 | }
|
39429 | }
|
39430 | if (extendedConfigPath !== null && host.exists(extendedConfigPath)) {
|
39431 | return extendedConfigPath;
|
39432 | }
|
39433 | return null;
|
39434 | }
|
39435 |
|
39436 | /**
|
39437 | * @license
|
39438 | * Copyright Google LLC All Rights Reserved.
|
39439 | *
|
39440 | * Use of this source code is governed by an MIT-style license that can be
|
39441 | * found in the LICENSE file at https://angular.io/license
|
39442 | */
|
39443 | /**
|
39444 | * Known values for global variables in `@angular/core` that Terser should set using
|
39445 | * https://github.com/terser-js/terser#conditional-compilation
|
39446 | */
|
39447 | const GLOBAL_DEFS_FOR_TERSER = {
|
39448 | ngDevMode: false,
|
39449 | ngI18nClosureMode: false,
|
39450 | };
|
39451 | const GLOBAL_DEFS_FOR_TERSER_WITH_AOT = Object.assign(Object.assign({}, GLOBAL_DEFS_FOR_TERSER), { ngJitMode: false });
|
39452 |
|
39453 | /**
|
39454 | * @license
|
39455 | * Copyright Google LLC All Rights Reserved.
|
39456 | *
|
39457 | * Use of this source code is governed by an MIT-style license that can be
|
39458 | * found in the LICENSE file at https://angular.io/license
|
39459 | */
|
39460 | setFileSystem(new NodeJSFileSystem());
|
39461 |
|
39462 | /**
|
39463 | * @license
|
39464 | * Copyright Google LLC All Rights Reserved.
|
39465 | *
|
39466 | * Use of this source code is governed by an MIT-style license that can be
|
39467 | * found in the LICENSE file at https://angular.io/license
|
39468 | */
|
39469 | // Reverse mappings of enum would generate strings
|
39470 | const ALIAS_NAME = ts$1.SymbolDisplayPartKind[ts$1.SymbolDisplayPartKind.aliasName];
|
39471 | const SYMBOL_INTERFACE = ts$1.SymbolDisplayPartKind[ts$1.SymbolDisplayPartKind.interfaceName];
|
39472 | const SYMBOL_PUNC = ts$1.SymbolDisplayPartKind[ts$1.SymbolDisplayPartKind.punctuation];
|
39473 | const SYMBOL_SPACE = ts$1.SymbolDisplayPartKind[ts$1.SymbolDisplayPartKind.space];
|
39474 | const SYMBOL_TEXT = ts$1.SymbolDisplayPartKind[ts$1.SymbolDisplayPartKind.text];
|
39475 | /**
|
39476 | * Label for various kinds of Angular entities for TS display info.
|
39477 | */
|
39478 | var DisplayInfoKind;
|
39479 | (function (DisplayInfoKind) {
|
39480 | DisplayInfoKind["ATTRIBUTE"] = "attribute";
|
39481 | DisplayInfoKind["COMPONENT"] = "component";
|
39482 | DisplayInfoKind["DIRECTIVE"] = "directive";
|
39483 | DisplayInfoKind["EVENT"] = "event";
|
39484 | DisplayInfoKind["REFERENCE"] = "reference";
|
39485 | DisplayInfoKind["ELEMENT"] = "element";
|
39486 | DisplayInfoKind["VARIABLE"] = "variable";
|
39487 | DisplayInfoKind["PIPE"] = "pipe";
|
39488 | DisplayInfoKind["PROPERTY"] = "property";
|
39489 | DisplayInfoKind["METHOD"] = "method";
|
39490 | DisplayInfoKind["TEMPLATE"] = "template";
|
39491 | })(DisplayInfoKind || (DisplayInfoKind = {}));
|
39492 | function getSymbolDisplayInfo(tsLS, typeChecker, symbol) {
|
39493 | let kind;
|
39494 | if (symbol.kind === SymbolKind.Reference) {
|
39495 | kind = DisplayInfoKind.REFERENCE;
|
39496 | }
|
39497 | else if (symbol.kind === SymbolKind.Variable) {
|
39498 | kind = DisplayInfoKind.VARIABLE;
|
39499 | }
|
39500 | else {
|
39501 | throw new Error(`AssertionError: unexpected symbol kind ${SymbolKind[symbol.kind]}`);
|
39502 | }
|
39503 | const displayParts = createDisplayParts(symbol.declaration.name, kind, /* containerName */ undefined, typeChecker.typeToString(symbol.tsType));
|
39504 | const documentation = symbol.kind === SymbolKind.Reference ?
|
39505 | getDocumentationFromTypeDefAtLocation(tsLS, symbol.targetLocation) :
|
39506 | getDocumentationFromTypeDefAtLocation(tsLS, symbol.initializerLocation);
|
39507 | return {
|
39508 | kind,
|
39509 | displayParts,
|
39510 | documentation,
|
39511 | };
|
39512 | }
|
39513 | /**
|
39514 | * Construct a compound `ts.SymbolDisplayPart[]` which incorporates the container and type of a
|
39515 | * target declaration.
|
39516 | * @param name Name of the target
|
39517 | * @param kind component, directive, pipe, etc.
|
39518 | * @param containerName either the Symbol's container or the NgModule that contains the directive
|
39519 | * @param type user-friendly name of the type
|
39520 | * @param documentation docstring or comment
|
39521 | */
|
39522 | function createDisplayParts(name, kind, containerName, type) {
|
39523 | const containerDisplayParts = containerName !== undefined ?
|
39524 | [
|
39525 | { text: containerName, kind: SYMBOL_INTERFACE },
|
39526 | { text: '.', kind: SYMBOL_PUNC },
|
39527 | ] :
|
39528 | [];
|
39529 | const typeDisplayParts = type !== undefined ?
|
39530 | [
|
39531 | { text: ':', kind: SYMBOL_PUNC },
|
39532 | { text: ' ', kind: SYMBOL_SPACE },
|
39533 | { text: type, kind: SYMBOL_INTERFACE },
|
39534 | ] :
|
39535 | [];
|
39536 | return [
|
39537 | { text: '(', kind: SYMBOL_PUNC },
|
39538 | { text: kind, kind: SYMBOL_TEXT },
|
39539 | { text: ')', kind: SYMBOL_PUNC },
|
39540 | { text: ' ', kind: SYMBOL_SPACE },
|
39541 | ...containerDisplayParts,
|
39542 | { text: name, kind: SYMBOL_INTERFACE },
|
39543 | ...typeDisplayParts,
|
39544 | ];
|
39545 | }
|
39546 | /**
|
39547 | * Convert a `SymbolDisplayInfoKind` to a `ts.ScriptElementKind` type, allowing it to pass through
|
39548 | * TypeScript APIs.
|
39549 | *
|
39550 | * In practice, this is an "illegal" type cast. Since `ts.ScriptElementKind` is a string, this is
|
39551 | * safe to do if TypeScript only uses the value in a string context. Consumers of this conversion
|
39552 | * function are responsible for ensuring this is the case.
|
39553 | */
|
39554 | function unsafeCastDisplayInfoKindToScriptElementKind(kind) {
|
39555 | return kind;
|
39556 | }
|
39557 | function getDocumentationFromTypeDefAtLocation(tsLS, shimLocation) {
|
39558 | var _a;
|
39559 | const typeDefs = tsLS.getTypeDefinitionAtPosition(shimLocation.shimPath, shimLocation.positionInShimFile);
|
39560 | if (typeDefs === undefined || typeDefs.length === 0) {
|
39561 | return undefined;
|
39562 | }
|
39563 | return (_a = tsLS.getQuickInfoAtPosition(typeDefs[0].fileName, typeDefs[0].textSpan.start)) === null || _a === void 0 ? void 0 : _a.documentation;
|
39564 | }
|
39565 | function getDirectiveDisplayInfo(tsLS, dir) {
|
39566 | var _a, _b;
|
39567 | const kind = dir.isComponent ? DisplayInfoKind.COMPONENT : DisplayInfoKind.DIRECTIVE;
|
39568 | const decl = dir.tsSymbol.declarations.find(ts$1.isClassDeclaration);
|
39569 | if (decl === undefined || decl.name === undefined) {
|
39570 | return { kind, displayParts: [], documentation: [] };
|
39571 | }
|
39572 | const res = tsLS.getQuickInfoAtPosition(decl.getSourceFile().fileName, decl.name.getStart());
|
39573 | if (res === undefined) {
|
39574 | return { kind, displayParts: [], documentation: [] };
|
39575 | }
|
39576 | const displayParts = createDisplayParts(dir.tsSymbol.name, kind, (_b = (_a = dir.ngModule) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.text, undefined);
|
39577 | return {
|
39578 | kind,
|
39579 | displayParts,
|
39580 | documentation: res.documentation,
|
39581 | };
|
39582 | }
|
39583 | function getTsSymbolDisplayInfo(tsLS, checker, symbol, kind, ownerName) {
|
39584 | const decl = symbol.valueDeclaration;
|
39585 | if (decl === undefined || (!ts$1.isPropertyDeclaration(decl) && !ts$1.isMethodDeclaration(decl)) ||
|
39586 | !ts$1.isIdentifier(decl.name)) {
|
39587 | return null;
|
39588 | }
|
39589 | const res = tsLS.getQuickInfoAtPosition(decl.getSourceFile().fileName, decl.name.getStart());
|
39590 | if (res === undefined) {
|
39591 | return { kind, displayParts: [], documentation: [] };
|
39592 | }
|
39593 | const type = checker.getDeclaredTypeOfSymbol(symbol);
|
39594 | const typeString = checker.typeToString(type);
|
39595 | const displayParts = createDisplayParts(symbol.name, kind, ownerName !== null && ownerName !== void 0 ? ownerName : undefined, typeString);
|
39596 | return {
|
39597 | kind,
|
39598 | displayParts,
|
39599 | documentation: res.documentation,
|
39600 | };
|
39601 | }
|
39602 |
|
39603 | /**
|
39604 | * @license
|
39605 | * Copyright Google LLC All Rights Reserved.
|
39606 | *
|
39607 | * Use of this source code is governed by an MIT-style license that can be
|
39608 | * found in the LICENSE file at https://angular.io/license
|
39609 | */
|
39610 | /**
|
39611 | * Return the node that most tightly encompasses the specified `position`.
|
39612 | * @param node The starting node to start the top-down search.
|
39613 | * @param position The target position within the `node`.
|
39614 | */
|
39615 | function findTightestNode(node, position) {
|
39616 | var _a;
|
39617 | if (node.getStart() <= position && position < node.getEnd()) {
|
39618 | return (_a = node.forEachChild(c => findTightestNode(c, position))) !== null && _a !== void 0 ? _a : node;
|
39619 | }
|
39620 | return undefined;
|
39621 | }
|
39622 | function getParentClassDeclaration(startNode) {
|
39623 | while (startNode) {
|
39624 | if (ts$1.isClassDeclaration(startNode)) {
|
39625 | return startNode;
|
39626 | }
|
39627 | startNode = startNode.parent;
|
39628 | }
|
39629 | return undefined;
|
39630 | }
|
39631 | /**
|
39632 | * Returns a property assignment from the assignment value if the property name
|
39633 | * matches the specified `key`, or `null` if there is no match.
|
39634 | */
|
39635 | function getPropertyAssignmentFromValue(value, key) {
|
39636 | const propAssignment = value.parent;
|
39637 | if (!propAssignment || !ts$1.isPropertyAssignment(propAssignment) ||
|
39638 | propAssignment.name.getText() !== key) {
|
39639 | return null;
|
39640 | }
|
39641 | return propAssignment;
|
39642 | }
|
39643 | /**
|
39644 | * Given a decorator property assignment, return the ClassDeclaration node that corresponds to the
|
39645 | * directive class the property applies to.
|
39646 | * If the property assignment is not on a class decorator, no declaration is returned.
|
39647 | *
|
39648 | * For example,
|
39649 | *
|
39650 | * @Component({
|
39651 | * template: '<div></div>'
|
39652 | * ^^^^^^^^^^^^^^^^^^^^^^^---- property assignment
|
39653 | * })
|
39654 | * class AppComponent {}
|
39655 | * ^---- class declaration node
|
39656 | *
|
39657 | * @param propAsgnNode property assignment
|
39658 | */
|
39659 | function getClassDeclFromDecoratorProp(propAsgnNode) {
|
39660 | if (!propAsgnNode.parent || !ts$1.isObjectLiteralExpression(propAsgnNode.parent)) {
|
39661 | return;
|
39662 | }
|
39663 | const objLitExprNode = propAsgnNode.parent;
|
39664 | if (!objLitExprNode.parent || !ts$1.isCallExpression(objLitExprNode.parent)) {
|
39665 | return;
|
39666 | }
|
39667 | const callExprNode = objLitExprNode.parent;
|
39668 | if (!callExprNode.parent || !ts$1.isDecorator(callExprNode.parent)) {
|
39669 | return;
|
39670 | }
|
39671 | const decorator = callExprNode.parent;
|
39672 | if (!decorator.parent || !ts$1.isClassDeclaration(decorator.parent)) {
|
39673 | return;
|
39674 | }
|
39675 | const classDeclNode = decorator.parent;
|
39676 | return classDeclNode;
|
39677 | }
|
39678 |
|
39679 | /**
|
39680 | * @license
|
39681 | * Copyright Google LLC All Rights Reserved.
|
39682 | *
|
39683 | * Use of this source code is governed by an MIT-style license that can be
|
39684 | * found in the LICENSE file at https://angular.io/license
|
39685 | */
|
39686 | function getTextSpanOfNode(node) {
|
39687 | if (isTemplateNodeWithKeyAndValue(node)) {
|
39688 | return toTextSpan(node.keySpan);
|
39689 | }
|
39690 | else if (node instanceof PropertyWrite || node instanceof MethodCall ||
|
39691 | node instanceof BindingPipe || node instanceof PropertyRead) {
|
39692 | // The `name` part of a `PropertyWrite`, `MethodCall`, and `BindingPipe` does not
|
39693 | // have its own AST so there is no way to retrieve a `Symbol` for just the `name` via a specific
|
39694 | // node.
|
39695 | return toTextSpan(node.nameSpan);
|
39696 | }
|
39697 | else {
|
39698 | return toTextSpan(node.sourceSpan);
|
39699 | }
|
39700 | }
|
39701 | function toTextSpan(span) {
|
39702 | let start, end;
|
39703 | if (span instanceof AbsoluteSourceSpan || span instanceof ParseSpan) {
|
39704 | start = span.start;
|
39705 | end = span.end;
|
39706 | }
|
39707 | else {
|
39708 | start = span.start.offset;
|
39709 | end = span.end.offset;
|
39710 | }
|
39711 | return { start, length: end - start };
|
39712 | }
|
39713 | function isTemplateNodeWithKeyAndValue(node) {
|
39714 | return isTemplateNode(node) && node.hasOwnProperty('keySpan');
|
39715 | }
|
39716 | function isWithinKeyValue(position, node) {
|
39717 | let { keySpan, valueSpan } = node;
|
39718 | if (valueSpan === undefined && node instanceof BoundEvent) {
|
39719 | valueSpan = node.handlerSpan;
|
39720 | }
|
39721 | const isWithinKeyValue = isWithin(position, keySpan) || !!(valueSpan && isWithin(position, valueSpan));
|
39722 | return isWithinKeyValue;
|
39723 | }
|
39724 | function isTemplateNode(node) {
|
39725 | // Template node implements the Node interface so we cannot use instanceof.
|
39726 | return node.sourceSpan instanceof ParseSourceSpan;
|
39727 | }
|
39728 | function getInlineTemplateInfoAtPosition(sf, position, compiler) {
|
39729 | const expression = findTightestNode(sf, position);
|
39730 | if (expression === undefined) {
|
39731 | return undefined;
|
39732 | }
|
39733 | const classDecl = getParentClassDeclaration(expression);
|
39734 | if (classDecl === undefined) {
|
39735 | return undefined;
|
39736 | }
|
39737 | // Return `undefined` if the position is not on the template expression or the template resource
|
39738 | // is not inline.
|
39739 | const resources = compiler.getComponentResources(classDecl);
|
39740 | if (resources === null || isExternalResource(resources.template) ||
|
39741 | expression !== resources.template.expression) {
|
39742 | return undefined;
|
39743 | }
|
39744 | const template = compiler.getTemplateTypeChecker().getTemplate(classDecl);
|
39745 | if (template === null) {
|
39746 | return undefined;
|
39747 | }
|
39748 | return { template, component: classDecl };
|
39749 | }
|
39750 | /**
|
39751 | * Retrieves the `ts.ClassDeclaration` at a location along with its template nodes.
|
39752 | */
|
39753 | function getTemplateInfoAtPosition(fileName, position, compiler) {
|
39754 | if (isTypeScriptFile(fileName)) {
|
39755 | const sf = compiler.getNextProgram().getSourceFile(fileName);
|
39756 | if (sf === undefined) {
|
39757 | return undefined;
|
39758 | }
|
39759 | return getInlineTemplateInfoAtPosition(sf, position, compiler);
|
39760 | }
|
39761 | else {
|
39762 | return getFirstComponentForTemplateFile(fileName, compiler);
|
39763 | }
|
39764 | }
|
39765 | /**
|
39766 | * First, attempt to sort component declarations by file name.
|
39767 | * If the files are the same, sort by start location of the declaration.
|
39768 | */
|
39769 | function tsDeclarationSortComparator(a, b) {
|
39770 | const aFile = a.getSourceFile().fileName;
|
39771 | const bFile = b.getSourceFile().fileName;
|
39772 | if (aFile < bFile) {
|
39773 | return -1;
|
39774 | }
|
39775 | else if (aFile > bFile) {
|
39776 | return 1;
|
39777 | }
|
39778 | else {
|
39779 | return b.getFullStart() - a.getFullStart();
|
39780 | }
|
39781 | }
|
39782 | function getFirstComponentForTemplateFile(fileName, compiler) {
|
39783 | const templateTypeChecker = compiler.getTemplateTypeChecker();
|
39784 | const components = compiler.getComponentsWithTemplateFile(fileName);
|
39785 | const sortedComponents = Array.from(components).sort(tsDeclarationSortComparator);
|
39786 | for (const component of sortedComponents) {
|
39787 | if (!ts$1.isClassDeclaration(component)) {
|
39788 | continue;
|
39789 | }
|
39790 | const template = templateTypeChecker.getTemplate(component);
|
39791 | if (template === null) {
|
39792 | continue;
|
39793 | }
|
39794 | return { template, component };
|
39795 | }
|
39796 | return undefined;
|
39797 | }
|
39798 | /**
|
39799 | * Given an attribute node, converts it to string form.
|
39800 | */
|
39801 | function toAttributeString(attribute) {
|
39802 | var _a, _b;
|
39803 | if (attribute instanceof BoundEvent) {
|
39804 | return `[${attribute.name}]`;
|
39805 | }
|
39806 | else {
|
39807 | return `[${attribute.name}=${(_b = (_a = attribute.valueSpan) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : ''}]`;
|
39808 | }
|
39809 | }
|
39810 | function getNodeName(node) {
|
39811 | return node instanceof Template ? node.tagName : node.name;
|
39812 | }
|
39813 | /**
|
39814 | * Given a template or element node, returns all attributes on the node.
|
39815 | */
|
39816 | function getAttributes(node) {
|
39817 | const attributes = [...node.attributes, ...node.inputs, ...node.outputs];
|
39818 | if (node instanceof Template) {
|
39819 | attributes.push(...node.templateAttrs);
|
39820 | }
|
39821 | return attributes;
|
39822 | }
|
39823 | /**
|
39824 | * Given two `Set`s, returns all items in the `left` which do not appear in the `right`.
|
39825 | */
|
39826 | function difference(left, right) {
|
39827 | const result = new Set();
|
39828 | for (const dir of left) {
|
39829 | if (!right.has(dir)) {
|
39830 | result.add(dir);
|
39831 | }
|
39832 | }
|
39833 | return result;
|
39834 | }
|
39835 | /**
|
39836 | * Given an element or template, determines which directives match because the tag is present. For
|
39837 | * example, if a directive selector is `div[myAttr]`, this would match div elements but would not if
|
39838 | * the selector were just `[myAttr]`. We find which directives are applied because of this tag by
|
39839 | * elimination: compare the directive matches with the tag present against the directive matches
|
39840 | * without it. The difference would be the directives which match because the tag is present.
|
39841 | *
|
39842 | * @param element The element or template node that the attribute/tag is part of.
|
39843 | * @param directives The list of directives to match against.
|
39844 | * @returns The list of directives matching the tag name via the strategy described above.
|
39845 | */
|
39846 | // TODO(atscott): Add unit tests for this and the one for attributes
|
39847 | function getDirectiveMatchesForElementTag(element, directives) {
|
39848 | const attributes = getAttributes(element);
|
39849 | const allAttrs = attributes.map(toAttributeString);
|
39850 | const allDirectiveMatches = getDirectiveMatchesForSelector(directives, getNodeName(element) + allAttrs.join(''));
|
39851 | const matchesWithoutElement = getDirectiveMatchesForSelector(directives, allAttrs.join(''));
|
39852 | return difference(allDirectiveMatches, matchesWithoutElement);
|
39853 | }
|
39854 | function makeElementSelector(element) {
|
39855 | const attributes = getAttributes(element);
|
39856 | const allAttrs = attributes.map(toAttributeString);
|
39857 | return getNodeName(element) + allAttrs.join('');
|
39858 | }
|
39859 | /**
|
39860 | * Given an attribute name, determines which directives match because the attribute is present. We
|
39861 | * find which directives are applied because of this attribute by elimination: compare the directive
|
39862 | * matches with the attribute present against the directive matches without it. The difference would
|
39863 | * be the directives which match because the attribute is present.
|
39864 | *
|
39865 | * @param name The name of the attribute
|
39866 | * @param hostNode The node which the attribute appears on
|
39867 | * @param directives The list of directives to match against.
|
39868 | * @returns The list of directives matching the tag name via the strategy described above.
|
39869 | */
|
39870 | function getDirectiveMatchesForAttribute(name, hostNode, directives) {
|
39871 | const attributes = getAttributes(hostNode);
|
39872 | const allAttrs = attributes.map(toAttributeString);
|
39873 | const allDirectiveMatches = getDirectiveMatchesForSelector(directives, getNodeName(hostNode) + allAttrs.join(''));
|
39874 | const attrsExcludingName = attributes.filter(a => a.name !== name).map(toAttributeString);
|
39875 | const matchesWithoutAttr = getDirectiveMatchesForSelector(directives, getNodeName(hostNode) + attrsExcludingName.join(''));
|
39876 | return difference(allDirectiveMatches, matchesWithoutAttr);
|
39877 | }
|
39878 | /**
|
39879 | * Given a list of directives and a text to use as a selector, returns the directives which match
|
39880 | * for the selector.
|
39881 | */
|
39882 | function getDirectiveMatchesForSelector(directives, selector) {
|
39883 | const selectors = CssSelector.parse(selector);
|
39884 | if (selectors.length === 0) {
|
39885 | return new Set();
|
39886 | }
|
39887 | return new Set(directives.filter((dir) => {
|
39888 | if (dir.selector === null) {
|
39889 | return false;
|
39890 | }
|
39891 | const matcher = new SelectorMatcher();
|
39892 | matcher.addSelectables(CssSelector.parse(dir.selector));
|
39893 | return selectors.some(selector => matcher.match(selector, null));
|
39894 | }));
|
39895 | }
|
39896 | /**
|
39897 | * Returns a new `ts.SymbolDisplayPart` array which has the alias imports from the tcb filtered
|
39898 | * out, i.e. `i0.NgForOf`.
|
39899 | */
|
39900 | function filterAliasImports(displayParts) {
|
39901 | const tcbAliasImportRegex = /i\d+/;
|
39902 | function isImportAlias(part) {
|
39903 | return part.kind === ALIAS_NAME && tcbAliasImportRegex.test(part.text);
|
39904 | }
|
39905 | function isDotPunctuation(part) {
|
39906 | return part.kind === SYMBOL_PUNC && part.text === '.';
|
39907 | }
|
39908 | return displayParts.filter((part, i) => {
|
39909 | const previousPart = displayParts[i - 1];
|
39910 | const nextPart = displayParts[i + 1];
|
39911 | const aliasNameFollowedByDot = isImportAlias(part) && nextPart !== undefined && isDotPunctuation(nextPart);
|
39912 | const dotPrecededByAlias = isDotPunctuation(part) && previousPart !== undefined && isImportAlias(previousPart);
|
39913 | return !aliasNameFollowedByDot && !dotPrecededByAlias;
|
39914 | });
|
39915 | }
|
39916 | function isDollarEvent(n) {
|
39917 | return n instanceof PropertyRead && n.name === '$event' &&
|
39918 | n.receiver instanceof ImplicitReceiver && !(n.receiver instanceof ThisReceiver);
|
39919 | }
|
39920 | /**
|
39921 | * Returns a new array formed by applying a given callback function to each element of the array,
|
39922 | * and then flattening the result by one level.
|
39923 | */
|
39924 | function flatMap(items, f) {
|
39925 | const results = [];
|
39926 | for (const x of items) {
|
39927 | results.push(...f(x));
|
39928 | }
|
39929 | return results;
|
39930 | }
|
39931 | function isTypeScriptFile(fileName) {
|
39932 | return fileName.endsWith('.ts');
|
39933 | }
|
39934 | function isWithin(position, span) {
|
39935 | let start, end;
|
39936 | if (span instanceof ParseSourceSpan) {
|
39937 | start = span.start.offset;
|
39938 | end = span.end.offset;
|
39939 | }
|
39940 | else {
|
39941 | start = span.start;
|
39942 | end = span.end;
|
39943 | }
|
39944 | // Note both start and end are inclusive because we want to match conditions
|
39945 | // like ¦start and end¦ where ¦ is the cursor.
|
39946 | return start <= position && position <= end;
|
39947 | }
|
39948 | /**
|
39949 | * For a given location in a shim file, retrieves the corresponding file url for the template and
|
39950 | * the span in the template.
|
39951 | */
|
39952 | function getTemplateLocationFromShimLocation(templateTypeChecker, shimPath, positionInShimFile) {
|
39953 | const mapping = templateTypeChecker.getTemplateMappingAtShimLocation({ shimPath, positionInShimFile });
|
39954 | if (mapping === null) {
|
39955 | return null;
|
39956 | }
|
39957 | const { templateSourceMapping, span } = mapping;
|
39958 | let templateUrl;
|
39959 | if (templateSourceMapping.type === 'direct') {
|
39960 | templateUrl = absoluteFromSourceFile(templateSourceMapping.node.getSourceFile());
|
39961 | }
|
39962 | else if (templateSourceMapping.type === 'external') {
|
39963 | templateUrl = absoluteFrom(templateSourceMapping.templateUrl);
|
39964 | }
|
39965 | else {
|
39966 | // This includes indirect mappings, which are difficult to map directly to the code
|
39967 | // location. Diagnostics similarly return a synthetic template string for this case rather
|
39968 | // than a real location.
|
39969 | return null;
|
39970 | }
|
39971 | return { templateUrl, span };
|
39972 | }
|
39973 |
|
39974 | /**
|
39975 | * @license
|
39976 | * Copyright Google LLC All Rights Reserved.
|
39977 | *
|
39978 | * Use of this source code is governed by an MIT-style license that can be
|
39979 | * found in the LICENSE file at https://angular.io/license
|
39980 | */
|
39981 | class LanguageServiceAdapter {
|
39982 | constructor(project) {
|
39983 | this.project = project;
|
39984 | this.entryPoint = null;
|
39985 | this.constructionDiagnostics = [];
|
39986 | this.ignoreForEmit = new Set();
|
39987 | this.factoryTracker = null; // no .ngfactory shims
|
39988 | this.unifiedModulesHost = null; // only used in Bazel
|
39989 | /**
|
39990 | * Map of resource filenames to the version of the file last read via `readResource`.
|
39991 | *
|
39992 | * Used to implement `getModifiedResourceFiles`.
|
39993 | */
|
39994 | this.lastReadResourceVersion = new Map();
|
39995 | this.rootDirs = getRootDirs(this, project.getCompilationSettings());
|
39996 | }
|
39997 | isShim(sf) {
|
39998 | return isShim(sf);
|
39999 | }
|
40000 | fileExists(fileName) {
|
40001 | return this.project.fileExists(fileName);
|
40002 | }
|
40003 | readFile(fileName) {
|
40004 | return this.project.readFile(fileName);
|
40005 | }
|
40006 | getCurrentDirectory() {
|
40007 | return this.project.getCurrentDirectory();
|
40008 | }
|
40009 | getCanonicalFileName(fileName) {
|
40010 | return this.project.projectService.toCanonicalFileName(fileName);
|
40011 | }
|
40012 | /**
|
40013 | * Return the real path of a symlink. This method is required in order to
|
40014 | * resolve symlinks in node_modules.
|
40015 | */
|
40016 | realpath(path) {
|
40017 | var _a, _b, _c;
|
40018 | return (_c = (_b = (_a = this.project).realpath) === null || _b === void 0 ? void 0 : _b.call(_a, path)) !== null && _c !== void 0 ? _c : path;
|
40019 | }
|
40020 | /**
|
40021 | * readResource() is an Angular-specific method for reading files that are not
|
40022 | * managed by the TS compiler host, namely templates and stylesheets.
|
40023 | * It is a method on ExtendedTsCompilerHost, see
|
40024 | * packages/compiler-cli/src/ngtsc/core/api/src/interfaces.ts
|
40025 | */
|
40026 | readResource(fileName) {
|
40027 | if (isTypeScriptFile(fileName)) {
|
40028 | throw new Error(`readResource() should not be called on TS file: ${fileName}`);
|
40029 | }
|
40030 | // Calling getScriptSnapshot() will actually create a ScriptInfo if it does
|
40031 | // not exist! The same applies for getScriptVersion().
|
40032 | // getScriptInfo() will not create one if it does not exist.
|
40033 | // In this case, we *want* a script info to be created so that we could
|
40034 | // keep track of its version.
|
40035 | const snapshot = this.project.getScriptSnapshot(fileName);
|
40036 | if (!snapshot) {
|
40037 | // This would fail if the file does not exist, or readFile() fails for
|
40038 | // whatever reasons.
|
40039 | throw new Error(`Failed to get script snapshot while trying to read ${fileName}`);
|
40040 | }
|
40041 | const version = this.project.getScriptVersion(fileName);
|
40042 | this.lastReadResourceVersion.set(fileName, version);
|
40043 | return snapshot.getText(0, snapshot.getLength());
|
40044 | }
|
40045 | getModifiedResourceFiles() {
|
40046 | const modifiedFiles = new Set();
|
40047 | for (const [fileName, oldVersion] of this.lastReadResourceVersion) {
|
40048 | if (this.project.getScriptVersion(fileName) !== oldVersion) {
|
40049 | modifiedFiles.add(fileName);
|
40050 | }
|
40051 | }
|
40052 | return modifiedFiles.size > 0 ? modifiedFiles : undefined;
|
40053 | }
|
40054 | }
|
40055 | /**
|
40056 | * Used to read configuration files.
|
40057 | *
|
40058 | * A language service parse configuration host is independent of the adapter
|
40059 | * because signatures of calls like `FileSystem#readFile` are a bit stricter
|
40060 | * than those on the adapter.
|
40061 | */
|
40062 | class LSParseConfigHost {
|
40063 | constructor(serverHost) {
|
40064 | this.serverHost = serverHost;
|
40065 | }
|
40066 | exists(path) {
|
40067 | return this.serverHost.fileExists(path) || this.serverHost.directoryExists(path);
|
40068 | }
|
40069 | readFile(path) {
|
40070 | const content = this.serverHost.readFile(path);
|
40071 | if (content === undefined) {
|
40072 | throw new Error(`LanguageServiceFS#readFile called on unavailable file ${path}`);
|
40073 | }
|
40074 | return content;
|
40075 | }
|
40076 | lstat(path) {
|
40077 | return {
|
40078 | isFile: () => {
|
40079 | return this.serverHost.fileExists(path);
|
40080 | },
|
40081 | isDirectory: () => {
|
40082 | return this.serverHost.directoryExists(path);
|
40083 | },
|
40084 | isSymbolicLink: () => {
|
40085 | throw new Error(`LanguageServiceFS#lstat#isSymbolicLink not implemented`);
|
40086 | },
|
40087 | };
|
40088 | }
|
40089 | pwd() {
|
40090 | return this.serverHost.getCurrentDirectory();
|
40091 | }
|
40092 | extname(path$1) {
|
40093 | return path.extname(path$1);
|
40094 | }
|
40095 | resolve(...paths) {
|
40096 | return path.resolve(...paths);
|
40097 | }
|
40098 | dirname(file) {
|
40099 | return path.dirname(file);
|
40100 | }
|
40101 | join(basePath, ...paths) {
|
40102 | return path.join(basePath, ...paths);
|
40103 | }
|
40104 | }
|
40105 |
|
40106 | /**
|
40107 | * @license
|
40108 | * Copyright Google LLC All Rights Reserved.
|
40109 | *
|
40110 | * Use of this source code is governed by an MIT-style license that can be
|
40111 | * found in the LICENSE file at https://angular.io/license
|
40112 | */
|
40113 | /**
|
40114 | * Manages the `NgCompiler` instance which backs the language service, updating or replacing it as
|
40115 | * needed to produce an up-to-date understanding of the current program.
|
40116 | *
|
40117 | * TODO(alxhub): currently the options used for the compiler are specified at `CompilerFactory`
|
40118 | * construction, and are not changable. In a real project, users can update `tsconfig.json`. We need
|
40119 | * to properly handle a change in the compiler options, either by having an API to update the
|
40120 | * `CompilerFactory` to use new options, or by replacing it entirely.
|
40121 | */
|
40122 | class CompilerFactory {
|
40123 | constructor(adapter, programStrategy, options) {
|
40124 | this.adapter = adapter;
|
40125 | this.programStrategy = programStrategy;
|
40126 | this.options = options;
|
40127 | this.incrementalStrategy = new TrackedIncrementalBuildStrategy();
|
40128 | this.compiler = null;
|
40129 | this.lastKnownProgram = null;
|
40130 | }
|
40131 | getOrCreate() {
|
40132 | var _a;
|
40133 | const program = this.programStrategy.getProgram();
|
40134 | const modifiedResourceFiles = (_a = this.adapter.getModifiedResourceFiles()) !== null && _a !== void 0 ? _a : new Set();
|
40135 | if (this.compiler !== null && program === this.lastKnownProgram) {
|
40136 | if (modifiedResourceFiles.size > 0) {
|
40137 | // Only resource files have changed since the last NgCompiler was created.
|
40138 | const ticket = resourceChangeTicket(this.compiler, modifiedResourceFiles);
|
40139 | this.compiler = NgCompiler.fromTicket(ticket, this.adapter);
|
40140 | }
|
40141 | return this.compiler;
|
40142 | }
|
40143 | let ticket;
|
40144 | if (this.compiler === null || this.lastKnownProgram === null) {
|
40145 | ticket = freshCompilationTicket(program, this.options, this.incrementalStrategy, this.programStrategy, true, true);
|
40146 | }
|
40147 | else {
|
40148 | ticket = incrementalFromCompilerTicket(this.compiler, program, this.incrementalStrategy, this.programStrategy, modifiedResourceFiles);
|
40149 | }
|
40150 | this.compiler = NgCompiler.fromTicket(ticket, this.adapter);
|
40151 | this.lastKnownProgram = program;
|
40152 | return this.compiler;
|
40153 | }
|
40154 | registerLastKnownProgram() {
|
40155 | this.lastKnownProgram = this.programStrategy.getProgram();
|
40156 | }
|
40157 | }
|
40158 |
|
40159 | /**
|
40160 | * @license
|
40161 | * Copyright Google LLC All Rights Reserved.
|
40162 | *
|
40163 | * Use of this source code is governed by an MIT-style license that can be
|
40164 | * found in the LICENSE file at https://angular.io/license
|
40165 | */
|
40166 | /**
|
40167 | * Differentiates different kinds of `AttributeCompletion`s.
|
40168 | */
|
40169 | var AttributeCompletionKind;
|
40170 | (function (AttributeCompletionKind) {
|
40171 | /**
|
40172 | * Completion of an attribute from the HTML schema.
|
40173 | *
|
40174 | * Attributes often have a corresponding DOM property of the same name.
|
40175 | */
|
40176 | AttributeCompletionKind[AttributeCompletionKind["DomAttribute"] = 0] = "DomAttribute";
|
40177 | /**
|
40178 | * Completion of a property from the DOM schema.
|
40179 | *
|
40180 | * `DomProperty` completions are generated only for properties which don't share their name with
|
40181 | * an HTML attribute.
|
40182 | */
|
40183 | AttributeCompletionKind[AttributeCompletionKind["DomProperty"] = 1] = "DomProperty";
|
40184 | /**
|
40185 | * Completion of an attribute that results in a new directive being matched on an element.
|
40186 | */
|
40187 | AttributeCompletionKind[AttributeCompletionKind["DirectiveAttribute"] = 2] = "DirectiveAttribute";
|
40188 | /**
|
40189 | * Completion of an attribute that results in a new structural directive being matched on an
|
40190 | * element.
|
40191 | */
|
40192 | AttributeCompletionKind[AttributeCompletionKind["StructuralDirectiveAttribute"] = 3] = "StructuralDirectiveAttribute";
|
40193 | /**
|
40194 | * Completion of an input from a directive which is either present on the element, or becomes
|
40195 | * present after the addition of this attribute.
|
40196 | */
|
40197 | AttributeCompletionKind[AttributeCompletionKind["DirectiveInput"] = 4] = "DirectiveInput";
|
40198 | /**
|
40199 | * Completion of an output from a directive which is either present on the element, or becomes
|
40200 | * present after the addition of this attribute.
|
40201 | */
|
40202 | AttributeCompletionKind[AttributeCompletionKind["DirectiveOutput"] = 5] = "DirectiveOutput";
|
40203 | })(AttributeCompletionKind || (AttributeCompletionKind = {}));
|
40204 | /**
|
40205 | * Given an element and its context, produce a `Map` of all possible attribute completions.
|
40206 | *
|
40207 | * 3 kinds of attributes are considered for completion, from highest to lowest priority:
|
40208 | *
|
40209 | * 1. Inputs/outputs of directives present on the element already.
|
40210 | * 2. Inputs/outputs of directives that are not present on the element, but which would become
|
40211 | * present if such a binding is added.
|
40212 | * 3. Attributes from the DOM schema for the element.
|
40213 | *
|
40214 | * The priority of these options determines which completions are added to the `Map`. If a directive
|
40215 | * input shares the same name as a DOM attribute, the `Map` will reflect the directive input
|
40216 | * completion, not the DOM completion for that name.
|
40217 | */
|
40218 | function buildAttributeCompletionTable(component, element, checker) {
|
40219 | const table = new Map();
|
40220 | // Use the `ElementSymbol` or `TemplateSymbol` to iterate over directives present on the node, and
|
40221 | // their inputs/outputs. These have the highest priority of completion results.
|
40222 | const symbol = checker.getSymbolOfNode(element, component);
|
40223 | const presentDirectives = new Set();
|
40224 | if (symbol !== null) {
|
40225 | // An `ElementSymbol` was available. This means inputs and outputs for directives on the
|
40226 | // element can be added to the completion table.
|
40227 | for (const dirSymbol of symbol.directives) {
|
40228 | const directive = dirSymbol.tsSymbol.valueDeclaration;
|
40229 | if (!ts$1.isClassDeclaration(directive)) {
|
40230 | continue;
|
40231 | }
|
40232 | presentDirectives.add(directive);
|
40233 | const meta = checker.getDirectiveMetadata(directive);
|
40234 | if (meta === null) {
|
40235 | continue;
|
40236 | }
|
40237 | for (const [classPropertyName, propertyName] of meta.inputs) {
|
40238 | if (table.has(propertyName)) {
|
40239 | continue;
|
40240 | }
|
40241 | table.set(propertyName, {
|
40242 | kind: AttributeCompletionKind.DirectiveInput,
|
40243 | propertyName,
|
40244 | directive: dirSymbol,
|
40245 | classPropertyName,
|
40246 | twoWayBindingSupported: meta.outputs.hasBindingPropertyName(propertyName + 'Change'),
|
40247 | });
|
40248 | }
|
40249 | for (const [classPropertyName, propertyName] of meta.outputs) {
|
40250 | if (table.has(propertyName)) {
|
40251 | continue;
|
40252 | }
|
40253 | table.set(propertyName, {
|
40254 | kind: AttributeCompletionKind.DirectiveOutput,
|
40255 | eventName: propertyName,
|
40256 | directive: dirSymbol,
|
40257 | classPropertyName,
|
40258 | });
|
40259 | }
|
40260 | }
|
40261 | }
|
40262 | // Next, explore hypothetical directives and determine if the addition of any single attributes
|
40263 | // can cause the directive to match the element.
|
40264 | const directivesInScope = checker.getDirectivesInScope(component);
|
40265 | if (directivesInScope !== null) {
|
40266 | const elementSelector = makeElementSelector(element);
|
40267 | for (const dirInScope of directivesInScope) {
|
40268 | const directive = dirInScope.tsSymbol.valueDeclaration;
|
40269 | // Skip directives that are present on the element.
|
40270 | if (!ts$1.isClassDeclaration(directive) || presentDirectives.has(directive)) {
|
40271 | continue;
|
40272 | }
|
40273 | const meta = checker.getDirectiveMetadata(directive);
|
40274 | if (meta === null || meta.selector === null) {
|
40275 | continue;
|
40276 | }
|
40277 | if (!meta.isStructural) {
|
40278 | // For non-structural directives, the directive's attribute selector(s) are matched against
|
40279 | // a hypothetical version of the element with those attributes. A match indicates that
|
40280 | // adding that attribute/input/output binding would cause the directive to become present,
|
40281 | // meaning that such a binding is a valid completion.
|
40282 | const selectors = CssSelector.parse(meta.selector);
|
40283 | const matcher = new SelectorMatcher();
|
40284 | matcher.addSelectables(selectors);
|
40285 | for (const selector of selectors) {
|
40286 | for (const [attrName, attrValue] of selectorAttributes(selector)) {
|
40287 | if (attrValue !== '') {
|
40288 | // This attribute selector requires a value, which is not supported in completion.
|
40289 | continue;
|
40290 | }
|
40291 | if (table.has(attrName)) {
|
40292 | // Skip this attribute as there's already a binding for it.
|
40293 | continue;
|
40294 | }
|
40295 | // Check whether adding this attribute would cause the directive to start matching.
|
40296 | const newElementSelector = elementSelector + `[${attrName}]`;
|
40297 | if (!matcher.match(CssSelector.parse(newElementSelector)[0], null)) {
|
40298 | // Nope, move on with our lives.
|
40299 | continue;
|
40300 | }
|
40301 | // Adding this attribute causes a new directive to be matched. Decide how to categorize
|
40302 | // it based on the directive's inputs and outputs.
|
40303 | if (meta.inputs.hasBindingPropertyName(attrName)) {
|
40304 | // This attribute corresponds to an input binding.
|
40305 | table.set(attrName, {
|
40306 | kind: AttributeCompletionKind.DirectiveInput,
|
40307 | directive: dirInScope,
|
40308 | propertyName: attrName,
|
40309 | classPropertyName: meta.inputs.getByBindingPropertyName(attrName)[0].classPropertyName,
|
40310 | twoWayBindingSupported: meta.outputs.hasBindingPropertyName(attrName + 'Change'),
|
40311 | });
|
40312 | }
|
40313 | else if (meta.outputs.hasBindingPropertyName(attrName)) {
|
40314 | // This attribute corresponds to an output binding.
|
40315 | table.set(attrName, {
|
40316 | kind: AttributeCompletionKind.DirectiveOutput,
|
40317 | directive: dirInScope,
|
40318 | eventName: attrName,
|
40319 | classPropertyName: meta.outputs.getByBindingPropertyName(attrName)[0].classPropertyName,
|
40320 | });
|
40321 | }
|
40322 | else {
|
40323 | // This attribute causes a new directive to be matched, but does not also correspond
|
40324 | // to an input or output binding.
|
40325 | table.set(attrName, {
|
40326 | kind: AttributeCompletionKind.DirectiveAttribute,
|
40327 | attribute: attrName,
|
40328 | directive: dirInScope,
|
40329 | });
|
40330 | }
|
40331 | }
|
40332 | }
|
40333 | }
|
40334 | else {
|
40335 | // Hypothetically matching a structural directive is a litle different than a plain
|
40336 | // directive. Use of the '*' structural directive syntactic sugar means that the actual
|
40337 | // directive is applied to a plain <ng-template> node, not the existing element with any
|
40338 | // other attributes it might already have.
|
40339 | // Additionally, more than one attribute/input might need to be present in order for the
|
40340 | // directive to match (e.g. `ngFor` has a selector of `[ngFor][ngForOf]`). This gets a
|
40341 | // little tricky.
|
40342 | const structuralAttributes = getStructuralAttributes(meta);
|
40343 | for (const attrName of structuralAttributes) {
|
40344 | table.set(attrName, {
|
40345 | kind: AttributeCompletionKind.StructuralDirectiveAttribute,
|
40346 | attribute: attrName,
|
40347 | directive: dirInScope,
|
40348 | });
|
40349 | }
|
40350 | }
|
40351 | }
|
40352 | }
|
40353 | // Finally, add any DOM attributes not already covered by inputs.
|
40354 | if (element instanceof Element) {
|
40355 | for (const { attribute, property } of checker.getPotentialDomBindings(element.name)) {
|
40356 | const isAlsoProperty = attribute === property;
|
40357 | if (!table.has(attribute)) {
|
40358 | table.set(attribute, {
|
40359 | kind: AttributeCompletionKind.DomAttribute,
|
40360 | attribute,
|
40361 | isAlsoProperty,
|
40362 | });
|
40363 | }
|
40364 | if (!isAlsoProperty && !table.has(property)) {
|
40365 | table.set(property, {
|
40366 | kind: AttributeCompletionKind.DomProperty,
|
40367 | property,
|
40368 | });
|
40369 | }
|
40370 | }
|
40371 | }
|
40372 | return table;
|
40373 | }
|
40374 | /**
|
40375 | * Given an `AttributeCompletion`, add any available completions to a `ts.CompletionEntry` array of
|
40376 | * results.
|
40377 | *
|
40378 | * The kind of completions generated depends on whether the current context is an attribute context
|
40379 | * or not. For example, completing on `<element attr|>` will generate two results: `attribute` and
|
40380 | * `[attribute]` - either a static attribute can be generated, or a property binding. However,
|
40381 | * `<element [attr|]>` is not an attribute context, and so only the property completion `attribute`
|
40382 | * is generated. Note that this completion does not have the `[]` property binding sugar as its
|
40383 | * implicitly present in a property binding context (we're already completing within an `[attr|]`
|
40384 | * expression).
|
40385 | */
|
40386 | function addAttributeCompletionEntries(entries, completion, isAttributeContext, isElementContext, replacementSpan) {
|
40387 | switch (completion.kind) {
|
40388 | case AttributeCompletionKind.DirectiveAttribute: {
|
40389 | entries.push({
|
40390 | kind: unsafeCastDisplayInfoKindToScriptElementKind(DisplayInfoKind.DIRECTIVE),
|
40391 | name: completion.attribute,
|
40392 | sortText: completion.attribute,
|
40393 | replacementSpan,
|
40394 | });
|
40395 | break;
|
40396 | }
|
40397 | case AttributeCompletionKind.StructuralDirectiveAttribute: {
|
40398 | // In an element, the completion is offered with a leading '*' to activate the structural
|
40399 | // directive. Once present, the structural attribute will be parsed as a template and not an
|
40400 | // element, and the prefix is no longer necessary.
|
40401 | const prefix = isElementContext ? '*' : '';
|
40402 | entries.push({
|
40403 | kind: unsafeCastDisplayInfoKindToScriptElementKind(DisplayInfoKind.DIRECTIVE),
|
40404 | name: prefix + completion.attribute,
|
40405 | sortText: prefix + completion.attribute,
|
40406 | replacementSpan,
|
40407 | });
|
40408 | break;
|
40409 | }
|
40410 | case AttributeCompletionKind.DirectiveInput: {
|
40411 | if (isAttributeContext) {
|
40412 | // Offer a completion of a property binding.
|
40413 | entries.push({
|
40414 | kind: unsafeCastDisplayInfoKindToScriptElementKind(DisplayInfoKind.PROPERTY),
|
40415 | name: `[${completion.propertyName}]`,
|
40416 | sortText: completion.propertyName,
|
40417 | replacementSpan,
|
40418 | });
|
40419 | // If the directive supports banana-in-a-box for this input, offer that as well.
|
40420 | if (completion.twoWayBindingSupported) {
|
40421 | entries.push({
|
40422 | kind: unsafeCastDisplayInfoKindToScriptElementKind(DisplayInfoKind.PROPERTY),
|
40423 | name: `[(${completion.propertyName})]`,
|
40424 | // This completion should sort after the property binding.
|
40425 | sortText: completion.propertyName + '_1',
|
40426 | replacementSpan,
|
40427 | });
|
40428 | }
|
40429 | // Offer a completion of the input binding as an attribute.
|
40430 | entries.push({
|
40431 | kind: unsafeCastDisplayInfoKindToScriptElementKind(DisplayInfoKind.ATTRIBUTE),
|
40432 | name: completion.propertyName,
|
40433 | // This completion should sort after both property binding options (one-way and two-way).
|
40434 | sortText: completion.propertyName + '_2',
|
40435 | replacementSpan,
|
40436 | });
|
40437 | }
|
40438 | else {
|
40439 | entries.push({
|
40440 | kind: unsafeCastDisplayInfoKindToScriptElementKind(DisplayInfoKind.PROPERTY),
|
40441 | name: completion.propertyName,
|
40442 | sortText: completion.propertyName,
|
40443 | replacementSpan,
|
40444 | });
|
40445 | }
|
40446 | break;
|
40447 | }
|
40448 | case AttributeCompletionKind.DirectiveOutput: {
|
40449 | if (isAttributeContext) {
|
40450 | entries.push({
|
40451 | kind: unsafeCastDisplayInfoKindToScriptElementKind(DisplayInfoKind.EVENT),
|
40452 | name: `(${completion.eventName})`,
|
40453 | sortText: completion.eventName,
|
40454 | replacementSpan,
|
40455 | });
|
40456 | }
|
40457 | else {
|
40458 | entries.push({
|
40459 | kind: unsafeCastDisplayInfoKindToScriptElementKind(DisplayInfoKind.EVENT),
|
40460 | name: completion.eventName,
|
40461 | sortText: completion.eventName,
|
40462 | replacementSpan,
|
40463 | });
|
40464 | }
|
40465 | break;
|
40466 | }
|
40467 | case AttributeCompletionKind.DomAttribute: {
|
40468 | if (isAttributeContext) {
|
40469 | // Offer a completion of an attribute binding.
|
40470 | entries.push({
|
40471 | kind: unsafeCastDisplayInfoKindToScriptElementKind(DisplayInfoKind.ATTRIBUTE),
|
40472 | name: completion.attribute,
|
40473 | sortText: completion.attribute,
|
40474 | replacementSpan,
|
40475 | });
|
40476 | if (completion.isAlsoProperty) {
|
40477 | // Offer a completion of a property binding to the DOM property.
|
40478 | entries.push({
|
40479 | kind: unsafeCastDisplayInfoKindToScriptElementKind(DisplayInfoKind.PROPERTY),
|
40480 | name: `[${completion.attribute}]`,
|
40481 | // In the case of DOM attributes, the property binding should sort after the attribute
|
40482 | // binding.
|
40483 | sortText: completion.attribute + '_1',
|
40484 | replacementSpan,
|
40485 | });
|
40486 | }
|
40487 | }
|
40488 | else if (completion.isAlsoProperty) {
|
40489 | entries.push({
|
40490 | kind: unsafeCastDisplayInfoKindToScriptElementKind(DisplayInfoKind.PROPERTY),
|
40491 | name: completion.attribute,
|
40492 | sortText: completion.attribute,
|
40493 | replacementSpan,
|
40494 | });
|
40495 | }
|
40496 | break;
|
40497 | }
|
40498 | case AttributeCompletionKind.DomProperty: {
|
40499 | if (!isAttributeContext) {
|
40500 | entries.push({
|
40501 | kind: unsafeCastDisplayInfoKindToScriptElementKind(DisplayInfoKind.PROPERTY),
|
40502 | name: completion.property,
|
40503 | sortText: completion.property,
|
40504 | replacementSpan,
|
40505 | });
|
40506 | }
|
40507 | }
|
40508 | }
|
40509 | }
|
40510 | function getAttributeCompletionSymbol(completion, checker) {
|
40511 | var _a;
|
40512 | switch (completion.kind) {
|
40513 | case AttributeCompletionKind.DomAttribute:
|
40514 | case AttributeCompletionKind.DomProperty:
|
40515 | return null;
|
40516 | case AttributeCompletionKind.DirectiveAttribute:
|
40517 | case AttributeCompletionKind.StructuralDirectiveAttribute:
|
40518 | return completion.directive.tsSymbol;
|
40519 | case AttributeCompletionKind.DirectiveInput:
|
40520 | case AttributeCompletionKind.DirectiveOutput:
|
40521 | return (_a = checker.getDeclaredTypeOfSymbol(completion.directive.tsSymbol)
|
40522 | .getProperty(completion.classPropertyName)) !== null && _a !== void 0 ? _a : null;
|
40523 | }
|
40524 | }
|
40525 | /**
|
40526 | * Iterates over `CssSelector` attributes, which are internally represented in a zipped array style
|
40527 | * which is not conducive to straightforward iteration.
|
40528 | */
|
40529 | function* selectorAttributes(selector) {
|
40530 | for (let i = 0; i < selector.attrs.length; i += 2) {
|
40531 | yield [selector.attrs[0], selector.attrs[1]];
|
40532 | }
|
40533 | }
|
40534 | function getStructuralAttributes(meta) {
|
40535 | if (meta.selector === null) {
|
40536 | return [];
|
40537 | }
|
40538 | const structuralAttributes = [];
|
40539 | const selectors = CssSelector.parse(meta.selector);
|
40540 | for (const selector of selectors) {
|
40541 | if (selector.element !== null && selector.element !== 'ng-template') {
|
40542 | // This particular selector does not apply under structural directive syntax.
|
40543 | continue;
|
40544 | }
|
40545 | // Every attribute of this selector must be name-only - no required values.
|
40546 | const attributeSelectors = Array.from(selectorAttributes(selector));
|
40547 | if (!attributeSelectors.every(([_, attrValue]) => attrValue === '')) {
|
40548 | continue;
|
40549 | }
|
40550 | // Get every named selector.
|
40551 | const attributes = attributeSelectors.map(([attrName, _]) => attrName);
|
40552 | // Find the shortest attribute. This is the structural directive "base", and all potential
|
40553 | // input bindings must begin with the base. E.g. in `*ngFor="let a of b"`, `ngFor` is the
|
40554 | // base attribute, and the `of` binding key corresponds to an input of `ngForOf`.
|
40555 | const baseAttr = attributes.reduce((prev, curr) => prev === null || curr.length < prev.length ? curr : prev, null);
|
40556 | if (baseAttr === null) {
|
40557 | // No attributes in this selector?
|
40558 | continue;
|
40559 | }
|
40560 | // Validate that the attributes are compatible with use as a structural directive.
|
40561 | const isValid = (attr) => {
|
40562 | // The base attribute is valid by default.
|
40563 | if (attr === baseAttr) {
|
40564 | return true;
|
40565 | }
|
40566 | // Non-base attributes must all be prefixed with the base attribute.
|
40567 | if (!attr.startsWith(baseAttr)) {
|
40568 | return false;
|
40569 | }
|
40570 | // Non-base attributes must also correspond to directive inputs.
|
40571 | if (!meta.inputs.hasBindingPropertyName(attr)) {
|
40572 | return false;
|
40573 | }
|
40574 | // This attribute is compatible.
|
40575 | return true;
|
40576 | };
|
40577 | if (!attributes.every(isValid)) {
|
40578 | continue;
|
40579 | }
|
40580 | // This attribute is valid as a structural attribute for this directive.
|
40581 | structuralAttributes.push(baseAttr);
|
40582 | }
|
40583 | return structuralAttributes;
|
40584 | }
|
40585 |
|
40586 | /**
|
40587 | * @license
|
40588 | * Copyright Google LLC All Rights Reserved.
|
40589 | *
|
40590 | * Use of this source code is governed by an MIT-style license that can be
|
40591 | * found in the LICENSE file at https://angular.io/license
|
40592 | */
|
40593 | /**
|
40594 | * Differentiates the various kinds of `TargetNode`s.
|
40595 | */
|
40596 | var TargetNodeKind;
|
40597 | (function (TargetNodeKind) {
|
40598 | TargetNodeKind[TargetNodeKind["RawExpression"] = 0] = "RawExpression";
|
40599 | TargetNodeKind[TargetNodeKind["RawTemplateNode"] = 1] = "RawTemplateNode";
|
40600 | TargetNodeKind[TargetNodeKind["ElementInTagContext"] = 2] = "ElementInTagContext";
|
40601 | TargetNodeKind[TargetNodeKind["ElementInBodyContext"] = 3] = "ElementInBodyContext";
|
40602 | TargetNodeKind[TargetNodeKind["AttributeInKeyContext"] = 4] = "AttributeInKeyContext";
|
40603 | TargetNodeKind[TargetNodeKind["AttributeInValueContext"] = 5] = "AttributeInValueContext";
|
40604 | TargetNodeKind[TargetNodeKind["TwoWayBindingContext"] = 6] = "TwoWayBindingContext";
|
40605 | })(TargetNodeKind || (TargetNodeKind = {}));
|
40606 | /**
|
40607 | * This special marker is added to the path when the cursor is within the sourceSpan but not the key
|
40608 | * or value span of a node with key/value spans.
|
40609 | */
|
40610 | const OUTSIDE_K_V_MARKER = new AST(new ParseSpan(-1, -1), new AbsoluteSourceSpan(-1, -1));
|
40611 | /**
|
40612 | * Return the template AST node or expression AST node that most accurately
|
40613 | * represents the node at the specified cursor `position`.
|
40614 | *
|
40615 | * @param template AST tree of the template
|
40616 | * @param position target cursor position
|
40617 | */
|
40618 | function getTargetAtPosition(template, position) {
|
40619 | const path = TemplateTargetVisitor.visitTemplate(template, position);
|
40620 | if (path.length === 0) {
|
40621 | return null;
|
40622 | }
|
40623 | const candidate = path[path.length - 1];
|
40624 | // Walk up the result nodes to find the nearest `t.Template` which contains the targeted node.
|
40625 | let context = null;
|
40626 | for (let i = path.length - 2; i >= 0; i--) {
|
40627 | const node = path[i];
|
40628 | if (node instanceof Template) {
|
40629 | context = node;
|
40630 | break;
|
40631 | }
|
40632 | }
|
40633 | // Given the candidate node, determine the full targeted context.
|
40634 | let nodeInContext;
|
40635 | if (candidate instanceof AST) {
|
40636 | nodeInContext = {
|
40637 | kind: TargetNodeKind.RawExpression,
|
40638 | node: candidate,
|
40639 | };
|
40640 | }
|
40641 | else if (candidate instanceof Element) {
|
40642 | // Elements have two contexts: the tag context (position is within the element tag) or the
|
40643 | // element body context (position is outside of the tag name, but still in the element).
|
40644 | // Calculate the end of the element tag name. Any position beyond this is in the element body.
|
40645 | const tagEndPos = candidate.sourceSpan.start.offset + 1 /* '<' element open */ + candidate.name.length;
|
40646 | if (position > tagEndPos) {
|
40647 | // Position is within the element body
|
40648 | nodeInContext = {
|
40649 | kind: TargetNodeKind.ElementInBodyContext,
|
40650 | node: candidate,
|
40651 | };
|
40652 | }
|
40653 | else {
|
40654 | nodeInContext = {
|
40655 | kind: TargetNodeKind.ElementInTagContext,
|
40656 | node: candidate,
|
40657 | };
|
40658 | }
|
40659 | }
|
40660 | else if ((candidate instanceof BoundAttribute || candidate instanceof BoundEvent ||
|
40661 | candidate instanceof TextAttribute) &&
|
40662 | candidate.keySpan !== undefined) {
|
40663 | const previousCandidate = path[path.length - 2];
|
40664 | if (candidate instanceof BoundEvent && previousCandidate instanceof BoundAttribute &&
|
40665 | candidate.name === previousCandidate.name + 'Change') {
|
40666 | const boundAttribute = previousCandidate;
|
40667 | const boundEvent = candidate;
|
40668 | nodeInContext = {
|
40669 | kind: TargetNodeKind.TwoWayBindingContext,
|
40670 | nodes: [boundAttribute, boundEvent],
|
40671 | };
|
40672 | }
|
40673 | else if (isWithin(position, candidate.keySpan)) {
|
40674 | nodeInContext = {
|
40675 | kind: TargetNodeKind.AttributeInKeyContext,
|
40676 | node: candidate,
|
40677 | };
|
40678 | }
|
40679 | else {
|
40680 | nodeInContext = {
|
40681 | kind: TargetNodeKind.AttributeInValueContext,
|
40682 | node: candidate,
|
40683 | };
|
40684 | }
|
40685 | }
|
40686 | else {
|
40687 | nodeInContext = {
|
40688 | kind: TargetNodeKind.RawTemplateNode,
|
40689 | node: candidate,
|
40690 | };
|
40691 | }
|
40692 | let parent = null;
|
40693 | if (nodeInContext.kind === TargetNodeKind.TwoWayBindingContext && path.length >= 3) {
|
40694 | parent = path[path.length - 3];
|
40695 | }
|
40696 | else if (path.length >= 2) {
|
40697 | parent = path[path.length - 2];
|
40698 | }
|
40699 | return { position, context: nodeInContext, template: context, parent };
|
40700 | }
|
40701 | /**
|
40702 | * Visitor which, given a position and a template, identifies the node within the template at that
|
40703 | * position, as well as records the path of increasingly nested nodes that were traversed to reach
|
40704 | * that position.
|
40705 | */
|
40706 | class TemplateTargetVisitor {
|
40707 | // Position must be absolute in the source file.
|
40708 | constructor(position) {
|
40709 | this.position = position;
|
40710 | // We need to keep a path instead of the last node because we might need more
|
40711 | // context for the last node, for example what is the parent node?
|
40712 | this.path = [];
|
40713 | }
|
40714 | static visitTemplate(template, position) {
|
40715 | const visitor = new TemplateTargetVisitor(position);
|
40716 | visitor.visitAll(template);
|
40717 | const { path } = visitor;
|
40718 | const strictPath = path.filter(v => v !== OUTSIDE_K_V_MARKER);
|
40719 | const candidate = strictPath[strictPath.length - 1];
|
40720 | const matchedASourceSpanButNotAKvSpan = path.some(v => v === OUTSIDE_K_V_MARKER);
|
40721 | if (matchedASourceSpanButNotAKvSpan &&
|
40722 | (candidate instanceof Template || candidate instanceof Element)) {
|
40723 | // Template nodes with key and value spans are always defined on a `t.Template` or
|
40724 | // `t.Element`. If we found a node on a template with a `sourceSpan` that includes the cursor,
|
40725 | // it is possible that we are outside the k/v spans (i.e. in-between them). If this is the
|
40726 | // case and we do not have any other candidate matches on the `t.Element` or `t.Template`, we
|
40727 | // want to return no results. Otherwise, the `t.Element`/`t.Template` result is incorrect for
|
40728 | // that cursor position.
|
40729 | return [];
|
40730 | }
|
40731 | return strictPath;
|
40732 | }
|
40733 | visit(node) {
|
40734 | const { start, end } = getSpanIncludingEndTag(node);
|
40735 | if (!isWithin(this.position, { start, end })) {
|
40736 | return;
|
40737 | }
|
40738 | const last = this.path[this.path.length - 1];
|
40739 | const withinKeySpanOfLastNode = last && isTemplateNodeWithKeyAndValue(last) && isWithin(this.position, last.keySpan);
|
40740 | const withinKeySpanOfCurrentNode = isTemplateNodeWithKeyAndValue(node) && isWithin(this.position, node.keySpan);
|
40741 | if (withinKeySpanOfLastNode && !withinKeySpanOfCurrentNode) {
|
40742 | // We've already identified that we are within a `keySpan` of a node.
|
40743 | // Unless we are _also_ in the `keySpan` of the current node (happens with two way bindings),
|
40744 | // we should stop processing nodes at this point to prevent matching any other nodes. This can
|
40745 | // happen when the end span of a different node touches the start of the keySpan for the
|
40746 | // candidate node. Because our `isWithin` logic is inclusive on both ends, we can match both
|
40747 | // nodes.
|
40748 | return;
|
40749 | }
|
40750 | if (isTemplateNodeWithKeyAndValue(node) && !isWithinKeyValue(this.position, node)) {
|
40751 | // If cursor is within source span but not within key span or value span,
|
40752 | // do not return the node.
|
40753 | this.path.push(OUTSIDE_K_V_MARKER);
|
40754 | }
|
40755 | else {
|
40756 | this.path.push(node);
|
40757 | node.visit(this);
|
40758 | }
|
40759 | }
|
40760 | visitElement(element) {
|
40761 | this.visitElementOrTemplate(element);
|
40762 | }
|
40763 | visitTemplate(template) {
|
40764 | this.visitElementOrTemplate(template);
|
40765 | }
|
40766 | visitElementOrTemplate(element) {
|
40767 | this.visitAll(element.attributes);
|
40768 | this.visitAll(element.inputs);
|
40769 | this.visitAll(element.outputs);
|
40770 | if (element instanceof Template) {
|
40771 | this.visitAll(element.templateAttrs);
|
40772 | }
|
40773 | this.visitAll(element.references);
|
40774 | if (element instanceof Template) {
|
40775 | this.visitAll(element.variables);
|
40776 | }
|
40777 | // If we get here and have not found a candidate node on the element itself, proceed with
|
40778 | // looking for a more specific node on the element children.
|
40779 | if (this.path[this.path.length - 1] !== element) {
|
40780 | return;
|
40781 | }
|
40782 | this.visitAll(element.children);
|
40783 | }
|
40784 | visitContent(content) {
|
40785 | visitAll(this, content.attributes);
|
40786 | }
|
40787 | visitVariable(variable) {
|
40788 | // Variable has no template nodes or expression nodes.
|
40789 | }
|
40790 | visitReference(reference) {
|
40791 | // Reference has no template nodes or expression nodes.
|
40792 | }
|
40793 | visitTextAttribute(attribute) {
|
40794 | // Text attribute has no template nodes or expression nodes.
|
40795 | }
|
40796 | visitBoundAttribute(attribute) {
|
40797 | const visitor = new ExpressionVisitor$1(this.position);
|
40798 | visitor.visit(attribute.value, this.path);
|
40799 | }
|
40800 | visitBoundEvent(event) {
|
40801 | // An event binding with no value (e.g. `(event|)`) parses to a `BoundEvent` with a
|
40802 | // `LiteralPrimitive` handler with value `'ERROR'`, as opposed to a property binding with no
|
40803 | // value which has an `EmptyExpr` as its value. This is a synthetic node created by the binding
|
40804 | // parser, and is not suitable to use for Language Service analysis. Skip it.
|
40805 | //
|
40806 | // TODO(alxhub): modify the parser to generate an `EmptyExpr` instead.
|
40807 | let handler = event.handler;
|
40808 | if (handler instanceof ASTWithSource) {
|
40809 | handler = handler.ast;
|
40810 | }
|
40811 | if (handler instanceof LiteralPrimitive && handler.value === 'ERROR') {
|
40812 | return;
|
40813 | }
|
40814 | const visitor = new ExpressionVisitor$1(this.position);
|
40815 | visitor.visit(event.handler, this.path);
|
40816 | }
|
40817 | visitText(text) {
|
40818 | // Text has no template nodes or expression nodes.
|
40819 | }
|
40820 | visitBoundText(text) {
|
40821 | const visitor = new ExpressionVisitor$1(this.position);
|
40822 | visitor.visit(text.value, this.path);
|
40823 | }
|
40824 | visitIcu(icu) {
|
40825 | for (const boundText of Object.values(icu.vars)) {
|
40826 | this.visit(boundText);
|
40827 | }
|
40828 | for (const boundTextOrText of Object.values(icu.placeholders)) {
|
40829 | this.visit(boundTextOrText);
|
40830 | }
|
40831 | }
|
40832 | visitAll(nodes) {
|
40833 | for (const node of nodes) {
|
40834 | this.visit(node);
|
40835 | }
|
40836 | }
|
40837 | }
|
40838 | class ExpressionVisitor$1 extends RecursiveAstVisitor {
|
40839 | // Position must be absolute in the source file.
|
40840 | constructor(position) {
|
40841 | super();
|
40842 | this.position = position;
|
40843 | }
|
40844 | visit(node, path) {
|
40845 | if (node instanceof ASTWithSource) {
|
40846 | // In order to reduce noise, do not include `ASTWithSource` in the path.
|
40847 | // For the purpose of source spans, there is no difference between
|
40848 | // `ASTWithSource` and and underlying node that it wraps.
|
40849 | node = node.ast;
|
40850 | }
|
40851 | // The third condition is to account for the implicit receiver, which should
|
40852 | // not be visited.
|
40853 | if (isWithin(this.position, node.sourceSpan) && !(node instanceof ImplicitReceiver)) {
|
40854 | path.push(node);
|
40855 | node.visit(this, path);
|
40856 | }
|
40857 | }
|
40858 | }
|
40859 | function getSpanIncludingEndTag(ast) {
|
40860 | const result = {
|
40861 | start: ast.sourceSpan.start.offset,
|
40862 | end: ast.sourceSpan.end.offset,
|
40863 | };
|
40864 | // For Element and Template node, sourceSpan.end is the end of the opening
|
40865 | // tag. For the purpose of language service, we need to actually recognize
|
40866 | // the end of the closing tag. Otherwise, for situation like
|
40867 | // <my-component></my-comp¦onent> where the cursor is in the closing tag
|
40868 | // we will not be able to return any information.
|
40869 | if ((ast instanceof Element || ast instanceof Template) && ast.endSourceSpan) {
|
40870 | result.end = ast.endSourceSpan.end.offset;
|
40871 | }
|
40872 | return result;
|
40873 | }
|
40874 |
|
40875 | /**
|
40876 | * @license
|
40877 | * Copyright Google LLC All Rights Reserved.
|
40878 | *
|
40879 | * Use of this source code is governed by an MIT-style license that can be
|
40880 | * found in the LICENSE file at https://angular.io/license
|
40881 | */
|
40882 | var CompletionNodeContext;
|
40883 | (function (CompletionNodeContext) {
|
40884 | CompletionNodeContext[CompletionNodeContext["None"] = 0] = "None";
|
40885 | CompletionNodeContext[CompletionNodeContext["ElementTag"] = 1] = "ElementTag";
|
40886 | CompletionNodeContext[CompletionNodeContext["ElementAttributeKey"] = 2] = "ElementAttributeKey";
|
40887 | CompletionNodeContext[CompletionNodeContext["ElementAttributeValue"] = 3] = "ElementAttributeValue";
|
40888 | CompletionNodeContext[CompletionNodeContext["EventValue"] = 4] = "EventValue";
|
40889 | CompletionNodeContext[CompletionNodeContext["TwoWayBinding"] = 5] = "TwoWayBinding";
|
40890 | })(CompletionNodeContext || (CompletionNodeContext = {}));
|
40891 | /**
|
40892 | * Performs autocompletion operations on a given node in the template.
|
40893 | *
|
40894 | * This class acts as a closure around all of the context required to perform the 3 autocompletion
|
40895 | * operations (completions, get details, and get symbol) at a specific node.
|
40896 | *
|
40897 | * The generic `N` type for the template node is narrowed internally for certain operations, as the
|
40898 | * compiler operations required to implement completion may be different for different node types.
|
40899 | *
|
40900 | * @param N type of the template node in question, narrowed accordingly.
|
40901 | */
|
40902 | class CompletionBuilder {
|
40903 | constructor(tsLS, compiler, component, node, targetDetails) {
|
40904 | this.tsLS = tsLS;
|
40905 | this.compiler = compiler;
|
40906 | this.component = component;
|
40907 | this.node = node;
|
40908 | this.targetDetails = targetDetails;
|
40909 | this.typeChecker = this.compiler.getNextProgram().getTypeChecker();
|
40910 | this.templateTypeChecker = this.compiler.getTemplateTypeChecker();
|
40911 | this.nodeParent = this.targetDetails.parent;
|
40912 | this.nodeContext = nodeContextFromTarget(this.targetDetails.context);
|
40913 | this.template = this.targetDetails.template;
|
40914 | this.position = this.targetDetails.position;
|
40915 | }
|
40916 | /**
|
40917 | * Analogue for `ts.LanguageService.getCompletionsAtPosition`.
|
40918 | */
|
40919 | getCompletionsAtPosition(options) {
|
40920 | if (this.isPropertyExpressionCompletion()) {
|
40921 | return this.getPropertyExpressionCompletion(options);
|
40922 | }
|
40923 | else if (this.isElementTagCompletion()) {
|
40924 | return this.getElementTagCompletion();
|
40925 | }
|
40926 | else if (this.isElementAttributeCompletion()) {
|
40927 | return this.getElementAttributeCompletions();
|
40928 | }
|
40929 | else if (this.isPipeCompletion()) {
|
40930 | return this.getPipeCompletions();
|
40931 | }
|
40932 | else {
|
40933 | return undefined;
|
40934 | }
|
40935 | }
|
40936 | /**
|
40937 | * Analogue for `ts.LanguageService.getCompletionEntryDetails`.
|
40938 | */
|
40939 | getCompletionEntryDetails(entryName, formatOptions, preferences) {
|
40940 | if (this.isPropertyExpressionCompletion()) {
|
40941 | return this.getPropertyExpressionCompletionDetails(entryName, formatOptions, preferences);
|
40942 | }
|
40943 | else if (this.isElementTagCompletion()) {
|
40944 | return this.getElementTagCompletionDetails(entryName);
|
40945 | }
|
40946 | else if (this.isElementAttributeCompletion()) {
|
40947 | return this.getElementAttributeCompletionDetails(entryName);
|
40948 | }
|
40949 | }
|
40950 | /**
|
40951 | * Analogue for `ts.LanguageService.getCompletionEntrySymbol`.
|
40952 | */
|
40953 | getCompletionEntrySymbol(name) {
|
40954 | if (this.isPropertyExpressionCompletion()) {
|
40955 | return this.getPropertyExpressionCompletionSymbol(name);
|
40956 | }
|
40957 | else if (this.isElementTagCompletion()) {
|
40958 | return this.getElementTagCompletionSymbol(name);
|
40959 | }
|
40960 | else if (this.isElementAttributeCompletion()) {
|
40961 | return this.getElementAttributeCompletionSymbol(name);
|
40962 | }
|
40963 | else {
|
40964 | return undefined;
|
40965 | }
|
40966 | }
|
40967 | /**
|
40968 | * Determine if the current node is the completion of a property expression, and narrow the type
|
40969 | * of `this.node` if so.
|
40970 | *
|
40971 | * This narrowing gives access to additional methods related to completion of property
|
40972 | * expressions.
|
40973 | */
|
40974 | isPropertyExpressionCompletion() {
|
40975 | return this.node instanceof PropertyRead || this.node instanceof MethodCall ||
|
40976 | this.node instanceof SafePropertyRead || this.node instanceof SafeMethodCall ||
|
40977 | this.node instanceof PropertyWrite || this.node instanceof EmptyExpr ||
|
40978 | // BoundEvent nodes only count as property completions if in an EventValue context.
|
40979 | (this.node instanceof BoundEvent && this.nodeContext === CompletionNodeContext.EventValue);
|
40980 | }
|
40981 | /**
|
40982 | * Get completions for property expressions.
|
40983 | */
|
40984 | getPropertyExpressionCompletion(options) {
|
40985 | if (this.node instanceof EmptyExpr || this.node instanceof BoundEvent ||
|
40986 | this.node.receiver instanceof ImplicitReceiver) {
|
40987 | return this.getGlobalPropertyExpressionCompletion(options);
|
40988 | }
|
40989 | else {
|
40990 | const location = this.compiler.getTemplateTypeChecker().getExpressionCompletionLocation(this.node, this.component);
|
40991 | if (location === null) {
|
40992 | return undefined;
|
40993 | }
|
40994 | const tsResults = this.tsLS.getCompletionsAtPosition(location.shimPath, location.positionInShimFile, options);
|
40995 | if (tsResults === undefined) {
|
40996 | return undefined;
|
40997 | }
|
40998 | const replacementSpan = makeReplacementSpanFromAst(this.node);
|
40999 | let ngResults = [];
|
41000 | for (const result of tsResults.entries) {
|
41001 | ngResults.push(Object.assign(Object.assign({}, result), { replacementSpan }));
|
41002 | }
|
41003 | return Object.assign(Object.assign({}, tsResults), { entries: ngResults });
|
41004 | }
|
41005 | }
|
41006 | /**
|
41007 | * Get the details of a specific completion for a property expression.
|
41008 | */
|
41009 | getPropertyExpressionCompletionDetails(entryName, formatOptions, preferences) {
|
41010 | let details = undefined;
|
41011 | if (this.node instanceof EmptyExpr || this.node instanceof BoundEvent ||
|
41012 | this.node.receiver instanceof ImplicitReceiver) {
|
41013 | details =
|
41014 | this.getGlobalPropertyExpressionCompletionDetails(entryName, formatOptions, preferences);
|
41015 | }
|
41016 | else {
|
41017 | const location = this.compiler.getTemplateTypeChecker().getExpressionCompletionLocation(this.node, this.component);
|
41018 | if (location === null) {
|
41019 | return undefined;
|
41020 | }
|
41021 | details = this.tsLS.getCompletionEntryDetails(location.shimPath, location.positionInShimFile, entryName, formatOptions,
|
41022 | /* source */ undefined, preferences);
|
41023 | }
|
41024 | if (details !== undefined) {
|
41025 | details.displayParts = filterAliasImports(details.displayParts);
|
41026 | }
|
41027 | return details;
|
41028 | }
|
41029 | /**
|
41030 | * Get the `ts.Symbol` for a specific completion for a property expression.
|
41031 | */
|
41032 | getPropertyExpressionCompletionSymbol(name) {
|
41033 | if (this.node instanceof EmptyExpr || this.node instanceof LiteralPrimitive ||
|
41034 | this.node instanceof BoundEvent || this.node.receiver instanceof ImplicitReceiver) {
|
41035 | return this.getGlobalPropertyExpressionCompletionSymbol(name);
|
41036 | }
|
41037 | else {
|
41038 | const location = this.compiler.getTemplateTypeChecker().getExpressionCompletionLocation(this.node, this.component);
|
41039 | if (location === null) {
|
41040 | return undefined;
|
41041 | }
|
41042 | return this.tsLS.getCompletionEntrySymbol(location.shimPath, location.positionInShimFile, name, /* source */ undefined);
|
41043 | }
|
41044 | }
|
41045 | /**
|
41046 | * Get completions for a property expression in a global context (e.g. `{{y|}}`).
|
41047 | */
|
41048 | getGlobalPropertyExpressionCompletion(options) {
|
41049 | const completions = this.templateTypeChecker.getGlobalCompletions(this.template, this.component);
|
41050 | if (completions === null) {
|
41051 | return undefined;
|
41052 | }
|
41053 | const { componentContext, templateContext } = completions;
|
41054 | const replacementSpan = makeReplacementSpanFromAst(this.node);
|
41055 | // Merge TS completion results with results from the template scope.
|
41056 | let entries = [];
|
41057 | const tsLsCompletions = this.tsLS.getCompletionsAtPosition(componentContext.shimPath, componentContext.positionInShimFile, options);
|
41058 | if (tsLsCompletions !== undefined) {
|
41059 | for (const tsCompletion of tsLsCompletions.entries) {
|
41060 | // Skip completions that are shadowed by a template entity definition.
|
41061 | if (templateContext.has(tsCompletion.name)) {
|
41062 | continue;
|
41063 | }
|
41064 | entries.push(Object.assign(Object.assign({}, tsCompletion), {
|
41065 | // Substitute the TS completion's `replacementSpan` (which uses offsets within the TCB)
|
41066 | // with the `replacementSpan` within the template source.
|
41067 | replacementSpan }));
|
41068 | }
|
41069 | }
|
41070 | for (const [name, entity] of templateContext) {
|
41071 | entries.push({
|
41072 | name,
|
41073 | sortText: name,
|
41074 | replacementSpan,
|
41075 | kindModifiers: ts$1.ScriptElementKindModifier.none,
|
41076 | kind: unsafeCastDisplayInfoKindToScriptElementKind(entity.kind === CompletionKind.Reference ? DisplayInfoKind.REFERENCE :
|
41077 | DisplayInfoKind.VARIABLE),
|
41078 | });
|
41079 | }
|
41080 | return {
|
41081 | entries,
|
41082 | // Although this completion is "global" in the sense of an Angular expression (there is no
|
41083 | // explicit receiver), it is not "global" in a TypeScript sense since Angular expressions have
|
41084 | // the component as an implicit receiver.
|
41085 | isGlobalCompletion: false,
|
41086 | isMemberCompletion: true,
|
41087 | isNewIdentifierLocation: false,
|
41088 | };
|
41089 | }
|
41090 | /**
|
41091 | * Get the details of a specific completion for a property expression in a global context (e.g.
|
41092 | * `{{y|}}`).
|
41093 | */
|
41094 | getGlobalPropertyExpressionCompletionDetails(entryName, formatOptions, preferences) {
|
41095 | const completions = this.templateTypeChecker.getGlobalCompletions(this.template, this.component);
|
41096 | if (completions === null) {
|
41097 | return undefined;
|
41098 | }
|
41099 | const { componentContext, templateContext } = completions;
|
41100 | if (templateContext.has(entryName)) {
|
41101 | const entry = templateContext.get(entryName);
|
41102 | // Entries that reference a symbol in the template context refer either to local references or
|
41103 | // variables.
|
41104 | const symbol = this.templateTypeChecker.getSymbolOfNode(entry.node, this.component);
|
41105 | if (symbol === null) {
|
41106 | return undefined;
|
41107 | }
|
41108 | const { kind, displayParts, documentation } = getSymbolDisplayInfo(this.tsLS, this.typeChecker, symbol);
|
41109 | return {
|
41110 | kind: unsafeCastDisplayInfoKindToScriptElementKind(kind),
|
41111 | name: entryName,
|
41112 | kindModifiers: ts$1.ScriptElementKindModifier.none,
|
41113 | displayParts,
|
41114 | documentation,
|
41115 | };
|
41116 | }
|
41117 | else {
|
41118 | return this.tsLS.getCompletionEntryDetails(componentContext.shimPath, componentContext.positionInShimFile, entryName, formatOptions,
|
41119 | /* source */ undefined, preferences);
|
41120 | }
|
41121 | }
|
41122 | /**
|
41123 | * Get the `ts.Symbol` of a specific completion for a property expression in a global context
|
41124 | * (e.g.
|
41125 | * `{{y|}}`).
|
41126 | */
|
41127 | getGlobalPropertyExpressionCompletionSymbol(entryName) {
|
41128 | const completions = this.templateTypeChecker.getGlobalCompletions(this.template, this.component);
|
41129 | if (completions === null) {
|
41130 | return undefined;
|
41131 | }
|
41132 | const { componentContext, templateContext } = completions;
|
41133 | if (templateContext.has(entryName)) {
|
41134 | const node = templateContext.get(entryName).node;
|
41135 | const symbol = this.templateTypeChecker.getSymbolOfNode(node, this.component);
|
41136 | if (symbol === null || symbol.tsSymbol === null) {
|
41137 | return undefined;
|
41138 | }
|
41139 | return symbol.tsSymbol;
|
41140 | }
|
41141 | else {
|
41142 | return this.tsLS.getCompletionEntrySymbol(componentContext.shimPath, componentContext.positionInShimFile, entryName,
|
41143 | /* source */ undefined);
|
41144 | }
|
41145 | }
|
41146 | isElementTagCompletion() {
|
41147 | if (this.node instanceof Text) {
|
41148 | const positionInTextNode = this.position - this.node.sourceSpan.start.offset;
|
41149 | // We only provide element completions in a text node when there is an open tag immediately to
|
41150 | // the left of the position.
|
41151 | return this.node.value.substring(0, positionInTextNode).endsWith('<');
|
41152 | }
|
41153 | else if (this.node instanceof Element) {
|
41154 | return this.nodeContext === CompletionNodeContext.ElementTag;
|
41155 | }
|
41156 | return false;
|
41157 | }
|
41158 | getElementTagCompletion() {
|
41159 | const templateTypeChecker = this.compiler.getTemplateTypeChecker();
|
41160 | let start;
|
41161 | let length;
|
41162 | if (this.node instanceof Element) {
|
41163 | // The replacementSpan is the tag name.
|
41164 | start = this.node.sourceSpan.start.offset + 1; // account for leading '<'
|
41165 | length = this.node.name.length;
|
41166 | }
|
41167 | else {
|
41168 | const positionInTextNode = this.position - this.node.sourceSpan.start.offset;
|
41169 | const textToLeftOfPosition = this.node.value.substring(0, positionInTextNode);
|
41170 | start = this.node.sourceSpan.start.offset + textToLeftOfPosition.lastIndexOf('<') + 1;
|
41171 | // We only autocomplete immediately after the < so we don't replace any existing text
|
41172 | length = 0;
|
41173 | }
|
41174 | const replacementSpan = { start, length };
|
41175 | const entries = Array.from(templateTypeChecker.getPotentialElementTags(this.component))
|
41176 | .map(([tag, directive]) => ({
|
41177 | kind: tagCompletionKind(directive),
|
41178 | name: tag,
|
41179 | sortText: tag,
|
41180 | replacementSpan,
|
41181 | }));
|
41182 | return {
|
41183 | entries,
|
41184 | isGlobalCompletion: false,
|
41185 | isMemberCompletion: false,
|
41186 | isNewIdentifierLocation: false,
|
41187 | };
|
41188 | }
|
41189 | getElementTagCompletionDetails(entryName) {
|
41190 | const templateTypeChecker = this.compiler.getTemplateTypeChecker();
|
41191 | const tagMap = templateTypeChecker.getPotentialElementTags(this.component);
|
41192 | if (!tagMap.has(entryName)) {
|
41193 | return undefined;
|
41194 | }
|
41195 | const directive = tagMap.get(entryName);
|
41196 | let displayParts;
|
41197 | let documentation = undefined;
|
41198 | if (directive === null) {
|
41199 | displayParts = [];
|
41200 | }
|
41201 | else {
|
41202 | const displayInfo = getDirectiveDisplayInfo(this.tsLS, directive);
|
41203 | displayParts = displayInfo.displayParts;
|
41204 | documentation = displayInfo.documentation;
|
41205 | }
|
41206 | return {
|
41207 | kind: tagCompletionKind(directive),
|
41208 | name: entryName,
|
41209 | kindModifiers: ts$1.ScriptElementKindModifier.none,
|
41210 | displayParts,
|
41211 | documentation,
|
41212 | };
|
41213 | }
|
41214 | getElementTagCompletionSymbol(entryName) {
|
41215 | const templateTypeChecker = this.compiler.getTemplateTypeChecker();
|
41216 | const tagMap = templateTypeChecker.getPotentialElementTags(this.component);
|
41217 | if (!tagMap.has(entryName)) {
|
41218 | return undefined;
|
41219 | }
|
41220 | const directive = tagMap.get(entryName);
|
41221 | return directive === null || directive === void 0 ? void 0 : directive.tsSymbol;
|
41222 | }
|
41223 | isElementAttributeCompletion() {
|
41224 | return (this.nodeContext === CompletionNodeContext.ElementAttributeKey ||
|
41225 | this.nodeContext === CompletionNodeContext.TwoWayBinding) &&
|
41226 | (this.node instanceof Element || this.node instanceof BoundAttribute ||
|
41227 | this.node instanceof TextAttribute || this.node instanceof BoundEvent);
|
41228 | }
|
41229 | getElementAttributeCompletions() {
|
41230 | let element;
|
41231 | if (this.node instanceof Element) {
|
41232 | element = this.node;
|
41233 | }
|
41234 | else if (this.nodeParent instanceof Element || this.nodeParent instanceof Template) {
|
41235 | element = this.nodeParent;
|
41236 | }
|
41237 | else {
|
41238 | // Nothing to do without an element to process.
|
41239 | return undefined;
|
41240 | }
|
41241 | let replacementSpan = undefined;
|
41242 | if ((this.node instanceof BoundAttribute || this.node instanceof BoundEvent ||
|
41243 | this.node instanceof TextAttribute) &&
|
41244 | this.node.keySpan !== undefined) {
|
41245 | replacementSpan = makeReplacementSpanFromParseSourceSpan(this.node.keySpan);
|
41246 | }
|
41247 | const attrTable = buildAttributeCompletionTable(this.component, element, this.compiler.getTemplateTypeChecker());
|
41248 | let entries = [];
|
41249 | for (const completion of attrTable.values()) {
|
41250 | // First, filter out completions that don't make sense for the current node. For example, if
|
41251 | // the user is completing on a property binding `[foo|]`, don't offer output event
|
41252 | // completions.
|
41253 | switch (completion.kind) {
|
41254 | case AttributeCompletionKind.DomAttribute:
|
41255 | case AttributeCompletionKind.DomProperty:
|
41256 | if (this.node instanceof BoundEvent) {
|
41257 | continue;
|
41258 | }
|
41259 | break;
|
41260 | case AttributeCompletionKind.DirectiveInput:
|
41261 | if (this.node instanceof BoundEvent) {
|
41262 | continue;
|
41263 | }
|
41264 | if (!completion.twoWayBindingSupported &&
|
41265 | this.nodeContext === CompletionNodeContext.TwoWayBinding) {
|
41266 | continue;
|
41267 | }
|
41268 | break;
|
41269 | case AttributeCompletionKind.DirectiveOutput:
|
41270 | if (this.node instanceof BoundAttribute) {
|
41271 | continue;
|
41272 | }
|
41273 | break;
|
41274 | case AttributeCompletionKind.DirectiveAttribute:
|
41275 | if (this.node instanceof BoundAttribute ||
|
41276 | this.node instanceof BoundEvent) {
|
41277 | continue;
|
41278 | }
|
41279 | break;
|
41280 | }
|
41281 | // Is the completion in an attribute context (instead of a property context)?
|
41282 | const isAttributeContext = (this.node instanceof Element || this.node instanceof TextAttribute);
|
41283 | // Is the completion for an element (not an <ng-template>)?
|
41284 | const isElementContext = this.node instanceof Element || this.nodeParent instanceof Element;
|
41285 | addAttributeCompletionEntries(entries, completion, isAttributeContext, isElementContext, replacementSpan);
|
41286 | }
|
41287 | return {
|
41288 | entries,
|
41289 | isGlobalCompletion: false,
|
41290 | isMemberCompletion: false,
|
41291 | isNewIdentifierLocation: true,
|
41292 | };
|
41293 | }
|
41294 | getElementAttributeCompletionDetails(entryName) {
|
41295 | // `entryName` here may be `foo` or `[foo]`, depending on which suggested completion the user
|
41296 | // chose. Strip off any binding syntax to get the real attribute name.
|
41297 | const { name, kind } = stripBindingSugar(entryName);
|
41298 | let element;
|
41299 | if (this.node instanceof Element || this.node instanceof Template) {
|
41300 | element = this.node;
|
41301 | }
|
41302 | else if (this.nodeParent instanceof Element || this.nodeParent instanceof Template) {
|
41303 | element = this.nodeParent;
|
41304 | }
|
41305 | else {
|
41306 | // Nothing to do without an element to process.
|
41307 | return undefined;
|
41308 | }
|
41309 | const attrTable = buildAttributeCompletionTable(this.component, element, this.compiler.getTemplateTypeChecker());
|
41310 | if (!attrTable.has(name)) {
|
41311 | return undefined;
|
41312 | }
|
41313 | const completion = attrTable.get(name);
|
41314 | let displayParts;
|
41315 | let documentation = undefined;
|
41316 | let info;
|
41317 | switch (completion.kind) {
|
41318 | case AttributeCompletionKind.DomAttribute:
|
41319 | case AttributeCompletionKind.DomProperty:
|
41320 | // TODO(alxhub): ideally we would show the same documentation as quick info here. However,
|
41321 | // since these bindings don't exist in the TCB, there is no straightforward way to retrieve
|
41322 | // a `ts.Symbol` for the field in the TS DOM definition.
|
41323 | displayParts = [];
|
41324 | break;
|
41325 | case AttributeCompletionKind.DirectiveAttribute:
|
41326 | info = getDirectiveDisplayInfo(this.tsLS, completion.directive);
|
41327 | displayParts = info.displayParts;
|
41328 | documentation = info.documentation;
|
41329 | break;
|
41330 | case AttributeCompletionKind.DirectiveInput:
|
41331 | case AttributeCompletionKind.DirectiveOutput:
|
41332 | const propertySymbol = getAttributeCompletionSymbol(completion, this.typeChecker);
|
41333 | if (propertySymbol === null) {
|
41334 | return undefined;
|
41335 | }
|
41336 | info = getTsSymbolDisplayInfo(this.tsLS, this.typeChecker, propertySymbol, completion.kind === AttributeCompletionKind.DirectiveInput ? DisplayInfoKind.PROPERTY :
|
41337 | DisplayInfoKind.EVENT, completion.directive.tsSymbol.name);
|
41338 | if (info === null) {
|
41339 | return undefined;
|
41340 | }
|
41341 | displayParts = info.displayParts;
|
41342 | documentation = info.documentation;
|
41343 | }
|
41344 | return {
|
41345 | name: entryName,
|
41346 | kind: unsafeCastDisplayInfoKindToScriptElementKind(kind),
|
41347 | kindModifiers: ts$1.ScriptElementKindModifier.none,
|
41348 | displayParts: [],
|
41349 | documentation,
|
41350 | };
|
41351 | }
|
41352 | getElementAttributeCompletionSymbol(attribute) {
|
41353 | var _a;
|
41354 | const { name } = stripBindingSugar(attribute);
|
41355 | let element;
|
41356 | if (this.node instanceof Element || this.node instanceof Template) {
|
41357 | element = this.node;
|
41358 | }
|
41359 | else if (this.nodeParent instanceof Element || this.nodeParent instanceof Template) {
|
41360 | element = this.nodeParent;
|
41361 | }
|
41362 | else {
|
41363 | // Nothing to do without an element to process.
|
41364 | return undefined;
|
41365 | }
|
41366 | const attrTable = buildAttributeCompletionTable(this.component, element, this.compiler.getTemplateTypeChecker());
|
41367 | if (!attrTable.has(name)) {
|
41368 | return undefined;
|
41369 | }
|
41370 | const completion = attrTable.get(name);
|
41371 | return (_a = getAttributeCompletionSymbol(completion, this.typeChecker)) !== null && _a !== void 0 ? _a : undefined;
|
41372 | }
|
41373 | isPipeCompletion() {
|
41374 | return this.node instanceof BindingPipe;
|
41375 | }
|
41376 | getPipeCompletions() {
|
41377 | const pipes = this.templateTypeChecker.getPipesInScope(this.component);
|
41378 | if (pipes === null) {
|
41379 | return undefined;
|
41380 | }
|
41381 | const replacementSpan = makeReplacementSpanFromAst(this.node);
|
41382 | const entries = pipes.map(pipe => ({
|
41383 | name: pipe.name,
|
41384 | sortText: pipe.name,
|
41385 | kind: unsafeCastDisplayInfoKindToScriptElementKind(DisplayInfoKind.PIPE),
|
41386 | replacementSpan,
|
41387 | }));
|
41388 | return {
|
41389 | entries,
|
41390 | isGlobalCompletion: false,
|
41391 | isMemberCompletion: false,
|
41392 | isNewIdentifierLocation: false,
|
41393 | };
|
41394 | }
|
41395 | }
|
41396 | function makeReplacementSpanFromParseSourceSpan(span) {
|
41397 | return {
|
41398 | start: span.start.offset,
|
41399 | length: span.end.offset - span.start.offset,
|
41400 | };
|
41401 | }
|
41402 | function makeReplacementSpanFromAst(node) {
|
41403 | if ((node instanceof EmptyExpr || node instanceof LiteralPrimitive ||
|
41404 | node instanceof BoundEvent)) {
|
41405 | // empty nodes do not replace any existing text
|
41406 | return undefined;
|
41407 | }
|
41408 | return {
|
41409 | start: node.nameSpan.start,
|
41410 | length: node.nameSpan.end - node.nameSpan.start,
|
41411 | };
|
41412 | }
|
41413 | function tagCompletionKind(directive) {
|
41414 | let kind;
|
41415 | if (directive === null) {
|
41416 | kind = DisplayInfoKind.ELEMENT;
|
41417 | }
|
41418 | else if (directive.isComponent) {
|
41419 | kind = DisplayInfoKind.COMPONENT;
|
41420 | }
|
41421 | else {
|
41422 | kind = DisplayInfoKind.DIRECTIVE;
|
41423 | }
|
41424 | return unsafeCastDisplayInfoKindToScriptElementKind(kind);
|
41425 | }
|
41426 | const BINDING_SUGAR = /[\[\(\)\]]/g;
|
41427 | function stripBindingSugar(binding) {
|
41428 | const name = binding.replace(BINDING_SUGAR, '');
|
41429 | if (binding.startsWith('[')) {
|
41430 | return { name, kind: DisplayInfoKind.PROPERTY };
|
41431 | }
|
41432 | else if (binding.startsWith('(')) {
|
41433 | return { name, kind: DisplayInfoKind.EVENT };
|
41434 | }
|
41435 | else {
|
41436 | return { name, kind: DisplayInfoKind.ATTRIBUTE };
|
41437 | }
|
41438 | }
|
41439 | function nodeContextFromTarget(target) {
|
41440 | switch (target.kind) {
|
41441 | case TargetNodeKind.ElementInTagContext:
|
41442 | return CompletionNodeContext.ElementTag;
|
41443 | case TargetNodeKind.ElementInBodyContext:
|
41444 | // Completions in element bodies are for new attributes.
|
41445 | return CompletionNodeContext.ElementAttributeKey;
|
41446 | case TargetNodeKind.TwoWayBindingContext:
|
41447 | return CompletionNodeContext.TwoWayBinding;
|
41448 | case TargetNodeKind.AttributeInKeyContext:
|
41449 | return CompletionNodeContext.ElementAttributeKey;
|
41450 | case TargetNodeKind.AttributeInValueContext:
|
41451 | if (target.node instanceof BoundEvent) {
|
41452 | return CompletionNodeContext.EventValue;
|
41453 | }
|
41454 | else {
|
41455 | return CompletionNodeContext.None;
|
41456 | }
|
41457 | default:
|
41458 | // No special context is available.
|
41459 | return CompletionNodeContext.None;
|
41460 | }
|
41461 | }
|
41462 |
|
41463 | /**
|
41464 | * @license
|
41465 | * Copyright Google LLC All Rights Reserved.
|
41466 | *
|
41467 | * Use of this source code is governed by an MIT-style license that can be
|
41468 | * found in the LICENSE file at https://angular.io/license
|
41469 | */
|
41470 | class DefinitionBuilder {
|
41471 | constructor(tsLS, compiler) {
|
41472 | this.tsLS = tsLS;
|
41473 | this.compiler = compiler;
|
41474 | }
|
41475 | getDefinitionAndBoundSpan(fileName, position) {
|
41476 | var _a;
|
41477 | const templateInfo = getTemplateInfoAtPosition(fileName, position, this.compiler);
|
41478 | if (templateInfo === undefined) {
|
41479 | // We were unable to get a template at the given position. If we are in a TS file, instead
|
41480 | // attempt to get an Angular definition at the location inside a TS file (examples of this
|
41481 | // would be templateUrl or a url in styleUrls).
|
41482 | if (!isTypeScriptFile(fileName)) {
|
41483 | return;
|
41484 | }
|
41485 | return getDefinitionForExpressionAtPosition(fileName, position, this.compiler);
|
41486 | }
|
41487 | const definitionMetas = this.getDefinitionMetaAtPosition(templateInfo, position);
|
41488 | if (definitionMetas === undefined) {
|
41489 | return undefined;
|
41490 | }
|
41491 | const definitions = [];
|
41492 | for (const definitionMeta of definitionMetas) {
|
41493 | // The `$event` of event handlers would point to the $event parameter in the shim file, as in
|
41494 | // `_t3["x"].subscribe(function ($event): any { $event }) ;`
|
41495 | // If we wanted to return something for this, it would be more appropriate for something like
|
41496 | // `getTypeDefinition`.
|
41497 | if (isDollarEvent(definitionMeta.node)) {
|
41498 | continue;
|
41499 | }
|
41500 | definitions.push(...((_a = this.getDefinitionsForSymbol(Object.assign(Object.assign({}, definitionMeta), templateInfo))) !== null && _a !== void 0 ? _a : []));
|
41501 | }
|
41502 | if (definitions.length === 0) {
|
41503 | return undefined;
|
41504 | }
|
41505 | return { definitions, textSpan: getTextSpanOfNode(definitionMetas[0].node) };
|
41506 | }
|
41507 | getDefinitionsForSymbol({ symbol, node, parent, component }) {
|
41508 | switch (symbol.kind) {
|
41509 | case SymbolKind.Directive:
|
41510 | case SymbolKind.Element:
|
41511 | case SymbolKind.Template:
|
41512 | case SymbolKind.DomBinding:
|
41513 | // Though it is generally more appropriate for the above symbol definitions to be
|
41514 | // associated with "type definitions" since the location in the template is the
|
41515 | // actual definition location, the better user experience would be to allow
|
41516 | // LS users to "go to definition" on an item in the template that maps to a class and be
|
41517 | // taken to the directive or HTML class.
|
41518 | return this.getTypeDefinitionsForTemplateInstance(symbol, node);
|
41519 | case SymbolKind.Pipe: {
|
41520 | if (symbol.tsSymbol !== null) {
|
41521 | return this.getDefinitionsForSymbols(symbol);
|
41522 | }
|
41523 | else {
|
41524 | // If there is no `ts.Symbol` for the pipe transform, we want to return the
|
41525 | // type definition (the pipe class).
|
41526 | return this.getTypeDefinitionsForSymbols(symbol.classSymbol);
|
41527 | }
|
41528 | }
|
41529 | case SymbolKind.Output:
|
41530 | case SymbolKind.Input: {
|
41531 | const bindingDefs = this.getDefinitionsForSymbols(...symbol.bindings);
|
41532 | // Also attempt to get directive matches for the input name. If there is a directive that
|
41533 | // has the input name as part of the selector, we want to return that as well.
|
41534 | const directiveDefs = this.getDirectiveTypeDefsForBindingNode(node, parent, component);
|
41535 | return [...bindingDefs, ...directiveDefs];
|
41536 | }
|
41537 | case SymbolKind.Variable:
|
41538 | case SymbolKind.Reference: {
|
41539 | const definitions = [];
|
41540 | if (symbol.declaration !== node) {
|
41541 | const shimLocation = symbol.kind === SymbolKind.Variable ? symbol.localVarLocation :
|
41542 | symbol.referenceVarLocation;
|
41543 | const mapping = getTemplateLocationFromShimLocation(this.compiler.getTemplateTypeChecker(), shimLocation.shimPath, shimLocation.positionInShimFile);
|
41544 | if (mapping !== null) {
|
41545 | definitions.push({
|
41546 | name: symbol.declaration.name,
|
41547 | containerName: '',
|
41548 | containerKind: ts$1.ScriptElementKind.unknown,
|
41549 | kind: ts$1.ScriptElementKind.variableElement,
|
41550 | textSpan: getTextSpanOfNode(symbol.declaration),
|
41551 | contextSpan: toTextSpan(symbol.declaration.sourceSpan),
|
41552 | fileName: mapping.templateUrl,
|
41553 | });
|
41554 | }
|
41555 | }
|
41556 | if (symbol.kind === SymbolKind.Variable) {
|
41557 | definitions.push(...this.getDefinitionsForSymbols({ shimLocation: symbol.initializerLocation }));
|
41558 | }
|
41559 | return definitions;
|
41560 | }
|
41561 | case SymbolKind.Expression: {
|
41562 | return this.getDefinitionsForSymbols(symbol);
|
41563 | }
|
41564 | }
|
41565 | }
|
41566 | getDefinitionsForSymbols(...symbols) {
|
41567 | return flatMap(symbols, ({ shimLocation }) => {
|
41568 | var _a;
|
41569 | const { shimPath, positionInShimFile } = shimLocation;
|
41570 | return (_a = this.tsLS.getDefinitionAtPosition(shimPath, positionInShimFile)) !== null && _a !== void 0 ? _a : [];
|
41571 | });
|
41572 | }
|
41573 | getTypeDefinitionsAtPosition(fileName, position) {
|
41574 | const templateInfo = getTemplateInfoAtPosition(fileName, position, this.compiler);
|
41575 | if (templateInfo === undefined) {
|
41576 | return;
|
41577 | }
|
41578 | const definitionMetas = this.getDefinitionMetaAtPosition(templateInfo, position);
|
41579 | if (definitionMetas === undefined) {
|
41580 | return undefined;
|
41581 | }
|
41582 | const definitions = [];
|
41583 | for (const { symbol, node, parent } of definitionMetas) {
|
41584 | switch (symbol.kind) {
|
41585 | case SymbolKind.Directive:
|
41586 | case SymbolKind.DomBinding:
|
41587 | case SymbolKind.Element:
|
41588 | case SymbolKind.Template:
|
41589 | definitions.push(...this.getTypeDefinitionsForTemplateInstance(symbol, node));
|
41590 | break;
|
41591 | case SymbolKind.Output:
|
41592 | case SymbolKind.Input: {
|
41593 | const bindingDefs = this.getTypeDefinitionsForSymbols(...symbol.bindings);
|
41594 | definitions.push(...bindingDefs);
|
41595 | // Also attempt to get directive matches for the input name. If there is a directive that
|
41596 | // has the input name as part of the selector, we want to return that as well.
|
41597 | const directiveDefs = this.getDirectiveTypeDefsForBindingNode(node, parent, templateInfo.component);
|
41598 | definitions.push(...directiveDefs);
|
41599 | break;
|
41600 | }
|
41601 | case SymbolKind.Pipe: {
|
41602 | if (symbol.tsSymbol !== null) {
|
41603 | definitions.push(...this.getTypeDefinitionsForSymbols(symbol));
|
41604 | }
|
41605 | else {
|
41606 | // If there is no `ts.Symbol` for the pipe transform, we want to return the
|
41607 | // type definition (the pipe class).
|
41608 | definitions.push(...this.getTypeDefinitionsForSymbols(symbol.classSymbol));
|
41609 | }
|
41610 | break;
|
41611 | }
|
41612 | case SymbolKind.Reference:
|
41613 | definitions.push(...this.getTypeDefinitionsForSymbols({ shimLocation: symbol.targetLocation }));
|
41614 | break;
|
41615 | case SymbolKind.Expression:
|
41616 | definitions.push(...this.getTypeDefinitionsForSymbols(symbol));
|
41617 | break;
|
41618 | case SymbolKind.Variable: {
|
41619 | definitions.push(...this.getTypeDefinitionsForSymbols({ shimLocation: symbol.initializerLocation }));
|
41620 | break;
|
41621 | }
|
41622 | }
|
41623 | return definitions;
|
41624 | }
|
41625 | }
|
41626 | getTypeDefinitionsForTemplateInstance(symbol, node) {
|
41627 | switch (symbol.kind) {
|
41628 | case SymbolKind.Template: {
|
41629 | const matches = getDirectiveMatchesForElementTag(symbol.templateNode, symbol.directives);
|
41630 | return this.getTypeDefinitionsForSymbols(...matches);
|
41631 | }
|
41632 | case SymbolKind.Element: {
|
41633 | const matches = getDirectiveMatchesForElementTag(symbol.templateNode, symbol.directives);
|
41634 | // If one of the directive matches is a component, we should not include the native element
|
41635 | // in the results because it is replaced by the component.
|
41636 | return Array.from(matches).some(dir => dir.isComponent) ?
|
41637 | this.getTypeDefinitionsForSymbols(...matches) :
|
41638 | this.getTypeDefinitionsForSymbols(...matches, symbol);
|
41639 | }
|
41640 | case SymbolKind.DomBinding: {
|
41641 | if (!(node instanceof TextAttribute)) {
|
41642 | return [];
|
41643 | }
|
41644 | const dirs = getDirectiveMatchesForAttribute(node.name, symbol.host.templateNode, symbol.host.directives);
|
41645 | return this.getTypeDefinitionsForSymbols(...dirs);
|
41646 | }
|
41647 | case SymbolKind.Directive:
|
41648 | return this.getTypeDefinitionsForSymbols(symbol);
|
41649 | }
|
41650 | }
|
41651 | getDirectiveTypeDefsForBindingNode(node, parent, component) {
|
41652 | if (!(node instanceof BoundAttribute) && !(node instanceof TextAttribute) &&
|
41653 | !(node instanceof BoundEvent)) {
|
41654 | return [];
|
41655 | }
|
41656 | if (parent === null ||
|
41657 | !(parent instanceof Template || parent instanceof Element)) {
|
41658 | return [];
|
41659 | }
|
41660 | const templateOrElementSymbol = this.compiler.getTemplateTypeChecker().getSymbolOfNode(parent, component);
|
41661 | if (templateOrElementSymbol === null ||
|
41662 | (templateOrElementSymbol.kind !== SymbolKind.Template &&
|
41663 | templateOrElementSymbol.kind !== SymbolKind.Element)) {
|
41664 | return [];
|
41665 | }
|
41666 | const dirs = getDirectiveMatchesForAttribute(node.name, parent, templateOrElementSymbol.directives);
|
41667 | return this.getTypeDefinitionsForSymbols(...dirs);
|
41668 | }
|
41669 | getTypeDefinitionsForSymbols(...symbols) {
|
41670 | return flatMap(symbols, ({ shimLocation }) => {
|
41671 | var _a;
|
41672 | const { shimPath, positionInShimFile } = shimLocation;
|
41673 | return (_a = this.tsLS.getTypeDefinitionAtPosition(shimPath, positionInShimFile)) !== null && _a !== void 0 ? _a : [];
|
41674 | });
|
41675 | }
|
41676 | getDefinitionMetaAtPosition({ template, component }, position) {
|
41677 | const target = getTargetAtPosition(template, position);
|
41678 | if (target === null) {
|
41679 | return undefined;
|
41680 | }
|
41681 | const { context, parent } = target;
|
41682 | const nodes = context.kind === TargetNodeKind.TwoWayBindingContext ? context.nodes : [context.node];
|
41683 | const definitionMetas = [];
|
41684 | for (const node of nodes) {
|
41685 | const symbol = this.compiler.getTemplateTypeChecker().getSymbolOfNode(node, component);
|
41686 | if (symbol === null) {
|
41687 | continue;
|
41688 | }
|
41689 | definitionMetas.push({ node, parent, symbol });
|
41690 | }
|
41691 | return definitionMetas.length > 0 ? definitionMetas : undefined;
|
41692 | }
|
41693 | }
|
41694 | /**
|
41695 | * Gets an Angular-specific definition in a TypeScript source file.
|
41696 | */
|
41697 | function getDefinitionForExpressionAtPosition(fileName, position, compiler) {
|
41698 | const sf = compiler.getNextProgram().getSourceFile(fileName);
|
41699 | if (sf === undefined) {
|
41700 | return;
|
41701 | }
|
41702 | const expression = findTightestNode(sf, position);
|
41703 | if (expression === undefined) {
|
41704 | return;
|
41705 | }
|
41706 | const classDeclaration = getParentClassDeclaration(expression);
|
41707 | if (classDeclaration === undefined) {
|
41708 | return;
|
41709 | }
|
41710 | const componentResources = compiler.getComponentResources(classDeclaration);
|
41711 | if (componentResources === null) {
|
41712 | return;
|
41713 | }
|
41714 | const allResources = [...componentResources.styles, componentResources.template];
|
41715 | const resourceForExpression = allResources.find(resource => resource.expression === expression);
|
41716 | if (resourceForExpression === undefined || !isExternalResource(resourceForExpression)) {
|
41717 | return;
|
41718 | }
|
41719 | const templateDefinitions = [{
|
41720 | kind: ts$1.ScriptElementKind.externalModuleName,
|
41721 | name: resourceForExpression.path,
|
41722 | containerKind: ts$1.ScriptElementKind.unknown,
|
41723 | containerName: '',
|
41724 | // Reading the template is expensive, so don't provide a preview.
|
41725 | // TODO(ayazhafiz): Consider providing an actual span:
|
41726 | // 1. We're likely to read the template anyway
|
41727 | // 2. We could show just the first 100 chars or so
|
41728 | textSpan: { start: 0, length: 0 },
|
41729 | fileName: resourceForExpression.path,
|
41730 | }];
|
41731 | return {
|
41732 | definitions: templateDefinitions,
|
41733 | textSpan: {
|
41734 | // Exclude opening and closing quotes in the url span.
|
41735 | start: expression.getStart() + 1,
|
41736 | length: expression.getWidth() - 2,
|
41737 | },
|
41738 | };
|
41739 | }
|
41740 |
|
41741 | /**
|
41742 | * @license
|
41743 | * Copyright Google LLC All Rights Reserved.
|
41744 | *
|
41745 | * Use of this source code is governed by an MIT-style license that can be
|
41746 | * found in the LICENSE file at https://angular.io/license
|
41747 | */
|
41748 | class QuickInfoBuilder {
|
41749 | constructor(tsLS, compiler, component, node) {
|
41750 | this.tsLS = tsLS;
|
41751 | this.compiler = compiler;
|
41752 | this.component = component;
|
41753 | this.node = node;
|
41754 | this.typeChecker = this.compiler.getNextProgram().getTypeChecker();
|
41755 | }
|
41756 | get() {
|
41757 | const symbol = this.compiler.getTemplateTypeChecker().getSymbolOfNode(this.node, this.component);
|
41758 | if (symbol === null) {
|
41759 | return isDollarAny(this.node) ? createDollarAnyQuickInfo(this.node) : undefined;
|
41760 | }
|
41761 | return this.getQuickInfoForSymbol(symbol);
|
41762 | }
|
41763 | getQuickInfoForSymbol(symbol) {
|
41764 | switch (symbol.kind) {
|
41765 | case SymbolKind.Input:
|
41766 | case SymbolKind.Output:
|
41767 | return this.getQuickInfoForBindingSymbol(symbol);
|
41768 | case SymbolKind.Template:
|
41769 | return createNgTemplateQuickInfo(this.node);
|
41770 | case SymbolKind.Element:
|
41771 | return this.getQuickInfoForElementSymbol(symbol);
|
41772 | case SymbolKind.Variable:
|
41773 | return this.getQuickInfoForVariableSymbol(symbol);
|
41774 | case SymbolKind.Reference:
|
41775 | return this.getQuickInfoForReferenceSymbol(symbol);
|
41776 | case SymbolKind.DomBinding:
|
41777 | return this.getQuickInfoForDomBinding(symbol);
|
41778 | case SymbolKind.Directive:
|
41779 | return this.getQuickInfoAtShimLocation(symbol.shimLocation);
|
41780 | case SymbolKind.Pipe:
|
41781 | return this.getQuickInfoForPipeSymbol(symbol);
|
41782 | case SymbolKind.Expression:
|
41783 | return this.getQuickInfoAtShimLocation(symbol.shimLocation);
|
41784 | }
|
41785 | }
|
41786 | getQuickInfoForBindingSymbol(symbol) {
|
41787 | if (symbol.bindings.length === 0) {
|
41788 | return undefined;
|
41789 | }
|
41790 | const kind = symbol.kind === SymbolKind.Input ? DisplayInfoKind.PROPERTY : DisplayInfoKind.EVENT;
|
41791 | const quickInfo = this.getQuickInfoAtShimLocation(symbol.bindings[0].shimLocation);
|
41792 | return quickInfo === undefined ? undefined : updateQuickInfoKind(quickInfo, kind);
|
41793 | }
|
41794 | getQuickInfoForElementSymbol(symbol) {
|
41795 | const { templateNode } = symbol;
|
41796 | const matches = getDirectiveMatchesForElementTag(templateNode, symbol.directives);
|
41797 | if (matches.size > 0) {
|
41798 | return this.getQuickInfoForDirectiveSymbol(matches.values().next().value, templateNode);
|
41799 | }
|
41800 | return createQuickInfo(templateNode.name, DisplayInfoKind.ELEMENT, getTextSpanOfNode(templateNode), undefined /* containerName */, this.typeChecker.typeToString(symbol.tsType));
|
41801 | }
|
41802 | getQuickInfoForVariableSymbol(symbol) {
|
41803 | const documentation = this.getDocumentationFromTypeDefAtLocation(symbol.initializerLocation);
|
41804 | return createQuickInfo(symbol.declaration.name, DisplayInfoKind.VARIABLE, getTextSpanOfNode(this.node), undefined /* containerName */, this.typeChecker.typeToString(symbol.tsType), documentation);
|
41805 | }
|
41806 | getQuickInfoForReferenceSymbol(symbol) {
|
41807 | const documentation = this.getDocumentationFromTypeDefAtLocation(symbol.targetLocation);
|
41808 | return createQuickInfo(symbol.declaration.name, DisplayInfoKind.REFERENCE, getTextSpanOfNode(this.node), undefined /* containerName */, this.typeChecker.typeToString(symbol.tsType), documentation);
|
41809 | }
|
41810 | getQuickInfoForPipeSymbol(symbol) {
|
41811 | if (symbol.tsSymbol !== null) {
|
41812 | const quickInfo = this.getQuickInfoAtShimLocation(symbol.shimLocation);
|
41813 | return quickInfo === undefined ? undefined :
|
41814 | updateQuickInfoKind(quickInfo, DisplayInfoKind.PIPE);
|
41815 | }
|
41816 | else {
|
41817 | return createQuickInfo(this.typeChecker.typeToString(symbol.classSymbol.tsType), DisplayInfoKind.PIPE, getTextSpanOfNode(this.node));
|
41818 | }
|
41819 | }
|
41820 | getQuickInfoForDomBinding(symbol) {
|
41821 | if (!(this.node instanceof TextAttribute) &&
|
41822 | !(this.node instanceof BoundAttribute)) {
|
41823 | return undefined;
|
41824 | }
|
41825 | const directives = getDirectiveMatchesForAttribute(this.node.name, symbol.host.templateNode, symbol.host.directives);
|
41826 | if (directives.size === 0) {
|
41827 | return undefined;
|
41828 | }
|
41829 | return this.getQuickInfoForDirectiveSymbol(directives.values().next().value);
|
41830 | }
|
41831 | getQuickInfoForDirectiveSymbol(dir, node = this.node) {
|
41832 | const kind = dir.isComponent ? DisplayInfoKind.COMPONENT : DisplayInfoKind.DIRECTIVE;
|
41833 | const documentation = this.getDocumentationFromTypeDefAtLocation(dir.shimLocation);
|
41834 | let containerName;
|
41835 | if (ts$1.isClassDeclaration(dir.tsSymbol.valueDeclaration) && dir.ngModule !== null) {
|
41836 | containerName = dir.ngModule.name.getText();
|
41837 | }
|
41838 | return createQuickInfo(this.typeChecker.typeToString(dir.tsType), kind, getTextSpanOfNode(this.node), containerName, undefined, documentation);
|
41839 | }
|
41840 | getDocumentationFromTypeDefAtLocation(shimLocation) {
|
41841 | var _a;
|
41842 | const typeDefs = this.tsLS.getTypeDefinitionAtPosition(shimLocation.shimPath, shimLocation.positionInShimFile);
|
41843 | if (typeDefs === undefined || typeDefs.length === 0) {
|
41844 | return undefined;
|
41845 | }
|
41846 | return (_a = this.tsLS.getQuickInfoAtPosition(typeDefs[0].fileName, typeDefs[0].textSpan.start)) === null || _a === void 0 ? void 0 : _a.documentation;
|
41847 | }
|
41848 | getQuickInfoAtShimLocation(location) {
|
41849 | const quickInfo = this.tsLS.getQuickInfoAtPosition(location.shimPath, location.positionInShimFile);
|
41850 | if (quickInfo === undefined || quickInfo.displayParts === undefined) {
|
41851 | return quickInfo;
|
41852 | }
|
41853 | quickInfo.displayParts = filterAliasImports(quickInfo.displayParts);
|
41854 | const textSpan = getTextSpanOfNode(this.node);
|
41855 | return Object.assign(Object.assign({}, quickInfo), { textSpan });
|
41856 | }
|
41857 | }
|
41858 | function updateQuickInfoKind(quickInfo, kind) {
|
41859 | if (quickInfo.displayParts === undefined) {
|
41860 | return quickInfo;
|
41861 | }
|
41862 | const startsWithKind = quickInfo.displayParts.length >= 3 &&
|
41863 | displayPartsEqual(quickInfo.displayParts[0], { text: '(', kind: SYMBOL_PUNC }) &&
|
41864 | quickInfo.displayParts[1].kind === SYMBOL_TEXT &&
|
41865 | displayPartsEqual(quickInfo.displayParts[2], { text: ')', kind: SYMBOL_PUNC });
|
41866 | if (startsWithKind) {
|
41867 | quickInfo.displayParts[1].text = kind;
|
41868 | }
|
41869 | else {
|
41870 | quickInfo.displayParts = [
|
41871 | { text: '(', kind: SYMBOL_PUNC },
|
41872 | { text: kind, kind: SYMBOL_TEXT },
|
41873 | { text: ')', kind: SYMBOL_PUNC },
|
41874 | { text: ' ', kind: SYMBOL_SPACE },
|
41875 | ...quickInfo.displayParts,
|
41876 | ];
|
41877 | }
|
41878 | return quickInfo;
|
41879 | }
|
41880 | function displayPartsEqual(a, b) {
|
41881 | return a.text === b.text && a.kind === b.kind;
|
41882 | }
|
41883 | function isDollarAny(node) {
|
41884 | return node instanceof MethodCall && node.receiver instanceof ImplicitReceiver &&
|
41885 | !(node.receiver instanceof ThisReceiver) && node.name === '$any' && node.args.length === 1;
|
41886 | }
|
41887 | function createDollarAnyQuickInfo(node) {
|
41888 | return createQuickInfo('$any', DisplayInfoKind.METHOD, getTextSpanOfNode(node),
|
41889 | /** containerName */ undefined, 'any', [{
|
41890 | kind: SYMBOL_TEXT,
|
41891 | text: 'function to cast an expression to the `any` type',
|
41892 | }]);
|
41893 | }
|
41894 | // TODO(atscott): Create special `ts.QuickInfo` for `ng-template` and `ng-container` as well.
|
41895 | function createNgTemplateQuickInfo(node) {
|
41896 | return createQuickInfo('ng-template', DisplayInfoKind.TEMPLATE, getTextSpanOfNode(node),
|
41897 | /** containerName */ undefined,
|
41898 | /** type */ undefined, [{
|
41899 | kind: SYMBOL_TEXT,
|
41900 | text: 'The `<ng-template>` is an Angular element for rendering HTML. It is never displayed directly.',
|
41901 | }]);
|
41902 | }
|
41903 | /**
|
41904 | * Construct a QuickInfo object taking into account its container and type.
|
41905 | * @param name Name of the QuickInfo target
|
41906 | * @param kind component, directive, pipe, etc.
|
41907 | * @param textSpan span of the target
|
41908 | * @param containerName either the Symbol's container or the NgModule that contains the directive
|
41909 | * @param type user-friendly name of the type
|
41910 | * @param documentation docstring or comment
|
41911 | */
|
41912 | function createQuickInfo(name, kind, textSpan, containerName, type, documentation) {
|
41913 | const displayParts = createDisplayParts(name, kind, containerName, type);
|
41914 | return {
|
41915 | kind: unsafeCastDisplayInfoKindToScriptElementKind(kind),
|
41916 | kindModifiers: ts$1.ScriptElementKindModifier.none,
|
41917 | textSpan: textSpan,
|
41918 | displayParts,
|
41919 | documentation,
|
41920 | };
|
41921 | }
|
41922 |
|
41923 | /**
|
41924 | * @license
|
41925 | * Copyright Google LLC All Rights Reserved.
|
41926 | *
|
41927 | * Use of this source code is governed by an MIT-style license that can be
|
41928 | * found in the LICENSE file at https://angular.io/license
|
41929 | */
|
41930 | function toFilePosition(shimLocation) {
|
41931 | return { fileName: shimLocation.shimPath, position: shimLocation.positionInShimFile };
|
41932 | }
|
41933 | var RequestKind;
|
41934 | (function (RequestKind) {
|
41935 | RequestKind[RequestKind["Template"] = 0] = "Template";
|
41936 | RequestKind[RequestKind["TypeScript"] = 1] = "TypeScript";
|
41937 | })(RequestKind || (RequestKind = {}));
|
41938 | class ReferencesAndRenameBuilder {
|
41939 | constructor(strategy, tsLS, compiler) {
|
41940 | this.strategy = strategy;
|
41941 | this.tsLS = tsLS;
|
41942 | this.compiler = compiler;
|
41943 | this.ttc = this.compiler.getTemplateTypeChecker();
|
41944 | }
|
41945 | getRenameInfo(filePath, position) {
|
41946 | const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
|
41947 | // We could not get a template at position so we assume the request came from outside the
|
41948 | // template.
|
41949 | if (templateInfo === undefined) {
|
41950 | return this.tsLS.getRenameInfo(filePath, position);
|
41951 | }
|
41952 | const allTargetDetails = this.getTargetDetailsAtTemplatePosition(templateInfo, position);
|
41953 | if (allTargetDetails === null) {
|
41954 | return { canRename: false, localizedErrorMessage: 'Could not find template node at position.' };
|
41955 | }
|
41956 | const { templateTarget } = allTargetDetails[0];
|
41957 | const templateTextAndSpan = getRenameTextAndSpanAtPosition(templateTarget, position);
|
41958 | if (templateTextAndSpan === null) {
|
41959 | return { canRename: false, localizedErrorMessage: 'Could not determine template node text.' };
|
41960 | }
|
41961 | const { text, span } = templateTextAndSpan;
|
41962 | return {
|
41963 | canRename: true,
|
41964 | displayName: text,
|
41965 | fullDisplayName: text,
|
41966 | triggerSpan: span,
|
41967 | };
|
41968 | }
|
41969 | findRenameLocations(filePath, position) {
|
41970 | this.ttc.generateAllTypeCheckBlocks();
|
41971 | const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
|
41972 | // We could not get a template at position so we assume the request came from outside the
|
41973 | // template.
|
41974 | if (templateInfo === undefined) {
|
41975 | const requestNode = this.getTsNodeAtPosition(filePath, position);
|
41976 | if (requestNode === null) {
|
41977 | return undefined;
|
41978 | }
|
41979 | const requestOrigin = { kind: RequestKind.TypeScript, requestNode };
|
41980 | return this.findRenameLocationsAtTypescriptPosition(filePath, position, requestOrigin);
|
41981 | }
|
41982 | return this.findRenameLocationsAtTemplatePosition(templateInfo, position);
|
41983 | }
|
41984 | findRenameLocationsAtTemplatePosition(templateInfo, position) {
|
41985 | const allTargetDetails = this.getTargetDetailsAtTemplatePosition(templateInfo, position);
|
41986 | if (allTargetDetails === null) {
|
41987 | return undefined;
|
41988 | }
|
41989 | const allRenameLocations = [];
|
41990 | for (const targetDetails of allTargetDetails) {
|
41991 | const requestOrigin = {
|
41992 | kind: RequestKind.Template,
|
41993 | requestNode: targetDetails.templateTarget,
|
41994 | position,
|
41995 | };
|
41996 | for (const location of targetDetails.typescriptLocations) {
|
41997 | const locations = this.findRenameLocationsAtTypescriptPosition(location.fileName, location.position, requestOrigin);
|
41998 | // If we couldn't find rename locations for _any_ result, we should not allow renaming to
|
41999 | // proceed instead of having a partially complete rename.
|
42000 | if (locations === undefined) {
|
42001 | return undefined;
|
42002 | }
|
42003 | allRenameLocations.push(...locations);
|
42004 | }
|
42005 | }
|
42006 | return allRenameLocations.length > 0 ? allRenameLocations : undefined;
|
42007 | }
|
42008 | getTsNodeAtPosition(filePath, position) {
|
42009 | var _a;
|
42010 | const sf = this.strategy.getProgram().getSourceFile(filePath);
|
42011 | if (!sf) {
|
42012 | return null;
|
42013 | }
|
42014 | return (_a = findTightestNode(sf, position)) !== null && _a !== void 0 ? _a : null;
|
42015 | }
|
42016 | findRenameLocationsAtTypescriptPosition(filePath, position, requestOrigin) {
|
42017 | let originalNodeText;
|
42018 | if (requestOrigin.kind === RequestKind.TypeScript) {
|
42019 | originalNodeText = requestOrigin.requestNode.getText();
|
42020 | }
|
42021 | else {
|
42022 | const templateNodeText = getRenameTextAndSpanAtPosition(requestOrigin.requestNode, requestOrigin.position);
|
42023 | if (templateNodeText === null) {
|
42024 | return undefined;
|
42025 | }
|
42026 | originalNodeText = templateNodeText.text;
|
42027 | }
|
42028 | const locations = this.tsLS.findRenameLocations(filePath, position, /*findInStrings*/ false, /*findInComments*/ false);
|
42029 | if (locations === undefined) {
|
42030 | return undefined;
|
42031 | }
|
42032 | const entries = new Map();
|
42033 | for (const location of locations) {
|
42034 | // TODO(atscott): Determine if a file is a shim file in a more robust way and make the API
|
42035 | // available in an appropriate location.
|
42036 | if (this.ttc.isTrackedTypeCheckFile(absoluteFrom(location.fileName))) {
|
42037 | const entry = this.convertToTemplateDocumentSpan(location, this.ttc, originalNodeText);
|
42038 | // There is no template node whose text matches the original rename request. Bail on
|
42039 | // renaming completely rather than providing incomplete results.
|
42040 | if (entry === null) {
|
42041 | return undefined;
|
42042 | }
|
42043 | entries.set(createLocationKey(entry), entry);
|
42044 | }
|
42045 | else {
|
42046 | // Ensure we only allow renaming a TS result with matching text
|
42047 | const refNode = this.getTsNodeAtPosition(location.fileName, location.textSpan.start);
|
42048 | if (refNode === null || refNode.getText() !== originalNodeText) {
|
42049 | return undefined;
|
42050 | }
|
42051 | entries.set(createLocationKey(location), location);
|
42052 | }
|
42053 | }
|
42054 | return Array.from(entries.values());
|
42055 | }
|
42056 | getReferencesAtPosition(filePath, position) {
|
42057 | this.ttc.generateAllTypeCheckBlocks();
|
42058 | const templateInfo = getTemplateInfoAtPosition(filePath, position, this.compiler);
|
42059 | if (templateInfo === undefined) {
|
42060 | return this.getReferencesAtTypescriptPosition(filePath, position);
|
42061 | }
|
42062 | return this.getReferencesAtTemplatePosition(templateInfo, position);
|
42063 | }
|
42064 | getReferencesAtTemplatePosition(templateInfo, position) {
|
42065 | const allTargetDetails = this.getTargetDetailsAtTemplatePosition(templateInfo, position);
|
42066 | if (allTargetDetails === null) {
|
42067 | return undefined;
|
42068 | }
|
42069 | const allReferences = [];
|
42070 | for (const targetDetails of allTargetDetails) {
|
42071 | for (const location of targetDetails.typescriptLocations) {
|
42072 | const refs = this.getReferencesAtTypescriptPosition(location.fileName, location.position);
|
42073 | if (refs !== undefined) {
|
42074 | allReferences.push(...refs);
|
42075 | }
|
42076 | }
|
42077 | }
|
42078 | return allReferences.length > 0 ? allReferences : undefined;
|
42079 | }
|
42080 | getTargetDetailsAtTemplatePosition({ template, component }, position) {
|
42081 | // Find the AST node in the template at the position.
|
42082 | const positionDetails = getTargetAtPosition(template, position);
|
42083 | if (positionDetails === null) {
|
42084 | return null;
|
42085 | }
|
42086 | const nodes = positionDetails.context.kind === TargetNodeKind.TwoWayBindingContext ?
|
42087 | positionDetails.context.nodes :
|
42088 | [positionDetails.context.node];
|
42089 | const details = [];
|
42090 | for (const node of nodes) {
|
42091 | // Get the information about the TCB at the template position.
|
42092 | const symbol = this.ttc.getSymbolOfNode(node, component);
|
42093 | if (symbol === null) {
|
42094 | continue;
|
42095 | }
|
42096 | const templateTarget = node;
|
42097 | switch (symbol.kind) {
|
42098 | case SymbolKind.Directive:
|
42099 | case SymbolKind.Template:
|
42100 | // References to elements, templates, and directives will be through template references
|
42101 | // (#ref). They shouldn't be used directly for a Language Service reference request.
|
42102 | break;
|
42103 | case SymbolKind.Element: {
|
42104 | const matches = getDirectiveMatchesForElementTag(symbol.templateNode, symbol.directives);
|
42105 | details.push({ typescriptLocations: this.getPositionsForDirectives(matches), templateTarget });
|
42106 | break;
|
42107 | }
|
42108 | case SymbolKind.DomBinding: {
|
42109 | // Dom bindings aren't currently type-checked (see `checkTypeOfDomBindings`) so they don't
|
42110 | // have a shim location. This means we can't match dom bindings to their lib.dom
|
42111 | // reference, but we can still see if they match to a directive.
|
42112 | if (!(node instanceof TextAttribute) && !(node instanceof BoundAttribute)) {
|
42113 | return null;
|
42114 | }
|
42115 | const directives = getDirectiveMatchesForAttribute(node.name, symbol.host.templateNode, symbol.host.directives);
|
42116 | details.push({
|
42117 | typescriptLocations: this.getPositionsForDirectives(directives),
|
42118 | templateTarget,
|
42119 | });
|
42120 | break;
|
42121 | }
|
42122 | case SymbolKind.Reference: {
|
42123 | details.push({
|
42124 | typescriptLocations: [toFilePosition(symbol.referenceVarLocation)],
|
42125 | templateTarget,
|
42126 | });
|
42127 | break;
|
42128 | }
|
42129 | case SymbolKind.Variable: {
|
42130 | if ((templateTarget instanceof Variable)) {
|
42131 | if (templateTarget.valueSpan !== undefined &&
|
42132 | isWithin(position, templateTarget.valueSpan)) {
|
42133 | // In the valueSpan of the variable, we want to get the reference of the initializer.
|
42134 | details.push({
|
42135 | typescriptLocations: [toFilePosition(symbol.initializerLocation)],
|
42136 | templateTarget,
|
42137 | });
|
42138 | }
|
42139 | else if (isWithin(position, templateTarget.keySpan)) {
|
42140 | // In the keySpan of the variable, we want to get the reference of the local variable.
|
42141 | details.push({
|
42142 | typescriptLocations: [toFilePosition(symbol.localVarLocation)],
|
42143 | templateTarget,
|
42144 | });
|
42145 | }
|
42146 | }
|
42147 | else {
|
42148 | // If the templateNode is not the `TmplAstVariable`, it must be a usage of the
|
42149 | // variable somewhere in the template.
|
42150 | details.push({
|
42151 | typescriptLocations: [toFilePosition(symbol.localVarLocation)],
|
42152 | templateTarget,
|
42153 | });
|
42154 | }
|
42155 | break;
|
42156 | }
|
42157 | case SymbolKind.Input:
|
42158 | case SymbolKind.Output: {
|
42159 | details.push({
|
42160 | typescriptLocations: symbol.bindings.map(binding => toFilePosition(binding.shimLocation)),
|
42161 | templateTarget,
|
42162 | });
|
42163 | break;
|
42164 | }
|
42165 | case SymbolKind.Pipe:
|
42166 | case SymbolKind.Expression: {
|
42167 | details.push({ typescriptLocations: [toFilePosition(symbol.shimLocation)], templateTarget });
|
42168 | break;
|
42169 | }
|
42170 | }
|
42171 | }
|
42172 | return details.length > 0 ? details : null;
|
42173 | }
|
42174 | getPositionsForDirectives(directives) {
|
42175 | const allDirectives = [];
|
42176 | for (const dir of directives.values()) {
|
42177 | const dirClass = dir.tsSymbol.valueDeclaration;
|
42178 | if (dirClass === undefined || !ts$1.isClassDeclaration(dirClass) ||
|
42179 | dirClass.name === undefined) {
|
42180 | continue;
|
42181 | }
|
42182 | const { fileName } = dirClass.getSourceFile();
|
42183 | const position = dirClass.name.getStart();
|
42184 | allDirectives.push({ fileName, position });
|
42185 | }
|
42186 | return allDirectives;
|
42187 | }
|
42188 | getReferencesAtTypescriptPosition(fileName, position) {
|
42189 | const refs = this.tsLS.getReferencesAtPosition(fileName, position);
|
42190 | if (refs === undefined) {
|
42191 | return undefined;
|
42192 | }
|
42193 | const entries = new Map();
|
42194 | for (const ref of refs) {
|
42195 | if (this.ttc.isTrackedTypeCheckFile(absoluteFrom(ref.fileName))) {
|
42196 | const entry = this.convertToTemplateDocumentSpan(ref, this.ttc);
|
42197 | if (entry !== null) {
|
42198 | entries.set(createLocationKey(entry), entry);
|
42199 | }
|
42200 | }
|
42201 | }
|
42202 | return Array.from(entries.values());
|
42203 | }
|
42204 | convertToTemplateDocumentSpan(shimDocumentSpan, templateTypeChecker, requiredNodeText) {
|
42205 | const sf = this.strategy.getProgram().getSourceFile(shimDocumentSpan.fileName);
|
42206 | if (sf === undefined) {
|
42207 | return null;
|
42208 | }
|
42209 | const tcbNode = findTightestNode(sf, shimDocumentSpan.textSpan.start);
|
42210 | if (tcbNode === undefined ||
|
42211 | hasExpressionIdentifier(sf, tcbNode, ExpressionIdentifier.EVENT_PARAMETER)) {
|
42212 | // If the reference result is the $event parameter in the subscribe/addEventListener
|
42213 | // function in the TCB, we want to filter this result out of the references. We really only
|
42214 | // want to return references to the parameter in the template itself.
|
42215 | return null;
|
42216 | }
|
42217 | // TODO(atscott): Determine how to consistently resolve paths. i.e. with the project
|
42218 | // serverHost or LSParseConfigHost in the adapter. We should have a better defined way to
|
42219 | // normalize paths.
|
42220 | const mapping = getTemplateLocationFromShimLocation(templateTypeChecker, absoluteFrom(shimDocumentSpan.fileName), shimDocumentSpan.textSpan.start);
|
42221 | if (mapping === null) {
|
42222 | return null;
|
42223 | }
|
42224 | const { span, templateUrl } = mapping;
|
42225 | if (requiredNodeText !== undefined && span.toString() !== requiredNodeText) {
|
42226 | return null;
|
42227 | }
|
42228 | return Object.assign(Object.assign({}, shimDocumentSpan), { fileName: templateUrl, textSpan: toTextSpan(span),
|
42229 | // Specifically clear other text span values because we do not have enough knowledge to
|
42230 | // convert these to spans in the template.
|
42231 | contextSpan: undefined, originalContextSpan: undefined, originalTextSpan: undefined });
|
42232 | }
|
42233 | }
|
42234 | function getRenameTextAndSpanAtPosition(node, position) {
|
42235 | if (node instanceof BoundAttribute || node instanceof TextAttribute ||
|
42236 | node instanceof BoundEvent) {
|
42237 | if (node.keySpan === undefined) {
|
42238 | return null;
|
42239 | }
|
42240 | return { text: node.name, span: toTextSpan(node.keySpan) };
|
42241 | }
|
42242 | else if (node instanceof Variable || node instanceof Reference) {
|
42243 | if (isWithin(position, node.keySpan)) {
|
42244 | return { text: node.keySpan.toString(), span: toTextSpan(node.keySpan) };
|
42245 | }
|
42246 | else if (node.valueSpan && isWithin(position, node.valueSpan)) {
|
42247 | return { text: node.valueSpan.toString(), span: toTextSpan(node.valueSpan) };
|
42248 | }
|
42249 | }
|
42250 | if (node instanceof BindingPipe) {
|
42251 | // TODO(atscott): Add support for renaming pipes
|
42252 | return null;
|
42253 | }
|
42254 | if (node instanceof PropertyRead || node instanceof MethodCall || node instanceof PropertyWrite ||
|
42255 | node instanceof SafePropertyRead || node instanceof SafeMethodCall) {
|
42256 | return { text: node.name, span: toTextSpan(node.nameSpan) };
|
42257 | }
|
42258 | else if (node instanceof LiteralPrimitive) {
|
42259 | const span = toTextSpan(node.sourceSpan);
|
42260 | const text = node.value;
|
42261 | if (typeof text === 'string') {
|
42262 | // The span of a string literal includes the quotes but they should be removed for renaming.
|
42263 | span.start += 1;
|
42264 | span.length -= 2;
|
42265 | }
|
42266 | return { text, span };
|
42267 | }
|
42268 | return null;
|
42269 | }
|
42270 | /**
|
42271 | * Creates a "key" for a rename/reference location by concatenating file name, span start, and span
|
42272 | * length. This allows us to de-duplicate template results when an item may appear several times
|
42273 | * in the TCB but map back to the same template location.
|
42274 | */
|
42275 | function createLocationKey(ds) {
|
42276 | return ds.fileName + ds.textSpan.start + ds.textSpan.length;
|
42277 | }
|
42278 |
|
42279 | /**
|
42280 | * @license
|
42281 | * Copyright Google LLC All Rights Reserved.
|
42282 | *
|
42283 | * Use of this source code is governed by an MIT-style license that can be
|
42284 | * found in the LICENSE file at https://angular.io/license
|
42285 | */
|
42286 | class LanguageService {
|
42287 | constructor(project, tsLS, config) {
|
42288 | this.project = project;
|
42289 | this.tsLS = tsLS;
|
42290 | this.config = config;
|
42291 | this.parseConfigHost = new LSParseConfigHost(project.projectService.host);
|
42292 | this.options = parseNgCompilerOptions(project, this.parseConfigHost, config);
|
42293 | logCompilerOptions(project, this.options);
|
42294 | this.strategy = createTypeCheckingProgramStrategy(project);
|
42295 | this.adapter = new LanguageServiceAdapter(project);
|
42296 | this.compilerFactory = new CompilerFactory(this.adapter, this.strategy, this.options);
|
42297 | this.watchConfigFile(project);
|
42298 | }
|
42299 | getCompilerOptions() {
|
42300 | return this.options;
|
42301 | }
|
42302 | getSemanticDiagnostics(fileName) {
|
42303 | const compiler = this.compilerFactory.getOrCreate();
|
42304 | const ttc = compiler.getTemplateTypeChecker();
|
42305 | const diagnostics = [];
|
42306 | if (isTypeScriptFile(fileName)) {
|
42307 | const program = compiler.getNextProgram();
|
42308 | const sourceFile = program.getSourceFile(fileName);
|
42309 | if (sourceFile) {
|
42310 | const ngDiagnostics = compiler.getDiagnosticsForFile(sourceFile, OptimizeFor.SingleFile);
|
42311 | // There are several kinds of diagnostics returned by `NgCompiler` for a source file:
|
42312 | //
|
42313 | // 1. Angular-related non-template diagnostics from decorated classes within that file.
|
42314 | // 2. Template diagnostics for components with direct inline templates (a string literal).
|
42315 | // 3. Template diagnostics for components with indirect inline templates (templates computed
|
42316 | // by expression).
|
42317 | // 4. Template diagnostics for components with external templates.
|
42318 | //
|
42319 | // When showing diagnostics for a TS source file, we want to only include kinds 1 and 2 -
|
42320 | // those diagnostics which are reported at a location within the TS file itself. Diagnostics
|
42321 | // for external templates will be shown when editing that template file (the `else` block)
|
42322 | // below.
|
42323 | //
|
42324 | // Currently, indirect inline template diagnostics (kind 3) are not shown at all by the
|
42325 | // Language Service, because there is no sensible location in the user's code for them. Such
|
42326 | // templates are an edge case, though, and should not be common.
|
42327 | //
|
42328 | // TODO(alxhub): figure out a good user experience for indirect template diagnostics and
|
42329 | // show them from within the Language Service.
|
42330 | diagnostics.push(...ngDiagnostics.filter(diag => diag.file !== undefined && diag.file.fileName === sourceFile.fileName));
|
42331 | }
|
42332 | }
|
42333 | else {
|
42334 | const components = compiler.getComponentsWithTemplateFile(fileName);
|
42335 | for (const component of components) {
|
42336 | if (ts.isClassDeclaration(component)) {
|
42337 | diagnostics.push(...ttc.getDiagnosticsForComponent(component));
|
42338 | }
|
42339 | }
|
42340 | }
|
42341 | this.compilerFactory.registerLastKnownProgram();
|
42342 | return diagnostics;
|
42343 | }
|
42344 | getDefinitionAndBoundSpan(fileName, position) {
|
42345 | return this.withCompiler((compiler) => {
|
42346 | if (!isInAngularContext(compiler.getNextProgram(), fileName, position)) {
|
42347 | return undefined;
|
42348 | }
|
42349 | return new DefinitionBuilder(this.tsLS, compiler)
|
42350 | .getDefinitionAndBoundSpan(fileName, position);
|
42351 | });
|
42352 | }
|
42353 | getTypeDefinitionAtPosition(fileName, position) {
|
42354 | return this.withCompiler((compiler) => {
|
42355 | if (!isTemplateContext(compiler.getNextProgram(), fileName, position)) {
|
42356 | return undefined;
|
42357 | }
|
42358 | return new DefinitionBuilder(this.tsLS, compiler)
|
42359 | .getTypeDefinitionsAtPosition(fileName, position);
|
42360 | });
|
42361 | }
|
42362 | getQuickInfoAtPosition(fileName, position) {
|
42363 | return this.withCompiler((compiler) => {
|
42364 | if (!isTemplateContext(compiler.getNextProgram(), fileName, position)) {
|
42365 | return undefined;
|
42366 | }
|
42367 | const templateInfo = getTemplateInfoAtPosition(fileName, position, compiler);
|
42368 | if (templateInfo === undefined) {
|
42369 | return undefined;
|
42370 | }
|
42371 | const positionDetails = getTargetAtPosition(templateInfo.template, position);
|
42372 | if (positionDetails === null) {
|
42373 | return undefined;
|
42374 | }
|
42375 | // Because we can only show 1 quick info, just use the bound attribute if the target is a two
|
42376 | // way binding. We may consider concatenating additional display parts from the other target
|
42377 | // nodes or representing the two way binding in some other manner in the future.
|
42378 | const node = positionDetails.context.kind === TargetNodeKind.TwoWayBindingContext ?
|
42379 | positionDetails.context.nodes[0] :
|
42380 | positionDetails.context.node;
|
42381 | return new QuickInfoBuilder(this.tsLS, compiler, templateInfo.component, node).get();
|
42382 | });
|
42383 | }
|
42384 | getReferencesAtPosition(fileName, position) {
|
42385 | const compiler = this.compilerFactory.getOrCreate();
|
42386 | const results = new ReferencesAndRenameBuilder(this.strategy, this.tsLS, compiler)
|
42387 | .getReferencesAtPosition(fileName, position);
|
42388 | this.compilerFactory.registerLastKnownProgram();
|
42389 | return results;
|
42390 | }
|
42391 | getRenameInfo(fileName, position) {
|
42392 | var _a, _b, _c;
|
42393 | const compiler = this.compilerFactory.getOrCreate();
|
42394 | const renameInfo = new ReferencesAndRenameBuilder(this.strategy, this.tsLS, compiler)
|
42395 | .getRenameInfo(absoluteFrom(fileName), position);
|
42396 | if (!renameInfo.canRename) {
|
42397 | return renameInfo;
|
42398 | }
|
42399 | const quickInfo = (_a = this.getQuickInfoAtPosition(fileName, position)) !== null && _a !== void 0 ? _a : this.tsLS.getQuickInfoAtPosition(fileName, position);
|
42400 | const kind = (_b = quickInfo === null || quickInfo === void 0 ? void 0 : quickInfo.kind) !== null && _b !== void 0 ? _b : ts.ScriptElementKind.unknown;
|
42401 | const kindModifiers = (_c = quickInfo === null || quickInfo === void 0 ? void 0 : quickInfo.kindModifiers) !== null && _c !== void 0 ? _c : ts.ScriptElementKind.unknown;
|
42402 | return Object.assign(Object.assign({}, renameInfo), { kind, kindModifiers });
|
42403 | }
|
42404 | findRenameLocations(fileName, position) {
|
42405 | const compiler = this.compilerFactory.getOrCreate();
|
42406 | const results = new ReferencesAndRenameBuilder(this.strategy, this.tsLS, compiler)
|
42407 | .findRenameLocations(fileName, position);
|
42408 | this.compilerFactory.registerLastKnownProgram();
|
42409 | return results;
|
42410 | }
|
42411 | getCompletionBuilder(fileName, position) {
|
42412 | const compiler = this.compilerFactory.getOrCreate();
|
42413 | const templateInfo = getTemplateInfoAtPosition(fileName, position, compiler);
|
42414 | if (templateInfo === undefined) {
|
42415 | return null;
|
42416 | }
|
42417 | const positionDetails = getTargetAtPosition(templateInfo.template, position);
|
42418 | if (positionDetails === null) {
|
42419 | return null;
|
42420 | }
|
42421 | // For two-way bindings, we actually only need to be concerned with the bound attribute because
|
42422 | // the bindings in the template are written with the attribute name, not the event name.
|
42423 | const node = positionDetails.context.kind === TargetNodeKind.TwoWayBindingContext ?
|
42424 | positionDetails.context.nodes[0] :
|
42425 | positionDetails.context.node;
|
42426 | return new CompletionBuilder(this.tsLS, compiler, templateInfo.component, node, positionDetails);
|
42427 | }
|
42428 | getCompletionsAtPosition(fileName, position, options) {
|
42429 | return this.withCompiler((compiler) => {
|
42430 | if (!isTemplateContext(compiler.getNextProgram(), fileName, position)) {
|
42431 | return undefined;
|
42432 | }
|
42433 | const builder = this.getCompletionBuilder(fileName, position);
|
42434 | if (builder === null) {
|
42435 | return undefined;
|
42436 | }
|
42437 | return builder.getCompletionsAtPosition(options);
|
42438 | });
|
42439 | }
|
42440 | getCompletionEntryDetails(fileName, position, entryName, formatOptions, preferences) {
|
42441 | return this.withCompiler((compiler) => {
|
42442 | if (!isTemplateContext(compiler.getNextProgram(), fileName, position)) {
|
42443 | return undefined;
|
42444 | }
|
42445 | const builder = this.getCompletionBuilder(fileName, position);
|
42446 | if (builder === null) {
|
42447 | return undefined;
|
42448 | }
|
42449 | return builder.getCompletionEntryDetails(entryName, formatOptions, preferences);
|
42450 | });
|
42451 | }
|
42452 | getCompletionEntrySymbol(fileName, position, entryName) {
|
42453 | return this.withCompiler((compiler) => {
|
42454 | if (!isTemplateContext(compiler.getNextProgram(), fileName, position)) {
|
42455 | return undefined;
|
42456 | }
|
42457 | const builder = this.getCompletionBuilder(fileName, position);
|
42458 | if (builder === null) {
|
42459 | return undefined;
|
42460 | }
|
42461 | const result = builder.getCompletionEntrySymbol(entryName);
|
42462 | this.compilerFactory.registerLastKnownProgram();
|
42463 | return result;
|
42464 | });
|
42465 | }
|
42466 | getTcb(fileName, position) {
|
42467 | return this.withCompiler(compiler => {
|
42468 | const templateInfo = getTemplateInfoAtPosition(fileName, position, compiler);
|
42469 | if (templateInfo === undefined) {
|
42470 | return undefined;
|
42471 | }
|
42472 | const tcb = compiler.getTemplateTypeChecker().getTypeCheckBlock(templateInfo.component);
|
42473 | if (tcb === null) {
|
42474 | return undefined;
|
42475 | }
|
42476 | const sf = tcb.getSourceFile();
|
42477 | let selections = [];
|
42478 | const target = getTargetAtPosition(templateInfo.template, position);
|
42479 | if (target !== null) {
|
42480 | let selectionSpans;
|
42481 | if ('nodes' in target.context) {
|
42482 | selectionSpans = target.context.nodes.map(n => n.sourceSpan);
|
42483 | }
|
42484 | else {
|
42485 | selectionSpans = [target.context.node.sourceSpan];
|
42486 | }
|
42487 | const selectionNodes = selectionSpans
|
42488 | .map(s => findFirstMatchingNode(tcb, {
|
42489 | withSpan: s,
|
42490 | filter: (node) => true,
|
42491 | }))
|
42492 | .filter((n) => n !== null);
|
42493 | selections = selectionNodes.map(n => {
|
42494 | return {
|
42495 | start: n.getStart(sf),
|
42496 | length: n.getEnd() - n.getStart(sf),
|
42497 | };
|
42498 | });
|
42499 | }
|
42500 | return {
|
42501 | fileName: sf.fileName,
|
42502 | content: sf.getFullText(),
|
42503 | selections,
|
42504 | };
|
42505 | });
|
42506 | }
|
42507 | withCompiler(p) {
|
42508 | const compiler = this.compilerFactory.getOrCreate();
|
42509 | const result = p(compiler);
|
42510 | this.compilerFactory.registerLastKnownProgram();
|
42511 | return result;
|
42512 | }
|
42513 | getCompilerOptionsDiagnostics() {
|
42514 | const project = this.project;
|
42515 | if (!(project instanceof ts.server.ConfiguredProject)) {
|
42516 | return [];
|
42517 | }
|
42518 | const diagnostics = [];
|
42519 | const configSourceFile = ts.readJsonConfigFile(project.getConfigFilePath(), (path) => project.readFile(path));
|
42520 | if (!this.options.strictTemplates && !this.options.fullTemplateTypeCheck) {
|
42521 | diagnostics.push({
|
42522 | messageText: 'Some language features are not available. ' +
|
42523 | 'To access all features, enable `strictTemplates` in `angularCompilerOptions`.',
|
42524 | category: ts.DiagnosticCategory.Suggestion,
|
42525 | code: ngErrorCode(ErrorCode.SUGGEST_STRICT_TEMPLATES),
|
42526 | file: configSourceFile,
|
42527 | start: undefined,
|
42528 | length: undefined,
|
42529 | });
|
42530 | }
|
42531 | const compiler = this.compilerFactory.getOrCreate();
|
42532 | diagnostics.push(...compiler.getOptionDiagnostics());
|
42533 | return diagnostics;
|
42534 | }
|
42535 | watchConfigFile(project) {
|
42536 | // TODO: Check the case when the project is disposed. An InferredProject
|
42537 | // could be disposed when a tsconfig.json is added to the workspace,
|
42538 | // in which case it becomes a ConfiguredProject (or vice-versa).
|
42539 | // We need to make sure that the FileWatcher is closed.
|
42540 | if (!(project instanceof ts.server.ConfiguredProject)) {
|
42541 | return;
|
42542 | }
|
42543 | const { host } = project.projectService;
|
42544 | host.watchFile(project.getConfigFilePath(), (fileName, eventKind) => {
|
42545 | project.log(`Config file changed: ${fileName}`);
|
42546 | if (eventKind === ts.FileWatcherEventKind.Changed) {
|
42547 | this.options = parseNgCompilerOptions(project, this.parseConfigHost, this.config);
|
42548 | logCompilerOptions(project, this.options);
|
42549 | }
|
42550 | });
|
42551 | }
|
42552 | }
|
42553 | function logCompilerOptions(project, options) {
|
42554 | const { logger } = project.projectService;
|
42555 | const projectName = project.getProjectName();
|
42556 | logger.info(`Angular compiler options for ${projectName}: ` + JSON.stringify(options, null, 2));
|
42557 | }
|
42558 | function parseNgCompilerOptions(project, host, config) {
|
42559 | if (!(project instanceof ts.server.ConfiguredProject)) {
|
42560 | return {};
|
42561 | }
|
42562 | const { options, errors } = readConfiguration(project.getConfigFilePath(), /* existingOptions */ undefined, host);
|
42563 | if (errors.length > 0) {
|
42564 | project.setProjectErrors(errors);
|
42565 | }
|
42566 | // Projects loaded into the Language Service often include test files which are not part of the
|
42567 | // app's main compilation unit, and these test files often include inline NgModules that declare
|
42568 | // components from the app. These declarations conflict with the main declarations of such
|
42569 | // components in the app's NgModules. This conflict is not normally present during regular
|
42570 | // compilation because the app and the tests are part of separate compilation units.
|
42571 | //
|
42572 | // As a temporary mitigation of this problem, we instruct the compiler to ignore classes which
|
42573 | // are not exported. In many cases, this ensures the test NgModules are ignored by the compiler
|
42574 | // and only the real component declaration is used.
|
42575 | options.compileNonExportedClasses = false;
|
42576 | // If `forceStrictTemplates` is true, always enable `strictTemplates`
|
42577 | // regardless of its value in tsconfig.json.
|
42578 | if (config.forceStrictTemplates === true) {
|
42579 | options.strictTemplates = true;
|
42580 | }
|
42581 | return options;
|
42582 | }
|
42583 | function createTypeCheckingProgramStrategy(project) {
|
42584 | return {
|
42585 | supportsInlineOperations: false,
|
42586 | shimPathForComponent(component) {
|
42587 | return TypeCheckShimGenerator.shimFor(absoluteFromSourceFile(component.getSourceFile()));
|
42588 | },
|
42589 | getProgram() {
|
42590 | const program = project.getLanguageService().getProgram();
|
42591 | if (!program) {
|
42592 | throw new Error('Language service does not have a program!');
|
42593 | }
|
42594 | return program;
|
42595 | },
|
42596 | updateFiles(contents) {
|
42597 | for (const [fileName, newText] of contents) {
|
42598 | const scriptInfo = getOrCreateTypeCheckScriptInfo(project, fileName);
|
42599 | const snapshot = scriptInfo.getSnapshot();
|
42600 | const length = snapshot.getLength();
|
42601 | scriptInfo.editContent(0, length, newText);
|
42602 | }
|
42603 | },
|
42604 | };
|
42605 | }
|
42606 | function getOrCreateTypeCheckScriptInfo(project, tcf) {
|
42607 | // First check if there is already a ScriptInfo for the tcf
|
42608 | const { projectService } = project;
|
42609 | let scriptInfo = projectService.getScriptInfo(tcf);
|
42610 | if (!scriptInfo) {
|
42611 | // ScriptInfo needs to be opened by client to be able to set its user-defined
|
42612 | // content. We must also provide file content, otherwise the service will
|
42613 | // attempt to fetch the content from disk and fail.
|
42614 | scriptInfo = projectService.getOrCreateScriptInfoForNormalizedPath(ts.server.toNormalizedPath(tcf), true, // openedByClient
|
42615 | '', // fileContent
|
42616 | // script info added by plugins should be marked as external, see
|
42617 | // https://github.com/microsoft/TypeScript/blob/b217f22e798c781f55d17da72ed099a9dee5c650/src/compiler/program.ts#L1897-L1899
|
42618 | ts.ScriptKind.External);
|
42619 | if (!scriptInfo) {
|
42620 | throw new Error(`Failed to create script info for ${tcf}`);
|
42621 | }
|
42622 | }
|
42623 | // Add ScriptInfo to project if it's missing. A ScriptInfo needs to be part of
|
42624 | // the project so that it becomes part of the program.
|
42625 | if (!project.containsScriptInfo(scriptInfo)) {
|
42626 | project.addRoot(scriptInfo);
|
42627 | }
|
42628 | return scriptInfo;
|
42629 | }
|
42630 | function isTemplateContext(program, fileName, position) {
|
42631 | if (!isTypeScriptFile(fileName)) {
|
42632 | // If we aren't in a TS file, we must be in an HTML file, which we treat as template context
|
42633 | return true;
|
42634 | }
|
42635 | const node = findTightestNodeAtPosition(program, fileName, position);
|
42636 | if (node === undefined) {
|
42637 | return false;
|
42638 | }
|
42639 | let asgn = getPropertyAssignmentFromValue(node, 'template');
|
42640 | if (asgn === null) {
|
42641 | return false;
|
42642 | }
|
42643 | return getClassDeclFromDecoratorProp(asgn) !== null;
|
42644 | }
|
42645 | function isInAngularContext(program, fileName, position) {
|
42646 | var _a, _b;
|
42647 | if (!isTypeScriptFile(fileName)) {
|
42648 | return true;
|
42649 | }
|
42650 | const node = findTightestNodeAtPosition(program, fileName, position);
|
42651 | if (node === undefined) {
|
42652 | return false;
|
42653 | }
|
42654 | const asgn = (_b = (_a = getPropertyAssignmentFromValue(node, 'template')) !== null && _a !== void 0 ? _a : getPropertyAssignmentFromValue(node, 'templateUrl')) !== null && _b !== void 0 ? _b : getPropertyAssignmentFromValue(node.parent, 'styleUrls');
|
42655 | return asgn !== null && getClassDeclFromDecoratorProp(asgn) !== null;
|
42656 | }
|
42657 | function findTightestNodeAtPosition(program, fileName, position) {
|
42658 | const sourceFile = program.getSourceFile(fileName);
|
42659 | if (sourceFile === undefined) {
|
42660 | return undefined;
|
42661 | }
|
42662 | return findTightestNode(sourceFile, position);
|
42663 | }
|
42664 |
|
42665 | /**
|
42666 | * @license
|
42667 | * Copyright Google LLC All Rights Reserved.
|
42668 | *
|
42669 | * Use of this source code is governed by an MIT-style license that can be
|
42670 | * found in the LICENSE file at https://angular.io/license
|
42671 | */
|
42672 | function create(info) {
|
42673 | const { project, languageService: tsLS, config } = info;
|
42674 | const angularOnly = (config === null || config === void 0 ? void 0 : config.angularOnly) === true;
|
42675 | const ngLS = new LanguageService(project, tsLS, config);
|
42676 | function getSemanticDiagnostics(fileName) {
|
42677 | const diagnostics = [];
|
42678 | if (!angularOnly) {
|
42679 | diagnostics.push(...tsLS.getSemanticDiagnostics(fileName));
|
42680 | }
|
42681 | diagnostics.push(...ngLS.getSemanticDiagnostics(fileName));
|
42682 | return diagnostics;
|
42683 | }
|
42684 | function getQuickInfoAtPosition(fileName, position) {
|
42685 | var _a;
|
42686 | if (angularOnly) {
|
42687 | return ngLS.getQuickInfoAtPosition(fileName, position);
|
42688 | }
|
42689 | else {
|
42690 | // If TS could answer the query, then return that result. Otherwise, return from Angular LS.
|
42691 | return (_a = tsLS.getQuickInfoAtPosition(fileName, position)) !== null && _a !== void 0 ? _a : ngLS.getQuickInfoAtPosition(fileName, position);
|
42692 | }
|
42693 | }
|
42694 | function getTypeDefinitionAtPosition(fileName, position) {
|
42695 | var _a;
|
42696 | if (angularOnly) {
|
42697 | return ngLS.getTypeDefinitionAtPosition(fileName, position);
|
42698 | }
|
42699 | else {
|
42700 | // If TS could answer the query, then return that result. Otherwise, return from Angular LS.
|
42701 | return (_a = tsLS.getTypeDefinitionAtPosition(fileName, position)) !== null && _a !== void 0 ? _a : ngLS.getTypeDefinitionAtPosition(fileName, position);
|
42702 | }
|
42703 | }
|
42704 | function getDefinitionAndBoundSpan(fileName, position) {
|
42705 | var _a;
|
42706 | if (angularOnly) {
|
42707 | return ngLS.getDefinitionAndBoundSpan(fileName, position);
|
42708 | }
|
42709 | else {
|
42710 | // If TS could answer the query, then return that result. Otherwise, return from Angular LS.
|
42711 | return (_a = tsLS.getDefinitionAndBoundSpan(fileName, position)) !== null && _a !== void 0 ? _a : ngLS.getDefinitionAndBoundSpan(fileName, position);
|
42712 | }
|
42713 | }
|
42714 | function getReferencesAtPosition(fileName, position) {
|
42715 | return ngLS.getReferencesAtPosition(fileName, position);
|
42716 | }
|
42717 | function findRenameLocations(fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename) {
|
42718 | // Most operations combine results from all extensions. However, rename locations are exclusive
|
42719 | // (results from only one extension are used) so our rename locations are a superset of the TS
|
42720 | // rename locations. As a result, we do not check the `angularOnly` flag here because we always
|
42721 | // want to include results at TS locations as well as locations in templates.
|
42722 | return ngLS.findRenameLocations(fileName, position);
|
42723 | }
|
42724 | function getRenameInfo(fileName, position) {
|
42725 | // See the comment in `findRenameLocations` explaining why we don't check the `angularOnly`
|
42726 | // flag.
|
42727 | return ngLS.getRenameInfo(fileName, position);
|
42728 | }
|
42729 | function getCompletionsAtPosition(fileName, position, options) {
|
42730 | var _a;
|
42731 | if (angularOnly) {
|
42732 | return ngLS.getCompletionsAtPosition(fileName, position, options);
|
42733 | }
|
42734 | else {
|
42735 | // If TS could answer the query, then return that result. Otherwise, return from Angular LS.
|
42736 | return (_a = tsLS.getCompletionsAtPosition(fileName, position, options)) !== null && _a !== void 0 ? _a : ngLS.getCompletionsAtPosition(fileName, position, options);
|
42737 | }
|
42738 | }
|
42739 | function getCompletionEntryDetails(fileName, position, entryName, formatOptions, source, preferences) {
|
42740 | var _a;
|
42741 | if (angularOnly) {
|
42742 | return ngLS.getCompletionEntryDetails(fileName, position, entryName, formatOptions, preferences);
|
42743 | }
|
42744 | else {
|
42745 | // If TS could answer the query, then return that result. Otherwise, return from Angular LS.
|
42746 | return (_a = tsLS.getCompletionEntryDetails(fileName, position, entryName, formatOptions, source, preferences)) !== null && _a !== void 0 ? _a : ngLS.getCompletionEntryDetails(fileName, position, entryName, formatOptions, preferences);
|
42747 | }
|
42748 | }
|
42749 | function getCompletionEntrySymbol(fileName, position, name, source) {
|
42750 | var _a;
|
42751 | if (angularOnly) {
|
42752 | return ngLS.getCompletionEntrySymbol(fileName, position, name);
|
42753 | }
|
42754 | else {
|
42755 | // If TS could answer the query, then return that result. Otherwise, return from Angular LS.
|
42756 | return (_a = tsLS.getCompletionEntrySymbol(fileName, position, name, source)) !== null && _a !== void 0 ? _a : ngLS.getCompletionEntrySymbol(fileName, position, name);
|
42757 | }
|
42758 | }
|
42759 | /**
|
42760 | * Gets global diagnostics related to the program configuration and compiler options.
|
42761 | */
|
42762 | function getCompilerOptionsDiagnostics() {
|
42763 | const diagnostics = [];
|
42764 | if (!angularOnly) {
|
42765 | diagnostics.push(...tsLS.getCompilerOptionsDiagnostics());
|
42766 | }
|
42767 | diagnostics.push(...ngLS.getCompilerOptionsDiagnostics());
|
42768 | return diagnostics;
|
42769 | }
|
42770 | function getTcb(fileName, position) {
|
42771 | return ngLS.getTcb(fileName, position);
|
42772 | }
|
42773 | return Object.assign(Object.assign({}, tsLS), { getSemanticDiagnostics,
|
42774 | getTypeDefinitionAtPosition,
|
42775 | getQuickInfoAtPosition,
|
42776 | getDefinitionAndBoundSpan,
|
42777 | getReferencesAtPosition,
|
42778 | findRenameLocations,
|
42779 | getRenameInfo,
|
42780 | getCompletionsAtPosition,
|
42781 | getCompletionEntryDetails,
|
42782 | getCompletionEntrySymbol,
|
42783 | getTcb,
|
42784 | getCompilerOptionsDiagnostics });
|
42785 | }
|
42786 | function getExternalFiles(project) {
|
42787 | if (!project.hasRoots()) {
|
42788 | return []; // project has not been initialized
|
42789 | }
|
42790 | const typecheckFiles = [];
|
42791 | for (const scriptInfo of project.getScriptInfos()) {
|
42792 | if (scriptInfo.scriptKind === ts.ScriptKind.External) {
|
42793 | // script info for typecheck file is marked as external, see
|
42794 | // getOrCreateTypeCheckScriptInfo() in
|
42795 | // packages/language-service/ivy/language_service.ts
|
42796 | typecheckFiles.push(scriptInfo.fileName);
|
42797 | }
|
42798 | }
|
42799 | return typecheckFiles;
|
42800 | }
|
42801 |
|
42802 | exports.create = create;
|
42803 | exports.getExternalFiles = getExternalFiles;
|
42804 |
|
42805 | Object.defineProperty(exports, '__esModule', { value: true });
|
42806 |
|
42807 | });
|
42808 | //# sourceMappingURL=ivy.js.map
|