source: imaps-frontend/node_modules/enhanced-resolve/lib/ResolverFactory.js@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 4 days ago

F4 Finalna Verzija

  • Property mode set to 100644
File size: 21.0 KB
RevLine 
[79a0317]1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const versions = require("process").versions;
9const Resolver = require("./Resolver");
10const { getType, PathType } = require("./util/path");
11
12const SyncAsyncFileSystemDecorator = require("./SyncAsyncFileSystemDecorator");
13
14const AliasFieldPlugin = require("./AliasFieldPlugin");
15const AliasPlugin = require("./AliasPlugin");
16const AppendPlugin = require("./AppendPlugin");
17const ConditionalPlugin = require("./ConditionalPlugin");
18const DescriptionFilePlugin = require("./DescriptionFilePlugin");
19const DirectoryExistsPlugin = require("./DirectoryExistsPlugin");
20const ExportsFieldPlugin = require("./ExportsFieldPlugin");
21const ExtensionAliasPlugin = require("./ExtensionAliasPlugin");
22const FileExistsPlugin = require("./FileExistsPlugin");
23const ImportsFieldPlugin = require("./ImportsFieldPlugin");
24const JoinRequestPartPlugin = require("./JoinRequestPartPlugin");
25const JoinRequestPlugin = require("./JoinRequestPlugin");
26const MainFieldPlugin = require("./MainFieldPlugin");
27const ModulesInHierarchicalDirectoriesPlugin = require("./ModulesInHierarchicalDirectoriesPlugin");
28const ModulesInRootPlugin = require("./ModulesInRootPlugin");
29const NextPlugin = require("./NextPlugin");
30const ParsePlugin = require("./ParsePlugin");
31const PnpPlugin = require("./PnpPlugin");
32const RestrictionsPlugin = require("./RestrictionsPlugin");
33const ResultPlugin = require("./ResultPlugin");
34const RootsPlugin = require("./RootsPlugin");
35const SelfReferencePlugin = require("./SelfReferencePlugin");
36const SymlinkPlugin = require("./SymlinkPlugin");
37const TryNextPlugin = require("./TryNextPlugin");
38const UnsafeCachePlugin = require("./UnsafeCachePlugin");
39const UseFilePlugin = require("./UseFilePlugin");
40
41/** @typedef {import("./AliasPlugin").AliasOption} AliasOptionEntry */
42/** @typedef {import("./ExtensionAliasPlugin").ExtensionAliasOption} ExtensionAliasOption */
43/** @typedef {import("./PnpPlugin").PnpApiImpl} PnpApi */
44/** @typedef {import("./Resolver").EnsuredHooks} EnsuredHooks */
45/** @typedef {import("./Resolver").FileSystem} FileSystem */
46/** @typedef {import("./Resolver").KnownHooks} KnownHooks */
47/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
48/** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
49
50/** @typedef {string|string[]|false} AliasOptionNewRequest */
51/** @typedef {{[k: string]: AliasOptionNewRequest}} AliasOptions */
52/** @typedef {{[k: string]: string|string[] }} ExtensionAliasOptions */
53/** @typedef {false | 0 | "" | null | undefined} Falsy */
54/** @typedef {{apply: function(Resolver): void} | (function(this: Resolver, Resolver): void) | Falsy} Plugin */
55
56/**
57 * @typedef {Object} UserResolveOptions
58 * @property {(AliasOptions | AliasOptionEntry[])=} alias A list of module alias configurations or an object which maps key to value
59 * @property {(AliasOptions | AliasOptionEntry[])=} fallback A list of module alias configurations or an object which maps key to value, applied only after modules option
60 * @property {ExtensionAliasOptions=} extensionAlias An object which maps extension to extension aliases
61 * @property {(string | string[])[]=} aliasFields A list of alias fields in description files
62 * @property {(function(ResolveRequest): boolean)=} cachePredicate A function which decides whether a request should be cached or not. An object is passed with at least `path` and `request` properties.
63 * @property {boolean=} cacheWithContext Whether or not the unsafeCache should include request context as part of the cache key.
64 * @property {string[]=} descriptionFiles A list of description files to read from
65 * @property {string[]=} conditionNames A list of exports field condition names.
66 * @property {boolean=} enforceExtension Enforce that a extension from extensions must be used
67 * @property {(string | string[])[]=} exportsFields A list of exports fields in description files
68 * @property {(string | string[])[]=} importsFields A list of imports fields in description files
69 * @property {string[]=} extensions A list of extensions which should be tried for files
70 * @property {FileSystem} fileSystem The file system which should be used
71 * @property {(object | boolean)=} unsafeCache Use this cache object to unsafely cache the successful requests
72 * @property {boolean=} symlinks Resolve symlinks to their symlinked location
73 * @property {Resolver=} resolver A prepared Resolver to which the plugins are attached
74 * @property {string[] | string=} modules A list of directories to resolve modules from, can be absolute path or folder name
75 * @property {(string | string[] | {name: string | string[], forceRelative: boolean})[]=} mainFields A list of main fields in description files
76 * @property {string[]=} mainFiles A list of main files in directories
77 * @property {Plugin[]=} plugins A list of additional resolve plugins which should be applied
78 * @property {PnpApi | null=} pnpApi A PnP API that should be used - null is "never", undefined is "auto"
79 * @property {string[]=} roots A list of root paths
80 * @property {boolean=} fullySpecified The request is already fully specified and no extensions or directories are resolved for it
81 * @property {boolean=} resolveToContext Resolve to a context instead of a file
82 * @property {(string|RegExp)[]=} restrictions A list of resolve restrictions
83 * @property {boolean=} useSyncFileSystemCalls Use only the sync constraints of the file system calls
84 * @property {boolean=} preferRelative Prefer to resolve module requests as relative requests before falling back to modules
85 * @property {boolean=} preferAbsolute Prefer to resolve server-relative urls as absolute paths before falling back to resolve in roots
86 */
87
88/**
89 * @typedef {Object} ResolveOptions
90 * @property {AliasOptionEntry[]} alias
91 * @property {AliasOptionEntry[]} fallback
92 * @property {Set<string | string[]>} aliasFields
93 * @property {ExtensionAliasOption[]} extensionAlias
94 * @property {(function(ResolveRequest): boolean)} cachePredicate
95 * @property {boolean} cacheWithContext
96 * @property {Set<string>} conditionNames A list of exports field condition names.
97 * @property {string[]} descriptionFiles
98 * @property {boolean} enforceExtension
99 * @property {Set<string | string[]>} exportsFields
100 * @property {Set<string | string[]>} importsFields
101 * @property {Set<string>} extensions
102 * @property {FileSystem} fileSystem
103 * @property {object | false} unsafeCache
104 * @property {boolean} symlinks
105 * @property {Resolver=} resolver
106 * @property {Array<string | string[]>} modules
107 * @property {{name: string[], forceRelative: boolean}[]} mainFields
108 * @property {Set<string>} mainFiles
109 * @property {Plugin[]} plugins
110 * @property {PnpApi | null} pnpApi
111 * @property {Set<string>} roots
112 * @property {boolean} fullySpecified
113 * @property {boolean} resolveToContext
114 * @property {Set<string|RegExp>} restrictions
115 * @property {boolean} preferRelative
116 * @property {boolean} preferAbsolute
117 */
118
119/**
120 * @param {PnpApi | null=} option option
121 * @returns {PnpApi | null} processed option
122 */
123function processPnpApiOption(option) {
124 if (
125 option === undefined &&
126 /** @type {NodeJS.ProcessVersions & {pnp: string}} */ versions.pnp
127 ) {
128 const _findPnpApi =
129 /** @type {function(string): PnpApi | null}} */
130 (
131 // @ts-ignore
132 require("module").findPnpApi
133 );
134
135 if (_findPnpApi) {
136 return {
137 resolveToUnqualified(request, issuer, opts) {
138 const pnpapi = _findPnpApi(issuer);
139
140 if (!pnpapi) {
141 // Issuer isn't managed by PnP
142 return null;
143 }
144
145 return pnpapi.resolveToUnqualified(request, issuer, opts);
146 }
147 };
148 }
149 }
150
151 return option || null;
152}
153
154/**
155 * @param {AliasOptions | AliasOptionEntry[] | undefined} alias alias
156 * @returns {AliasOptionEntry[]} normalized aliases
157 */
158function normalizeAlias(alias) {
159 return typeof alias === "object" && !Array.isArray(alias) && alias !== null
160 ? Object.keys(alias).map(key => {
161 /** @type {AliasOptionEntry} */
162 const obj = { name: key, onlyModule: false, alias: alias[key] };
163
164 if (/\$$/.test(key)) {
165 obj.onlyModule = true;
166 obj.name = key.slice(0, -1);
167 }
168
169 return obj;
170 })
171 : /** @type {Array<AliasOptionEntry>} */ (alias) || [];
172}
173
174/**
175 * @param {UserResolveOptions} options input options
176 * @returns {ResolveOptions} output options
177 */
178function createOptions(options) {
179 const mainFieldsSet = new Set(options.mainFields || ["main"]);
180 /** @type {ResolveOptions["mainFields"]} */
181 const mainFields = [];
182
183 for (const item of mainFieldsSet) {
184 if (typeof item === "string") {
185 mainFields.push({
186 name: [item],
187 forceRelative: true
188 });
189 } else if (Array.isArray(item)) {
190 mainFields.push({
191 name: item,
192 forceRelative: true
193 });
194 } else {
195 mainFields.push({
196 name: Array.isArray(item.name) ? item.name : [item.name],
197 forceRelative: item.forceRelative
198 });
199 }
200 }
201
202 return {
203 alias: normalizeAlias(options.alias),
204 fallback: normalizeAlias(options.fallback),
205 aliasFields: new Set(options.aliasFields),
206 cachePredicate:
207 options.cachePredicate ||
208 function () {
209 return true;
210 },
211 cacheWithContext:
212 typeof options.cacheWithContext !== "undefined"
213 ? options.cacheWithContext
214 : true,
215 exportsFields: new Set(options.exportsFields || ["exports"]),
216 importsFields: new Set(options.importsFields || ["imports"]),
217 conditionNames: new Set(options.conditionNames),
218 descriptionFiles: Array.from(
219 new Set(options.descriptionFiles || ["package.json"])
220 ),
221 enforceExtension:
222 options.enforceExtension === undefined
223 ? options.extensions && options.extensions.includes("")
224 ? true
225 : false
226 : options.enforceExtension,
227 extensions: new Set(options.extensions || [".js", ".json", ".node"]),
228 extensionAlias: options.extensionAlias
229 ? Object.keys(options.extensionAlias).map(k => ({
230 extension: k,
231 alias: /** @type {ExtensionAliasOptions} */ (options.extensionAlias)[
232 k
233 ]
234 }))
235 : [],
236 fileSystem: options.useSyncFileSystemCalls
237 ? new SyncAsyncFileSystemDecorator(
238 /** @type {SyncFileSystem} */ (
239 /** @type {unknown} */ (options.fileSystem)
240 )
241 )
242 : options.fileSystem,
243 unsafeCache:
244 options.unsafeCache && typeof options.unsafeCache !== "object"
245 ? {}
246 : options.unsafeCache || false,
247 symlinks: typeof options.symlinks !== "undefined" ? options.symlinks : true,
248 resolver: options.resolver,
249 modules: mergeFilteredToArray(
250 Array.isArray(options.modules)
251 ? options.modules
252 : options.modules
253 ? [options.modules]
254 : ["node_modules"],
255 item => {
256 const type = getType(item);
257 return type === PathType.Normal || type === PathType.Relative;
258 }
259 ),
260 mainFields,
261 mainFiles: new Set(options.mainFiles || ["index"]),
262 plugins: options.plugins || [],
263 pnpApi: processPnpApiOption(options.pnpApi),
264 roots: new Set(options.roots || undefined),
265 fullySpecified: options.fullySpecified || false,
266 resolveToContext: options.resolveToContext || false,
267 preferRelative: options.preferRelative || false,
268 preferAbsolute: options.preferAbsolute || false,
269 restrictions: new Set(options.restrictions)
270 };
271}
272
273/**
274 * @param {UserResolveOptions} options resolve options
275 * @returns {Resolver} created resolver
276 */
277exports.createResolver = function (options) {
278 const normalizedOptions = createOptions(options);
279
280 const {
281 alias,
282 fallback,
283 aliasFields,
284 cachePredicate,
285 cacheWithContext,
286 conditionNames,
287 descriptionFiles,
288 enforceExtension,
289 exportsFields,
290 extensionAlias,
291 importsFields,
292 extensions,
293 fileSystem,
294 fullySpecified,
295 mainFields,
296 mainFiles,
297 modules,
298 plugins: userPlugins,
299 pnpApi,
300 resolveToContext,
301 preferRelative,
302 preferAbsolute,
303 symlinks,
304 unsafeCache,
305 resolver: customResolver,
306 restrictions,
307 roots
308 } = normalizedOptions;
309
310 const plugins = userPlugins.slice();
311
312 const resolver = customResolver
313 ? customResolver
314 : new Resolver(fileSystem, normalizedOptions);
315
316 //// pipeline ////
317
318 resolver.ensureHook("resolve");
319 resolver.ensureHook("internalResolve");
320 resolver.ensureHook("newInternalResolve");
321 resolver.ensureHook("parsedResolve");
322 resolver.ensureHook("describedResolve");
323 resolver.ensureHook("rawResolve");
324 resolver.ensureHook("normalResolve");
325 resolver.ensureHook("internal");
326 resolver.ensureHook("rawModule");
327 resolver.ensureHook("alternateRawModule");
328 resolver.ensureHook("module");
329 resolver.ensureHook("resolveAsModule");
330 resolver.ensureHook("undescribedResolveInPackage");
331 resolver.ensureHook("resolveInPackage");
332 resolver.ensureHook("resolveInExistingDirectory");
333 resolver.ensureHook("relative");
334 resolver.ensureHook("describedRelative");
335 resolver.ensureHook("directory");
336 resolver.ensureHook("undescribedExistingDirectory");
337 resolver.ensureHook("existingDirectory");
338 resolver.ensureHook("undescribedRawFile");
339 resolver.ensureHook("rawFile");
340 resolver.ensureHook("file");
341 resolver.ensureHook("finalFile");
342 resolver.ensureHook("existingFile");
343 resolver.ensureHook("resolved");
344
345 // TODO remove in next major
346 // cspell:word Interal
347 // Backward-compat
348 // @ts-ignore
349 resolver.hooks.newInteralResolve = resolver.hooks.newInternalResolve;
350
351 // resolve
352 for (const { source, resolveOptions } of [
353 { source: "resolve", resolveOptions: { fullySpecified } },
354 { source: "internal-resolve", resolveOptions: { fullySpecified: false } }
355 ]) {
356 if (unsafeCache) {
357 plugins.push(
358 new UnsafeCachePlugin(
359 source,
360 cachePredicate,
361 /** @type {import("./UnsafeCachePlugin").Cache} */ (unsafeCache),
362 cacheWithContext,
363 `new-${source}`
364 )
365 );
366 plugins.push(
367 new ParsePlugin(`new-${source}`, resolveOptions, "parsed-resolve")
368 );
369 } else {
370 plugins.push(new ParsePlugin(source, resolveOptions, "parsed-resolve"));
371 }
372 }
373
374 // parsed-resolve
375 plugins.push(
376 new DescriptionFilePlugin(
377 "parsed-resolve",
378 descriptionFiles,
379 false,
380 "described-resolve"
381 )
382 );
383 plugins.push(new NextPlugin("after-parsed-resolve", "described-resolve"));
384
385 // described-resolve
386 plugins.push(new NextPlugin("described-resolve", "raw-resolve"));
387 if (fallback.length > 0) {
388 plugins.push(
389 new AliasPlugin("described-resolve", fallback, "internal-resolve")
390 );
391 }
392
393 // raw-resolve
394 if (alias.length > 0) {
395 plugins.push(new AliasPlugin("raw-resolve", alias, "internal-resolve"));
396 }
397 aliasFields.forEach(item => {
398 plugins.push(new AliasFieldPlugin("raw-resolve", item, "internal-resolve"));
399 });
400 extensionAlias.forEach(item =>
401 plugins.push(
402 new ExtensionAliasPlugin("raw-resolve", item, "normal-resolve")
403 )
404 );
405 plugins.push(new NextPlugin("raw-resolve", "normal-resolve"));
406
407 // normal-resolve
408 if (preferRelative) {
409 plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
410 }
411 plugins.push(
412 new ConditionalPlugin(
413 "after-normal-resolve",
414 { module: true },
415 "resolve as module",
416 false,
417 "raw-module"
418 )
419 );
420 plugins.push(
421 new ConditionalPlugin(
422 "after-normal-resolve",
423 { internal: true },
424 "resolve as internal import",
425 false,
426 "internal"
427 )
428 );
429 if (preferAbsolute) {
430 plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
431 }
432 if (roots.size > 0) {
433 plugins.push(new RootsPlugin("after-normal-resolve", roots, "relative"));
434 }
435 if (!preferRelative && !preferAbsolute) {
436 plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
437 }
438
439 // internal
440 importsFields.forEach(importsField => {
441 plugins.push(
442 new ImportsFieldPlugin(
443 "internal",
444 conditionNames,
445 importsField,
446 "relative",
447 "internal-resolve"
448 )
449 );
450 });
451
452 // raw-module
453 exportsFields.forEach(exportsField => {
454 plugins.push(
455 new SelfReferencePlugin("raw-module", exportsField, "resolve-as-module")
456 );
457 });
458 modules.forEach(item => {
459 if (Array.isArray(item)) {
460 if (item.includes("node_modules") && pnpApi) {
461 plugins.push(
462 new ModulesInHierarchicalDirectoriesPlugin(
463 "raw-module",
464 item.filter(i => i !== "node_modules"),
465 "module"
466 )
467 );
468 plugins.push(
469 new PnpPlugin(
470 "raw-module",
471 pnpApi,
472 "undescribed-resolve-in-package",
473 "alternate-raw-module"
474 )
475 );
476
477 plugins.push(
478 new ModulesInHierarchicalDirectoriesPlugin(
479 "alternate-raw-module",
480 ["node_modules"],
481 "module"
482 )
483 );
484 } else {
485 plugins.push(
486 new ModulesInHierarchicalDirectoriesPlugin(
487 "raw-module",
488 item,
489 "module"
490 )
491 );
492 }
493 } else {
494 plugins.push(new ModulesInRootPlugin("raw-module", item, "module"));
495 }
496 });
497
498 // module
499 plugins.push(new JoinRequestPartPlugin("module", "resolve-as-module"));
500
501 // resolve-as-module
502 if (!resolveToContext) {
503 plugins.push(
504 new ConditionalPlugin(
505 "resolve-as-module",
506 { directory: false, request: "." },
507 "single file module",
508 true,
509 "undescribed-raw-file"
510 )
511 );
512 }
513 plugins.push(
514 new DirectoryExistsPlugin(
515 "resolve-as-module",
516 "undescribed-resolve-in-package"
517 )
518 );
519
520 // undescribed-resolve-in-package
521 plugins.push(
522 new DescriptionFilePlugin(
523 "undescribed-resolve-in-package",
524 descriptionFiles,
525 false,
526 "resolve-in-package"
527 )
528 );
529 plugins.push(
530 new NextPlugin("after-undescribed-resolve-in-package", "resolve-in-package")
531 );
532
533 // resolve-in-package
534 exportsFields.forEach(exportsField => {
535 plugins.push(
536 new ExportsFieldPlugin(
537 "resolve-in-package",
538 conditionNames,
539 exportsField,
540 "relative"
541 )
542 );
543 });
544 plugins.push(
545 new NextPlugin("resolve-in-package", "resolve-in-existing-directory")
546 );
547
548 // resolve-in-existing-directory
549 plugins.push(
550 new JoinRequestPlugin("resolve-in-existing-directory", "relative")
551 );
552
553 // relative
554 plugins.push(
555 new DescriptionFilePlugin(
556 "relative",
557 descriptionFiles,
558 true,
559 "described-relative"
560 )
561 );
562 plugins.push(new NextPlugin("after-relative", "described-relative"));
563
564 // described-relative
565 if (resolveToContext) {
566 plugins.push(new NextPlugin("described-relative", "directory"));
567 } else {
568 plugins.push(
569 new ConditionalPlugin(
570 "described-relative",
571 { directory: false },
572 null,
573 true,
574 "raw-file"
575 )
576 );
577 plugins.push(
578 new ConditionalPlugin(
579 "described-relative",
580 { fullySpecified: false },
581 "as directory",
582 true,
583 "directory"
584 )
585 );
586 }
587
588 // directory
589 plugins.push(
590 new DirectoryExistsPlugin("directory", "undescribed-existing-directory")
591 );
592
593 if (resolveToContext) {
594 // undescribed-existing-directory
595 plugins.push(new NextPlugin("undescribed-existing-directory", "resolved"));
596 } else {
597 // undescribed-existing-directory
598 plugins.push(
599 new DescriptionFilePlugin(
600 "undescribed-existing-directory",
601 descriptionFiles,
602 false,
603 "existing-directory"
604 )
605 );
606 mainFiles.forEach(item => {
607 plugins.push(
608 new UseFilePlugin(
609 "undescribed-existing-directory",
610 item,
611 "undescribed-raw-file"
612 )
613 );
614 });
615
616 // described-existing-directory
617 mainFields.forEach(item => {
618 plugins.push(
619 new MainFieldPlugin(
620 "existing-directory",
621 item,
622 "resolve-in-existing-directory"
623 )
624 );
625 });
626 mainFiles.forEach(item => {
627 plugins.push(
628 new UseFilePlugin("existing-directory", item, "undescribed-raw-file")
629 );
630 });
631
632 // undescribed-raw-file
633 plugins.push(
634 new DescriptionFilePlugin(
635 "undescribed-raw-file",
636 descriptionFiles,
637 true,
638 "raw-file"
639 )
640 );
641 plugins.push(new NextPlugin("after-undescribed-raw-file", "raw-file"));
642
643 // raw-file
644 plugins.push(
645 new ConditionalPlugin(
646 "raw-file",
647 { fullySpecified: true },
648 null,
649 false,
650 "file"
651 )
652 );
653 if (!enforceExtension) {
654 plugins.push(new TryNextPlugin("raw-file", "no extension", "file"));
655 }
656 extensions.forEach(item => {
657 plugins.push(new AppendPlugin("raw-file", item, "file"));
658 });
659
660 // file
661 if (alias.length > 0)
662 plugins.push(new AliasPlugin("file", alias, "internal-resolve"));
663 aliasFields.forEach(item => {
664 plugins.push(new AliasFieldPlugin("file", item, "internal-resolve"));
665 });
666 plugins.push(new NextPlugin("file", "final-file"));
667
668 // final-file
669 plugins.push(new FileExistsPlugin("final-file", "existing-file"));
670
671 // existing-file
672 if (symlinks)
673 plugins.push(new SymlinkPlugin("existing-file", "existing-file"));
674 plugins.push(new NextPlugin("existing-file", "resolved"));
675 }
676
677 const resolved =
678 /** @type {KnownHooks & EnsuredHooks} */
679 (resolver.hooks).resolved;
680
681 // resolved
682 if (restrictions.size > 0) {
683 plugins.push(new RestrictionsPlugin(resolved, restrictions));
684 }
685
686 plugins.push(new ResultPlugin(resolved));
687
688 //// RESOLVER ////
689
690 for (const plugin of plugins) {
691 if (typeof plugin === "function") {
692 /** @type {function(this: Resolver, Resolver): void} */
693 (plugin).call(resolver, resolver);
694 } else if (plugin) {
695 plugin.apply(resolver);
696 }
697 }
698
699 return resolver;
700};
701
702/**
703 * Merging filtered elements
704 * @param {string[]} array source array
705 * @param {function(string): boolean} filter predicate
706 * @returns {Array<string | string[]>} merge result
707 */
708function mergeFilteredToArray(array, filter) {
709 /** @type {Array<string | string[]>} */
710 const result = [];
711 const set = new Set(array);
712
713 for (const item of set) {
714 if (filter(item)) {
715 const lastElement =
716 result.length > 0 ? result[result.length - 1] : undefined;
717 if (Array.isArray(lastElement)) {
718 lastElement.push(item);
719 } else {
720 result.push([item]);
721 }
722 } else {
723 result.push(item);
724 }
725 }
726
727 return result;
728}
Note: See TracBrowser for help on using the repository browser.