5743 lines
226 KiB
JavaScript
Executable File
5743 lines
226 KiB
JavaScript
Executable File
|
|
import {createRequire as __cjsCompatRequire} from 'module';
|
|
const require = __cjsCompatRequire(import.meta.url);
|
|
|
|
import {
|
|
angularJitApplicationTransform
|
|
} from "./chunk-XC6P36QJ.js";
|
|
import {
|
|
AbsoluteModuleStrategy,
|
|
ActivePerfRecorder,
|
|
AliasStrategy,
|
|
COMPILER_ERRORS_WITH_GUIDES,
|
|
CompilationMode,
|
|
ComponentDecoratorHandler,
|
|
ComponentScopeKind,
|
|
CompoundComponentScopeReader,
|
|
CompoundMetadataReader,
|
|
CompoundMetadataRegistry,
|
|
DefaultImportTracker,
|
|
DeferredSymbolTracker,
|
|
DelegatingPerfRecorder,
|
|
DirectiveDecoratorHandler,
|
|
DtsMetadataReader,
|
|
DtsTransformRegistry,
|
|
ERROR_DETAILS_PAGE_BASE_URL,
|
|
ErrorCode,
|
|
ExportedProviderStatusResolver,
|
|
ExtendedTemplateDiagnosticName,
|
|
HostDirectivesResolver,
|
|
INPUT_INITIALIZER_FN,
|
|
ImportedSymbolsTracker,
|
|
InjectableClassRegistry,
|
|
InjectableDecoratorHandler,
|
|
JitDeclarationRegistry,
|
|
LocalCompilationExtraImportsTracker,
|
|
LocalIdentifierStrategy,
|
|
LocalMetadataRegistry,
|
|
LocalModuleScopeRegistry,
|
|
LogicalProjectStrategy,
|
|
MODEL_INITIALIZER_FN,
|
|
MetaKind,
|
|
MetadataDtsModuleScopeResolver,
|
|
ModuleResolver,
|
|
NgModuleDecoratorHandler,
|
|
NgOriginalFile,
|
|
NoopImportRewriter,
|
|
NoopReferencesRegistry,
|
|
OUTPUT_INITIALIZER_FNS,
|
|
OptimizeFor,
|
|
PartialEvaluator,
|
|
PerfCheckpoint,
|
|
PerfEvent,
|
|
PerfPhase,
|
|
PipeDecoratorHandler,
|
|
PrivateExportAliasingHost,
|
|
QUERY_INITIALIZER_FNS,
|
|
R3SymbolsImportRewriter,
|
|
Reference,
|
|
ReferenceEmitter,
|
|
RelativePathStrategy,
|
|
ResourceRegistry,
|
|
SelectorlessComponentScopeReader,
|
|
SemanticDepGraphUpdater,
|
|
ShimAdapter,
|
|
ShimReferenceTagger,
|
|
SymbolKind,
|
|
TemplateTypeCheckerImpl,
|
|
TraitCompiler,
|
|
TsCreateProgramDriver,
|
|
TypeCheckScopeRegistry,
|
|
TypeCheckShimGenerator,
|
|
TypeScriptReflectionHost,
|
|
UnifiedModulesAliasingHost,
|
|
UnifiedModulesStrategy,
|
|
aliasTransformFactory,
|
|
declarationTransformFactory,
|
|
getRootDirs,
|
|
getSourceFileOrNull,
|
|
isDtsPath,
|
|
isFatalDiagnosticError,
|
|
isNamedClassDeclaration,
|
|
isNonDeclarationTsPath,
|
|
isShim,
|
|
ivyTransformFactory,
|
|
makeDiagnostic,
|
|
ngErrorCode,
|
|
normalizeSeparators,
|
|
relativePathBetween,
|
|
replaceTsWithNgInErrors,
|
|
retagAllTsFiles,
|
|
signalMetadataTransform,
|
|
toUnredirectedSourceFile,
|
|
tryParseInitializerApi,
|
|
untagAllTsFiles
|
|
} from "./chunk-YDQJH54W.js";
|
|
import {
|
|
LogicalFileSystem,
|
|
absoluteFrom,
|
|
absoluteFromSourceFile,
|
|
createFileSystemTsReadDirectoryFn,
|
|
dirname,
|
|
getFileSystem,
|
|
join,
|
|
resolve
|
|
} from "./chunk-GWZQLAGK.js";
|
|
|
|
// packages/compiler-cli/src/transformers/api.js
|
|
var DEFAULT_ERROR_CODE = 100;
|
|
var UNKNOWN_ERROR_CODE = 500;
|
|
var SOURCE = "angular";
|
|
function isTsDiagnostic(diagnostic) {
|
|
return diagnostic != null && diagnostic.source !== "angular";
|
|
}
|
|
var EmitFlags;
|
|
(function(EmitFlags2) {
|
|
EmitFlags2[EmitFlags2["DTS"] = 1] = "DTS";
|
|
EmitFlags2[EmitFlags2["JS"] = 2] = "JS";
|
|
EmitFlags2[EmitFlags2["Metadata"] = 4] = "Metadata";
|
|
EmitFlags2[EmitFlags2["I18nBundle"] = 8] = "I18nBundle";
|
|
EmitFlags2[EmitFlags2["Codegen"] = 16] = "Codegen";
|
|
EmitFlags2[EmitFlags2["Default"] = 19] = "Default";
|
|
EmitFlags2[EmitFlags2["All"] = 31] = "All";
|
|
})(EmitFlags || (EmitFlags = {}));
|
|
|
|
// packages/compiler-cli/src/transformers/compiler_host.js
|
|
import ts from "typescript";
|
|
var wrapHostForTest = null;
|
|
function createCompilerHost({ options, tsHost = ts.createCompilerHost(options, true) }) {
|
|
if (wrapHostForTest !== null) {
|
|
tsHost = wrapHostForTest(tsHost);
|
|
}
|
|
return tsHost;
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/entities.js
|
|
var EntryType;
|
|
(function(EntryType2) {
|
|
EntryType2["Block"] = "block";
|
|
EntryType2["Component"] = "component";
|
|
EntryType2["Constant"] = "constant";
|
|
EntryType2["Decorator"] = "decorator";
|
|
EntryType2["Directive"] = "directive";
|
|
EntryType2["Element"] = "element";
|
|
EntryType2["Enum"] = "enum";
|
|
EntryType2["Function"] = "function";
|
|
EntryType2["Interface"] = "interface";
|
|
EntryType2["NgModule"] = "ng_module";
|
|
EntryType2["Pipe"] = "pipe";
|
|
EntryType2["TypeAlias"] = "type_alias";
|
|
EntryType2["UndecoratedClass"] = "undecorated_class";
|
|
EntryType2["InitializerApiFunction"] = "initializer_api_function";
|
|
})(EntryType || (EntryType = {}));
|
|
var MemberType;
|
|
(function(MemberType2) {
|
|
MemberType2["Property"] = "property";
|
|
MemberType2["Method"] = "method";
|
|
MemberType2["Getter"] = "getter";
|
|
MemberType2["Setter"] = "setter";
|
|
MemberType2["EnumItem"] = "enum_item";
|
|
})(MemberType || (MemberType = {}));
|
|
var DecoratorType;
|
|
(function(DecoratorType2) {
|
|
DecoratorType2["Class"] = "class";
|
|
DecoratorType2["Member"] = "member";
|
|
DecoratorType2["Parameter"] = "parameter";
|
|
})(DecoratorType || (DecoratorType = {}));
|
|
var MemberTags;
|
|
(function(MemberTags2) {
|
|
MemberTags2["Abstract"] = "abstract";
|
|
MemberTags2["Static"] = "static";
|
|
MemberTags2["Readonly"] = "readonly";
|
|
MemberTags2["Protected"] = "protected";
|
|
MemberTags2["Optional"] = "optional";
|
|
MemberTags2["Input"] = "input";
|
|
MemberTags2["Output"] = "output";
|
|
MemberTags2["Inherited"] = "override";
|
|
})(MemberTags || (MemberTags = {}));
|
|
function isDocEntryWithSourceInfo(entry) {
|
|
return "source" in entry;
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/extractor.js
|
|
import ts14 from "typescript";
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/class_extractor.js
|
|
import ts7 from "typescript";
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/jsdoc_extractor.js
|
|
import ts2 from "typescript";
|
|
var decoratorExpression = /@(?=(Injectable|Component|Directive|Pipe|NgModule|Input|Output|HostBinding|HostListener|Inject|Optional|Self|Host|SkipSelf|ViewChild|ViewChildren|ContentChild|ContentChildren))/g;
|
|
function extractJsDocTags(node) {
|
|
const escapedNode = getEscapedNode(node);
|
|
return ts2.getJSDocTags(escapedNode).map((t) => {
|
|
return {
|
|
name: t.tagName.getText(),
|
|
comment: unescapeAngularDecorators(ts2.getTextOfJSDocComment(t.comment) ?? "")
|
|
};
|
|
});
|
|
}
|
|
function extractJsDocDescription(node) {
|
|
const escapedNode = getEscapedNode(node);
|
|
const commentOrTag = ts2.getJSDocCommentsAndTags(escapedNode).find((d) => {
|
|
return ts2.isJSDoc(d) || ts2.isJSDocParameterTag(d);
|
|
});
|
|
const comment = commentOrTag?.comment ?? "";
|
|
const description = typeof comment === "string" ? comment : ts2.getTextOfJSDocComment(comment) ?? "";
|
|
return unescapeAngularDecorators(description);
|
|
}
|
|
function extractRawJsDoc(node) {
|
|
const comment = ts2.getJSDocCommentsAndTags(node).find(ts2.isJSDoc)?.getFullText() ?? "";
|
|
return unescapeAngularDecorators(comment);
|
|
}
|
|
function getEscapedNode(node) {
|
|
if (ts2.isParameter(node)) {
|
|
return node;
|
|
}
|
|
const rawComment = extractRawJsDoc(node);
|
|
const escaped = escapeAngularDecorators(rawComment);
|
|
const file = ts2.createSourceFile("x.ts", `${escaped}class X {}`, ts2.ScriptTarget.ES2020, true);
|
|
return file.statements.find((s) => ts2.isClassDeclaration(s));
|
|
}
|
|
function escapeAngularDecorators(comment) {
|
|
return comment.replace(decoratorExpression, "_NG_AT_");
|
|
}
|
|
function unescapeAngularDecorators(comment) {
|
|
return comment.replace(/_NG_AT_/g, "@");
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/properties_extractor.js
|
|
import ts6 from "typescript";
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/filters.js
|
|
function isAngularPrivateName(name) {
|
|
const firstChar = name[0] ?? "";
|
|
return firstChar === "\u0275" || firstChar === "_";
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/function_extractor.js
|
|
import ts4 from "typescript";
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/generics_extractor.js
|
|
function extractGenerics(declaration) {
|
|
return declaration.typeParameters?.map((typeParam) => ({
|
|
name: typeParam.name.getText(),
|
|
constraint: typeParam.constraint?.getText(),
|
|
default: typeParam.default?.getText()
|
|
})) ?? [];
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/type_extractor.js
|
|
import ts3 from "typescript";
|
|
function extractResolvedTypeString(node, checker) {
|
|
return checker.typeToString(checker.getTypeAtLocation(node), void 0, ts3.TypeFormatFlags.NoTruncation);
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/function_extractor.js
|
|
var FunctionExtractor = class {
|
|
name;
|
|
exportDeclaration;
|
|
typeChecker;
|
|
constructor(name, exportDeclaration, typeChecker) {
|
|
this.name = name;
|
|
this.exportDeclaration = exportDeclaration;
|
|
this.typeChecker = typeChecker;
|
|
}
|
|
extract() {
|
|
const signature = this.typeChecker.getSignatureFromDeclaration(this.exportDeclaration);
|
|
const returnType = signature ? extractReturnType(signature, this.typeChecker) : "unknown";
|
|
const implementation = findImplementationOfFunction(this.exportDeclaration, this.typeChecker) ?? this.exportDeclaration;
|
|
const type = this.typeChecker.getTypeAtLocation(this.exportDeclaration);
|
|
const overloads = ts4.isConstructorDeclaration(this.exportDeclaration) ? constructorOverloads(this.exportDeclaration, this.typeChecker) : extractCallSignatures(this.name, this.typeChecker, type);
|
|
const jsdocsTags = extractJsDocTags(implementation);
|
|
const description = extractJsDocDescription(implementation);
|
|
return {
|
|
name: this.name,
|
|
signatures: overloads,
|
|
implementation: {
|
|
params: extractAllParams(implementation.parameters, this.typeChecker),
|
|
isNewType: ts4.isConstructSignatureDeclaration(implementation),
|
|
returnType,
|
|
returnDescription: jsdocsTags.find((tag) => tag.name === "returns")?.comment,
|
|
generics: extractGenerics(implementation),
|
|
name: this.name,
|
|
description,
|
|
entryType: EntryType.Function,
|
|
jsdocTags: jsdocsTags,
|
|
rawComment: extractRawJsDoc(implementation)
|
|
},
|
|
entryType: EntryType.Function,
|
|
description,
|
|
jsdocTags: jsdocsTags,
|
|
rawComment: extractRawJsDoc(implementation)
|
|
};
|
|
}
|
|
};
|
|
function constructorOverloads(constructorDeclaration, typeChecker) {
|
|
const classDeclaration = constructorDeclaration.parent;
|
|
const constructorNode = classDeclaration.members.filter((member) => {
|
|
return ts4.isConstructorDeclaration(member) && !member.body;
|
|
});
|
|
return constructorNode.map((n) => {
|
|
return {
|
|
name: "constructor",
|
|
params: extractAllParams(n.parameters, typeChecker),
|
|
returnType: typeChecker.getTypeAtLocation(classDeclaration)?.symbol.name,
|
|
description: extractJsDocDescription(n),
|
|
entryType: EntryType.Function,
|
|
jsdocTags: extractJsDocTags(n),
|
|
rawComment: extractRawJsDoc(n),
|
|
generics: extractGenerics(n),
|
|
isNewType: false
|
|
};
|
|
});
|
|
}
|
|
function extractAllParams(params, typeChecker) {
|
|
return params.map((param) => ({
|
|
name: param.name.getText(),
|
|
description: extractJsDocDescription(param),
|
|
type: extractResolvedTypeString(param, typeChecker),
|
|
isOptional: !!(param.questionToken || param.initializer),
|
|
isRestParam: !!param.dotDotDotToken
|
|
}));
|
|
}
|
|
function filterSignatureDeclarations(signatures) {
|
|
const result = [];
|
|
for (const signature of signatures) {
|
|
const decl = signature.getDeclaration();
|
|
if (ts4.isFunctionDeclaration(decl) || ts4.isCallSignatureDeclaration(decl) || ts4.isMethodDeclaration(decl) || ts4.isConstructSignatureDeclaration(decl)) {
|
|
result.push({ signature, decl });
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
function extractCallSignatures(name, typeChecker, type) {
|
|
return filterSignatureDeclarations(type.getCallSignatures()).map(({ decl, signature }) => ({
|
|
name,
|
|
entryType: EntryType.Function,
|
|
description: extractJsDocDescription(decl),
|
|
generics: extractGenerics(decl),
|
|
isNewType: false,
|
|
jsdocTags: extractJsDocTags(decl),
|
|
params: extractAllParams(decl.parameters, typeChecker),
|
|
rawComment: extractRawJsDoc(decl),
|
|
returnType: extractReturnType(signature, typeChecker)
|
|
}));
|
|
}
|
|
function extractReturnType(signature, typeChecker) {
|
|
if (signature?.declaration?.type && ts4.isTypePredicateNode(signature.declaration.type)) {
|
|
return signature.declaration.type.getText();
|
|
}
|
|
return typeChecker.typeToString(
|
|
typeChecker.getReturnTypeOfSignature(signature),
|
|
void 0,
|
|
// This ensures that e.g. `T | undefined` is not reduced to `T`.
|
|
ts4.TypeFormatFlags.NoTypeReduction | ts4.TypeFormatFlags.NoTruncation
|
|
);
|
|
}
|
|
function findImplementationOfFunction(node, typeChecker) {
|
|
if (node.body !== void 0 || node.name === void 0) {
|
|
return node;
|
|
}
|
|
const symbol = typeChecker.getSymbolAtLocation(node.name);
|
|
const implementation = symbol?.declarations?.find((s) => ts4.isFunctionDeclaration(s) && s.body !== void 0);
|
|
return implementation;
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/internal.js
|
|
import ts5 from "typescript";
|
|
function isInternal(member) {
|
|
return extractJsDocTags(member).some((tag) => tag.name === "internal") || hasLeadingInternalComment(member);
|
|
}
|
|
function hasLeadingInternalComment(member) {
|
|
const memberText = member.getSourceFile().text;
|
|
return ts5.reduceEachLeadingCommentRange(
|
|
memberText,
|
|
member.getFullStart(),
|
|
(pos, end, kind, hasTrailingNewLine, containsInternal) => {
|
|
return containsInternal || memberText.slice(pos, end).includes("@internal");
|
|
},
|
|
/* state */
|
|
false,
|
|
/* initial */
|
|
false
|
|
) ?? false;
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/properties_extractor.js
|
|
var PropertiesExtractor = class {
|
|
declaration;
|
|
typeChecker;
|
|
constructor(declaration, typeChecker) {
|
|
this.declaration = declaration;
|
|
this.typeChecker = typeChecker;
|
|
}
|
|
/** Extract docs info specific to classes. */
|
|
extract() {
|
|
return {
|
|
members: this.extractSignatures().concat(this.extractAllClassMembers()),
|
|
generics: extractGenerics(this.declaration)
|
|
};
|
|
}
|
|
/** Extracts doc info for a class's members. */
|
|
extractAllClassMembers() {
|
|
const members = [];
|
|
for (const member of this.getMemberDeclarations()) {
|
|
if (this.isMemberExcluded(member))
|
|
continue;
|
|
const memberEntry = this.extractClassMember(member);
|
|
if (memberEntry) {
|
|
members.push(memberEntry);
|
|
}
|
|
}
|
|
return members;
|
|
}
|
|
/** Extract docs for a class's members (methods and properties). */
|
|
extractClassMember(memberDeclaration) {
|
|
if (this.isMethod(memberDeclaration)) {
|
|
return this.extractMethod(memberDeclaration);
|
|
} else if (this.isProperty(memberDeclaration) && !this.hasPrivateComputedProperty(memberDeclaration)) {
|
|
return this.extractClassProperty(memberDeclaration);
|
|
} else if (ts6.isAccessor(memberDeclaration)) {
|
|
return this.extractGetterSetter(memberDeclaration);
|
|
} else if (ts6.isConstructorDeclaration(memberDeclaration) && memberDeclaration.parameters.length > 0) {
|
|
return this.extractConstructor(memberDeclaration);
|
|
}
|
|
return void 0;
|
|
}
|
|
/** Extract docs for all call signatures in the current class/interface. */
|
|
extractSignatures() {
|
|
return this.computeAllSignatureDeclarations().map((s) => this.extractSignature(s));
|
|
}
|
|
/** Extracts docs for a class method. */
|
|
extractMethod(methodDeclaration) {
|
|
const functionExtractor = new FunctionExtractor(methodDeclaration.name.getText(), methodDeclaration, this.typeChecker);
|
|
return {
|
|
...functionExtractor.extract(),
|
|
memberType: MemberType.Method,
|
|
memberTags: this.getMemberTags(methodDeclaration)
|
|
};
|
|
}
|
|
/** Extracts docs for a signature element (usually inside an interface). */
|
|
extractSignature(signature) {
|
|
const functionExtractor = new FunctionExtractor(ts6.isConstructSignatureDeclaration(signature) ? "new" : "", signature, this.typeChecker);
|
|
return {
|
|
...functionExtractor.extract(),
|
|
memberType: MemberType.Method,
|
|
memberTags: []
|
|
};
|
|
}
|
|
/** Extracts doc info for a property declaration. */
|
|
extractClassProperty(propertyDeclaration) {
|
|
return {
|
|
name: propertyDeclaration.name.getText(),
|
|
type: extractResolvedTypeString(propertyDeclaration, this.typeChecker),
|
|
memberType: MemberType.Property,
|
|
memberTags: this.getMemberTags(propertyDeclaration),
|
|
description: extractJsDocDescription(propertyDeclaration),
|
|
jsdocTags: extractJsDocTags(propertyDeclaration)
|
|
};
|
|
}
|
|
/** Extracts doc info for an accessor member (getter/setter). */
|
|
extractGetterSetter(accessor) {
|
|
return {
|
|
...this.extractClassProperty(accessor),
|
|
memberType: ts6.isGetAccessor(accessor) ? MemberType.Getter : MemberType.Setter
|
|
};
|
|
}
|
|
extractConstructor(constructorDeclaration) {
|
|
const functionExtractor = new FunctionExtractor("constructor", constructorDeclaration, this.typeChecker);
|
|
return {
|
|
...functionExtractor.extract(),
|
|
memberType: MemberType.Method,
|
|
memberTags: this.getMemberTags(constructorDeclaration)
|
|
};
|
|
}
|
|
extractInterfaceConformance(declaration) {
|
|
const implementClause = declaration.heritageClauses?.find((clause) => clause.token === ts6.SyntaxKind.ImplementsKeyword);
|
|
return implementClause?.types.map((m) => m.getText()) ?? [];
|
|
}
|
|
/** Gets the tags for a member (protected, readonly, static, etc.) */
|
|
getMemberTags(member) {
|
|
const tags = this.getMemberTagsFromModifiers(member.modifiers ?? []);
|
|
if (member.questionToken) {
|
|
tags.push(MemberTags.Optional);
|
|
}
|
|
if (member.parent !== this.declaration) {
|
|
tags.push(MemberTags.Inherited);
|
|
}
|
|
return tags;
|
|
}
|
|
/** Computes all signature declarations of the class/interface. */
|
|
computeAllSignatureDeclarations() {
|
|
const type = this.typeChecker.getTypeAtLocation(this.declaration);
|
|
const signatures = [...type.getCallSignatures(), ...type.getConstructSignatures()];
|
|
const result = [];
|
|
for (const signature of signatures) {
|
|
const decl = signature.getDeclaration();
|
|
if (this.isDocumentableSignature(decl) && this.isDocumentableMember(decl)) {
|
|
result.push(decl);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
/** Gets all member declarations, including inherited members. */
|
|
getMemberDeclarations() {
|
|
const type = this.typeChecker.getTypeAtLocation(this.declaration);
|
|
const members = type.getProperties();
|
|
const constructor = type.getSymbol()?.members?.get(ts6.InternalSymbolName.Constructor);
|
|
const typeOfConstructor = this.typeChecker.getTypeOfSymbol(type.symbol);
|
|
const staticMembers = typeOfConstructor.getProperties();
|
|
const result = [];
|
|
for (const member of [...constructor ? [constructor] : [], ...members, ...staticMembers]) {
|
|
const memberDeclarations = this.filterMethodOverloads(member.getDeclarations() ?? []);
|
|
for (const memberDeclaration of memberDeclarations) {
|
|
if (this.isDocumentableMember(memberDeclaration)) {
|
|
result.push(memberDeclaration);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
/** The result only contains properties, method implementations and abstracts */
|
|
filterMethodOverloads(declarations) {
|
|
return declarations.filter((declaration, index) => {
|
|
if (ts6.isFunctionDeclaration(declaration) || ts6.isMethodDeclaration(declaration) || ts6.isConstructorDeclaration(declaration)) {
|
|
const nextDeclaration = declarations[index + 1];
|
|
const isNextMethodWithSameName = nextDeclaration && (ts6.isMethodDeclaration(nextDeclaration) && nextDeclaration.name.getText() === declaration.name?.getText() || ts6.isConstructorDeclaration(nextDeclaration) && ts6.isConstructorDeclaration(declaration));
|
|
return !isNextMethodWithSameName;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
/** Get the tags for a member that come from the declaration modifiers. */
|
|
getMemberTagsFromModifiers(mods) {
|
|
const tags = [];
|
|
for (const mod of mods) {
|
|
const tag = this.getTagForMemberModifier(mod);
|
|
if (tag)
|
|
tags.push(tag);
|
|
}
|
|
return tags;
|
|
}
|
|
/** Gets the doc tag corresponding to a class member modifier (readonly, protected, etc.). */
|
|
getTagForMemberModifier(mod) {
|
|
switch (mod.kind) {
|
|
case ts6.SyntaxKind.StaticKeyword:
|
|
return MemberTags.Static;
|
|
case ts6.SyntaxKind.ReadonlyKeyword:
|
|
return MemberTags.Readonly;
|
|
case ts6.SyntaxKind.ProtectedKeyword:
|
|
return MemberTags.Protected;
|
|
case ts6.SyntaxKind.AbstractKeyword:
|
|
return MemberTags.Abstract;
|
|
default:
|
|
return void 0;
|
|
}
|
|
}
|
|
/**
|
|
* Gets whether a given class member should be excluded from public API docs.
|
|
* This is the case if:
|
|
* - The member does not have a name
|
|
* - The member is neither a method nor property
|
|
* - The member is private
|
|
* - The member has a name that marks it as Angular-internal.
|
|
* - The member is marked as internal via JSDoc.
|
|
*/
|
|
isMemberExcluded(member) {
|
|
if (ts6.isConstructorDeclaration(member)) {
|
|
return false;
|
|
}
|
|
return !member.name || !this.isDocumentableMember(member) || !ts6.isCallSignatureDeclaration(member) && member.modifiers?.some((mod) => mod.kind === ts6.SyntaxKind.PrivateKeyword) || member.name.getText() === "prototype" || isAngularPrivateName(member.name.getText()) || isInternal(member);
|
|
}
|
|
/** Gets whether a class member is a method, property, or accessor. */
|
|
isDocumentableMember(member) {
|
|
return this.isMethod(member) || this.isProperty(member) || ts6.isAccessor(member) || ts6.isConstructorDeclaration(member) || // Signatures are documentable if they are part of an interface.
|
|
ts6.isCallSignatureDeclaration(member);
|
|
}
|
|
/** Check if the parameter is a constructor parameter with a public modifier */
|
|
isPublicConstructorParameterProperty(node) {
|
|
if (ts6.isParameterPropertyDeclaration(node, node.parent) && node.modifiers) {
|
|
return node.modifiers.some((modifier) => modifier.kind === ts6.SyntaxKind.PublicKeyword);
|
|
}
|
|
return false;
|
|
}
|
|
/** Gets whether a member is a property. */
|
|
isProperty(member) {
|
|
return ts6.isPropertyDeclaration(member) || ts6.isPropertySignature(member) || this.isPublicConstructorParameterProperty(member);
|
|
}
|
|
/** Gets whether a member is a method. */
|
|
isMethod(member) {
|
|
return ts6.isMethodDeclaration(member) || ts6.isMethodSignature(member);
|
|
}
|
|
/** Gets whether the given signature declaration is documentable. */
|
|
isDocumentableSignature(signature) {
|
|
return ts6.isConstructSignatureDeclaration(signature) || ts6.isCallSignatureDeclaration(signature);
|
|
}
|
|
/**
|
|
* Check wether a member has a private computed property name like [ɵWRITABLE_SIGNAL]
|
|
*
|
|
* This will prevent exposing private computed properties in the docs.
|
|
*/
|
|
hasPrivateComputedProperty(property) {
|
|
return ts6.isComputedPropertyName(property.name) && property.name.expression.getText().startsWith("\u0275");
|
|
}
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/class_extractor.js
|
|
var ClassExtractor = class extends PropertiesExtractor {
|
|
constructor(declaration, typeChecker) {
|
|
super(declaration, typeChecker);
|
|
}
|
|
/** Extract docs info specific to classes. */
|
|
extract() {
|
|
return {
|
|
name: this.declaration.name.text,
|
|
isAbstract: this.isAbstract(),
|
|
entryType: EntryType.UndecoratedClass,
|
|
...super.extract(),
|
|
description: extractJsDocDescription(this.declaration),
|
|
jsdocTags: extractJsDocTags(this.declaration),
|
|
rawComment: extractRawJsDoc(this.declaration),
|
|
extends: this.extractInheritance(this.declaration),
|
|
implements: this.extractInterfaceConformance(this.declaration)
|
|
};
|
|
}
|
|
/** Gets whether the declaration for this extractor is abstract. */
|
|
isAbstract() {
|
|
const modifiers = this.declaration.modifiers ?? [];
|
|
return modifiers.some((mod) => mod.kind === ts7.SyntaxKind.AbstractKeyword);
|
|
}
|
|
extractInheritance(declaration) {
|
|
if (!declaration.heritageClauses) {
|
|
return void 0;
|
|
}
|
|
for (const clause of declaration.heritageClauses) {
|
|
if (clause.token === ts7.SyntaxKind.ExtendsKeyword) {
|
|
const types = clause.types;
|
|
if (types.length > 0) {
|
|
const baseClass = types[0];
|
|
return baseClass.getText();
|
|
}
|
|
}
|
|
}
|
|
return void 0;
|
|
}
|
|
};
|
|
var DirectiveExtractor = class extends ClassExtractor {
|
|
reference;
|
|
metadata;
|
|
constructor(declaration, reference, metadata, checker) {
|
|
super(declaration, checker);
|
|
this.reference = reference;
|
|
this.metadata = metadata;
|
|
}
|
|
/** Extract docs info for directives and components (including underlying class info). */
|
|
extract() {
|
|
return {
|
|
...super.extract(),
|
|
isStandalone: this.metadata.isStandalone,
|
|
selector: this.metadata.selector ?? "",
|
|
exportAs: this.metadata.exportAs ?? [],
|
|
entryType: this.metadata.isComponent ? EntryType.Component : EntryType.Directive
|
|
};
|
|
}
|
|
/** Extracts docs info for a directive property, including input/output metadata. */
|
|
extractClassProperty(propertyDeclaration) {
|
|
const entry = super.extractClassProperty(propertyDeclaration);
|
|
const inputMetadata = this.getInputMetadata(propertyDeclaration);
|
|
if (inputMetadata) {
|
|
entry.memberTags.push(MemberTags.Input);
|
|
entry.inputAlias = inputMetadata.bindingPropertyName;
|
|
entry.isRequiredInput = inputMetadata.required;
|
|
}
|
|
const outputMetadata = this.getOutputMetadata(propertyDeclaration);
|
|
if (outputMetadata) {
|
|
entry.memberTags.push(MemberTags.Output);
|
|
entry.outputAlias = outputMetadata.bindingPropertyName;
|
|
}
|
|
return entry;
|
|
}
|
|
/** Gets the input metadata for a directive property. */
|
|
getInputMetadata(prop) {
|
|
const propName = prop.name.getText();
|
|
return this.metadata.inputs?.getByClassPropertyName(propName) ?? void 0;
|
|
}
|
|
/** Gets the output metadata for a directive property. */
|
|
getOutputMetadata(prop) {
|
|
const propName = prop.name.getText();
|
|
return this.metadata?.outputs?.getByClassPropertyName(propName) ?? void 0;
|
|
}
|
|
};
|
|
var PipeExtractor = class extends ClassExtractor {
|
|
reference;
|
|
metadata;
|
|
constructor(declaration, reference, metadata, typeChecker) {
|
|
super(declaration, typeChecker);
|
|
this.reference = reference;
|
|
this.metadata = metadata;
|
|
}
|
|
extract() {
|
|
return {
|
|
...super.extract(),
|
|
pipeName: this.metadata.name,
|
|
entryType: EntryType.Pipe,
|
|
isStandalone: this.metadata.isStandalone,
|
|
usage: extractPipeSyntax(this.metadata, this.declaration),
|
|
isPure: this.metadata.isPure
|
|
};
|
|
}
|
|
};
|
|
var NgModuleExtractor = class extends ClassExtractor {
|
|
reference;
|
|
metadata;
|
|
constructor(declaration, reference, metadata, typeChecker) {
|
|
super(declaration, typeChecker);
|
|
this.reference = reference;
|
|
this.metadata = metadata;
|
|
}
|
|
extract() {
|
|
return {
|
|
...super.extract(),
|
|
entryType: EntryType.NgModule
|
|
};
|
|
}
|
|
};
|
|
function extractClass(classDeclaration, metadataReader, typeChecker) {
|
|
const ref = new Reference(classDeclaration);
|
|
let extractor;
|
|
let directiveMetadata = metadataReader.getDirectiveMetadata(ref);
|
|
let pipeMetadata = metadataReader.getPipeMetadata(ref);
|
|
let ngModuleMetadata = metadataReader.getNgModuleMetadata(ref);
|
|
if (directiveMetadata) {
|
|
extractor = new DirectiveExtractor(classDeclaration, ref, directiveMetadata, typeChecker);
|
|
} else if (pipeMetadata) {
|
|
extractor = new PipeExtractor(classDeclaration, ref, pipeMetadata, typeChecker);
|
|
} else if (ngModuleMetadata) {
|
|
extractor = new NgModuleExtractor(classDeclaration, ref, ngModuleMetadata, typeChecker);
|
|
} else {
|
|
extractor = new ClassExtractor(classDeclaration, typeChecker);
|
|
}
|
|
return extractor.extract();
|
|
}
|
|
function extractPipeSyntax(metadata, classDeclaration) {
|
|
const transformParams = classDeclaration.members.find((member) => {
|
|
return ts7.isMethodDeclaration(member) && member.name && ts7.isIdentifier(member.name) && member.name.getText() === "transform";
|
|
});
|
|
let paramNames = transformParams.parameters.slice(1).map((param) => {
|
|
return param.name.getText();
|
|
});
|
|
return `{{ value_expression | ${metadata.name}${paramNames.length ? ":" + paramNames.join(":") : ""} }}`;
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/constant_extractor.js
|
|
import ts8 from "typescript";
|
|
var LITERAL_AS_ENUM_TAG = "object-literal-as-enum";
|
|
function extractConstant(declaration, typeChecker) {
|
|
const resolvedType = typeChecker.getBaseTypeOfLiteralType(typeChecker.getTypeAtLocation(declaration));
|
|
const rawComment = extractRawJsDoc(declaration.parent.parent);
|
|
const jsdocTags = extractJsDocTags(declaration);
|
|
const description = extractJsDocDescription(declaration);
|
|
const name = declaration.name.getText();
|
|
if (jsdocTags.some((tag) => tag.name === LITERAL_AS_ENUM_TAG)) {
|
|
return {
|
|
name,
|
|
entryType: EntryType.Enum,
|
|
members: extractLiteralPropertiesAsEnumMembers(declaration),
|
|
rawComment,
|
|
description,
|
|
jsdocTags: jsdocTags.filter((tag) => tag.name !== LITERAL_AS_ENUM_TAG)
|
|
};
|
|
}
|
|
return {
|
|
name,
|
|
type: typeChecker.typeToString(resolvedType),
|
|
entryType: EntryType.Constant,
|
|
rawComment,
|
|
description,
|
|
jsdocTags
|
|
};
|
|
}
|
|
function isSyntheticAngularConstant(declaration) {
|
|
return declaration.name.getText() === "USED_FOR_NG_TYPE_CHECKING";
|
|
}
|
|
function extractLiteralPropertiesAsEnumMembers(declaration) {
|
|
let initializer = declaration.initializer;
|
|
while (initializer && (ts8.isAsExpression(initializer) || ts8.isParenthesizedExpression(initializer))) {
|
|
initializer = initializer.expression;
|
|
}
|
|
if (initializer === void 0 || !ts8.isObjectLiteralExpression(initializer)) {
|
|
throw new Error(`Declaration tagged with "${LITERAL_AS_ENUM_TAG}" must be initialized to an object literal, but received ${initializer ? ts8.SyntaxKind[initializer.kind] : "undefined"}`);
|
|
}
|
|
return initializer.properties.map((prop) => {
|
|
if (!ts8.isPropertyAssignment(prop) || !ts8.isIdentifier(prop.name)) {
|
|
throw new Error(`Property in declaration tagged with "${LITERAL_AS_ENUM_TAG}" must be a property assignment with a static name`);
|
|
}
|
|
if (!ts8.isNumericLiteral(prop.initializer) && !ts8.isStringLiteralLike(prop.initializer)) {
|
|
throw new Error(`Property in declaration tagged with "${LITERAL_AS_ENUM_TAG}" must be initialized to a number or string literal`);
|
|
}
|
|
return {
|
|
name: prop.name.text,
|
|
type: `${declaration.name.getText()}.${prop.name.text}`,
|
|
value: prop.initializer.getText(),
|
|
memberType: MemberType.EnumItem,
|
|
jsdocTags: extractJsDocTags(prop),
|
|
description: extractJsDocDescription(prop),
|
|
memberTags: []
|
|
};
|
|
});
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/decorator_extractor.js
|
|
import ts10 from "typescript";
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/interface_extractor.js
|
|
import ts9 from "typescript";
|
|
var InterfaceExtractor = class extends PropertiesExtractor {
|
|
constructor(declaration, typeChecker) {
|
|
super(declaration, typeChecker);
|
|
}
|
|
/** Extract docs info specific to classes. */
|
|
extract() {
|
|
return {
|
|
name: this.declaration.name.text,
|
|
entryType: EntryType.Interface,
|
|
...super.extract(),
|
|
description: extractJsDocDescription(this.declaration),
|
|
jsdocTags: extractJsDocTags(this.declaration),
|
|
rawComment: extractRawJsDoc(this.declaration),
|
|
extends: this.extractInheritance(this.declaration),
|
|
implements: this.extractInterfaceConformance(this.declaration)
|
|
};
|
|
}
|
|
extractInheritance(declaration) {
|
|
if (!declaration.heritageClauses) {
|
|
return [];
|
|
}
|
|
for (const clause of declaration.heritageClauses) {
|
|
if (clause.token === ts9.SyntaxKind.ExtendsKeyword) {
|
|
const types = clause.types;
|
|
if (types.length > 0) {
|
|
return types.map((t) => t.getText());
|
|
}
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
};
|
|
function extractInterface(declaration, typeChecker) {
|
|
const extractor = new InterfaceExtractor(declaration, typeChecker);
|
|
return extractor.extract();
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/decorator_extractor.js
|
|
function extractorDecorator(declaration, typeChecker) {
|
|
const documentedNode = getDecoratorJsDocNode(declaration, typeChecker);
|
|
const decoratorType = getDecoratorType(declaration);
|
|
if (!decoratorType) {
|
|
throw new Error(`"${declaration.name.getText()} is not a decorator."`);
|
|
}
|
|
const members = getDecoratorProperties(declaration, typeChecker);
|
|
let signatures = [];
|
|
if (!members) {
|
|
const decoratorInterface = getDecoratorDeclaration(declaration, typeChecker);
|
|
const callSignatures = decoratorInterface.members.filter(ts10.isCallSignatureDeclaration);
|
|
signatures = getDecoratorSignatures(callSignatures, typeChecker);
|
|
}
|
|
return {
|
|
name: declaration.name.getText(),
|
|
decoratorType,
|
|
entryType: EntryType.Decorator,
|
|
rawComment: extractRawJsDoc(documentedNode),
|
|
description: extractJsDocDescription(documentedNode),
|
|
jsdocTags: extractJsDocTags(documentedNode),
|
|
members,
|
|
signatures
|
|
};
|
|
}
|
|
function isDecoratorDeclaration(declaration) {
|
|
return !!getDecoratorType(declaration);
|
|
}
|
|
function isDecoratorOptionsInterface(declaration) {
|
|
return declaration.getSourceFile().statements.some((s) => ts10.isVariableStatement(s) && s.declarationList.declarations.some((d) => isDecoratorDeclaration(d) && d.name.getText() === declaration.name.getText()));
|
|
}
|
|
function getDecoratorType(declaration) {
|
|
const initializer = declaration.initializer?.getFullText() ?? "";
|
|
if (initializer.includes("makeDecorator"))
|
|
return DecoratorType.Class;
|
|
if (initializer.includes("makePropDecorator"))
|
|
return DecoratorType.Member;
|
|
if (initializer.includes("makeParamDecorator"))
|
|
return DecoratorType.Parameter;
|
|
return void 0;
|
|
}
|
|
function getDecoratorDeclaration(declaration, typeChecker) {
|
|
const decoratorName = declaration.name.getText();
|
|
const decoratorDeclaration = declaration;
|
|
const decoratorType = typeChecker.getTypeAtLocation(decoratorDeclaration);
|
|
const aliasDeclaration = decoratorType.getSymbol().getDeclarations()[0];
|
|
const decoratorInterface = aliasDeclaration;
|
|
if (!decoratorInterface || !ts10.isInterfaceDeclaration(decoratorInterface)) {
|
|
throw new Error(`No decorator interface found for "${decoratorName}".`);
|
|
}
|
|
return decoratorInterface;
|
|
}
|
|
function getDecoratorProperties(declaration, typeChecker) {
|
|
const decoratorCallSig = getDecoratorJsDocNode(declaration, typeChecker);
|
|
const decoratorFirstParam = decoratorCallSig.parameters[0];
|
|
const firstParamType = typeChecker.getTypeAtLocation(decoratorFirstParam);
|
|
let firstParamTypeDecl;
|
|
if (firstParamType.isUnion()) {
|
|
const firstParamTypeUnion = firstParamType.types.find((t) => (t.flags & ts10.TypeFlags.Undefined) === 0);
|
|
firstParamTypeDecl = firstParamTypeUnion?.getSymbol()?.getDeclarations()[0];
|
|
} else {
|
|
firstParamTypeDecl = firstParamType.getSymbol()?.getDeclarations()[0];
|
|
}
|
|
if (!firstParamTypeDecl || !ts10.isInterfaceDeclaration(firstParamTypeDecl)) {
|
|
return null;
|
|
}
|
|
const interfaceDeclaration = firstParamTypeDecl;
|
|
return extractInterface(interfaceDeclaration, typeChecker).members;
|
|
}
|
|
function getDecoratorSignatures(callSignatures, typeChecker) {
|
|
return callSignatures.map((signatureDecl) => {
|
|
return {
|
|
parameters: extractParams(signatureDecl.parameters, typeChecker),
|
|
jsdocTags: extractJsDocTags(signatureDecl)
|
|
};
|
|
});
|
|
}
|
|
function extractParams(params, typeChecker) {
|
|
return params.map((param) => ({
|
|
name: param.name.getText(),
|
|
description: extractJsDocDescription(param),
|
|
type: getParamTypeString(param, typeChecker),
|
|
isOptional: !!(param.questionToken || param.initializer),
|
|
isRestParam: !!param.dotDotDotToken
|
|
}));
|
|
}
|
|
function getDecoratorInterface(declaration, typeChecker) {
|
|
const name = declaration.name.getText();
|
|
const symbol = typeChecker.getSymbolAtLocation(declaration.name);
|
|
const decoratorType = typeChecker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration);
|
|
const decoratorInterface = decoratorType.getSymbol()?.getDeclarations()[0];
|
|
if (!decoratorInterface || !ts10.isInterfaceDeclaration(decoratorInterface)) {
|
|
throw new Error(`No decorator interface found for "${name}".`);
|
|
}
|
|
return decoratorInterface;
|
|
}
|
|
function getDecoratorJsDocNode(declaration, typeChecker) {
|
|
const name = declaration.name.getText();
|
|
const decoratorInterface = getDecoratorInterface(declaration, typeChecker);
|
|
const callSignature = decoratorInterface.members.filter((node) => {
|
|
return ts10.isCallSignatureDeclaration(node) && extractRawJsDoc(node);
|
|
}).at(-1);
|
|
if (!callSignature || !ts10.isCallSignatureDeclaration(callSignature)) {
|
|
throw new Error(`No call signature with JsDoc on "${name}Decorator"`);
|
|
}
|
|
return callSignature;
|
|
}
|
|
function getParamTypeString(paramNode, typeChecker) {
|
|
const type = typeChecker.getTypeAtLocation(paramNode);
|
|
const printer = ts10.createPrinter({ removeComments: true });
|
|
const sourceFile = paramNode.getSourceFile();
|
|
const replace = [];
|
|
if (type.isUnion()) {
|
|
for (const subType of type.types) {
|
|
const decl = subType.getSymbol()?.getDeclarations()?.[0];
|
|
if (decl && ts10.isInterfaceDeclaration(decl) && decl.name.text !== "Function") {
|
|
replace.push({
|
|
initial: subType.symbol.name,
|
|
replacedWith: expandType(decl, sourceFile, printer)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
let result = printer.printNode(ts10.EmitHint.Unspecified, paramNode, sourceFile).replace(new RegExp(`${paramNode.name.getText()}\\??: `), "").replaceAll(/\s+/g, " ");
|
|
for (const { initial, replacedWith } of replace) {
|
|
result = result.replace(initial, replacedWith);
|
|
}
|
|
return result;
|
|
}
|
|
function expandType(decl, sourceFile, printer) {
|
|
const props = decl.members.map((member) => printer.printNode(ts10.EmitHint.Unspecified, member, sourceFile)).join(" ").replaceAll(/\s+/g, " ");
|
|
return `{${props}}`;
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/enum_extractor.js
|
|
import ts11 from "typescript";
|
|
function extractEnum(declaration, typeChecker) {
|
|
return {
|
|
name: declaration.name.getText(),
|
|
entryType: EntryType.Enum,
|
|
members: extractEnumMembers(declaration, typeChecker),
|
|
rawComment: extractRawJsDoc(declaration),
|
|
description: extractJsDocDescription(declaration),
|
|
jsdocTags: extractJsDocTags(declaration)
|
|
};
|
|
}
|
|
function extractEnumMembers(declaration, checker) {
|
|
return declaration.members.map((member) => ({
|
|
name: member.name.getText(),
|
|
type: extractResolvedTypeString(member, checker),
|
|
value: getEnumMemberValue(member),
|
|
memberType: MemberType.EnumItem,
|
|
jsdocTags: extractJsDocTags(member),
|
|
description: extractJsDocDescription(member),
|
|
memberTags: []
|
|
}));
|
|
}
|
|
function getEnumMemberValue(memberNode) {
|
|
const literal = memberNode.getChildren().find((n) => {
|
|
return ts11.isNumericLiteral(n) || ts11.isStringLiteral(n) || ts11.isPrefixUnaryExpression(n) && n.operator === ts11.SyntaxKind.MinusToken && ts11.isNumericLiteral(n.operand);
|
|
});
|
|
return literal?.getText() ?? "";
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/initializer_api_function_extractor.js
|
|
import ts12 from "typescript";
|
|
var initializerApiTag = "initializerApiFunction";
|
|
function isInitializerApiFunction(node, typeChecker) {
|
|
if (ts12.isFunctionDeclaration(node) && node.name !== void 0 && node.body === void 0) {
|
|
const implementation = findImplementationOfFunction(node, typeChecker);
|
|
if (implementation !== void 0) {
|
|
node = implementation;
|
|
}
|
|
}
|
|
if (!ts12.isFunctionDeclaration(node) && !ts12.isVariableDeclaration(node)) {
|
|
return false;
|
|
}
|
|
let tagContainer = ts12.isFunctionDeclaration(node) ? node : getContainerVariableStatement(node);
|
|
if (tagContainer === null) {
|
|
return false;
|
|
}
|
|
const tags = ts12.getJSDocTags(tagContainer);
|
|
return tags.some((t) => t.tagName.text === initializerApiTag);
|
|
}
|
|
function extractInitializerApiFunction(node, typeChecker) {
|
|
if (node.name === void 0 || !ts12.isIdentifier(node.name)) {
|
|
throw new Error(`Initializer API: Expected literal variable name.`);
|
|
}
|
|
const container = ts12.isFunctionDeclaration(node) ? node : getContainerVariableStatement(node);
|
|
if (container === null) {
|
|
throw new Error("Initializer API: Could not find container AST node of variable.");
|
|
}
|
|
const name = node.name.text;
|
|
const type = typeChecker.getTypeAtLocation(node);
|
|
const callFunction = extractFunctionWithOverloads(name, type, typeChecker);
|
|
const subFunctions = [];
|
|
for (const property of type.getProperties()) {
|
|
const subName = property.getName();
|
|
const subDecl = property.getDeclarations()?.[0];
|
|
if (subDecl === void 0 || !ts12.isPropertySignature(subDecl)) {
|
|
throw new Error(`Initializer API: Could not resolve declaration of sub-property: ${name}.${subName}`);
|
|
}
|
|
const subType = typeChecker.getTypeAtLocation(subDecl);
|
|
subFunctions.push(extractFunctionWithOverloads(subName, subType, typeChecker));
|
|
}
|
|
let jsdocTags;
|
|
let description;
|
|
let rawComment;
|
|
if (ts12.isFunctionDeclaration(node)) {
|
|
const implementation = findImplementationOfFunction(node, typeChecker);
|
|
if (implementation === void 0) {
|
|
throw new Error(`Initializer API: Could not find implementation of function: ${name}`);
|
|
}
|
|
callFunction.implementation = {
|
|
name,
|
|
entryType: EntryType.Function,
|
|
isNewType: false,
|
|
description: extractJsDocDescription(implementation),
|
|
generics: extractGenerics(implementation),
|
|
jsdocTags: extractJsDocTags(implementation),
|
|
params: extractAllParams(implementation.parameters, typeChecker),
|
|
rawComment: extractRawJsDoc(implementation),
|
|
returnType: typeChecker.typeToString(typeChecker.getReturnTypeOfSignature(typeChecker.getSignatureFromDeclaration(implementation)))
|
|
};
|
|
jsdocTags = callFunction.implementation.jsdocTags;
|
|
description = callFunction.implementation.description;
|
|
rawComment = callFunction.implementation.description;
|
|
} else {
|
|
jsdocTags = extractJsDocTags(container);
|
|
description = extractJsDocDescription(container);
|
|
rawComment = extractRawJsDoc(container);
|
|
}
|
|
const metadataTag = jsdocTags.find((t) => t.name === initializerApiTag);
|
|
if (metadataTag === void 0) {
|
|
throw new Error(`Initializer API: Detected initializer API function does not have "@initializerApiFunction" tag: ${name}`);
|
|
}
|
|
let parsedMetadata = void 0;
|
|
if (metadataTag.comment.trim() !== "") {
|
|
try {
|
|
parsedMetadata = JSON.parse(metadataTag.comment);
|
|
} catch (e) {
|
|
throw new Error(`Could not parse initializer API function metadata: ${e}`);
|
|
}
|
|
}
|
|
return {
|
|
entryType: EntryType.InitializerApiFunction,
|
|
name,
|
|
description,
|
|
jsdocTags,
|
|
rawComment,
|
|
callFunction,
|
|
subFunctions,
|
|
__docsMetadata__: parsedMetadata
|
|
};
|
|
}
|
|
function getContainerVariableStatement(node) {
|
|
if (!ts12.isVariableDeclarationList(node.parent)) {
|
|
return null;
|
|
}
|
|
if (!ts12.isVariableStatement(node.parent.parent)) {
|
|
return null;
|
|
}
|
|
return node.parent.parent;
|
|
}
|
|
function extractFunctionWithOverloads(name, type, typeChecker) {
|
|
return {
|
|
name,
|
|
signatures: extractCallSignatures(name, typeChecker, type),
|
|
// Implementation may be populated later.
|
|
implementation: null
|
|
};
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/type_alias_extractor.js
|
|
function extractTypeAlias(declaration) {
|
|
return {
|
|
name: declaration.name.getText(),
|
|
type: declaration.type.getText(),
|
|
entryType: EntryType.TypeAlias,
|
|
generics: extractGenerics(declaration),
|
|
rawComment: extractRawJsDoc(declaration),
|
|
description: extractJsDocDescription(declaration),
|
|
jsdocTags: extractJsDocTags(declaration)
|
|
};
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/import_extractor.js
|
|
import ts13 from "typescript";
|
|
function getImportedSymbols(sourceFile) {
|
|
const importSpecifiers = /* @__PURE__ */ new Map();
|
|
function visit(node) {
|
|
if (ts13.isImportDeclaration(node)) {
|
|
let moduleSpecifier = node.moduleSpecifier.getText(sourceFile).replace(/['"]/g, "");
|
|
if (moduleSpecifier.startsWith("@angular/")) {
|
|
const namedBindings = node.importClause?.namedBindings;
|
|
if (namedBindings && ts13.isNamedImports(namedBindings)) {
|
|
namedBindings.elements.forEach((importSpecifier) => {
|
|
const importName = importSpecifier.name.text;
|
|
const importAlias = importSpecifier.propertyName ? importSpecifier.propertyName.text : void 0;
|
|
importSpecifiers.set(importAlias ?? importName, moduleSpecifier);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
ts13.forEachChild(node, visit);
|
|
}
|
|
visit(sourceFile);
|
|
return importSpecifiers;
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/docs/src/extractor.js
|
|
var DocsExtractor = class {
|
|
typeChecker;
|
|
metadataReader;
|
|
constructor(typeChecker, metadataReader) {
|
|
this.typeChecker = typeChecker;
|
|
this.metadataReader = metadataReader;
|
|
}
|
|
/**
|
|
* Gets the set of all documentable entries from a source file, including
|
|
* declarations that are re-exported from this file as an entry-point.
|
|
*
|
|
* @param sourceFile The file from which to extract documentable entries.
|
|
*/
|
|
extractAll(sourceFile, rootDir, privateModules) {
|
|
const entries = [];
|
|
const symbols = /* @__PURE__ */ new Map();
|
|
const exportedDeclarations = this.getExportedDeclarations(sourceFile);
|
|
for (const [exportName, node] of exportedDeclarations) {
|
|
if (isAngularPrivateName(exportName)) {
|
|
continue;
|
|
}
|
|
const entry = this.extractDeclaration(node);
|
|
if (entry && !isIgnoredDocEntry(entry)) {
|
|
const realSourceFile = node.getSourceFile();
|
|
const importedSymbols = getImportedSymbols(realSourceFile);
|
|
importedSymbols.forEach((moduleName, symbolName) => {
|
|
if (symbolName.startsWith("\u0275") || privateModules.has(moduleName)) {
|
|
return;
|
|
}
|
|
if (symbols.has(symbolName) && symbols.get(symbolName) !== moduleName) {
|
|
throw new Error(`Ambigous symbol \`${symbolName}\` exported by both ${symbols.get(symbolName)} & ${moduleName}`);
|
|
}
|
|
symbols.set(symbolName, moduleName);
|
|
});
|
|
entry.source = {
|
|
filePath: getRelativeFilePath(realSourceFile, rootDir),
|
|
// Start & End are off by 1
|
|
startLine: ts14.getLineAndCharacterOfPosition(realSourceFile, node.getStart()).line + 1,
|
|
endLine: ts14.getLineAndCharacterOfPosition(realSourceFile, node.getEnd()).line + 1
|
|
};
|
|
entries.push({ ...entry, name: exportName });
|
|
}
|
|
}
|
|
return { entries, symbols };
|
|
}
|
|
/** Extract the doc entry for a single declaration. */
|
|
extractDeclaration(node) {
|
|
if (isNamedClassDeclaration(node)) {
|
|
return extractClass(node, this.metadataReader, this.typeChecker);
|
|
}
|
|
if (isInitializerApiFunction(node, this.typeChecker)) {
|
|
return extractInitializerApiFunction(node, this.typeChecker);
|
|
}
|
|
if (ts14.isInterfaceDeclaration(node) && !isIgnoredInterface(node)) {
|
|
return extractInterface(node, this.typeChecker);
|
|
}
|
|
if (ts14.isFunctionDeclaration(node)) {
|
|
const functionExtractor = new FunctionExtractor(node.name.getText(), node, this.typeChecker);
|
|
return functionExtractor.extract();
|
|
}
|
|
if (ts14.isVariableDeclaration(node) && !isSyntheticAngularConstant(node)) {
|
|
return isDecoratorDeclaration(node) ? extractorDecorator(node, this.typeChecker) : extractConstant(node, this.typeChecker);
|
|
}
|
|
if (ts14.isTypeAliasDeclaration(node)) {
|
|
return extractTypeAlias(node);
|
|
}
|
|
if (ts14.isEnumDeclaration(node)) {
|
|
return extractEnum(node, this.typeChecker);
|
|
}
|
|
return null;
|
|
}
|
|
/** Gets the list of exported declarations for doc extraction. */
|
|
getExportedDeclarations(sourceFile) {
|
|
const reflector = new TypeScriptReflectionHost(this.typeChecker, false, true);
|
|
const exportedDeclarationMap = reflector.getExportsOfModule(sourceFile);
|
|
let exportedDeclarations = Array.from(exportedDeclarationMap?.entries() ?? []).map(([exportName, declaration]) => [exportName, declaration.node]);
|
|
return exportedDeclarations.sort(([a, declarationA], [b, declarationB]) => declarationA.pos - declarationB.pos);
|
|
}
|
|
};
|
|
function isIgnoredInterface(node) {
|
|
return node.name.getText().endsWith("Decorator") || isDecoratorOptionsInterface(node);
|
|
}
|
|
function isIgnoredDocEntry(entry) {
|
|
const isDocsPrivate = entry.jsdocTags.find((e) => e.name === "docsPrivate");
|
|
if (isDocsPrivate !== void 0 && isDocsPrivate.comment === "") {
|
|
throw new Error(`Docs extraction: Entry "${entry.name}" is marked as "@docsPrivate" but without reasoning.`);
|
|
}
|
|
return isDocsPrivate !== void 0;
|
|
}
|
|
function getRelativeFilePath(sourceFile, rootDir) {
|
|
const fullPath = sourceFile.fileName;
|
|
const relativePath = fullPath.replace(rootDir, "");
|
|
return relativePath;
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/program.js
|
|
import { HtmlParser, MessageBundle } from "@angular/compiler";
|
|
import ts30 from "typescript";
|
|
|
|
// packages/compiler-cli/src/transformers/i18n.js
|
|
import { Xliff, Xliff2, Xmb } from "@angular/compiler";
|
|
import * as path from "path";
|
|
function i18nGetExtension(formatName) {
|
|
const format = formatName.toLowerCase();
|
|
switch (format) {
|
|
case "xmb":
|
|
return "xmb";
|
|
case "xlf":
|
|
case "xlif":
|
|
case "xliff":
|
|
case "xlf2":
|
|
case "xliff2":
|
|
return "xlf";
|
|
}
|
|
throw new Error(`Unsupported format "${formatName}"`);
|
|
}
|
|
function i18nExtract(formatName, outFile, host, options, bundle, pathResolve = path.resolve) {
|
|
formatName = formatName || "xlf";
|
|
const ext = i18nGetExtension(formatName);
|
|
const content = i18nSerialize(bundle, formatName, options);
|
|
const dstFile = outFile || `messages.${ext}`;
|
|
const dstPath = pathResolve(options.outDir || options.basePath, dstFile);
|
|
host.writeFile(dstPath, content, false, void 0, []);
|
|
return [dstPath];
|
|
}
|
|
function i18nSerialize(bundle, formatName, options) {
|
|
const format = formatName.toLowerCase();
|
|
let serializer;
|
|
switch (format) {
|
|
case "xmb":
|
|
serializer = new Xmb();
|
|
break;
|
|
case "xliff2":
|
|
case "xlf2":
|
|
serializer = new Xliff2();
|
|
break;
|
|
case "xlf":
|
|
case "xliff":
|
|
default:
|
|
serializer = new Xliff();
|
|
}
|
|
return bundle.write(serializer, getPathNormalizer(options.basePath));
|
|
}
|
|
function getPathNormalizer(basePath) {
|
|
return (sourcePath) => {
|
|
sourcePath = basePath ? path.relative(basePath, sourcePath) : sourcePath;
|
|
return sourcePath.split(path.sep).join("/");
|
|
};
|
|
}
|
|
|
|
// packages/compiler-cli/src/typescript_support.js
|
|
import ts15 from "typescript";
|
|
|
|
// packages/compiler-cli/src/version_helpers.js
|
|
function toNumbers(value) {
|
|
const suffixIndex = value.lastIndexOf("-");
|
|
return value.slice(0, suffixIndex === -1 ? value.length : suffixIndex).split(".").map((segment) => {
|
|
const parsed = parseInt(segment, 10);
|
|
if (isNaN(parsed)) {
|
|
throw Error(`Unable to parse version string ${value}.`);
|
|
}
|
|
return parsed;
|
|
});
|
|
}
|
|
function compareNumbers(a, b) {
|
|
const max = Math.max(a.length, b.length);
|
|
const min = Math.min(a.length, b.length);
|
|
for (let i = 0; i < min; i++) {
|
|
if (a[i] > b[i])
|
|
return 1;
|
|
if (a[i] < b[i])
|
|
return -1;
|
|
}
|
|
if (min !== max) {
|
|
const longestArray = a.length === max ? a : b;
|
|
const comparisonResult = a.length === max ? 1 : -1;
|
|
for (let i = min; i < max; i++) {
|
|
if (longestArray[i] > 0) {
|
|
return comparisonResult;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
function compareVersions(v1, v2) {
|
|
return compareNumbers(toNumbers(v1), toNumbers(v2));
|
|
}
|
|
|
|
// packages/compiler-cli/src/typescript_support.js
|
|
var MIN_TS_VERSION = "5.8.0";
|
|
var MAX_TS_VERSION = "6.0.0";
|
|
var tsVersion = ts15.version;
|
|
function checkVersion(version, minVersion, maxVersion) {
|
|
if (compareVersions(version, minVersion) < 0 || compareVersions(version, maxVersion) >= 0) {
|
|
throw new Error(`The Angular Compiler requires TypeScript >=${minVersion} and <${maxVersion} but ${version} was found instead.`);
|
|
}
|
|
}
|
|
function verifySupportedTypeScriptVersion() {
|
|
checkVersion(tsVersion, MIN_TS_VERSION, MAX_TS_VERSION);
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/core/src/compiler.js
|
|
import ts28 from "typescript";
|
|
|
|
// packages/compiler-cli/src/ngtsc/cycles/src/analyzer.js
|
|
var CycleAnalyzer = class {
|
|
importGraph;
|
|
/**
|
|
* Cycle detection is requested with the same `from` source file for all used directives and pipes
|
|
* within a component, which makes it beneficial to cache the results as long as the `from` source
|
|
* file has not changed. This avoids visiting the import graph that is reachable from multiple
|
|
* directives/pipes more than once.
|
|
*/
|
|
cachedResults = null;
|
|
constructor(importGraph) {
|
|
this.importGraph = importGraph;
|
|
}
|
|
/**
|
|
* Check for a cycle to be created in the `ts.Program` by adding an import between `from` and
|
|
* `to`.
|
|
*
|
|
* @returns a `Cycle` object if an import between `from` and `to` would create a cycle; `null`
|
|
* otherwise.
|
|
*/
|
|
wouldCreateCycle(from, to) {
|
|
if (this.cachedResults === null || this.cachedResults.from !== from) {
|
|
this.cachedResults = new CycleResults(from, this.importGraph);
|
|
}
|
|
return this.cachedResults.wouldBeCyclic(to) ? new Cycle(this.importGraph, from, to) : null;
|
|
}
|
|
/**
|
|
* Record a synthetic import from `from` to `to`.
|
|
*
|
|
* This is an import that doesn't exist in the `ts.Program` but will be considered as part of the
|
|
* import graph for cycle creation.
|
|
*/
|
|
recordSyntheticImport(from, to) {
|
|
this.cachedResults = null;
|
|
this.importGraph.addSyntheticImport(from, to);
|
|
}
|
|
};
|
|
var NgCyclicResult = Symbol("NgCyclicResult");
|
|
var CycleResults = class {
|
|
from;
|
|
importGraph;
|
|
cyclic = {};
|
|
acyclic = {};
|
|
constructor(from, importGraph) {
|
|
this.from = from;
|
|
this.importGraph = importGraph;
|
|
}
|
|
wouldBeCyclic(sf) {
|
|
const cached = this.getCachedResult(sf);
|
|
if (cached !== null) {
|
|
return cached;
|
|
}
|
|
if (sf === this.from) {
|
|
return true;
|
|
}
|
|
this.markAcyclic(sf);
|
|
const imports = this.importGraph.importsOf(sf);
|
|
for (const imported of imports) {
|
|
if (this.wouldBeCyclic(imported)) {
|
|
this.markCyclic(sf);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Returns whether the source file is already known to be cyclic, or `null` if the result is not
|
|
* yet known.
|
|
*/
|
|
getCachedResult(sf) {
|
|
const result = sf[NgCyclicResult];
|
|
if (result === this.cyclic) {
|
|
return true;
|
|
} else if (result === this.acyclic) {
|
|
return false;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
markCyclic(sf) {
|
|
sf[NgCyclicResult] = this.cyclic;
|
|
}
|
|
markAcyclic(sf) {
|
|
sf[NgCyclicResult] = this.acyclic;
|
|
}
|
|
};
|
|
var Cycle = class {
|
|
importGraph;
|
|
from;
|
|
to;
|
|
constructor(importGraph, from, to) {
|
|
this.importGraph = importGraph;
|
|
this.from = from;
|
|
this.to = to;
|
|
}
|
|
/**
|
|
* Compute an array of source-files that illustrates the cyclic path between `from` and `to`.
|
|
*
|
|
* Note that a `Cycle` will not be created unless a path is available between `to` and `from`,
|
|
* so `findPath()` will never return `null`.
|
|
*/
|
|
getPath() {
|
|
return [this.from, ...this.importGraph.findPath(this.to, this.from)];
|
|
}
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/cycles/src/imports.js
|
|
import ts16 from "typescript";
|
|
var ImportGraph = class {
|
|
checker;
|
|
perf;
|
|
imports = /* @__PURE__ */ new Map();
|
|
constructor(checker, perf) {
|
|
this.checker = checker;
|
|
this.perf = perf;
|
|
}
|
|
/**
|
|
* List the direct (not transitive) imports of a given `ts.SourceFile`.
|
|
*
|
|
* This operation is cached.
|
|
*/
|
|
importsOf(sf) {
|
|
if (!this.imports.has(sf)) {
|
|
this.imports.set(sf, this.scanImports(sf));
|
|
}
|
|
return this.imports.get(sf);
|
|
}
|
|
/**
|
|
* Find an import path from the `start` SourceFile to the `end` SourceFile.
|
|
*
|
|
* This function implements a breadth first search that results in finding the
|
|
* shortest path between the `start` and `end` points.
|
|
*
|
|
* @param start the starting point of the path.
|
|
* @param end the ending point of the path.
|
|
* @returns an array of source files that connect the `start` and `end` source files, or `null` if
|
|
* no path could be found.
|
|
*/
|
|
findPath(start, end) {
|
|
if (start === end) {
|
|
return [start];
|
|
}
|
|
const found = /* @__PURE__ */ new Set([start]);
|
|
const queue = [new Found(start, null)];
|
|
while (queue.length > 0) {
|
|
const current = queue.shift();
|
|
const imports = this.importsOf(current.sourceFile);
|
|
for (const importedFile of imports) {
|
|
if (!found.has(importedFile)) {
|
|
const next = new Found(importedFile, current);
|
|
if (next.sourceFile === end) {
|
|
return next.toPath();
|
|
}
|
|
found.add(importedFile);
|
|
queue.push(next);
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
* Add a record of an import from `sf` to `imported`, that's not present in the original
|
|
* `ts.Program` but will be remembered by the `ImportGraph`.
|
|
*/
|
|
addSyntheticImport(sf, imported) {
|
|
if (isLocalFile(imported)) {
|
|
this.importsOf(sf).add(imported);
|
|
}
|
|
}
|
|
scanImports(sf) {
|
|
return this.perf.inPhase(PerfPhase.CycleDetection, () => {
|
|
const imports = /* @__PURE__ */ new Set();
|
|
for (const stmt of sf.statements) {
|
|
if (!ts16.isImportDeclaration(stmt) && !ts16.isExportDeclaration(stmt) || stmt.moduleSpecifier === void 0) {
|
|
continue;
|
|
}
|
|
if (ts16.isImportDeclaration(stmt) && stmt.importClause !== void 0 && isTypeOnlyImportClause(stmt.importClause)) {
|
|
continue;
|
|
}
|
|
const symbol = this.checker.getSymbolAtLocation(stmt.moduleSpecifier);
|
|
if (symbol === void 0 || symbol.valueDeclaration === void 0) {
|
|
continue;
|
|
}
|
|
const moduleFile = symbol.valueDeclaration;
|
|
if (ts16.isSourceFile(moduleFile) && isLocalFile(moduleFile)) {
|
|
imports.add(moduleFile);
|
|
}
|
|
}
|
|
return imports;
|
|
});
|
|
}
|
|
};
|
|
function isLocalFile(sf) {
|
|
return !sf.isDeclarationFile;
|
|
}
|
|
function isTypeOnlyImportClause(node) {
|
|
if (node.isTypeOnly) {
|
|
return true;
|
|
}
|
|
if (node.namedBindings !== void 0 && ts16.isNamedImports(node.namedBindings) && node.namedBindings.elements.every((specifier) => specifier.isTypeOnly)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
var Found = class {
|
|
sourceFile;
|
|
parent;
|
|
constructor(sourceFile, parent) {
|
|
this.sourceFile = sourceFile;
|
|
this.parent = parent;
|
|
}
|
|
/**
|
|
* Back track through this found SourceFile and its ancestors to generate an array of
|
|
* SourceFiles that form am import path between two SourceFiles.
|
|
*/
|
|
toPath() {
|
|
const array = [];
|
|
let current = this;
|
|
while (current !== null) {
|
|
array.push(current.sourceFile);
|
|
current = current.parent;
|
|
}
|
|
return array.reverse();
|
|
}
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/entry_point/src/generator.js
|
|
import ts17 from "typescript";
|
|
var FlatIndexGenerator = class {
|
|
entryPoint;
|
|
moduleName;
|
|
flatIndexPath;
|
|
shouldEmit = true;
|
|
constructor(entryPoint, relativeFlatIndexPath, moduleName) {
|
|
this.entryPoint = entryPoint;
|
|
this.moduleName = moduleName;
|
|
this.flatIndexPath = join(dirname(entryPoint), relativeFlatIndexPath).replace(/\.js$/, "") + ".ts";
|
|
}
|
|
makeTopLevelShim() {
|
|
const relativeEntryPoint = relativePathBetween(this.flatIndexPath, this.entryPoint);
|
|
const contents = `/**
|
|
* Generated bundle index. Do not edit.
|
|
*/
|
|
|
|
export * from '${relativeEntryPoint}';
|
|
`;
|
|
const genFile = ts17.createSourceFile(this.flatIndexPath, contents, ts17.ScriptTarget.ES2015, true, ts17.ScriptKind.TS);
|
|
if (this.moduleName !== null) {
|
|
genFile.moduleName = this.moduleName;
|
|
}
|
|
return genFile;
|
|
}
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/entry_point/src/logic.js
|
|
function findFlatIndexEntryPoint(rootFiles) {
|
|
const tsFiles = rootFiles.filter((file) => isNonDeclarationTsPath(file));
|
|
let resolvedEntryPoint = null;
|
|
if (tsFiles.length === 1) {
|
|
resolvedEntryPoint = tsFiles[0];
|
|
} else {
|
|
for (const tsFile of tsFiles) {
|
|
if (getFileSystem().basename(tsFile) === "index.ts" && (resolvedEntryPoint === null || tsFile.length <= resolvedEntryPoint.length)) {
|
|
resolvedEntryPoint = tsFile;
|
|
}
|
|
}
|
|
}
|
|
return resolvedEntryPoint;
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/entry_point/src/private_export_checker.js
|
|
import ts18 from "typescript";
|
|
function checkForPrivateExports(entryPoint, checker, refGraph) {
|
|
const diagnostics = [];
|
|
const topLevelExports = /* @__PURE__ */ new Set();
|
|
const moduleSymbol = checker.getSymbolAtLocation(entryPoint);
|
|
if (moduleSymbol === void 0) {
|
|
throw new Error(`Internal error: failed to get symbol for entrypoint`);
|
|
}
|
|
const exportedSymbols = checker.getExportsOfModule(moduleSymbol);
|
|
exportedSymbols.forEach((symbol) => {
|
|
if (symbol.flags & ts18.SymbolFlags.Alias) {
|
|
symbol = checker.getAliasedSymbol(symbol);
|
|
}
|
|
const decl = symbol.valueDeclaration;
|
|
if (decl !== void 0) {
|
|
topLevelExports.add(decl);
|
|
}
|
|
});
|
|
const checkedSet = /* @__PURE__ */ new Set();
|
|
topLevelExports.forEach((mainExport) => {
|
|
refGraph.transitiveReferencesOf(mainExport).forEach((transitiveReference) => {
|
|
if (checkedSet.has(transitiveReference)) {
|
|
return;
|
|
}
|
|
checkedSet.add(transitiveReference);
|
|
if (!topLevelExports.has(transitiveReference)) {
|
|
const descriptor = getDescriptorOfDeclaration(transitiveReference);
|
|
const name = getNameOfDeclaration(transitiveReference);
|
|
let visibleVia = "NgModule exports";
|
|
const transitivePath = refGraph.pathFrom(mainExport, transitiveReference);
|
|
if (transitivePath !== null) {
|
|
visibleVia = transitivePath.map((seg) => getNameOfDeclaration(seg)).join(" -> ");
|
|
}
|
|
const diagnostic = {
|
|
category: ts18.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.`
|
|
};
|
|
diagnostics.push(diagnostic);
|
|
}
|
|
});
|
|
});
|
|
return diagnostics;
|
|
}
|
|
function getPosOfDeclaration(decl) {
|
|
const node = getIdentifierOfDeclaration(decl) || decl;
|
|
return {
|
|
start: node.getStart(),
|
|
length: node.getEnd() + 1 - node.getStart()
|
|
};
|
|
}
|
|
function getIdentifierOfDeclaration(decl) {
|
|
if ((ts18.isClassDeclaration(decl) || ts18.isVariableDeclaration(decl) || ts18.isFunctionDeclaration(decl)) && decl.name !== void 0 && ts18.isIdentifier(decl.name)) {
|
|
return decl.name;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
function getNameOfDeclaration(decl) {
|
|
const id = getIdentifierOfDeclaration(decl);
|
|
return id !== null ? id.text : "(unnamed)";
|
|
}
|
|
function getDescriptorOfDeclaration(decl) {
|
|
switch (decl.kind) {
|
|
case ts18.SyntaxKind.ClassDeclaration:
|
|
return "class";
|
|
case ts18.SyntaxKind.FunctionDeclaration:
|
|
return "function";
|
|
case ts18.SyntaxKind.VariableDeclaration:
|
|
return "variable";
|
|
case ts18.SyntaxKind.EnumDeclaration:
|
|
return "enum";
|
|
default:
|
|
return "declaration";
|
|
}
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/entry_point/src/reference_graph.js
|
|
var ReferenceGraph = class {
|
|
references = /* @__PURE__ */ new Map();
|
|
add(from, to) {
|
|
if (!this.references.has(from)) {
|
|
this.references.set(from, /* @__PURE__ */ new Set());
|
|
}
|
|
this.references.get(from).add(to);
|
|
}
|
|
transitiveReferencesOf(target) {
|
|
const set = /* @__PURE__ */ new Set();
|
|
this.collectTransitiveReferences(set, target);
|
|
return set;
|
|
}
|
|
pathFrom(source, target) {
|
|
return this.collectPathFrom(source, target, /* @__PURE__ */ new Set());
|
|
}
|
|
collectPathFrom(source, target, seen) {
|
|
if (source === target) {
|
|
return [target];
|
|
} else if (seen.has(source)) {
|
|
return null;
|
|
}
|
|
seen.add(source);
|
|
if (!this.references.has(source)) {
|
|
return null;
|
|
} else {
|
|
let candidatePath = null;
|
|
this.references.get(source).forEach((edge) => {
|
|
if (candidatePath !== null) {
|
|
return;
|
|
}
|
|
const partialPath = this.collectPathFrom(edge, target, seen);
|
|
if (partialPath !== null) {
|
|
candidatePath = [source, ...partialPath];
|
|
}
|
|
});
|
|
return candidatePath;
|
|
}
|
|
}
|
|
collectTransitiveReferences(set, decl) {
|
|
if (this.references.has(decl)) {
|
|
this.references.get(decl).forEach((ref) => {
|
|
if (!set.has(ref)) {
|
|
set.add(ref);
|
|
this.collectTransitiveReferences(set, ref);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/incremental/src/dependency_tracking.js
|
|
var FileDependencyGraph = class {
|
|
nodes = /* @__PURE__ */ new Map();
|
|
addDependency(from, on) {
|
|
this.nodeFor(from).dependsOn.add(absoluteFromSourceFile(on));
|
|
}
|
|
addResourceDependency(from, resource) {
|
|
this.nodeFor(from).usesResources.add(resource);
|
|
}
|
|
recordDependencyAnalysisFailure(file) {
|
|
this.nodeFor(file).failedAnalysis = true;
|
|
}
|
|
getResourceDependencies(from) {
|
|
const node = this.nodes.get(from);
|
|
return node ? [...node.usesResources] : [];
|
|
}
|
|
/**
|
|
* Update the current dependency graph from a previous one, incorporating a set of physical
|
|
* changes.
|
|
*
|
|
* This method performs two tasks:
|
|
*
|
|
* 1. For files which have not logically changed, their dependencies from `previous` are added to
|
|
* `this` graph.
|
|
* 2. For files which have logically changed, they're added to a set of logically changed files
|
|
* which is eventually returned.
|
|
*
|
|
* In essence, for build `n`, this method performs:
|
|
*
|
|
* G(n) + L(n) = G(n - 1) + P(n)
|
|
*
|
|
* where:
|
|
*
|
|
* G(n) = the dependency graph of build `n`
|
|
* L(n) = the logically changed files from build n - 1 to build n.
|
|
* P(n) = the physically changed files from build n - 1 to build n.
|
|
*/
|
|
updateWithPhysicalChanges(previous, changedTsPaths, deletedTsPaths, changedResources) {
|
|
const logicallyChanged = /* @__PURE__ */ new Set();
|
|
for (const sf of previous.nodes.keys()) {
|
|
const sfPath = absoluteFromSourceFile(sf);
|
|
const node = previous.nodeFor(sf);
|
|
if (isLogicallyChanged(sf, node, changedTsPaths, deletedTsPaths, changedResources)) {
|
|
logicallyChanged.add(sfPath);
|
|
} else if (!deletedTsPaths.has(sfPath)) {
|
|
this.nodes.set(sf, {
|
|
dependsOn: new Set(node.dependsOn),
|
|
usesResources: new Set(node.usesResources),
|
|
failedAnalysis: false
|
|
});
|
|
}
|
|
}
|
|
return logicallyChanged;
|
|
}
|
|
nodeFor(sf) {
|
|
if (!this.nodes.has(sf)) {
|
|
this.nodes.set(sf, {
|
|
dependsOn: /* @__PURE__ */ new Set(),
|
|
usesResources: /* @__PURE__ */ new Set(),
|
|
failedAnalysis: false
|
|
});
|
|
}
|
|
return this.nodes.get(sf);
|
|
}
|
|
};
|
|
function isLogicallyChanged(sf, node, changedTsPaths, deletedTsPaths, changedResources) {
|
|
if (node.failedAnalysis) {
|
|
return true;
|
|
}
|
|
const sfPath = absoluteFromSourceFile(sf);
|
|
if (changedTsPaths.has(sfPath) || deletedTsPaths.has(sfPath)) {
|
|
return true;
|
|
}
|
|
for (const dep of node.dependsOn) {
|
|
if (changedTsPaths.has(dep) || deletedTsPaths.has(dep)) {
|
|
return true;
|
|
}
|
|
}
|
|
for (const dep of node.usesResources) {
|
|
if (changedResources.has(dep)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/incremental/src/state.js
|
|
var IncrementalStateKind;
|
|
(function(IncrementalStateKind2) {
|
|
IncrementalStateKind2[IncrementalStateKind2["Fresh"] = 0] = "Fresh";
|
|
IncrementalStateKind2[IncrementalStateKind2["Delta"] = 1] = "Delta";
|
|
IncrementalStateKind2[IncrementalStateKind2["Analyzed"] = 2] = "Analyzed";
|
|
})(IncrementalStateKind || (IncrementalStateKind = {}));
|
|
|
|
// packages/compiler-cli/src/ngtsc/incremental/src/incremental.js
|
|
var PhaseKind;
|
|
(function(PhaseKind2) {
|
|
PhaseKind2[PhaseKind2["Analysis"] = 0] = "Analysis";
|
|
PhaseKind2[PhaseKind2["TypeCheckAndEmit"] = 1] = "TypeCheckAndEmit";
|
|
})(PhaseKind || (PhaseKind = {}));
|
|
var IncrementalCompilation = class _IncrementalCompilation {
|
|
depGraph;
|
|
versions;
|
|
step;
|
|
phase;
|
|
/**
|
|
* `IncrementalState` of this compilation if it were to be reused in a subsequent incremental
|
|
* compilation at the current moment.
|
|
*
|
|
* Exposed via the `state` read-only getter.
|
|
*/
|
|
_state;
|
|
constructor(state, depGraph, versions, step) {
|
|
this.depGraph = depGraph;
|
|
this.versions = versions;
|
|
this.step = step;
|
|
this._state = state;
|
|
this.phase = {
|
|
kind: PhaseKind.Analysis,
|
|
semanticDepGraphUpdater: new SemanticDepGraphUpdater(step !== null ? step.priorState.semanticDepGraph : null)
|
|
};
|
|
}
|
|
/**
|
|
* Begin a fresh `IncrementalCompilation`.
|
|
*/
|
|
static fresh(program, versions) {
|
|
const state = {
|
|
kind: IncrementalStateKind.Fresh
|
|
};
|
|
return new _IncrementalCompilation(
|
|
state,
|
|
new FileDependencyGraph(),
|
|
versions,
|
|
/* reuse */
|
|
null
|
|
);
|
|
}
|
|
static incremental(program, newVersions, oldProgram, oldState, modifiedResourceFiles, perf) {
|
|
return perf.inPhase(PerfPhase.Reconciliation, () => {
|
|
const physicallyChangedTsFiles = /* @__PURE__ */ new Set();
|
|
const changedResourceFiles = new Set(modifiedResourceFiles ?? []);
|
|
let priorAnalysis;
|
|
switch (oldState.kind) {
|
|
case IncrementalStateKind.Fresh:
|
|
return _IncrementalCompilation.fresh(program, newVersions);
|
|
case IncrementalStateKind.Analyzed:
|
|
priorAnalysis = oldState;
|
|
break;
|
|
case IncrementalStateKind.Delta:
|
|
priorAnalysis = oldState.lastAnalyzedState;
|
|
for (const sfPath of oldState.physicallyChangedTsFiles) {
|
|
physicallyChangedTsFiles.add(sfPath);
|
|
}
|
|
for (const resourcePath of oldState.changedResourceFiles) {
|
|
changedResourceFiles.add(resourcePath);
|
|
}
|
|
break;
|
|
}
|
|
const oldVersions = priorAnalysis.versions;
|
|
const oldFilesArray = oldProgram.getSourceFiles().map(toOriginalSourceFile);
|
|
const oldFiles = new Set(oldFilesArray);
|
|
const deletedTsFiles = new Set(oldFilesArray.map((sf) => absoluteFromSourceFile(sf)));
|
|
for (const possiblyRedirectedNewFile of program.getSourceFiles()) {
|
|
const sf = toOriginalSourceFile(possiblyRedirectedNewFile);
|
|
const sfPath = absoluteFromSourceFile(sf);
|
|
deletedTsFiles.delete(sfPath);
|
|
if (oldFiles.has(sf)) {
|
|
if (oldVersions === null || newVersions === null) {
|
|
continue;
|
|
}
|
|
if (oldVersions.has(sfPath) && newVersions.has(sfPath) && oldVersions.get(sfPath) === newVersions.get(sfPath)) {
|
|
continue;
|
|
}
|
|
}
|
|
if (sf.isDeclarationFile) {
|
|
return _IncrementalCompilation.fresh(program, newVersions);
|
|
}
|
|
physicallyChangedTsFiles.add(sfPath);
|
|
}
|
|
for (const deletedFileName of deletedTsFiles) {
|
|
physicallyChangedTsFiles.delete(resolve(deletedFileName));
|
|
}
|
|
const depGraph = new FileDependencyGraph();
|
|
const logicallyChangedTsFiles = depGraph.updateWithPhysicalChanges(priorAnalysis.depGraph, physicallyChangedTsFiles, deletedTsFiles, changedResourceFiles);
|
|
for (const sfPath of physicallyChangedTsFiles) {
|
|
logicallyChangedTsFiles.add(sfPath);
|
|
}
|
|
const state = {
|
|
kind: IncrementalStateKind.Delta,
|
|
physicallyChangedTsFiles,
|
|
changedResourceFiles,
|
|
lastAnalyzedState: priorAnalysis
|
|
};
|
|
return new _IncrementalCompilation(state, depGraph, newVersions, {
|
|
priorState: priorAnalysis,
|
|
logicallyChangedTsFiles
|
|
});
|
|
});
|
|
}
|
|
get state() {
|
|
return this._state;
|
|
}
|
|
get semanticDepGraphUpdater() {
|
|
if (this.phase.kind !== PhaseKind.Analysis) {
|
|
throw new Error(`AssertionError: Cannot update the SemanticDepGraph after analysis completes`);
|
|
}
|
|
return this.phase.semanticDepGraphUpdater;
|
|
}
|
|
recordSuccessfulAnalysis(traitCompiler) {
|
|
if (this.phase.kind !== PhaseKind.Analysis) {
|
|
throw new Error(`AssertionError: Incremental compilation in phase ${PhaseKind[this.phase.kind]}, expected Analysis`);
|
|
}
|
|
const { needsEmit, needsTypeCheckEmit, newGraph } = this.phase.semanticDepGraphUpdater.finalize();
|
|
let emitted;
|
|
if (this.step === null) {
|
|
emitted = /* @__PURE__ */ new Set();
|
|
} else {
|
|
emitted = new Set(this.step.priorState.emitted);
|
|
for (const sfPath of this.step.logicallyChangedTsFiles) {
|
|
emitted.delete(sfPath);
|
|
}
|
|
for (const sfPath of needsEmit) {
|
|
emitted.delete(sfPath);
|
|
}
|
|
}
|
|
this._state = {
|
|
kind: IncrementalStateKind.Analyzed,
|
|
versions: this.versions,
|
|
depGraph: this.depGraph,
|
|
semanticDepGraph: newGraph,
|
|
priorAnalysis: traitCompiler.getAnalyzedRecords(),
|
|
typeCheckResults: null,
|
|
emitted
|
|
};
|
|
this.phase = {
|
|
kind: PhaseKind.TypeCheckAndEmit,
|
|
needsEmit,
|
|
needsTypeCheckEmit
|
|
};
|
|
}
|
|
recordSuccessfulTypeCheck(results) {
|
|
if (this._state.kind !== IncrementalStateKind.Analyzed) {
|
|
throw new Error(`AssertionError: Expected successfully analyzed compilation.`);
|
|
} else if (this.phase.kind !== PhaseKind.TypeCheckAndEmit) {
|
|
throw new Error(`AssertionError: Incremental compilation in phase ${PhaseKind[this.phase.kind]}, expected TypeCheck`);
|
|
}
|
|
this._state.typeCheckResults = results;
|
|
}
|
|
recordSuccessfulEmit(sf) {
|
|
if (this._state.kind !== IncrementalStateKind.Analyzed) {
|
|
throw new Error(`AssertionError: Expected successfully analyzed compilation.`);
|
|
}
|
|
this._state.emitted.add(absoluteFromSourceFile(sf));
|
|
}
|
|
priorAnalysisFor(sf) {
|
|
if (this.step === null) {
|
|
return null;
|
|
}
|
|
const sfPath = absoluteFromSourceFile(sf);
|
|
if (this.step.logicallyChangedTsFiles.has(sfPath)) {
|
|
return null;
|
|
}
|
|
const priorAnalysis = this.step.priorState.priorAnalysis;
|
|
if (!priorAnalysis.has(sf)) {
|
|
return null;
|
|
}
|
|
return priorAnalysis.get(sf);
|
|
}
|
|
priorTypeCheckingResultsFor(sf) {
|
|
if (this.phase.kind !== PhaseKind.TypeCheckAndEmit) {
|
|
throw new Error(`AssertionError: Expected successfully analyzed compilation.`);
|
|
}
|
|
if (this.step === null) {
|
|
return null;
|
|
}
|
|
const sfPath = absoluteFromSourceFile(sf);
|
|
if (this.step.logicallyChangedTsFiles.has(sfPath) || this.phase.needsTypeCheckEmit.has(sfPath)) {
|
|
return null;
|
|
}
|
|
if (this.step.priorState.typeCheckResults === null || !this.step.priorState.typeCheckResults.has(sfPath)) {
|
|
return null;
|
|
}
|
|
const priorResults = this.step.priorState.typeCheckResults.get(sfPath);
|
|
if (priorResults.hasInlines) {
|
|
return null;
|
|
}
|
|
return priorResults;
|
|
}
|
|
safeToSkipEmit(sf) {
|
|
if (this.step === null) {
|
|
return false;
|
|
}
|
|
const sfPath = absoluteFromSourceFile(sf);
|
|
if (this.step.logicallyChangedTsFiles.has(sfPath)) {
|
|
return false;
|
|
}
|
|
if (this.phase.kind !== PhaseKind.TypeCheckAndEmit) {
|
|
throw new Error(`AssertionError: Expected successful analysis before attempting to emit files`);
|
|
}
|
|
if (this.phase.needsEmit.has(sfPath)) {
|
|
return false;
|
|
}
|
|
return this.step.priorState.emitted.has(sfPath);
|
|
}
|
|
};
|
|
function toOriginalSourceFile(sf) {
|
|
const unredirectedSf = toUnredirectedSourceFile(sf);
|
|
const originalFile = unredirectedSf[NgOriginalFile];
|
|
if (originalFile !== void 0) {
|
|
return originalFile;
|
|
} else {
|
|
return unredirectedSf;
|
|
}
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/incremental/src/strategy.js
|
|
var TrackedIncrementalBuildStrategy = class _TrackedIncrementalBuildStrategy {
|
|
state = null;
|
|
isSet = false;
|
|
getIncrementalState() {
|
|
return this.state;
|
|
}
|
|
setIncrementalState(state) {
|
|
this.state = state;
|
|
this.isSet = true;
|
|
}
|
|
toNextBuildStrategy() {
|
|
const strategy = new _TrackedIncrementalBuildStrategy();
|
|
strategy.state = this.isSet ? this.state : null;
|
|
return strategy;
|
|
}
|
|
};
|
|
var PatchedProgramIncrementalBuildStrategy = class {
|
|
getIncrementalState(program) {
|
|
const state = program[SYM_INCREMENTAL_STATE];
|
|
if (state === void 0) {
|
|
return null;
|
|
}
|
|
return state;
|
|
}
|
|
setIncrementalState(state, program) {
|
|
program[SYM_INCREMENTAL_STATE] = state;
|
|
}
|
|
toNextBuildStrategy() {
|
|
return this;
|
|
}
|
|
};
|
|
var SYM_INCREMENTAL_STATE = Symbol("NgIncrementalState");
|
|
|
|
// packages/compiler-cli/src/ngtsc/indexer/src/api.js
|
|
var IdentifierKind;
|
|
(function(IdentifierKind2) {
|
|
IdentifierKind2[IdentifierKind2["Property"] = 0] = "Property";
|
|
IdentifierKind2[IdentifierKind2["Method"] = 1] = "Method";
|
|
IdentifierKind2[IdentifierKind2["Element"] = 2] = "Element";
|
|
IdentifierKind2[IdentifierKind2["Template"] = 3] = "Template";
|
|
IdentifierKind2[IdentifierKind2["Attribute"] = 4] = "Attribute";
|
|
IdentifierKind2[IdentifierKind2["Reference"] = 5] = "Reference";
|
|
IdentifierKind2[IdentifierKind2["Variable"] = 6] = "Variable";
|
|
IdentifierKind2[IdentifierKind2["LetDeclaration"] = 7] = "LetDeclaration";
|
|
IdentifierKind2[IdentifierKind2["Component"] = 8] = "Component";
|
|
IdentifierKind2[IdentifierKind2["Directive"] = 9] = "Directive";
|
|
})(IdentifierKind || (IdentifierKind = {}));
|
|
var AbsoluteSourceSpan = class {
|
|
start;
|
|
end;
|
|
constructor(start, end) {
|
|
this.start = start;
|
|
this.end = end;
|
|
}
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/indexer/src/context.js
|
|
var IndexingContext = class {
|
|
components = /* @__PURE__ */ new Set();
|
|
/**
|
|
* Adds a component to the context.
|
|
*/
|
|
addComponent(info) {
|
|
this.components.add(info);
|
|
}
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/indexer/src/transform.js
|
|
import { ParseSourceFile } from "@angular/compiler";
|
|
|
|
// packages/compiler-cli/src/ngtsc/indexer/src/template.js
|
|
import { ASTWithSource, CombinedRecursiveAstVisitor, ImplicitReceiver, PropertyRead, TmplAstComponent, TmplAstDirective, TmplAstElement, TmplAstReference, TmplAstTemplate, TmplAstVariable, tmplAstVisitAll } from "@angular/compiler";
|
|
var TemplateVisitor = class extends CombinedRecursiveAstVisitor {
|
|
boundTemplate;
|
|
// Identifiers of interest found in the template.
|
|
identifiers = /* @__PURE__ */ new Set();
|
|
errors = [];
|
|
currentAstWithSource = null;
|
|
// Map of targets in a template to their identifiers.
|
|
targetIdentifierCache = /* @__PURE__ */ new Map();
|
|
// Map of elements and templates to their identifiers.
|
|
directiveHostIdentifierCache = /* @__PURE__ */ new Map();
|
|
/**
|
|
* Creates a template visitor for a bound template target. The bound target can be used when
|
|
* deferred to the expression visitor to get information about the target of an expression.
|
|
*
|
|
* @param boundTemplate bound template target
|
|
*/
|
|
constructor(boundTemplate) {
|
|
super();
|
|
this.boundTemplate = boundTemplate;
|
|
}
|
|
/**
|
|
* Add an identifier for an HTML element and visit its children recursively.
|
|
*
|
|
* @param element
|
|
*/
|
|
visitElement(element) {
|
|
const elementIdentifier = this.directiveHostToIdentifier(element);
|
|
if (elementIdentifier !== null) {
|
|
this.identifiers.add(elementIdentifier);
|
|
}
|
|
super.visitElement(element);
|
|
}
|
|
visitTemplate(template) {
|
|
const templateIdentifier = this.directiveHostToIdentifier(template);
|
|
if (templateIdentifier !== null) {
|
|
this.identifiers.add(templateIdentifier);
|
|
}
|
|
super.visitTemplate(template);
|
|
}
|
|
visitReference(reference) {
|
|
const referenceIdentifier = this.targetToIdentifier(reference);
|
|
if (referenceIdentifier !== null) {
|
|
this.identifiers.add(referenceIdentifier);
|
|
}
|
|
super.visitReference(reference);
|
|
}
|
|
visitVariable(variable) {
|
|
const variableIdentifier = this.targetToIdentifier(variable);
|
|
if (variableIdentifier !== null) {
|
|
this.identifiers.add(variableIdentifier);
|
|
}
|
|
super.visitVariable(variable);
|
|
}
|
|
visitLetDeclaration(decl) {
|
|
const identifier = this.targetToIdentifier(decl);
|
|
if (identifier !== null) {
|
|
this.identifiers.add(identifier);
|
|
}
|
|
super.visitLetDeclaration(decl);
|
|
}
|
|
visitComponent(component) {
|
|
const identifier = this.directiveHostToIdentifier(component);
|
|
if (identifier !== null) {
|
|
this.identifiers.add(identifier);
|
|
}
|
|
super.visitComponent(component);
|
|
}
|
|
visitDirective(directive) {
|
|
const identifier = this.directiveHostToIdentifier(directive);
|
|
if (identifier !== null) {
|
|
this.identifiers.add(identifier);
|
|
}
|
|
super.visitDirective(directive);
|
|
}
|
|
visitPropertyRead(ast) {
|
|
this.visitIdentifier(ast, IdentifierKind.Property);
|
|
super.visitPropertyRead(ast, null);
|
|
}
|
|
visitBoundAttribute(attribute) {
|
|
const previous = this.currentAstWithSource;
|
|
this.currentAstWithSource = {
|
|
source: attribute.valueSpan?.toString() || null,
|
|
absoluteOffset: attribute.valueSpan ? attribute.valueSpan.start.offset : -1
|
|
};
|
|
this.visit(attribute.value instanceof ASTWithSource ? attribute.value.ast : attribute.value);
|
|
this.currentAstWithSource = previous;
|
|
}
|
|
/** Creates an identifier for a template element or template node. */
|
|
directiveHostToIdentifier(node) {
|
|
if (this.directiveHostIdentifierCache.has(node)) {
|
|
return this.directiveHostIdentifierCache.get(node);
|
|
}
|
|
let name;
|
|
let kind;
|
|
if (node instanceof TmplAstTemplate) {
|
|
name = node.tagName ?? "ng-template";
|
|
kind = IdentifierKind.Template;
|
|
} else if (node instanceof TmplAstElement) {
|
|
name = node.name;
|
|
kind = IdentifierKind.Element;
|
|
} else if (node instanceof TmplAstComponent) {
|
|
name = node.fullName;
|
|
kind = IdentifierKind.Component;
|
|
} else {
|
|
name = node.name;
|
|
kind = IdentifierKind.Directive;
|
|
}
|
|
if ((node instanceof TmplAstTemplate || node instanceof TmplAstElement) && name.startsWith(":")) {
|
|
name = name.split(":").pop();
|
|
}
|
|
const sourceSpan = node.startSourceSpan;
|
|
const start = this.getStartLocation(name, sourceSpan);
|
|
if (start === null) {
|
|
return null;
|
|
}
|
|
const absoluteSpan = new AbsoluteSourceSpan(start, start + name.length);
|
|
const attributes = node.attributes.map(({ name: name2, sourceSpan: sourceSpan2 }) => {
|
|
return {
|
|
name: name2,
|
|
span: new AbsoluteSourceSpan(sourceSpan2.start.offset, sourceSpan2.end.offset),
|
|
kind: IdentifierKind.Attribute
|
|
};
|
|
});
|
|
const usedDirectives = this.boundTemplate.getDirectivesOfNode(node) || [];
|
|
const identifier = {
|
|
name,
|
|
span: absoluteSpan,
|
|
kind,
|
|
attributes: new Set(attributes),
|
|
usedDirectives: new Set(usedDirectives.map((dir) => {
|
|
return {
|
|
node: dir.ref.node,
|
|
selector: dir.selector
|
|
};
|
|
}))
|
|
// cast b/c pre-TypeScript 3.5 unions aren't well discriminated
|
|
};
|
|
this.directiveHostIdentifierCache.set(node, identifier);
|
|
return identifier;
|
|
}
|
|
/** Creates an identifier for a template reference or template variable target. */
|
|
targetToIdentifier(node) {
|
|
if (this.targetIdentifierCache.has(node)) {
|
|
return this.targetIdentifierCache.get(node);
|
|
}
|
|
const { name, sourceSpan } = node;
|
|
const start = this.getStartLocation(name, sourceSpan);
|
|
if (start === null) {
|
|
return null;
|
|
}
|
|
const span = new AbsoluteSourceSpan(start, start + name.length);
|
|
let identifier;
|
|
if (node instanceof TmplAstReference) {
|
|
const refTarget = this.boundTemplate.getReferenceTarget(node);
|
|
let target = null;
|
|
if (refTarget) {
|
|
let node2 = null;
|
|
let directive = null;
|
|
if (refTarget instanceof TmplAstElement || refTarget instanceof TmplAstTemplate || refTarget instanceof TmplAstComponent || refTarget instanceof TmplAstDirective) {
|
|
node2 = this.directiveHostToIdentifier(refTarget);
|
|
} else {
|
|
node2 = this.directiveHostToIdentifier(refTarget.node);
|
|
directive = refTarget.directive.ref.node;
|
|
}
|
|
if (node2 === null) {
|
|
return null;
|
|
}
|
|
target = {
|
|
node: node2,
|
|
directive
|
|
};
|
|
}
|
|
identifier = {
|
|
name,
|
|
span,
|
|
kind: IdentifierKind.Reference,
|
|
target
|
|
};
|
|
} else if (node instanceof TmplAstVariable) {
|
|
identifier = {
|
|
name,
|
|
span,
|
|
kind: IdentifierKind.Variable
|
|
};
|
|
} else {
|
|
identifier = {
|
|
name,
|
|
span,
|
|
kind: IdentifierKind.LetDeclaration
|
|
};
|
|
}
|
|
this.targetIdentifierCache.set(node, identifier);
|
|
return identifier;
|
|
}
|
|
/** Gets the start location of a string in a SourceSpan */
|
|
getStartLocation(name, context) {
|
|
const localStr = context.toString();
|
|
if (!localStr.includes(name)) {
|
|
this.errors.push(new Error(`Impossible state: "${name}" not found in "${localStr}"`));
|
|
return null;
|
|
}
|
|
return context.start.offset + localStr.indexOf(name);
|
|
}
|
|
/**
|
|
* Visits a node's expression and adds its identifiers, if any, to the visitor's state.
|
|
* Only ASTs with information about the expression source and its location are visited.
|
|
*
|
|
* @param node node whose expression to visit
|
|
*/
|
|
visit(node) {
|
|
if (node instanceof ASTWithSource) {
|
|
const previous = this.currentAstWithSource;
|
|
this.currentAstWithSource = { source: node.source, absoluteOffset: node.sourceSpan.start };
|
|
super.visit(node.ast);
|
|
this.currentAstWithSource = previous;
|
|
} else {
|
|
super.visit(node);
|
|
}
|
|
}
|
|
/**
|
|
* Visits an identifier, adding it to the identifier store if it is useful for indexing.
|
|
*
|
|
* @param ast expression AST the identifier is in
|
|
* @param kind identifier kind
|
|
*/
|
|
visitIdentifier(ast, kind) {
|
|
if (this.currentAstWithSource === null || this.currentAstWithSource.source === null) {
|
|
return;
|
|
}
|
|
if (!(ast.receiver instanceof ImplicitReceiver)) {
|
|
return;
|
|
}
|
|
const { absoluteOffset, source: expressionStr } = this.currentAstWithSource;
|
|
let identifierStart = ast.sourceSpan.start - absoluteOffset;
|
|
if (ast instanceof PropertyRead) {
|
|
identifierStart = ast.nameSpan.start - absoluteOffset;
|
|
}
|
|
if (!expressionStr.substring(identifierStart).startsWith(ast.name)) {
|
|
this.errors.push(new Error(`Impossible state: "${ast.name}" not found in "${expressionStr}" at location ${identifierStart}`));
|
|
return;
|
|
}
|
|
const absoluteStart = absoluteOffset + identifierStart;
|
|
const span = new AbsoluteSourceSpan(absoluteStart, absoluteStart + ast.name.length);
|
|
const targetAst = this.boundTemplate.getExpressionTarget(ast);
|
|
const target = targetAst ? this.targetToIdentifier(targetAst) : null;
|
|
const identifier = {
|
|
name: ast.name,
|
|
span,
|
|
kind,
|
|
target
|
|
};
|
|
this.identifiers.add(identifier);
|
|
}
|
|
};
|
|
function getTemplateIdentifiers(boundTemplate) {
|
|
const visitor = new TemplateVisitor(boundTemplate);
|
|
if (boundTemplate.target.template !== void 0) {
|
|
tmplAstVisitAll(visitor, boundTemplate.target.template);
|
|
}
|
|
return { identifiers: visitor.identifiers, errors: visitor.errors };
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/indexer/src/transform.js
|
|
function generateAnalysis(context) {
|
|
const analysis = /* @__PURE__ */ new Map();
|
|
context.components.forEach(({ declaration, selector, boundTemplate, templateMeta }) => {
|
|
const name = declaration.name.getText();
|
|
const usedComponents = /* @__PURE__ */ new Set();
|
|
const usedDirs = boundTemplate.getUsedDirectives();
|
|
usedDirs.forEach((dir) => {
|
|
if (dir.isComponent) {
|
|
usedComponents.add(dir.ref.node);
|
|
}
|
|
});
|
|
const componentFile = new ParseSourceFile(declaration.getSourceFile().getFullText(), declaration.getSourceFile().fileName);
|
|
let templateFile;
|
|
if (templateMeta.isInline) {
|
|
templateFile = componentFile;
|
|
} else {
|
|
templateFile = templateMeta.file;
|
|
}
|
|
const { identifiers, errors } = getTemplateIdentifiers(boundTemplate);
|
|
analysis.set(declaration, {
|
|
name,
|
|
selector,
|
|
file: componentFile,
|
|
template: {
|
|
identifiers,
|
|
usedComponents,
|
|
isInline: templateMeta.isInline,
|
|
file: templateFile
|
|
},
|
|
errors
|
|
});
|
|
});
|
|
return analysis;
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/metadata/src/ng_module_index.js
|
|
var NgModuleIndexImpl = class {
|
|
metaReader;
|
|
localReader;
|
|
constructor(metaReader, localReader) {
|
|
this.metaReader = metaReader;
|
|
this.localReader = localReader;
|
|
}
|
|
// A map from an NgModule's Class Declaration to the "main" reference to that module, aka the one
|
|
// present in the reader metadata object
|
|
ngModuleAuthoritativeReference = /* @__PURE__ */ new Map();
|
|
// A map from a Directive/Pipe's class declaration to the class declarations of all re-exporting
|
|
// NgModules
|
|
typeToExportingModules = /* @__PURE__ */ new Map();
|
|
indexed = false;
|
|
updateWith(cache, key, elem) {
|
|
if (cache.has(key)) {
|
|
cache.get(key).add(elem);
|
|
} else {
|
|
const set = /* @__PURE__ */ new Set();
|
|
set.add(elem);
|
|
cache.set(key, set);
|
|
}
|
|
}
|
|
index() {
|
|
const seenTypesWithReexports = /* @__PURE__ */ new Map();
|
|
const locallyDeclaredDirsAndNgModules = [
|
|
...this.localReader.getKnown(MetaKind.NgModule),
|
|
...this.localReader.getKnown(MetaKind.Directive)
|
|
];
|
|
for (const decl of locallyDeclaredDirsAndNgModules) {
|
|
this.indexTrait(new Reference(decl), seenTypesWithReexports);
|
|
}
|
|
this.indexed = true;
|
|
}
|
|
indexTrait(ref, seenTypesWithReexports) {
|
|
if (seenTypesWithReexports.has(ref.node)) {
|
|
return;
|
|
}
|
|
seenTypesWithReexports.set(ref.node, /* @__PURE__ */ new Set());
|
|
const meta = this.metaReader.getDirectiveMetadata(ref) ?? this.metaReader.getNgModuleMetadata(ref);
|
|
if (meta === null) {
|
|
return;
|
|
}
|
|
if (meta.imports !== null) {
|
|
for (const childRef of meta.imports) {
|
|
this.indexTrait(childRef, seenTypesWithReexports);
|
|
}
|
|
}
|
|
if (meta.kind === MetaKind.NgModule) {
|
|
if (!this.ngModuleAuthoritativeReference.has(ref.node)) {
|
|
this.ngModuleAuthoritativeReference.set(ref.node, ref);
|
|
}
|
|
for (const childRef of meta.exports) {
|
|
this.indexTrait(childRef, seenTypesWithReexports);
|
|
const childMeta = this.metaReader.getDirectiveMetadata(childRef) ?? this.metaReader.getPipeMetadata(childRef) ?? this.metaReader.getNgModuleMetadata(childRef);
|
|
if (childMeta === null) {
|
|
continue;
|
|
}
|
|
switch (childMeta.kind) {
|
|
case MetaKind.Directive:
|
|
case MetaKind.Pipe:
|
|
this.updateWith(this.typeToExportingModules, childRef.node, ref.node);
|
|
this.updateWith(seenTypesWithReexports, ref.node, childRef.node);
|
|
break;
|
|
case MetaKind.NgModule:
|
|
if (seenTypesWithReexports.has(childRef.node)) {
|
|
for (const reexported of seenTypesWithReexports.get(childRef.node)) {
|
|
this.updateWith(this.typeToExportingModules, reexported, ref.node);
|
|
this.updateWith(seenTypesWithReexports, ref.node, reexported);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
getNgModulesExporting(directiveOrPipe) {
|
|
if (!this.indexed) {
|
|
this.index();
|
|
}
|
|
if (!this.typeToExportingModules.has(directiveOrPipe)) {
|
|
return [];
|
|
}
|
|
const refs = [];
|
|
for (const ngModule of this.typeToExportingModules.get(directiveOrPipe)) {
|
|
if (this.ngModuleAuthoritativeReference.has(ngModule)) {
|
|
refs.push(this.ngModuleAuthoritativeReference.get(ngModule));
|
|
}
|
|
}
|
|
return refs;
|
|
}
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/resource/src/loader.js
|
|
import ts19 from "typescript";
|
|
var CSS_PREPROCESSOR_EXT = /(\.scss|\.sass|\.less|\.styl)$/;
|
|
var RESOURCE_MARKER = ".$ngresource$";
|
|
var RESOURCE_MARKER_TS = RESOURCE_MARKER + ".ts";
|
|
var AdapterResourceLoader = class {
|
|
adapter;
|
|
options;
|
|
cache = /* @__PURE__ */ new Map();
|
|
fetching = /* @__PURE__ */ new Map();
|
|
lookupResolutionHost;
|
|
canPreload;
|
|
canPreprocess;
|
|
constructor(adapter, options) {
|
|
this.adapter = adapter;
|
|
this.options = options;
|
|
this.lookupResolutionHost = createLookupResolutionHost(this.adapter);
|
|
this.canPreload = !!this.adapter.readResource;
|
|
this.canPreprocess = !!this.adapter.transformResource;
|
|
}
|
|
/**
|
|
* Resolve the url of a resource relative to the file that contains the reference to it.
|
|
* The return value of this method can be used in the `load()` and `preload()` methods.
|
|
*
|
|
* Uses the provided CompilerHost if it supports mapping resources to filenames.
|
|
* Otherwise, uses a fallback mechanism that searches the module resolution candidates.
|
|
*
|
|
* @param url The, possibly relative, url of the resource.
|
|
* @param fromFile The path to the file that contains the URL of the resource.
|
|
* @returns A resolved url of resource.
|
|
* @throws An error if the resource cannot be resolved.
|
|
*/
|
|
resolve(url, fromFile) {
|
|
let resolvedUrl = null;
|
|
if (this.adapter.resourceNameToFileName) {
|
|
resolvedUrl = this.adapter.resourceNameToFileName(url, fromFile, (url2, fromFile2) => this.fallbackResolve(url2, fromFile2));
|
|
} else {
|
|
resolvedUrl = this.fallbackResolve(url, fromFile);
|
|
}
|
|
if (resolvedUrl === null) {
|
|
throw new Error(`HostResourceResolver: could not resolve ${url} in context of ${fromFile})`);
|
|
}
|
|
return resolvedUrl;
|
|
}
|
|
/**
|
|
* Preload the specified resource, asynchronously.
|
|
*
|
|
* Once the resource is loaded, its value is cached so it can be accessed synchronously via the
|
|
* `load()` method.
|
|
*
|
|
* @param resolvedUrl The url (resolved by a call to `resolve()`) of the resource to preload.
|
|
* @param context Information about the resource such as the type and containing file.
|
|
* @returns A Promise that is resolved once the resource has been loaded or `undefined` if the
|
|
* file has already been loaded.
|
|
* @throws An Error if pre-loading is not available.
|
|
*/
|
|
preload(resolvedUrl, context) {
|
|
if (!this.adapter.readResource) {
|
|
throw new Error("HostResourceLoader: the CompilerHost provided does not support pre-loading resources.");
|
|
}
|
|
if (this.cache.has(resolvedUrl)) {
|
|
return void 0;
|
|
} else if (this.fetching.has(resolvedUrl)) {
|
|
return this.fetching.get(resolvedUrl);
|
|
}
|
|
let result = this.adapter.readResource(resolvedUrl);
|
|
if (this.adapter.transformResource && context.type === "style") {
|
|
const resourceContext = {
|
|
type: "style",
|
|
containingFile: context.containingFile,
|
|
resourceFile: resolvedUrl,
|
|
className: context.className
|
|
};
|
|
result = Promise.resolve(result).then(async (str) => {
|
|
const transformResult = await this.adapter.transformResource(str, resourceContext);
|
|
return transformResult === null ? str : transformResult.content;
|
|
});
|
|
}
|
|
if (typeof result === "string") {
|
|
this.cache.set(resolvedUrl, result);
|
|
return void 0;
|
|
} else {
|
|
const fetchCompletion = result.then((str) => {
|
|
this.fetching.delete(resolvedUrl);
|
|
this.cache.set(resolvedUrl, str);
|
|
});
|
|
this.fetching.set(resolvedUrl, fetchCompletion);
|
|
return fetchCompletion;
|
|
}
|
|
}
|
|
/**
|
|
* Preprocess the content data of an inline resource, asynchronously.
|
|
*
|
|
* @param data The existing content data from the inline resource.
|
|
* @param context Information regarding the resource such as the type and containing file.
|
|
* @returns A Promise that resolves to the processed data. If no processing occurs, the
|
|
* same data string that was passed to the function will be resolved.
|
|
*/
|
|
async preprocessInline(data, context) {
|
|
if (!this.adapter.transformResource || context.type !== "style") {
|
|
return data;
|
|
}
|
|
const transformResult = await this.adapter.transformResource(data, {
|
|
type: "style",
|
|
containingFile: context.containingFile,
|
|
resourceFile: null,
|
|
order: context.order,
|
|
className: context.className
|
|
});
|
|
if (transformResult === null) {
|
|
return data;
|
|
}
|
|
return transformResult.content;
|
|
}
|
|
/**
|
|
* Load the resource at the given url, synchronously.
|
|
*
|
|
* The contents of the resource may have been cached by a previous call to `preload()`.
|
|
*
|
|
* @param resolvedUrl The url (resolved by a call to `resolve()`) of the resource to load.
|
|
* @returns The contents of the resource.
|
|
*/
|
|
load(resolvedUrl) {
|
|
if (this.cache.has(resolvedUrl)) {
|
|
return this.cache.get(resolvedUrl);
|
|
}
|
|
const result = this.adapter.readResource ? this.adapter.readResource(resolvedUrl) : this.adapter.readFile(resolvedUrl);
|
|
if (typeof result !== "string") {
|
|
throw new Error(`HostResourceLoader: loader(${resolvedUrl}) returned a Promise`);
|
|
}
|
|
this.cache.set(resolvedUrl, result);
|
|
return result;
|
|
}
|
|
/**
|
|
* Invalidate the entire resource cache.
|
|
*/
|
|
invalidate() {
|
|
this.cache.clear();
|
|
}
|
|
/**
|
|
* Attempt to resolve `url` in the context of `fromFile`, while respecting the rootDirs
|
|
* option from the tsconfig. First, normalize the file name.
|
|
*/
|
|
fallbackResolve(url, fromFile) {
|
|
let candidateLocations;
|
|
if (url.startsWith("/")) {
|
|
candidateLocations = this.getRootedCandidateLocations(url);
|
|
} else {
|
|
if (!url.startsWith(".")) {
|
|
url = `./${url}`;
|
|
}
|
|
candidateLocations = this.getResolvedCandidateLocations(url, fromFile);
|
|
}
|
|
for (const candidate of candidateLocations) {
|
|
if (this.adapter.fileExists(candidate)) {
|
|
return candidate;
|
|
} else if (CSS_PREPROCESSOR_EXT.test(candidate)) {
|
|
const cssFallbackUrl = candidate.replace(CSS_PREPROCESSOR_EXT, ".css");
|
|
if (this.adapter.fileExists(cssFallbackUrl)) {
|
|
return cssFallbackUrl;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
getRootedCandidateLocations(url) {
|
|
const segment = "." + url;
|
|
return this.adapter.rootDirs.map((rootDir) => join(rootDir, segment));
|
|
}
|
|
/**
|
|
* TypeScript provides utilities to resolve module names, but not resource files (which aren't
|
|
* a part of the ts.Program). However, TypeScript's module resolution can be used creatively
|
|
* to locate where resource files should be expected to exist. Since module resolution returns
|
|
* a list of file names that were considered, the loader can enumerate the possible locations
|
|
* for the file by setting up a module resolution for it that will fail.
|
|
*/
|
|
getResolvedCandidateLocations(url, fromFile) {
|
|
const failedLookup = ts19.resolveModuleName(url + RESOURCE_MARKER, fromFile, this.options, this.lookupResolutionHost);
|
|
if (failedLookup.failedLookupLocations === void 0) {
|
|
throw new Error(`Internal error: expected to find failedLookupLocations during resolution of resource '${url}' in context of ${fromFile}`);
|
|
}
|
|
return failedLookup.failedLookupLocations.filter((candidate) => candidate.endsWith(RESOURCE_MARKER_TS)).map((candidate) => candidate.slice(0, -RESOURCE_MARKER_TS.length));
|
|
}
|
|
};
|
|
function createLookupResolutionHost(adapter) {
|
|
return {
|
|
directoryExists(directoryName) {
|
|
if (directoryName.includes(RESOURCE_MARKER)) {
|
|
return false;
|
|
} else if (adapter.directoryExists !== void 0) {
|
|
return adapter.directoryExists(directoryName);
|
|
} else {
|
|
return true;
|
|
}
|
|
},
|
|
fileExists(fileName) {
|
|
if (fileName.includes(RESOURCE_MARKER)) {
|
|
return false;
|
|
} else {
|
|
return adapter.fileExists(fileName);
|
|
}
|
|
},
|
|
readFile: adapter.readFile.bind(adapter),
|
|
getCurrentDirectory: adapter.getCurrentDirectory.bind(adapter),
|
|
getDirectories: adapter.getDirectories?.bind(adapter),
|
|
realpath: adapter.realpath?.bind(adapter),
|
|
trace: adapter.trace?.bind(adapter),
|
|
useCaseSensitiveFileNames: typeof adapter.useCaseSensitiveFileNames === "function" ? adapter.useCaseSensitiveFileNames.bind(adapter) : adapter.useCaseSensitiveFileNames
|
|
};
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/scope/src/standalone.js
|
|
var StandaloneComponentScopeReader = class {
|
|
metaReader;
|
|
localModuleReader;
|
|
dtsModuleReader;
|
|
cache = /* @__PURE__ */ new Map();
|
|
constructor(metaReader, localModuleReader, dtsModuleReader) {
|
|
this.metaReader = metaReader;
|
|
this.localModuleReader = localModuleReader;
|
|
this.dtsModuleReader = dtsModuleReader;
|
|
}
|
|
getScopeForComponent(clazz) {
|
|
if (!this.cache.has(clazz)) {
|
|
const clazzRef = new Reference(clazz);
|
|
const clazzMeta = this.metaReader.getDirectiveMetadata(clazzRef);
|
|
if (clazzMeta === null || !clazzMeta.isComponent || !clazzMeta.isStandalone || clazzMeta.selectorlessEnabled) {
|
|
this.cache.set(clazz, null);
|
|
return null;
|
|
}
|
|
const dependencies = /* @__PURE__ */ new Set([clazzMeta]);
|
|
const deferredDependencies = /* @__PURE__ */ new Set();
|
|
const seen = /* @__PURE__ */ new Set([clazz]);
|
|
let isPoisoned = clazzMeta.isPoisoned;
|
|
if (clazzMeta.imports !== null) {
|
|
for (const ref of clazzMeta.imports) {
|
|
if (seen.has(ref.node)) {
|
|
continue;
|
|
}
|
|
seen.add(ref.node);
|
|
const dirMeta = this.metaReader.getDirectiveMetadata(ref);
|
|
if (dirMeta !== null) {
|
|
dependencies.add({ ...dirMeta, ref });
|
|
isPoisoned = isPoisoned || dirMeta.isPoisoned || !dirMeta.isStandalone;
|
|
continue;
|
|
}
|
|
const pipeMeta = this.metaReader.getPipeMetadata(ref);
|
|
if (pipeMeta !== null) {
|
|
dependencies.add({ ...pipeMeta, ref });
|
|
isPoisoned = isPoisoned || !pipeMeta.isStandalone;
|
|
continue;
|
|
}
|
|
const ngModuleMeta = this.metaReader.getNgModuleMetadata(ref);
|
|
if (ngModuleMeta !== null) {
|
|
dependencies.add({ ...ngModuleMeta, ref });
|
|
let ngModuleScope;
|
|
if (ref.node.getSourceFile().isDeclarationFile) {
|
|
ngModuleScope = this.dtsModuleReader.resolve(ref);
|
|
} else {
|
|
ngModuleScope = this.localModuleReader.getScopeOfModule(ref.node);
|
|
}
|
|
if (ngModuleScope === null) {
|
|
isPoisoned = true;
|
|
continue;
|
|
}
|
|
isPoisoned = isPoisoned || ngModuleScope.exported.isPoisoned;
|
|
for (const dep of ngModuleScope.exported.dependencies) {
|
|
if (!seen.has(dep.ref.node)) {
|
|
seen.add(dep.ref.node);
|
|
dependencies.add(dep);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
isPoisoned = true;
|
|
}
|
|
}
|
|
if (clazzMeta.deferredImports !== null) {
|
|
for (const ref of clazzMeta.deferredImports) {
|
|
const dirMeta = this.metaReader.getDirectiveMetadata(ref);
|
|
if (dirMeta !== null) {
|
|
deferredDependencies.add({ ...dirMeta, ref, isExplicitlyDeferred: true });
|
|
isPoisoned = isPoisoned || dirMeta.isPoisoned || !dirMeta.isStandalone;
|
|
continue;
|
|
}
|
|
const pipeMeta = this.metaReader.getPipeMetadata(ref);
|
|
if (pipeMeta !== null) {
|
|
deferredDependencies.add({ ...pipeMeta, ref, isExplicitlyDeferred: true });
|
|
isPoisoned = isPoisoned || !pipeMeta.isStandalone;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
this.cache.set(clazz, {
|
|
kind: ComponentScopeKind.Standalone,
|
|
component: clazz,
|
|
dependencies: Array.from(dependencies),
|
|
deferredDependencies: Array.from(deferredDependencies),
|
|
isPoisoned,
|
|
schemas: clazzMeta.schemas ?? []
|
|
});
|
|
}
|
|
return this.cache.get(clazz);
|
|
}
|
|
getRemoteScope() {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/checks/interpolated_signal_not_invoked/index.js
|
|
import { ASTWithSource as ASTWithSource2, BindingType, Interpolation, PrefixNot, PropertyRead as PropertyRead2, TmplAstBoundAttribute, TmplAstElement as TmplAstElement2, TmplAstIfBlock, TmplAstSwitchBlock, TmplAstTemplate as TmplAstTemplate2 } from "@angular/compiler";
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/src/symbol_util.js
|
|
import ts20 from "typescript";
|
|
var SIGNAL_FNS = /* @__PURE__ */ new Set([
|
|
"WritableSignal",
|
|
"Signal",
|
|
"InputSignal",
|
|
"InputSignalWithTransform",
|
|
"ModelSignal"
|
|
]);
|
|
function isSignalReference(symbol) {
|
|
return (symbol.kind === SymbolKind.Expression || symbol.kind === SymbolKind.Variable || symbol.kind === SymbolKind.LetDeclaration) && // Note that `tsType.symbol` isn't optional in the typings,
|
|
// but it appears that it can be undefined at runtime.
|
|
(symbol.tsType.symbol !== void 0 && isSignalSymbol(symbol.tsType.symbol) || symbol.tsType.aliasSymbol !== void 0 && isSignalSymbol(symbol.tsType.aliasSymbol));
|
|
}
|
|
function isSignalSymbol(symbol) {
|
|
const declarations = symbol.getDeclarations();
|
|
return declarations !== void 0 && declarations.some((decl) => {
|
|
const fileName = decl.getSourceFile().fileName;
|
|
return (ts20.isInterfaceDeclaration(decl) || ts20.isTypeAliasDeclaration(decl)) && SIGNAL_FNS.has(decl.name.text) && (fileName.includes("@angular/core") || fileName.includes("angular2/rc/packages/core") || fileName.includes("bin/packages/core"));
|
|
});
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/api/api.js
|
|
import { CombinedRecursiveAstVisitor as CombinedRecursiveAstVisitor2 } from "@angular/compiler";
|
|
var TemplateCheckWithVisitor = class {
|
|
/**
|
|
* When extended diagnostics were first introduced, the visitor wasn't implemented correctly
|
|
* which meant that it wasn't visiting the `templateAttrs` of structural directives (e.g.
|
|
* the expression of `*ngIf`). Fixing the issue causes a lot of internal breakages and will likely
|
|
* need to be done in a major version to avoid external breakages. This flag is used to opt out
|
|
* pre-existing diagnostics from the correct behavior until the breakages have been fixed while
|
|
* ensuring that newly-written diagnostics are correct from the beginning.
|
|
* TODO(crisbeto): remove this flag and fix the internal brekages.
|
|
*/
|
|
canVisitStructuralAttributes = true;
|
|
/**
|
|
* Base implementation for run function, visits all nodes in template and calls
|
|
* `visitNode()` for each one.
|
|
*/
|
|
run(ctx, component, template) {
|
|
const visitor = new TemplateVisitor2(ctx, component, this);
|
|
return visitor.getDiagnostics(template);
|
|
}
|
|
};
|
|
var TemplateVisitor2 = class extends CombinedRecursiveAstVisitor2 {
|
|
ctx;
|
|
component;
|
|
check;
|
|
diagnostics = [];
|
|
constructor(ctx, component, check) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.component = component;
|
|
this.check = check;
|
|
}
|
|
visit(node) {
|
|
this.diagnostics.push(...this.check.visitNode(this.ctx, this.component, node));
|
|
super.visit(node);
|
|
}
|
|
visitTemplate(template) {
|
|
const isInlineTemplate = template.tagName === "ng-template";
|
|
this.visitAllTemplateNodes(template.attributes);
|
|
if (isInlineTemplate) {
|
|
this.visitAllTemplateNodes(template.inputs);
|
|
this.visitAllTemplateNodes(template.outputs);
|
|
}
|
|
this.visitAllTemplateNodes(template.directives);
|
|
if (this.check.canVisitStructuralAttributes || isInlineTemplate) {
|
|
this.visitAllTemplateNodes(template.templateAttrs);
|
|
}
|
|
this.visitAllTemplateNodes(template.variables);
|
|
this.visitAllTemplateNodes(template.references);
|
|
this.visitAllTemplateNodes(template.children);
|
|
}
|
|
getDiagnostics(template) {
|
|
this.diagnostics = [];
|
|
this.visitAllTemplateNodes(template);
|
|
return this.diagnostics;
|
|
}
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/checks/interpolated_signal_not_invoked/index.js
|
|
var SIGNAL_INSTANCE_PROPERTIES = /* @__PURE__ */ new Set(["set", "update", "asReadonly"]);
|
|
var FUNCTION_INSTANCE_PROPERTIES = /* @__PURE__ */ new Set(["name", "length", "prototype"]);
|
|
var InterpolatedSignalCheck = class extends TemplateCheckWithVisitor {
|
|
code = ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED;
|
|
visitNode(ctx, component, node) {
|
|
if (node instanceof Interpolation) {
|
|
return node.expressions.map((item) => item instanceof PrefixNot ? item.expression : item).filter((item) => item instanceof PropertyRead2).flatMap((item) => buildDiagnosticForSignal(ctx, item, component));
|
|
} else if (node instanceof TmplAstElement2 && node.inputs.length > 0) {
|
|
const directivesOfElement = ctx.templateTypeChecker.getDirectivesOfNode(component, node);
|
|
return node.inputs.flatMap((input) => checkBoundAttribute(ctx, component, directivesOfElement, input));
|
|
} else if (node instanceof TmplAstTemplate2 && node.tagName === "ng-template") {
|
|
const directivesOfElement = ctx.templateTypeChecker.getDirectivesOfNode(component, node);
|
|
const inputDiagnostics = node.inputs.flatMap((input) => {
|
|
return checkBoundAttribute(ctx, component, directivesOfElement, input);
|
|
});
|
|
const templateAttrDiagnostics = node.templateAttrs.flatMap((attr) => {
|
|
if (!(attr instanceof TmplAstBoundAttribute)) {
|
|
return [];
|
|
}
|
|
return checkBoundAttribute(ctx, component, directivesOfElement, attr);
|
|
});
|
|
return inputDiagnostics.concat(templateAttrDiagnostics);
|
|
} else if (node instanceof TmplAstIfBlock) {
|
|
return node.branches.map((branch) => branch.expression).filter((expr) => expr instanceof ASTWithSource2).map((expr) => {
|
|
const ast = expr.ast;
|
|
return ast instanceof PrefixNot ? ast.expression : ast;
|
|
}).filter((ast) => ast instanceof PropertyRead2).flatMap((item) => buildDiagnosticForSignal(ctx, item, component));
|
|
} else if (node instanceof TmplAstSwitchBlock && node.expression instanceof ASTWithSource2) {
|
|
const expression = node.expression.ast instanceof PrefixNot ? node.expression.ast.expression : node.expression.ast;
|
|
if (expression instanceof PropertyRead2) {
|
|
return buildDiagnosticForSignal(ctx, expression, component);
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
};
|
|
function checkBoundAttribute(ctx, component, directivesOfElement, node) {
|
|
if (directivesOfElement !== null && directivesOfElement.some((dir) => dir.inputs.getByBindingPropertyName(node.name) !== null)) {
|
|
return [];
|
|
}
|
|
const nodeAst = isPropertyReadNodeAst(node);
|
|
if (
|
|
// a bound property like `[prop]="mySignal"`
|
|
(node.type === BindingType.Property || // or a class binding like `[class.myClass]="mySignal"`
|
|
node.type === BindingType.Class || // or a style binding like `[style.width]="mySignal"`
|
|
node.type === BindingType.Style || // or an attribute binding like `[attr.role]="mySignal"`
|
|
node.type === BindingType.Attribute || // or an animation binding like `[animate.enter]="mySignal"`
|
|
node.type === BindingType.Animation || // or an animation binding like `[@myAnimation]="mySignal"`
|
|
node.type === BindingType.LegacyAnimation) && nodeAst
|
|
) {
|
|
return buildDiagnosticForSignal(ctx, nodeAst, component);
|
|
}
|
|
return [];
|
|
}
|
|
function isPropertyReadNodeAst(node) {
|
|
if (node.value instanceof ASTWithSource2 === false) {
|
|
return void 0;
|
|
}
|
|
if (node.value.ast instanceof PrefixNot && node.value.ast.expression instanceof PropertyRead2) {
|
|
return node.value.ast.expression;
|
|
}
|
|
if (node.value.ast instanceof PropertyRead2) {
|
|
return node.value.ast;
|
|
}
|
|
return void 0;
|
|
}
|
|
function isFunctionInstanceProperty(name) {
|
|
return FUNCTION_INSTANCE_PROPERTIES.has(name);
|
|
}
|
|
function isSignalInstanceProperty(name) {
|
|
return SIGNAL_INSTANCE_PROPERTIES.has(name);
|
|
}
|
|
function buildDiagnosticForSignal(ctx, node, component) {
|
|
const symbol = ctx.templateTypeChecker.getSymbolOfNode(node, component);
|
|
if (symbol !== null && symbol.kind === SymbolKind.Expression && isSignalReference(symbol)) {
|
|
const templateMapping = ctx.templateTypeChecker.getSourceMappingAtTcbLocation(symbol.tcbLocation);
|
|
const errorString = `${node.name} is a function and should be invoked: ${node.name}()`;
|
|
const diagnostic = ctx.makeTemplateDiagnostic(templateMapping.span, errorString);
|
|
return [diagnostic];
|
|
}
|
|
if (!isFunctionInstanceProperty(node.name) && !isSignalInstanceProperty(node.name)) {
|
|
return [];
|
|
}
|
|
const symbolOfReceiver = ctx.templateTypeChecker.getSymbolOfNode(node.receiver, component);
|
|
if (symbolOfReceiver !== null && symbolOfReceiver.kind === SymbolKind.Expression && isSignalReference(symbolOfReceiver)) {
|
|
const templateMapping = ctx.templateTypeChecker.getSourceMappingAtTcbLocation(symbolOfReceiver.tcbLocation);
|
|
const errorString = `${node.receiver.name} is a function and should be invoked: ${node.receiver.name}()`;
|
|
const diagnostic = ctx.makeTemplateDiagnostic(templateMapping.span, errorString);
|
|
return [diagnostic];
|
|
}
|
|
return [];
|
|
}
|
|
var factory = {
|
|
code: ErrorCode.INTERPOLATED_SIGNAL_NOT_INVOKED,
|
|
name: ExtendedTemplateDiagnosticName.INTERPOLATED_SIGNAL_NOT_INVOKED,
|
|
create: () => new InterpolatedSignalCheck()
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/checks/invalid_banana_in_box/index.js
|
|
import { TmplAstBoundEvent } from "@angular/compiler";
|
|
var InvalidBananaInBoxCheck = class extends TemplateCheckWithVisitor {
|
|
code = ErrorCode.INVALID_BANANA_IN_BOX;
|
|
visitNode(ctx, component, node) {
|
|
if (!(node instanceof TmplAstBoundEvent))
|
|
return [];
|
|
const name = node.name;
|
|
if (!name.startsWith("[") || !name.endsWith("]"))
|
|
return [];
|
|
const boundSyntax = node.sourceSpan.toString();
|
|
const expectedBoundSyntax = boundSyntax.replace(`(${name})`, `[(${name.slice(1, -1)})]`);
|
|
const diagnostic = ctx.makeTemplateDiagnostic(node.sourceSpan, `In the two-way binding syntax the parentheses should be inside the brackets, ex. '${expectedBoundSyntax}'.
|
|
Find more at https://angular.dev/guide/templates/two-way-binding`);
|
|
return [diagnostic];
|
|
}
|
|
};
|
|
var factory2 = {
|
|
code: ErrorCode.INVALID_BANANA_IN_BOX,
|
|
name: ExtendedTemplateDiagnosticName.INVALID_BANANA_IN_BOX,
|
|
create: () => new InvalidBananaInBoxCheck()
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/checks/missing_control_flow_directive/index.js
|
|
import { TmplAstTemplate as TmplAstTemplate3 } from "@angular/compiler";
|
|
var KNOWN_CONTROL_FLOW_DIRECTIVES = /* @__PURE__ */ new Map([
|
|
["ngIf", { directive: "NgIf", builtIn: "@if" }],
|
|
["ngFor", { directive: "NgFor", builtIn: "@for" }],
|
|
["ngSwitchCase", { directive: "NgSwitchCase", builtIn: "@switch with @case" }],
|
|
["ngSwitchDefault", { directive: "NgSwitchDefault", builtIn: "@switch with @default" }]
|
|
]);
|
|
var MissingControlFlowDirectiveCheck = class extends TemplateCheckWithVisitor {
|
|
code = ErrorCode.MISSING_CONTROL_FLOW_DIRECTIVE;
|
|
run(ctx, component, template) {
|
|
const componentMetadata = ctx.templateTypeChecker.getDirectiveMetadata(component);
|
|
if (!componentMetadata || !componentMetadata.isStandalone) {
|
|
return [];
|
|
}
|
|
return super.run(ctx, component, template);
|
|
}
|
|
visitNode(ctx, component, node) {
|
|
if (!(node instanceof TmplAstTemplate3))
|
|
return [];
|
|
const controlFlowAttr = node.templateAttrs.find((attr) => KNOWN_CONTROL_FLOW_DIRECTIVES.has(attr.name));
|
|
if (!controlFlowAttr)
|
|
return [];
|
|
const symbol = ctx.templateTypeChecker.getSymbolOfNode(node, component);
|
|
if (symbol === null || symbol.directives.length > 0) {
|
|
return [];
|
|
}
|
|
const sourceSpan = controlFlowAttr.keySpan || controlFlowAttr.sourceSpan;
|
|
const directiveAndBuiltIn = KNOWN_CONTROL_FLOW_DIRECTIVES.get(controlFlowAttr.name);
|
|
const errorMessage = `The \`*${controlFlowAttr.name}\` directive was used in the template, but neither the \`${directiveAndBuiltIn?.directive}\` directive nor the \`CommonModule\` was imported. Use Angular's built-in control flow ${directiveAndBuiltIn?.builtIn} or make sure that either the \`${directiveAndBuiltIn?.directive}\` directive or the \`CommonModule\` is included in the \`@Component.imports\` array of this component.`;
|
|
const diagnostic = ctx.makeTemplateDiagnostic(sourceSpan, errorMessage);
|
|
return [diagnostic];
|
|
}
|
|
};
|
|
var factory3 = {
|
|
code: ErrorCode.MISSING_CONTROL_FLOW_DIRECTIVE,
|
|
name: ExtendedTemplateDiagnosticName.MISSING_CONTROL_FLOW_DIRECTIVE,
|
|
create: (options) => {
|
|
return new MissingControlFlowDirectiveCheck();
|
|
}
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/checks/missing_ngforof_let/index.js
|
|
import { TmplAstTemplate as TmplAstTemplate4 } from "@angular/compiler";
|
|
var MissingNgForOfLetCheck = class extends TemplateCheckWithVisitor {
|
|
code = ErrorCode.MISSING_NGFOROF_LET;
|
|
visitNode(ctx, component, node) {
|
|
const isTemplate = node instanceof TmplAstTemplate4;
|
|
if (!(node instanceof TmplAstTemplate4)) {
|
|
return [];
|
|
}
|
|
if (node.templateAttrs.length === 0) {
|
|
return [];
|
|
}
|
|
const attr = node.templateAttrs.find((x) => x.name === "ngFor");
|
|
if (attr === void 0) {
|
|
return [];
|
|
}
|
|
if (node.variables.length > 0) {
|
|
return [];
|
|
}
|
|
const errorString = "Your ngFor is missing a value. Did you forget to add the `let` keyword?";
|
|
const diagnostic = ctx.makeTemplateDiagnostic(attr.sourceSpan, errorString);
|
|
return [diagnostic];
|
|
}
|
|
};
|
|
var factory4 = {
|
|
code: ErrorCode.MISSING_NGFOROF_LET,
|
|
name: ExtendedTemplateDiagnosticName.MISSING_NGFOROF_LET,
|
|
create: () => new MissingNgForOfLetCheck()
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/checks/missing_structural_directive/index.js
|
|
import { TmplAstTemplate as TmplAstTemplate5 } from "@angular/compiler";
|
|
var KNOWN_CONTROL_FLOW_DIRECTIVES2 = /* @__PURE__ */ new Set([
|
|
"ngIf",
|
|
"ngFor",
|
|
"ngForOf",
|
|
"ngForTrackBy",
|
|
"ngSwitchCase",
|
|
"ngSwitchDefault"
|
|
]);
|
|
var MissingStructuralDirectiveCheck = class extends TemplateCheckWithVisitor {
|
|
code = ErrorCode.MISSING_STRUCTURAL_DIRECTIVE;
|
|
run(ctx, component, template) {
|
|
const componentMetadata = ctx.templateTypeChecker.getDirectiveMetadata(component);
|
|
if (!componentMetadata || !componentMetadata.isStandalone) {
|
|
return [];
|
|
}
|
|
return super.run(ctx, component, template);
|
|
}
|
|
visitNode(ctx, component, node) {
|
|
if (!(node instanceof TmplAstTemplate5))
|
|
return [];
|
|
const customStructuralDirective = node.templateAttrs.find((attr) => !KNOWN_CONTROL_FLOW_DIRECTIVES2.has(attr.name));
|
|
if (!customStructuralDirective)
|
|
return [];
|
|
const symbol = ctx.templateTypeChecker.getSymbolOfNode(node, component);
|
|
if (symbol?.directives.length) {
|
|
return [];
|
|
}
|
|
const sourceSpan = customStructuralDirective.keySpan || customStructuralDirective.sourceSpan;
|
|
const errorMessage = `A structural directive \`${customStructuralDirective.name}\` was used in the template without a corresponding import in the component. Make sure that the directive is included in the \`@Component.imports\` array of this component.`;
|
|
return [ctx.makeTemplateDiagnostic(sourceSpan, errorMessage)];
|
|
}
|
|
};
|
|
var factory5 = {
|
|
code: ErrorCode.MISSING_STRUCTURAL_DIRECTIVE,
|
|
name: ExtendedTemplateDiagnosticName.MISSING_STRUCTURAL_DIRECTIVE,
|
|
create: () => new MissingStructuralDirectiveCheck()
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/checks/nullish_coalescing_not_nullable/index.js
|
|
import { Binary } from "@angular/compiler";
|
|
import ts21 from "typescript";
|
|
var NullishCoalescingNotNullableCheck = class extends TemplateCheckWithVisitor {
|
|
canVisitStructuralAttributes = false;
|
|
code = ErrorCode.NULLISH_COALESCING_NOT_NULLABLE;
|
|
visitNode(ctx, component, node) {
|
|
if (!(node instanceof Binary) || node.operation !== "??")
|
|
return [];
|
|
const symbolLeft = ctx.templateTypeChecker.getSymbolOfNode(node.left, component);
|
|
if (symbolLeft === null || symbolLeft.kind !== SymbolKind.Expression) {
|
|
return [];
|
|
}
|
|
const typeLeft = symbolLeft.tsType;
|
|
if (typeLeft.flags & (ts21.TypeFlags.Any | ts21.TypeFlags.Unknown)) {
|
|
return [];
|
|
}
|
|
if (typeLeft.getNonNullableType() !== typeLeft)
|
|
return [];
|
|
const symbol = ctx.templateTypeChecker.getSymbolOfNode(node, component);
|
|
if (symbol.kind !== SymbolKind.Expression) {
|
|
return [];
|
|
}
|
|
const templateMapping = ctx.templateTypeChecker.getSourceMappingAtTcbLocation(symbol.tcbLocation);
|
|
if (templateMapping === null) {
|
|
return [];
|
|
}
|
|
const diagnostic = ctx.makeTemplateDiagnostic(templateMapping.span, `The left side of this nullish coalescing operation does not include 'null' or 'undefined' in its type, therefore the '??' operator can be safely removed.`);
|
|
return [diagnostic];
|
|
}
|
|
};
|
|
var factory6 = {
|
|
code: ErrorCode.NULLISH_COALESCING_NOT_NULLABLE,
|
|
name: ExtendedTemplateDiagnosticName.NULLISH_COALESCING_NOT_NULLABLE,
|
|
create: (options) => {
|
|
const strictNullChecks = options.strictNullChecks === void 0 ? !!options.strict : !!options.strictNullChecks;
|
|
if (!strictNullChecks) {
|
|
return null;
|
|
}
|
|
return new NullishCoalescingNotNullableCheck();
|
|
}
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/checks/optional_chain_not_nullable/index.js
|
|
import { KeyedRead, SafeCall, SafeKeyedRead, SafePropertyRead } from "@angular/compiler";
|
|
import ts22 from "typescript";
|
|
var OptionalChainNotNullableCheck = class extends TemplateCheckWithVisitor {
|
|
noUncheckedIndexedAccess;
|
|
canVisitStructuralAttributes = false;
|
|
code = ErrorCode.OPTIONAL_CHAIN_NOT_NULLABLE;
|
|
constructor(noUncheckedIndexedAccess) {
|
|
super();
|
|
this.noUncheckedIndexedAccess = noUncheckedIndexedAccess;
|
|
}
|
|
visitNode(ctx, component, node) {
|
|
if (!(node instanceof SafeCall) && !(node instanceof SafePropertyRead) && !(node instanceof SafeKeyedRead)) {
|
|
return [];
|
|
}
|
|
if (node.receiver instanceof KeyedRead && !this.noUncheckedIndexedAccess) {
|
|
return [];
|
|
}
|
|
const symbolLeft = ctx.templateTypeChecker.getSymbolOfNode(node.receiver, component);
|
|
if (symbolLeft === null || symbolLeft.kind !== SymbolKind.Expression) {
|
|
return [];
|
|
}
|
|
const typeLeft = symbolLeft.tsType;
|
|
if (typeLeft.flags & (ts22.TypeFlags.Any | ts22.TypeFlags.Unknown)) {
|
|
return [];
|
|
}
|
|
if (typeLeft.getNonNullableType() !== typeLeft)
|
|
return [];
|
|
const symbol = ctx.templateTypeChecker.getSymbolOfNode(node, component);
|
|
if (symbol.kind !== SymbolKind.Expression) {
|
|
return [];
|
|
}
|
|
const templateMapping = ctx.templateTypeChecker.getSourceMappingAtTcbLocation(symbol.tcbLocation);
|
|
if (templateMapping === null) {
|
|
return [];
|
|
}
|
|
const advice = node instanceof SafePropertyRead ? `the '?.' operator can be replaced with the '.' operator` : `the '?.' operator can be safely removed`;
|
|
const diagnostic = ctx.makeTemplateDiagnostic(templateMapping.span, `The left side of this optional chain operation does not include 'null' or 'undefined' in its type, therefore ${advice}.`);
|
|
return [diagnostic];
|
|
}
|
|
};
|
|
var factory7 = {
|
|
code: ErrorCode.OPTIONAL_CHAIN_NOT_NULLABLE,
|
|
name: ExtendedTemplateDiagnosticName.OPTIONAL_CHAIN_NOT_NULLABLE,
|
|
create: (options) => {
|
|
const strictNullChecks = options.strictNullChecks === void 0 ? !!options.strict : !!options.strictNullChecks;
|
|
if (!strictNullChecks) {
|
|
return null;
|
|
}
|
|
const noUncheckedIndexedAccess = !!options.noUncheckedIndexedAccess;
|
|
return new OptionalChainNotNullableCheck(noUncheckedIndexedAccess);
|
|
}
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/checks/skip_hydration_not_static/index.js
|
|
import { TmplAstBoundAttribute as TmplAstBoundAttribute2, TmplAstTextAttribute } from "@angular/compiler";
|
|
var NG_SKIP_HYDRATION_ATTR_NAME = "ngSkipHydration";
|
|
var NgSkipHydrationSpec = class extends TemplateCheckWithVisitor {
|
|
code = ErrorCode.SKIP_HYDRATION_NOT_STATIC;
|
|
visitNode(ctx, component, node) {
|
|
if (node instanceof TmplAstBoundAttribute2 && node.name === NG_SKIP_HYDRATION_ATTR_NAME) {
|
|
const errorString = `ngSkipHydration should not be used as a binding.`;
|
|
const diagnostic = ctx.makeTemplateDiagnostic(node.sourceSpan, errorString);
|
|
return [diagnostic];
|
|
}
|
|
const acceptedValues = [
|
|
"true",
|
|
""
|
|
/* empty string */
|
|
];
|
|
if (node instanceof TmplAstTextAttribute && node.name === NG_SKIP_HYDRATION_ATTR_NAME && !acceptedValues.includes(node.value) && node.value !== void 0) {
|
|
const errorString = `ngSkipHydration only accepts "true" or "" as value or no value at all. For example 'ngSkipHydration="true"' or 'ngSkipHydration'`;
|
|
const diagnostic = ctx.makeTemplateDiagnostic(node.sourceSpan, errorString);
|
|
return [diagnostic];
|
|
}
|
|
return [];
|
|
}
|
|
};
|
|
var factory8 = {
|
|
code: ErrorCode.SKIP_HYDRATION_NOT_STATIC,
|
|
name: ExtendedTemplateDiagnosticName.SKIP_HYDRATION_NOT_STATIC,
|
|
create: () => new NgSkipHydrationSpec()
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/checks/suffix_not_supported/index.js
|
|
import { TmplAstBoundAttribute as TmplAstBoundAttribute3 } from "@angular/compiler";
|
|
var STYLE_SUFFIXES = ["px", "%", "em"];
|
|
var SuffixNotSupportedCheck = class extends TemplateCheckWithVisitor {
|
|
code = ErrorCode.SUFFIX_NOT_SUPPORTED;
|
|
visitNode(ctx, component, node) {
|
|
if (!(node instanceof TmplAstBoundAttribute3))
|
|
return [];
|
|
if (!node.keySpan.toString().startsWith("attr.") || !STYLE_SUFFIXES.some((suffix) => node.name.endsWith(`.${suffix}`))) {
|
|
return [];
|
|
}
|
|
const diagnostic = ctx.makeTemplateDiagnostic(node.keySpan, `The ${STYLE_SUFFIXES.map((suffix) => `'.${suffix}'`).join(", ")} suffixes are only supported on style bindings.`);
|
|
return [diagnostic];
|
|
}
|
|
};
|
|
var factory9 = {
|
|
code: ErrorCode.SUFFIX_NOT_SUPPORTED,
|
|
name: ExtendedTemplateDiagnosticName.SUFFIX_NOT_SUPPORTED,
|
|
create: () => new SuffixNotSupportedCheck()
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/checks/text_attribute_not_binding/index.js
|
|
import { TmplAstTextAttribute as TmplAstTextAttribute2 } from "@angular/compiler";
|
|
var TextAttributeNotBindingSpec = class extends TemplateCheckWithVisitor {
|
|
code = ErrorCode.TEXT_ATTRIBUTE_NOT_BINDING;
|
|
visitNode(ctx, component, node) {
|
|
if (!(node instanceof TmplAstTextAttribute2))
|
|
return [];
|
|
const name = node.name;
|
|
if (!name.startsWith("attr.") && !name.startsWith("style.") && !name.startsWith("class.")) {
|
|
return [];
|
|
}
|
|
let errorString;
|
|
if (name.startsWith("attr.")) {
|
|
const staticAttr = name.replace("attr.", "");
|
|
errorString = `Static attributes should be written without the 'attr.' prefix.`;
|
|
if (node.value) {
|
|
errorString += ` For example, ${staticAttr}="${node.value}".`;
|
|
}
|
|
} else {
|
|
const expectedKey = `[${name}]`;
|
|
const expectedValue = (
|
|
// true/false are special cases because we don't want to convert them to strings but
|
|
// rather maintain the logical true/false when bound.
|
|
node.value === "true" || node.value === "false" ? node.value : `'${node.value}'`
|
|
);
|
|
errorString = "Attribute, style, and class bindings should be enclosed with square braces.";
|
|
if (node.value) {
|
|
errorString += ` For example, '${expectedKey}="${expectedValue}"'.`;
|
|
}
|
|
}
|
|
const diagnostic = ctx.makeTemplateDiagnostic(node.sourceSpan, errorString);
|
|
return [diagnostic];
|
|
}
|
|
};
|
|
var factory10 = {
|
|
code: ErrorCode.TEXT_ATTRIBUTE_NOT_BINDING,
|
|
name: ExtendedTemplateDiagnosticName.TEXT_ATTRIBUTE_NOT_BINDING,
|
|
create: () => new TextAttributeNotBindingSpec()
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/checks/uninvoked_function_in_event_binding/index.js
|
|
import { ASTWithSource as ASTWithSource3, Call, Chain, Conditional, ParsedEventType, PropertyRead as PropertyRead3, SafeCall as SafeCall2, SafePropertyRead as SafePropertyRead2, TmplAstBoundEvent as TmplAstBoundEvent2 } from "@angular/compiler";
|
|
var UninvokedFunctionInEventBindingSpec = class extends TemplateCheckWithVisitor {
|
|
code = ErrorCode.UNINVOKED_FUNCTION_IN_EVENT_BINDING;
|
|
visitNode(ctx, component, node) {
|
|
if (!(node instanceof TmplAstBoundEvent2))
|
|
return [];
|
|
if (node.type !== ParsedEventType.Regular && node.type !== ParsedEventType.LegacyAnimation)
|
|
return [];
|
|
if (!(node.handler instanceof ASTWithSource3))
|
|
return [];
|
|
const sourceExpressionText = node.handler.source || "";
|
|
if (node.handler.ast instanceof Chain) {
|
|
return node.handler.ast.expressions.flatMap((expression) => assertExpressionInvoked(expression, component, node, sourceExpressionText, ctx));
|
|
}
|
|
if (node.handler.ast instanceof Conditional) {
|
|
const { trueExp, falseExp } = node.handler.ast;
|
|
return [trueExp, falseExp].flatMap((expression) => assertExpressionInvoked(expression, component, node, sourceExpressionText, ctx));
|
|
}
|
|
return assertExpressionInvoked(node.handler.ast, component, node, sourceExpressionText, ctx);
|
|
}
|
|
};
|
|
function assertExpressionInvoked(expression, component, node, expressionText, ctx) {
|
|
if (expression instanceof Call || expression instanceof SafeCall2) {
|
|
return [];
|
|
}
|
|
if (!(expression instanceof PropertyRead3) && !(expression instanceof SafePropertyRead2)) {
|
|
return [];
|
|
}
|
|
const symbol = ctx.templateTypeChecker.getSymbolOfNode(expression, component);
|
|
if (symbol !== null && symbol.kind === SymbolKind.Expression) {
|
|
if (symbol.tsType.getCallSignatures()?.length > 0) {
|
|
const fullExpressionText = generateStringFromExpression(expression, expressionText);
|
|
const errorString = `Function in event binding should be invoked: ${fullExpressionText}()`;
|
|
return [ctx.makeTemplateDiagnostic(node.sourceSpan, errorString)];
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
function generateStringFromExpression(expression, source) {
|
|
return source.substring(expression.span.start, expression.span.end);
|
|
}
|
|
var factory11 = {
|
|
code: ErrorCode.UNINVOKED_FUNCTION_IN_EVENT_BINDING,
|
|
name: ExtendedTemplateDiagnosticName.UNINVOKED_FUNCTION_IN_EVENT_BINDING,
|
|
create: () => new UninvokedFunctionInEventBindingSpec()
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/checks/unparenthesized_nullish_coalescing/index.js
|
|
import { Binary as Binary2 } from "@angular/compiler";
|
|
var UnparenthesizedNullishCoalescing = class extends TemplateCheckWithVisitor {
|
|
code = ErrorCode.UNPARENTHESIZED_NULLISH_COALESCING;
|
|
visitNode(ctx, component, node) {
|
|
if (node instanceof Binary2) {
|
|
if (node.operation === "&&" || node.operation === "||") {
|
|
if (node.left instanceof Binary2 && node.left.operation === "??" || node.right instanceof Binary2 && node.right.operation === "??") {
|
|
const symbol = ctx.templateTypeChecker.getSymbolOfNode(node, component);
|
|
if (symbol?.kind !== SymbolKind.Expression) {
|
|
return [];
|
|
}
|
|
const sourceMapping = ctx.templateTypeChecker.getSourceMappingAtTcbLocation(symbol.tcbLocation);
|
|
if (sourceMapping === null) {
|
|
return [];
|
|
}
|
|
const diagnostic = ctx.makeTemplateDiagnostic(sourceMapping.span, `Parentheses are required to disambiguate precedence when mixing '??' with '&&' and '||'.`);
|
|
return [diagnostic];
|
|
}
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
};
|
|
var factory12 = {
|
|
code: ErrorCode.UNPARENTHESIZED_NULLISH_COALESCING,
|
|
name: ExtendedTemplateDiagnosticName.UNPARENTHESIZED_NULLISH_COALESCING,
|
|
create: () => new UnparenthesizedNullishCoalescing()
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/checks/unused_let_declaration/index.js
|
|
import { AST, ASTWithSource as ASTWithSource4, TmplAstLetDeclaration } from "@angular/compiler";
|
|
var UnusedLetDeclarationCheck = class extends TemplateCheckWithVisitor {
|
|
code = ErrorCode.UNUSED_LET_DECLARATION;
|
|
analysis = /* @__PURE__ */ new Map();
|
|
run(ctx, component, template) {
|
|
super.run(ctx, component, template);
|
|
const diagnostics = [];
|
|
const { allLetDeclarations, usedLetDeclarations } = this.getAnalysis(component);
|
|
for (const decl of allLetDeclarations) {
|
|
if (!usedLetDeclarations.has(decl)) {
|
|
diagnostics.push(ctx.makeTemplateDiagnostic(decl.sourceSpan, `@let ${decl.name} is declared but its value is never read.`));
|
|
}
|
|
}
|
|
this.analysis.clear();
|
|
return diagnostics;
|
|
}
|
|
visitNode(ctx, component, node) {
|
|
if (node instanceof TmplAstLetDeclaration) {
|
|
this.getAnalysis(component).allLetDeclarations.add(node);
|
|
} else if (node instanceof AST) {
|
|
const unwrappedNode = node instanceof ASTWithSource4 ? node.ast : node;
|
|
const target = ctx.templateTypeChecker.getExpressionTarget(unwrappedNode, component);
|
|
if (target !== null && target instanceof TmplAstLetDeclaration) {
|
|
this.getAnalysis(component).usedLetDeclarations.add(target);
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
getAnalysis(node) {
|
|
if (!this.analysis.has(node)) {
|
|
this.analysis.set(node, { allLetDeclarations: /* @__PURE__ */ new Set(), usedLetDeclarations: /* @__PURE__ */ new Set() });
|
|
}
|
|
return this.analysis.get(node);
|
|
}
|
|
};
|
|
var factory13 = {
|
|
code: ErrorCode.UNUSED_LET_DECLARATION,
|
|
name: ExtendedTemplateDiagnosticName.UNUSED_LET_DECLARATION,
|
|
create: () => new UnusedLetDeclarationCheck()
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/checks/uninvoked_track_function/index.js
|
|
import { Call as Call2, PropertyRead as PropertyRead4, SafeCall as SafeCall3, SafePropertyRead as SafePropertyRead3, TmplAstForLoopBlock } from "@angular/compiler";
|
|
var UninvokedTrackFunctionCheck = class extends TemplateCheckWithVisitor {
|
|
code = ErrorCode.UNINVOKED_TRACK_FUNCTION;
|
|
visitNode(ctx, component, node) {
|
|
if (!(node instanceof TmplAstForLoopBlock) || !node.trackBy) {
|
|
return [];
|
|
}
|
|
if (node.trackBy.ast instanceof Call2 || node.trackBy.ast instanceof SafeCall3) {
|
|
return [];
|
|
}
|
|
if (!(node.trackBy.ast instanceof PropertyRead4) && !(node.trackBy.ast instanceof SafePropertyRead3)) {
|
|
return [];
|
|
}
|
|
const symbol = ctx.templateTypeChecker.getSymbolOfNode(node.trackBy.ast, component);
|
|
if (symbol !== null && symbol.kind === SymbolKind.Expression && symbol.tsType.getCallSignatures()?.length > 0) {
|
|
const fullExpressionText = generateStringFromExpression2(node.trackBy.ast, node.trackBy.source || "");
|
|
const errorString = `The track function in the @for block should be invoked: ${fullExpressionText}(/* arguments */)`;
|
|
return [ctx.makeTemplateDiagnostic(node.sourceSpan, errorString)];
|
|
}
|
|
return [];
|
|
}
|
|
};
|
|
function generateStringFromExpression2(expression, source) {
|
|
return source.substring(expression.span.start, expression.span.end);
|
|
}
|
|
var factory14 = {
|
|
code: ErrorCode.UNINVOKED_TRACK_FUNCTION,
|
|
name: ExtendedTemplateDiagnosticName.UNINVOKED_TRACK_FUNCTION,
|
|
create: () => new UninvokedTrackFunctionCheck()
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/checks/uninvoked_function_in_text_interpolation/index.js
|
|
import { Interpolation as Interpolation2, PropertyRead as PropertyRead5, SafePropertyRead as SafePropertyRead4 } from "@angular/compiler";
|
|
var UninvokedFunctionInTextInterpolation = class extends TemplateCheckWithVisitor {
|
|
code = ErrorCode.UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION;
|
|
visitNode(ctx, component, node) {
|
|
if (node instanceof Interpolation2) {
|
|
return node.expressions.flatMap((item) => assertExpressionInvoked2(item, component, ctx));
|
|
}
|
|
return [];
|
|
}
|
|
};
|
|
function assertExpressionInvoked2(expression, component, ctx) {
|
|
if (!(expression instanceof PropertyRead5) && !(expression instanceof SafePropertyRead4)) {
|
|
return [];
|
|
}
|
|
const symbol = ctx.templateTypeChecker.getSymbolOfNode(expression, component);
|
|
if (symbol !== null && symbol.kind === SymbolKind.Expression) {
|
|
if (symbol.tsType.getCallSignatures()?.length > 0) {
|
|
const errorString = `Function in text interpolation should be invoked: ${expression.name}()`;
|
|
const templateMapping = ctx.templateTypeChecker.getSourceMappingAtTcbLocation(symbol.tcbLocation);
|
|
return [ctx.makeTemplateDiagnostic(templateMapping.span, errorString)];
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
var factory15 = {
|
|
code: ErrorCode.UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION,
|
|
name: ExtendedTemplateDiagnosticName.UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION,
|
|
create: () => new UninvokedFunctionInTextInterpolation()
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/src/extended_template_checker.js
|
|
import ts23 from "typescript";
|
|
|
|
// packages/compiler-cli/src/ngtsc/core/api/src/public_options.js
|
|
var DiagnosticCategoryLabel;
|
|
(function(DiagnosticCategoryLabel2) {
|
|
DiagnosticCategoryLabel2["Warning"] = "warning";
|
|
DiagnosticCategoryLabel2["Error"] = "error";
|
|
DiagnosticCategoryLabel2["Suppress"] = "suppress";
|
|
})(DiagnosticCategoryLabel || (DiagnosticCategoryLabel = {}));
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/src/extended_template_checker.js
|
|
var ExtendedTemplateCheckerImpl = class {
|
|
partialCtx;
|
|
templateChecks;
|
|
constructor(templateTypeChecker, typeChecker, templateCheckFactories, options) {
|
|
this.partialCtx = { templateTypeChecker, typeChecker };
|
|
this.templateChecks = /* @__PURE__ */ new Map();
|
|
for (const factory16 of templateCheckFactories) {
|
|
const category = diagnosticLabelToCategory(options?.extendedDiagnostics?.checks?.[factory16.name] ?? options?.extendedDiagnostics?.defaultCategory ?? DiagnosticCategoryLabel.Warning);
|
|
if (category === null) {
|
|
continue;
|
|
}
|
|
const check = factory16.create(options);
|
|
if (check === null) {
|
|
continue;
|
|
}
|
|
this.templateChecks.set(check, category);
|
|
}
|
|
}
|
|
getDiagnosticsForComponent(component) {
|
|
const template = this.partialCtx.templateTypeChecker.getTemplate(component);
|
|
if (template === null) {
|
|
return [];
|
|
}
|
|
const diagnostics = [];
|
|
for (const [check, category] of this.templateChecks.entries()) {
|
|
const ctx = {
|
|
...this.partialCtx,
|
|
// Wrap `templateTypeChecker.makeTemplateDiagnostic()` to implicitly provide all the known
|
|
// options.
|
|
makeTemplateDiagnostic: (span, message, relatedInformation) => {
|
|
return this.partialCtx.templateTypeChecker.makeTemplateDiagnostic(component, span, category, check.code, message, relatedInformation);
|
|
}
|
|
};
|
|
diagnostics.push(...check.run(ctx, component, template));
|
|
}
|
|
return diagnostics;
|
|
}
|
|
};
|
|
function diagnosticLabelToCategory(label) {
|
|
switch (label) {
|
|
case DiagnosticCategoryLabel.Warning:
|
|
return ts23.DiagnosticCategory.Warning;
|
|
case DiagnosticCategoryLabel.Error:
|
|
return ts23.DiagnosticCategory.Error;
|
|
case DiagnosticCategoryLabel.Suppress:
|
|
return null;
|
|
default:
|
|
return assertNever(label);
|
|
}
|
|
}
|
|
function assertNever(value) {
|
|
throw new Error(`Unexpected call to 'assertNever()' with value:
|
|
${value}`);
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/extended/index.js
|
|
var ALL_DIAGNOSTIC_FACTORIES = [
|
|
factory2,
|
|
factory6,
|
|
factory7,
|
|
factory3,
|
|
factory10,
|
|
factory4,
|
|
factory5,
|
|
factory9,
|
|
factory,
|
|
factory11,
|
|
factory13,
|
|
factory8,
|
|
factory12,
|
|
factory14,
|
|
factory15
|
|
];
|
|
var SUPPORTED_DIAGNOSTIC_NAMES = /* @__PURE__ */ new Set([
|
|
ExtendedTemplateDiagnosticName.CONTROL_FLOW_PREVENTING_CONTENT_PROJECTION,
|
|
ExtendedTemplateDiagnosticName.UNUSED_STANDALONE_IMPORTS,
|
|
...ALL_DIAGNOSTIC_FACTORIES.map((factory16) => factory16.name)
|
|
]);
|
|
|
|
// packages/compiler-cli/src/ngtsc/typecheck/template_semantics/src/template_semantics_checker.js
|
|
import { ASTWithSource as ASTWithSource5, ImplicitReceiver as ImplicitReceiver2, ParsedEventType as ParsedEventType2, PropertyRead as PropertyRead6, Binary as Binary3, RecursiveAstVisitor, TmplAstBoundEvent as TmplAstBoundEvent3, TmplAstLetDeclaration as TmplAstLetDeclaration2, TmplAstRecursiveVisitor, TmplAstVariable as TmplAstVariable2 } from "@angular/compiler";
|
|
import ts24 from "typescript";
|
|
var TemplateSemanticsCheckerImpl = class {
|
|
templateTypeChecker;
|
|
constructor(templateTypeChecker) {
|
|
this.templateTypeChecker = templateTypeChecker;
|
|
}
|
|
getDiagnosticsForComponent(component) {
|
|
const template = this.templateTypeChecker.getTemplate(component);
|
|
return template !== null ? TemplateSemanticsVisitor.visit(template, component, this.templateTypeChecker) : [];
|
|
}
|
|
};
|
|
var TemplateSemanticsVisitor = class _TemplateSemanticsVisitor extends TmplAstRecursiveVisitor {
|
|
expressionVisitor;
|
|
constructor(expressionVisitor) {
|
|
super();
|
|
this.expressionVisitor = expressionVisitor;
|
|
}
|
|
static visit(nodes, component, templateTypeChecker) {
|
|
const diagnostics = [];
|
|
const expressionVisitor = new ExpressionsSemanticsVisitor(templateTypeChecker, component, diagnostics);
|
|
const templateVisitor = new _TemplateSemanticsVisitor(expressionVisitor);
|
|
nodes.forEach((node) => node.visit(templateVisitor));
|
|
return diagnostics;
|
|
}
|
|
visitBoundEvent(event) {
|
|
super.visitBoundEvent(event);
|
|
event.handler.visit(this.expressionVisitor, event);
|
|
}
|
|
};
|
|
var ExpressionsSemanticsVisitor = class extends RecursiveAstVisitor {
|
|
templateTypeChecker;
|
|
component;
|
|
diagnostics;
|
|
constructor(templateTypeChecker, component, diagnostics) {
|
|
super();
|
|
this.templateTypeChecker = templateTypeChecker;
|
|
this.component = component;
|
|
this.diagnostics = diagnostics;
|
|
}
|
|
visitBinary(ast, context) {
|
|
if (Binary3.isAssignmentOperation(ast.operation) && ast.left instanceof PropertyRead6) {
|
|
this.checkForIllegalWriteInEventBinding(ast.left, context);
|
|
} else {
|
|
super.visitBinary(ast, context);
|
|
}
|
|
}
|
|
visitPropertyRead(ast, context) {
|
|
super.visitPropertyRead(ast, context);
|
|
this.checkForIllegalWriteInTwoWayBinding(ast, context);
|
|
}
|
|
checkForIllegalWriteInEventBinding(ast, context) {
|
|
if (!(context instanceof TmplAstBoundEvent3) || !(ast.receiver instanceof ImplicitReceiver2)) {
|
|
return;
|
|
}
|
|
const target = this.templateTypeChecker.getExpressionTarget(ast, this.component);
|
|
if (target instanceof TmplAstVariable2) {
|
|
const errorMessage = `Cannot use variable '${target.name}' as the left-hand side of an assignment expression. Template variables are read-only.`;
|
|
this.diagnostics.push(this.makeIllegalTemplateVarDiagnostic(target, context, errorMessage));
|
|
}
|
|
}
|
|
checkForIllegalWriteInTwoWayBinding(ast, context) {
|
|
if (!(context instanceof TmplAstBoundEvent3) || context.type !== ParsedEventType2.TwoWay || !(ast.receiver instanceof ImplicitReceiver2) || ast !== unwrapAstWithSource(context.handler)) {
|
|
return;
|
|
}
|
|
const target = this.templateTypeChecker.getExpressionTarget(ast, this.component);
|
|
const isVariable = target instanceof TmplAstVariable2;
|
|
const isLet = target instanceof TmplAstLetDeclaration2;
|
|
if (!isVariable && !isLet) {
|
|
return;
|
|
}
|
|
const symbol = this.templateTypeChecker.getSymbolOfNode(target, this.component);
|
|
if (symbol !== null && !isSignalReference(symbol)) {
|
|
let errorMessage;
|
|
if (isVariable) {
|
|
errorMessage = `Cannot use a non-signal variable '${target.name}' in a two-way binding expression. Template variables are read-only.`;
|
|
} else {
|
|
errorMessage = `Cannot use non-signal @let declaration '${target.name}' in a two-way binding expression. @let declarations are read-only.`;
|
|
}
|
|
this.diagnostics.push(this.makeIllegalTemplateVarDiagnostic(target, context, errorMessage));
|
|
}
|
|
}
|
|
makeIllegalTemplateVarDiagnostic(target, expressionNode, errorMessage) {
|
|
const span = target instanceof TmplAstVariable2 ? target.valueSpan || target.sourceSpan : target.sourceSpan;
|
|
return this.templateTypeChecker.makeTemplateDiagnostic(this.component, expressionNode.handlerSpan, ts24.DiagnosticCategory.Error, ngErrorCode(ErrorCode.WRITE_TO_READ_ONLY_VARIABLE), errorMessage, [
|
|
{
|
|
text: `'${target.name}' is declared here.`,
|
|
start: span.start.offset,
|
|
end: span.end.offset,
|
|
sourceFile: this.component.getSourceFile()
|
|
}
|
|
]);
|
|
}
|
|
};
|
|
function unwrapAstWithSource(ast) {
|
|
return ast instanceof ASTWithSource5 ? ast.ast : ast;
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/validation/src/rules/initializer_api_usage_rule.js
|
|
import ts25 from "typescript";
|
|
var APIS_TO_CHECK = [
|
|
INPUT_INITIALIZER_FN,
|
|
MODEL_INITIALIZER_FN,
|
|
...OUTPUT_INITIALIZER_FNS,
|
|
...QUERY_INITIALIZER_FNS
|
|
];
|
|
var InitializerApiUsageRule = class {
|
|
reflector;
|
|
importedSymbolsTracker;
|
|
constructor(reflector, importedSymbolsTracker) {
|
|
this.reflector = reflector;
|
|
this.importedSymbolsTracker = importedSymbolsTracker;
|
|
}
|
|
shouldCheck(sourceFile) {
|
|
return APIS_TO_CHECK.some(({ functionName, owningModule }) => {
|
|
return this.importedSymbolsTracker.hasNamedImport(sourceFile, functionName, owningModule) || this.importedSymbolsTracker.hasNamespaceImport(sourceFile, owningModule);
|
|
});
|
|
}
|
|
checkNode(node) {
|
|
if (!ts25.isCallExpression(node)) {
|
|
return null;
|
|
}
|
|
while (node.parent && (ts25.isParenthesizedExpression(node.parent) || ts25.isAsExpression(node.parent))) {
|
|
node = node.parent;
|
|
}
|
|
if (!node.parent || !ts25.isCallExpression(node)) {
|
|
return null;
|
|
}
|
|
const identifiedInitializer = tryParseInitializerApi(APIS_TO_CHECK, node, this.reflector, this.importedSymbolsTracker);
|
|
if (identifiedInitializer === null) {
|
|
return null;
|
|
}
|
|
const functionName = identifiedInitializer.api.functionName + (identifiedInitializer.isRequired ? ".required" : "");
|
|
if (ts25.isPropertyDeclaration(node.parent) && node.parent.initializer === node) {
|
|
let closestClass = node.parent;
|
|
while (closestClass && !ts25.isClassDeclaration(closestClass)) {
|
|
closestClass = closestClass.parent;
|
|
}
|
|
if (closestClass && ts25.isClassDeclaration(closestClass)) {
|
|
const decorators = this.reflector.getDecoratorsOfDeclaration(closestClass);
|
|
const isComponentOrDirective = decorators !== null && decorators.some((decorator) => {
|
|
return decorator.import?.from === "@angular/core" && (decorator.name === "Component" || decorator.name === "Directive");
|
|
});
|
|
return isComponentOrDirective ? null : makeDiagnostic(ErrorCode.UNSUPPORTED_INITIALIZER_API_USAGE, node, `Unsupported call to the ${functionName} function. This function can only be used as the initializer of a property on a @Component or @Directive class.`);
|
|
}
|
|
}
|
|
return makeDiagnostic(ErrorCode.UNSUPPORTED_INITIALIZER_API_USAGE, node, `Unsupported call to the ${functionName} function. This function can only be called in the initializer of a class member.`);
|
|
}
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/validation/src/rules/unused_standalone_imports_rule.js
|
|
import ts26 from "typescript";
|
|
var UnusedStandaloneImportsRule = class {
|
|
templateTypeChecker;
|
|
typeCheckingConfig;
|
|
importedSymbolsTracker;
|
|
constructor(templateTypeChecker, typeCheckingConfig, importedSymbolsTracker) {
|
|
this.templateTypeChecker = templateTypeChecker;
|
|
this.typeCheckingConfig = typeCheckingConfig;
|
|
this.importedSymbolsTracker = importedSymbolsTracker;
|
|
}
|
|
shouldCheck(sourceFile) {
|
|
return this.typeCheckingConfig.unusedStandaloneImports !== "suppress" && (this.importedSymbolsTracker.hasNamedImport(sourceFile, "Component", "@angular/core") || this.importedSymbolsTracker.hasNamespaceImport(sourceFile, "@angular/core"));
|
|
}
|
|
checkNode(node) {
|
|
if (!ts26.isClassDeclaration(node)) {
|
|
return null;
|
|
}
|
|
const metadata = this.templateTypeChecker.getDirectiveMetadata(node);
|
|
if (!metadata || !metadata.isStandalone || metadata.rawImports === null || metadata.imports === null || metadata.imports.length === 0) {
|
|
return null;
|
|
}
|
|
const usedDirectives = this.templateTypeChecker.getUsedDirectives(node);
|
|
const usedPipes = this.templateTypeChecker.getUsedPipes(node);
|
|
if (!usedDirectives || !usedPipes) {
|
|
return null;
|
|
}
|
|
const unused = this.getUnusedSymbols(metadata, new Set(usedDirectives.map((dir) => dir.ref.node)), new Set(usedPipes));
|
|
if (unused === null) {
|
|
return null;
|
|
}
|
|
const propertyAssignment = closestNode(metadata.rawImports, ts26.isPropertyAssignment);
|
|
const category = this.typeCheckingConfig.unusedStandaloneImports === "error" ? ts26.DiagnosticCategory.Error : ts26.DiagnosticCategory.Warning;
|
|
if (unused.length === metadata.imports.length && propertyAssignment !== null) {
|
|
return makeDiagnostic(ErrorCode.UNUSED_STANDALONE_IMPORTS, propertyAssignment.name, "All imports are unused", void 0, category);
|
|
}
|
|
return unused.map((ref) => {
|
|
const diagnosticNode = ref.getIdentityInExpression(metadata.rawImports) || ref.getIdentityIn(node.getSourceFile()) || metadata.rawImports;
|
|
return makeDiagnostic(ErrorCode.UNUSED_STANDALONE_IMPORTS, diagnosticNode, `${ref.node.name.text} is not used within the template of ${metadata.name}`, void 0, category);
|
|
});
|
|
}
|
|
getUnusedSymbols(metadata, usedDirectives, usedPipes) {
|
|
const { imports, rawImports } = metadata;
|
|
if (imports === null || rawImports === null) {
|
|
return null;
|
|
}
|
|
let unused = null;
|
|
for (const current of imports) {
|
|
const currentNode = current.node;
|
|
const dirMeta = this.templateTypeChecker.getDirectiveMetadata(currentNode);
|
|
if (dirMeta !== null) {
|
|
if (dirMeta.isStandalone && !usedDirectives.has(currentNode) && !this.isPotentialSharedReference(current, rawImports)) {
|
|
unused ??= [];
|
|
unused.push(current);
|
|
}
|
|
continue;
|
|
}
|
|
const pipeMeta = this.templateTypeChecker.getPipeMetadata(currentNode);
|
|
if (pipeMeta !== null && pipeMeta.isStandalone && pipeMeta.name !== null && !usedPipes.has(pipeMeta.name) && !this.isPotentialSharedReference(current, rawImports)) {
|
|
unused ??= [];
|
|
unused.push(current);
|
|
}
|
|
}
|
|
return unused;
|
|
}
|
|
/**
|
|
* Determines if an import reference *might* be coming from a shared imports array.
|
|
* @param reference Reference to be checked.
|
|
* @param rawImports AST node that defines the `imports` array.
|
|
*/
|
|
isPotentialSharedReference(reference, rawImports) {
|
|
if (reference.getIdentityInExpression(rawImports) !== null) {
|
|
return false;
|
|
}
|
|
let current = reference.getIdentityIn(rawImports.getSourceFile());
|
|
while (current !== null) {
|
|
if (ts26.isVariableStatement(current)) {
|
|
return !!current.modifiers?.some((m) => m.kind === ts26.SyntaxKind.ExportKeyword);
|
|
}
|
|
current = current.parent ?? null;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
function closestNode(start, predicate) {
|
|
let current = start.parent;
|
|
while (current) {
|
|
if (predicate(current)) {
|
|
return current;
|
|
} else {
|
|
current = current.parent;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/validation/src/rules/forbidden_required_initializer_invocation_rule.js
|
|
import ts27 from "typescript";
|
|
var APIS_TO_CHECK2 = [
|
|
INPUT_INITIALIZER_FN,
|
|
MODEL_INITIALIZER_FN,
|
|
...QUERY_INITIALIZER_FNS
|
|
];
|
|
var ForbiddenRequiredInitializersInvocationRule = class {
|
|
reflector;
|
|
importedSymbolsTracker;
|
|
constructor(reflector, importedSymbolsTracker) {
|
|
this.reflector = reflector;
|
|
this.importedSymbolsTracker = importedSymbolsTracker;
|
|
}
|
|
shouldCheck(sourceFile) {
|
|
return APIS_TO_CHECK2.some(({ functionName, owningModule }) => {
|
|
return this.importedSymbolsTracker.hasNamedImport(sourceFile, functionName, owningModule) || this.importedSymbolsTracker.hasNamespaceImport(sourceFile, owningModule);
|
|
});
|
|
}
|
|
checkNode(node) {
|
|
if (!ts27.isClassDeclaration(node))
|
|
return null;
|
|
const requiredInitializerDeclarations = node.members.filter((m) => ts27.isPropertyDeclaration(m) && this.isPropDeclarationARequiredInitializer(m));
|
|
const diagnostics = [];
|
|
for (let decl of node.members) {
|
|
if (!ts27.isPropertyDeclaration(decl))
|
|
continue;
|
|
const initiallizerExpr = decl.initializer;
|
|
if (!initiallizerExpr)
|
|
continue;
|
|
checkForbiddenInvocation(initiallizerExpr);
|
|
}
|
|
function checkForbiddenInvocation(node2) {
|
|
if (ts27.isArrowFunction(node2) || ts27.isFunctionExpression(node2))
|
|
return;
|
|
if (ts27.isPropertyAccessExpression(node2) && node2.expression.kind === ts27.SyntaxKind.ThisKeyword && // With the following we make sure we only flag invoked required initializers
|
|
ts27.isCallExpression(node2.parent) && node2.parent.expression === node2) {
|
|
const requiredProp = requiredInitializerDeclarations.find((prop) => prop.name.getText() === node2.name.getText());
|
|
if (requiredProp) {
|
|
const initializerFn = requiredProp.initializer.expression.expression.getText();
|
|
diagnostics.push(makeDiagnostic(ErrorCode.FORBIDDEN_REQUIRED_INITIALIZER_INVOCATION, node2, `\`${node2.name.getText()}\` is a required \`${initializerFn}\` and does not have a value in this context.`));
|
|
}
|
|
}
|
|
return node2.forEachChild(checkForbiddenInvocation);
|
|
}
|
|
const ctor = getConstructorFromClass(node);
|
|
if (ctor) {
|
|
checkForbiddenInvocation(ctor);
|
|
}
|
|
return diagnostics;
|
|
}
|
|
isPropDeclarationARequiredInitializer(node) {
|
|
if (!node.initializer)
|
|
return false;
|
|
const identifiedInitializer = tryParseInitializerApi(APIS_TO_CHECK2, node.initializer, this.reflector, this.importedSymbolsTracker);
|
|
if (identifiedInitializer === null || !identifiedInitializer.isRequired)
|
|
return false;
|
|
return true;
|
|
}
|
|
};
|
|
function getConstructorFromClass(node) {
|
|
return node.members.find((m) => ts27.isConstructorDeclaration(m) && m.body !== void 0);
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/validation/src/source_file_validator.js
|
|
var SourceFileValidator = class {
|
|
rules;
|
|
constructor(reflector, importedSymbolsTracker, templateTypeChecker, typeCheckingConfig) {
|
|
this.rules = [new InitializerApiUsageRule(reflector, importedSymbolsTracker)];
|
|
this.rules.push(new UnusedStandaloneImportsRule(templateTypeChecker, typeCheckingConfig, importedSymbolsTracker));
|
|
this.rules.push(new ForbiddenRequiredInitializersInvocationRule(reflector, importedSymbolsTracker));
|
|
}
|
|
/**
|
|
* Gets the diagnostics for a specific file, or null if the file is valid.
|
|
* @param sourceFile File to be checked.
|
|
*/
|
|
getDiagnosticsForFile(sourceFile) {
|
|
if (sourceFile.isDeclarationFile || sourceFile.fileName.endsWith(".ngtypecheck.ts")) {
|
|
return null;
|
|
}
|
|
let rulesToRun = null;
|
|
for (const rule of this.rules) {
|
|
if (rule.shouldCheck(sourceFile)) {
|
|
rulesToRun ??= [];
|
|
rulesToRun.push(rule);
|
|
}
|
|
}
|
|
if (rulesToRun === null) {
|
|
return null;
|
|
}
|
|
let fileDiagnostics = null;
|
|
sourceFile.forEachChild(function walk(node) {
|
|
for (const rule of rulesToRun) {
|
|
const nodeDiagnostics = rule.checkNode(node);
|
|
if (nodeDiagnostics !== null) {
|
|
fileDiagnostics ??= [];
|
|
if (Array.isArray(nodeDiagnostics)) {
|
|
fileDiagnostics.push(...nodeDiagnostics);
|
|
} else {
|
|
fileDiagnostics.push(nodeDiagnostics);
|
|
}
|
|
}
|
|
}
|
|
node.forEachChild(walk);
|
|
});
|
|
return fileDiagnostics;
|
|
}
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/core/src/feature_detection.js
|
|
import semver from "semver";
|
|
function coreVersionSupportsFeature(coreVersion, minVersion) {
|
|
if (coreVersion === `0.0.0-${"PLACEHOLDER"}`) {
|
|
return true;
|
|
}
|
|
return semver.satisfies(coreVersion, minVersion, { includePrerelease: true });
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/core/src/compiler.js
|
|
var CompilationTicketKind;
|
|
(function(CompilationTicketKind2) {
|
|
CompilationTicketKind2[CompilationTicketKind2["Fresh"] = 0] = "Fresh";
|
|
CompilationTicketKind2[CompilationTicketKind2["IncrementalTypeScript"] = 1] = "IncrementalTypeScript";
|
|
CompilationTicketKind2[CompilationTicketKind2["IncrementalResource"] = 2] = "IncrementalResource";
|
|
})(CompilationTicketKind || (CompilationTicketKind = {}));
|
|
function freshCompilationTicket(tsProgram, options, incrementalBuildStrategy, programDriver, perfRecorder, enableTemplateTypeChecker, usePoisonedData) {
|
|
return {
|
|
kind: CompilationTicketKind.Fresh,
|
|
tsProgram,
|
|
options,
|
|
incrementalBuildStrategy,
|
|
programDriver,
|
|
enableTemplateTypeChecker,
|
|
usePoisonedData,
|
|
perfRecorder: perfRecorder ?? ActivePerfRecorder.zeroedToNow()
|
|
};
|
|
}
|
|
function incrementalFromCompilerTicket(oldCompiler, newProgram, incrementalBuildStrategy, programDriver, modifiedResourceFiles, perfRecorder) {
|
|
const oldProgram = oldCompiler.getCurrentProgram();
|
|
const oldState = oldCompiler.incrementalStrategy.getIncrementalState(oldProgram);
|
|
if (oldState === null) {
|
|
return freshCompilationTicket(newProgram, oldCompiler.options, incrementalBuildStrategy, programDriver, perfRecorder, oldCompiler.enableTemplateTypeChecker, oldCompiler.usePoisonedData);
|
|
}
|
|
if (perfRecorder === null) {
|
|
perfRecorder = ActivePerfRecorder.zeroedToNow();
|
|
}
|
|
const incrementalCompilation = IncrementalCompilation.incremental(newProgram, versionMapFromProgram(newProgram, programDriver), oldProgram, oldState, modifiedResourceFiles, perfRecorder);
|
|
return {
|
|
kind: CompilationTicketKind.IncrementalTypeScript,
|
|
enableTemplateTypeChecker: oldCompiler.enableTemplateTypeChecker,
|
|
usePoisonedData: oldCompiler.usePoisonedData,
|
|
options: oldCompiler.options,
|
|
incrementalBuildStrategy,
|
|
incrementalCompilation,
|
|
programDriver,
|
|
newProgram,
|
|
perfRecorder
|
|
};
|
|
}
|
|
function incrementalFromStateTicket(oldProgram, oldState, newProgram, options, incrementalBuildStrategy, programDriver, modifiedResourceFiles, perfRecorder, enableTemplateTypeChecker, usePoisonedData) {
|
|
if (perfRecorder === null) {
|
|
perfRecorder = ActivePerfRecorder.zeroedToNow();
|
|
}
|
|
const incrementalCompilation = IncrementalCompilation.incremental(newProgram, versionMapFromProgram(newProgram, programDriver), oldProgram, oldState, modifiedResourceFiles, perfRecorder);
|
|
return {
|
|
kind: CompilationTicketKind.IncrementalTypeScript,
|
|
newProgram,
|
|
options,
|
|
incrementalBuildStrategy,
|
|
incrementalCompilation,
|
|
programDriver,
|
|
enableTemplateTypeChecker,
|
|
usePoisonedData,
|
|
perfRecorder
|
|
};
|
|
}
|
|
var NgCompiler = class _NgCompiler {
|
|
adapter;
|
|
options;
|
|
inputProgram;
|
|
programDriver;
|
|
incrementalStrategy;
|
|
incrementalCompilation;
|
|
usePoisonedData;
|
|
livePerfRecorder;
|
|
/**
|
|
* Lazily evaluated state of the compilation.
|
|
*
|
|
* This is created on demand by calling `ensureAnalyzed`.
|
|
*/
|
|
compilation = null;
|
|
/**
|
|
* Any diagnostics related to the construction of the compilation.
|
|
*
|
|
* These are diagnostics which arose during setup of the host and/or program.
|
|
*/
|
|
constructionDiagnostics = [];
|
|
/**
|
|
* Non-template diagnostics related to the program itself. Does not include template
|
|
* diagnostics because the template type checker memoizes them itself.
|
|
*
|
|
* This is set by (and memoizes) `getNonTemplateDiagnostics`.
|
|
*/
|
|
nonTemplateDiagnostics = null;
|
|
closureCompilerEnabled;
|
|
currentProgram;
|
|
entryPoint;
|
|
moduleResolver;
|
|
resourceManager;
|
|
cycleAnalyzer;
|
|
ignoreForDiagnostics;
|
|
ignoreForEmit;
|
|
enableTemplateTypeChecker;
|
|
enableBlockSyntax;
|
|
enableLetSyntax;
|
|
angularCoreVersion;
|
|
enableHmr;
|
|
implicitStandaloneValue;
|
|
enableSelectorless;
|
|
emitDeclarationOnly;
|
|
/**
|
|
* `NgCompiler` can be reused for multiple compilations (for resource-only changes), and each
|
|
* new compilation uses a fresh `PerfRecorder`. Thus, classes created with a lifespan of the
|
|
* `NgCompiler` use a `DelegatingPerfRecorder` so the `PerfRecorder` they write to can be updated
|
|
* with each fresh compilation.
|
|
*/
|
|
delegatingPerfRecorder;
|
|
/**
|
|
* Convert a `CompilationTicket` into an `NgCompiler` instance for the requested compilation.
|
|
*
|
|
* Depending on the nature of the compilation request, the `NgCompiler` instance may be reused
|
|
* from a previous compilation and updated with any changes, it may be a new instance which
|
|
* incrementally reuses state from a previous compilation, or it may represent a fresh
|
|
* compilation entirely.
|
|
*/
|
|
static fromTicket(ticket, adapter) {
|
|
switch (ticket.kind) {
|
|
case CompilationTicketKind.Fresh:
|
|
return new _NgCompiler(adapter, ticket.options, ticket.tsProgram, ticket.programDriver, ticket.incrementalBuildStrategy, IncrementalCompilation.fresh(ticket.tsProgram, versionMapFromProgram(ticket.tsProgram, ticket.programDriver)), ticket.enableTemplateTypeChecker, ticket.usePoisonedData, ticket.perfRecorder);
|
|
case CompilationTicketKind.IncrementalTypeScript:
|
|
return new _NgCompiler(adapter, ticket.options, ticket.newProgram, ticket.programDriver, ticket.incrementalBuildStrategy, ticket.incrementalCompilation, ticket.enableTemplateTypeChecker, ticket.usePoisonedData, ticket.perfRecorder);
|
|
case CompilationTicketKind.IncrementalResource:
|
|
const compiler = ticket.compiler;
|
|
compiler.updateWithChangedResources(ticket.modifiedResourceFiles, ticket.perfRecorder);
|
|
return compiler;
|
|
}
|
|
}
|
|
constructor(adapter, options, inputProgram, programDriver, incrementalStrategy, incrementalCompilation, enableTemplateTypeChecker, usePoisonedData, livePerfRecorder) {
|
|
this.adapter = adapter;
|
|
this.options = options;
|
|
this.inputProgram = inputProgram;
|
|
this.programDriver = programDriver;
|
|
this.incrementalStrategy = incrementalStrategy;
|
|
this.incrementalCompilation = incrementalCompilation;
|
|
this.usePoisonedData = usePoisonedData;
|
|
this.livePerfRecorder = livePerfRecorder;
|
|
this.angularCoreVersion = options["_angularCoreVersion"] ?? null;
|
|
this.delegatingPerfRecorder = new DelegatingPerfRecorder(this.perfRecorder);
|
|
this.usePoisonedData = usePoisonedData || !!options._compilePoisonedComponents;
|
|
this.enableTemplateTypeChecker = enableTemplateTypeChecker || !!options._enableTemplateTypeChecker;
|
|
this.enableBlockSyntax = options["_enableBlockSyntax"] ?? true;
|
|
this.enableLetSyntax = options["_enableLetSyntax"] ?? true;
|
|
this.enableSelectorless = options["_enableSelectorless"] ?? false;
|
|
this.emitDeclarationOnly = !!options.emitDeclarationOnly && !!options._experimentalAllowEmitDeclarationOnly;
|
|
this.implicitStandaloneValue = this.angularCoreVersion === null || coreVersionSupportsFeature(this.angularCoreVersion, ">= 19.0.0");
|
|
this.enableHmr = !!options["_enableHmr"];
|
|
this.constructionDiagnostics.push(...this.adapter.constructionDiagnostics, ...verifyCompatibleTypeCheckOptions(this.options));
|
|
this.currentProgram = inputProgram;
|
|
this.closureCompilerEnabled = !!this.options.annotateForClosureCompiler;
|
|
this.entryPoint = adapter.entryPoint !== null ? getSourceFileOrNull(inputProgram, adapter.entryPoint) : null;
|
|
const moduleResolutionCache = ts28.createModuleResolutionCache(
|
|
this.adapter.getCurrentDirectory(),
|
|
// doen't retain a reference to `this`, if other closures in the constructor here reference
|
|
// `this` internally then a closure created here would retain them. This can cause major
|
|
// memory leak issues since the `moduleResolutionCache` is a long-lived object and finds its
|
|
// way into all kinds of places inside TS internal objects.
|
|
this.adapter.getCanonicalFileName.bind(this.adapter)
|
|
);
|
|
this.moduleResolver = new ModuleResolver(inputProgram, this.options, this.adapter, moduleResolutionCache);
|
|
this.resourceManager = new AdapterResourceLoader(adapter, this.options);
|
|
this.cycleAnalyzer = new CycleAnalyzer(new ImportGraph(inputProgram.getTypeChecker(), this.delegatingPerfRecorder));
|
|
this.incrementalStrategy.setIncrementalState(this.incrementalCompilation.state, inputProgram);
|
|
this.ignoreForDiagnostics = new Set(inputProgram.getSourceFiles().filter((sf) => this.adapter.isShim(sf)));
|
|
this.ignoreForEmit = this.adapter.ignoreForEmit;
|
|
let dtsFileCount = 0;
|
|
let nonDtsFileCount = 0;
|
|
for (const sf of inputProgram.getSourceFiles()) {
|
|
if (sf.isDeclarationFile) {
|
|
dtsFileCount++;
|
|
} else {
|
|
nonDtsFileCount++;
|
|
}
|
|
}
|
|
livePerfRecorder.eventCount(PerfEvent.InputDtsFile, dtsFileCount);
|
|
livePerfRecorder.eventCount(PerfEvent.InputTsFile, nonDtsFileCount);
|
|
}
|
|
get perfRecorder() {
|
|
return this.livePerfRecorder;
|
|
}
|
|
updateWithChangedResources(changedResources, perfRecorder) {
|
|
this.livePerfRecorder = perfRecorder;
|
|
this.delegatingPerfRecorder.target = perfRecorder;
|
|
perfRecorder.inPhase(PerfPhase.ResourceUpdate, () => {
|
|
if (this.compilation === null) {
|
|
return;
|
|
}
|
|
this.resourceManager.invalidate();
|
|
const classesToUpdate = /* @__PURE__ */ new Set();
|
|
for (const resourceFile of changedResources) {
|
|
for (const templateClass of this.getComponentsWithTemplateFile(resourceFile)) {
|
|
classesToUpdate.add(templateClass);
|
|
}
|
|
for (const styleClass of this.getComponentsWithStyleFile(resourceFile)) {
|
|
classesToUpdate.add(styleClass);
|
|
}
|
|
}
|
|
for (const clazz of classesToUpdate) {
|
|
this.compilation.traitCompiler.updateResources(clazz);
|
|
if (!ts28.isClassDeclaration(clazz)) {
|
|
continue;
|
|
}
|
|
this.compilation.templateTypeChecker.invalidateClass(clazz);
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* Get the resource dependencies of a file.
|
|
*
|
|
* If the file is not part of the compilation, an empty array will be returned.
|
|
*/
|
|
getResourceDependencies(file) {
|
|
this.ensureAnalyzed();
|
|
return this.incrementalCompilation.depGraph.getResourceDependencies(file);
|
|
}
|
|
/**
|
|
* Get all Angular-related diagnostics for this compilation.
|
|
*/
|
|
getDiagnostics() {
|
|
const diagnostics = [...this.getNonTemplateDiagnostics()];
|
|
try {
|
|
diagnostics.push(...this.getTemplateDiagnostics(), ...this.runAdditionalChecks());
|
|
} catch (err) {
|
|
if (!isFatalDiagnosticError(err)) {
|
|
throw err;
|
|
}
|
|
diagnostics.push(err.toDiagnostic());
|
|
}
|
|
return this.addMessageTextDetails(diagnostics);
|
|
}
|
|
/**
|
|
* Get all Angular-related diagnostics for this compilation.
|
|
*
|
|
* If a `ts.SourceFile` is passed, only diagnostics related to that file are returned.
|
|
*/
|
|
getDiagnosticsForFile(file, optimizeFor) {
|
|
const diagnostics = [
|
|
...this.getNonTemplateDiagnostics().filter((diag) => diag.file === file)
|
|
];
|
|
try {
|
|
diagnostics.push(...this.getTemplateDiagnosticsForFile(file, optimizeFor), ...this.runAdditionalChecks(file));
|
|
} catch (err) {
|
|
if (!isFatalDiagnosticError(err)) {
|
|
throw err;
|
|
}
|
|
diagnostics.push(err.toDiagnostic());
|
|
}
|
|
return this.addMessageTextDetails(diagnostics);
|
|
}
|
|
/**
|
|
* Get all `ts.Diagnostic`s currently available that pertain to the given component.
|
|
*/
|
|
getDiagnosticsForComponent(component) {
|
|
const compilation = this.ensureAnalyzed();
|
|
const ttc = compilation.templateTypeChecker;
|
|
const diagnostics = [];
|
|
try {
|
|
diagnostics.push(...ttc.getDiagnosticsForComponent(component));
|
|
const { extendedTemplateChecker, templateSemanticsChecker } = compilation;
|
|
if (templateSemanticsChecker !== null) {
|
|
diagnostics.push(...templateSemanticsChecker.getDiagnosticsForComponent(component));
|
|
}
|
|
if (this.options.strictTemplates && extendedTemplateChecker !== null) {
|
|
diagnostics.push(...extendedTemplateChecker.getDiagnosticsForComponent(component));
|
|
}
|
|
} catch (err) {
|
|
if (!isFatalDiagnosticError(err)) {
|
|
throw err;
|
|
}
|
|
diagnostics.push(err.toDiagnostic());
|
|
}
|
|
return this.addMessageTextDetails(diagnostics);
|
|
}
|
|
/**
|
|
* Add Angular.io error guide links to diagnostics for this compilation.
|
|
*/
|
|
addMessageTextDetails(diagnostics) {
|
|
return diagnostics.map((diag) => {
|
|
if (diag.code && COMPILER_ERRORS_WITH_GUIDES.has(ngErrorCode(diag.code))) {
|
|
return {
|
|
...diag,
|
|
messageText: diag.messageText + `. Find more at ${ERROR_DETAILS_PAGE_BASE_URL}/NG${ngErrorCode(diag.code)}`
|
|
};
|
|
}
|
|
return diag;
|
|
});
|
|
}
|
|
/**
|
|
* Get all setup-related diagnostics for this compilation.
|
|
*/
|
|
getOptionDiagnostics() {
|
|
return this.constructionDiagnostics;
|
|
}
|
|
/**
|
|
* Get the current `ts.Program` known to this `NgCompiler`.
|
|
*
|
|
* Compilation begins with an input `ts.Program`, and during template type-checking operations new
|
|
* `ts.Program`s may be produced using the `ProgramDriver`. The most recent such `ts.Program` to
|
|
* be produced is available here.
|
|
*
|
|
* This `ts.Program` serves two key purposes:
|
|
*
|
|
* * As an incremental starting point for creating the next `ts.Program` based on files that the
|
|
* user has changed (for clients using the TS compiler program APIs).
|
|
*
|
|
* * As the "before" point for an incremental compilation invocation, to determine what's changed
|
|
* between the old and new programs (for all compilations).
|
|
*/
|
|
getCurrentProgram() {
|
|
return this.currentProgram;
|
|
}
|
|
getTemplateTypeChecker() {
|
|
if (!this.enableTemplateTypeChecker) {
|
|
throw new Error("The `TemplateTypeChecker` does not work without `enableTemplateTypeChecker`.");
|
|
}
|
|
return this.ensureAnalyzed().templateTypeChecker;
|
|
}
|
|
/**
|
|
* Retrieves the `ts.Declaration`s for any component(s) which use the given template file.
|
|
*/
|
|
getComponentsWithTemplateFile(templateFilePath) {
|
|
const { resourceRegistry } = this.ensureAnalyzed();
|
|
return resourceRegistry.getComponentsWithTemplate(resolve(templateFilePath));
|
|
}
|
|
/**
|
|
* Retrieves the `ts.Declaration`s for any component(s) which use the given template file.
|
|
*/
|
|
getComponentsWithStyleFile(styleFilePath) {
|
|
const { resourceRegistry } = this.ensureAnalyzed();
|
|
return resourceRegistry.getComponentsWithStyle(resolve(styleFilePath));
|
|
}
|
|
/**
|
|
* Retrieves external resources for the given directive.
|
|
*/
|
|
getDirectiveResources(classDecl) {
|
|
if (!isNamedClassDeclaration(classDecl)) {
|
|
return null;
|
|
}
|
|
const { resourceRegistry } = this.ensureAnalyzed();
|
|
const styles = resourceRegistry.getStyles(classDecl);
|
|
const template = resourceRegistry.getTemplate(classDecl);
|
|
const hostBindings = resourceRegistry.getHostBindings(classDecl);
|
|
return { styles, template, hostBindings };
|
|
}
|
|
getMeta(classDecl) {
|
|
if (!isNamedClassDeclaration(classDecl)) {
|
|
return null;
|
|
}
|
|
const ref = new Reference(classDecl);
|
|
const { metaReader } = this.ensureAnalyzed();
|
|
const meta = metaReader.getPipeMetadata(ref) ?? metaReader.getDirectiveMetadata(ref);
|
|
if (meta === null) {
|
|
return null;
|
|
}
|
|
return meta;
|
|
}
|
|
/**
|
|
* Perform Angular's analysis step (as a precursor to `getDiagnostics` or `prepareEmit`)
|
|
* asynchronously.
|
|
*
|
|
* Normally, this operation happens lazily whenever `getDiagnostics` or `prepareEmit` are called.
|
|
* However, certain consumers may wish to allow for an asynchronous phase of analysis, where
|
|
* resources such as `styleUrls` are resolved asynchronously. In these cases `analyzeAsync` must
|
|
* be called first, and its `Promise` awaited prior to calling any other APIs of `NgCompiler`.
|
|
*/
|
|
async analyzeAsync() {
|
|
if (this.compilation !== null) {
|
|
return;
|
|
}
|
|
await this.perfRecorder.inPhase(PerfPhase.Analysis, async () => {
|
|
this.compilation = this.makeCompilation();
|
|
const promises = [];
|
|
for (const sf of this.inputProgram.getSourceFiles()) {
|
|
if (sf.isDeclarationFile) {
|
|
continue;
|
|
}
|
|
let analysisPromise = this.compilation.traitCompiler.analyzeAsync(sf);
|
|
if (analysisPromise !== void 0) {
|
|
promises.push(analysisPromise);
|
|
}
|
|
}
|
|
await Promise.all(promises);
|
|
this.perfRecorder.memory(PerfCheckpoint.Analysis);
|
|
this.resolveCompilation(this.compilation.traitCompiler);
|
|
});
|
|
}
|
|
/**
|
|
* Fetch transformers and other information which is necessary for a consumer to `emit` the
|
|
* program with Angular-added definitions.
|
|
*/
|
|
prepareEmit() {
|
|
const compilation = this.ensureAnalyzed();
|
|
untagAllTsFiles(this.inputProgram);
|
|
const coreImportsFrom = compilation.isCore ? getR3SymbolsFile(this.inputProgram) : null;
|
|
let importRewriter;
|
|
if (coreImportsFrom !== null) {
|
|
importRewriter = new R3SymbolsImportRewriter(coreImportsFrom.fileName);
|
|
} else {
|
|
importRewriter = new NoopImportRewriter();
|
|
}
|
|
const defaultImportTracker = new DefaultImportTracker();
|
|
const before = [
|
|
ivyTransformFactory(compilation.traitCompiler, compilation.reflector, importRewriter, defaultImportTracker, compilation.localCompilationExtraImportsTracker, this.delegatingPerfRecorder, compilation.isCore, this.closureCompilerEnabled, this.emitDeclarationOnly),
|
|
aliasTransformFactory(compilation.traitCompiler.exportStatements),
|
|
defaultImportTracker.importPreservingTransformer()
|
|
];
|
|
if (compilation.supportJitMode && compilation.jitDeclarationRegistry.jitDeclarations.size > 0) {
|
|
const { jitDeclarations } = compilation.jitDeclarationRegistry;
|
|
const jitDeclarationsArray = Array.from(jitDeclarations);
|
|
const jitDeclarationOriginalNodes = new Set(jitDeclarationsArray.map((d) => ts28.getOriginalNode(d)));
|
|
const sourceFilesWithJit = new Set(jitDeclarationsArray.map((d) => d.getSourceFile().fileName));
|
|
before.push((ctx) => {
|
|
const reflectionHost = new TypeScriptReflectionHost(this.inputProgram.getTypeChecker());
|
|
const jitTransform = angularJitApplicationTransform(this.inputProgram, compilation.isCore, (node) => {
|
|
node = ts28.getOriginalNode(node, ts28.isClassDeclaration);
|
|
return reflectionHost.isClass(node) && jitDeclarationOriginalNodes.has(node);
|
|
})(ctx);
|
|
return (sourceFile) => {
|
|
if (!sourceFilesWithJit.has(sourceFile.fileName)) {
|
|
return sourceFile;
|
|
}
|
|
return jitTransform(sourceFile);
|
|
};
|
|
});
|
|
}
|
|
before.push(signalMetadataTransform(this.inputProgram));
|
|
const afterDeclarations = [];
|
|
if ((this.options.compilationMode !== "experimental-local" || this.emitDeclarationOnly) && compilation.dtsTransforms !== null) {
|
|
if (this.emitDeclarationOnly) {
|
|
afterDeclarations.push(...before);
|
|
}
|
|
afterDeclarations.push(declarationTransformFactory(compilation.dtsTransforms, compilation.reflector, compilation.refEmitter, importRewriter));
|
|
}
|
|
if (compilation.aliasingHost !== null && compilation.aliasingHost.aliasExportsInDts) {
|
|
afterDeclarations.push(aliasTransformFactory(compilation.traitCompiler.exportStatements));
|
|
}
|
|
return { transformers: { before, afterDeclarations } };
|
|
}
|
|
/**
|
|
* Run the indexing process and return a `Map` of all indexed components.
|
|
*
|
|
* See the `indexing` package for more details.
|
|
*/
|
|
getIndexedComponents() {
|
|
const compilation = this.ensureAnalyzed();
|
|
const context = new IndexingContext();
|
|
compilation.traitCompiler.index(context);
|
|
return generateAnalysis(context);
|
|
}
|
|
/**
|
|
* Gets information for the current program that may be used to generate API
|
|
* reference documentation. This includes Angular-specific information, such
|
|
* as component inputs and outputs.
|
|
*
|
|
* @param entryPoint Path to the entry point for the package for which API
|
|
* docs should be extracted.
|
|
*
|
|
* @returns A map of symbols with their associated module, eg: ApplicationRef => @angular/core
|
|
*/
|
|
getApiDocumentation(entryPoint, privateModules) {
|
|
const compilation = this.ensureAnalyzed();
|
|
const checker = this.inputProgram.getTypeChecker();
|
|
const docsExtractor = new DocsExtractor(checker, compilation.metaReader);
|
|
const entryPointSourceFile = this.inputProgram.getSourceFiles().find((sourceFile) => {
|
|
return sourceFile.fileName.includes(entryPoint);
|
|
});
|
|
if (!entryPointSourceFile) {
|
|
throw new Error(`Entry point "${entryPoint}" not found in program sources.`);
|
|
}
|
|
const rootDir = this.inputProgram.getCurrentDirectory();
|
|
return docsExtractor.extractAll(entryPointSourceFile, rootDir, privateModules);
|
|
}
|
|
/**
|
|
* Collect i18n messages into the `Xi18nContext`.
|
|
*/
|
|
xi18n(ctx) {
|
|
const compilation = this.ensureAnalyzed();
|
|
compilation.traitCompiler.xi18n(ctx);
|
|
}
|
|
/**
|
|
* Emits the JavaScript module that can be used to replace the metadata of a class during HMR.
|
|
* @param node Class for which to generate the update module.
|
|
*/
|
|
emitHmrUpdateModule(node) {
|
|
const { traitCompiler, reflector } = this.ensureAnalyzed();
|
|
if (!reflector.isClass(node)) {
|
|
return null;
|
|
}
|
|
const callback = traitCompiler.compileHmrUpdateCallback(node);
|
|
if (callback === null) {
|
|
return null;
|
|
}
|
|
const sourceFile = node.getSourceFile();
|
|
const printer = ts28.createPrinter();
|
|
const nodeText = printer.printNode(ts28.EmitHint.Unspecified, callback, sourceFile);
|
|
return ts28.transpileModule(nodeText, {
|
|
compilerOptions: {
|
|
...this.options,
|
|
// Some module types can produce additional code (see #60795) whereas we need the
|
|
// HMR update module to use a native `export`. Override the `target` and `module`
|
|
// to ensure that it looks as expected.
|
|
module: ts28.ModuleKind.ES2022,
|
|
target: ts28.ScriptTarget.ES2022
|
|
},
|
|
fileName: sourceFile.fileName,
|
|
reportDiagnostics: false
|
|
}).outputText;
|
|
}
|
|
ensureAnalyzed() {
|
|
if (this.compilation === null) {
|
|
this.analyzeSync();
|
|
}
|
|
return this.compilation;
|
|
}
|
|
analyzeSync() {
|
|
this.perfRecorder.inPhase(PerfPhase.Analysis, () => {
|
|
this.compilation = this.makeCompilation();
|
|
for (const sf of this.inputProgram.getSourceFiles()) {
|
|
if (sf.isDeclarationFile) {
|
|
continue;
|
|
}
|
|
this.compilation.traitCompiler.analyzeSync(sf);
|
|
}
|
|
this.perfRecorder.memory(PerfCheckpoint.Analysis);
|
|
this.resolveCompilation(this.compilation.traitCompiler);
|
|
});
|
|
}
|
|
resolveCompilation(traitCompiler) {
|
|
this.perfRecorder.inPhase(PerfPhase.Resolve, () => {
|
|
traitCompiler.resolve();
|
|
this.incrementalCompilation.recordSuccessfulAnalysis(traitCompiler);
|
|
this.perfRecorder.memory(PerfCheckpoint.Resolve);
|
|
});
|
|
}
|
|
get fullTemplateTypeCheck() {
|
|
const strictTemplates = !!this.options.strictTemplates;
|
|
return strictTemplates || !!this.options.fullTemplateTypeCheck;
|
|
}
|
|
getTypeCheckingConfig() {
|
|
const strictTemplates = !!this.options.strictTemplates;
|
|
const useInlineTypeConstructors = this.programDriver.supportsInlineOperations;
|
|
const checkTwoWayBoundEvents = this.options["_checkTwoWayBoundEvents"] ?? false;
|
|
const allowSignalsInTwoWayBindings = this.angularCoreVersion === null || coreVersionSupportsFeature(this.angularCoreVersion, ">= 17.2.0-0");
|
|
const allowDomEventAssertion = this.angularCoreVersion === null || coreVersionSupportsFeature(this.angularCoreVersion, ">= 20.2.0");
|
|
let typeCheckingConfig;
|
|
if (this.fullTemplateTypeCheck) {
|
|
typeCheckingConfig = {
|
|
applyTemplateContextGuards: strictTemplates,
|
|
checkQueries: false,
|
|
checkTemplateBodies: true,
|
|
alwaysCheckSchemaInTemplateBodies: true,
|
|
checkTypeOfInputBindings: strictTemplates,
|
|
honorAccessModifiersForInputBindings: false,
|
|
checkControlFlowBodies: true,
|
|
strictNullInputBindings: strictTemplates,
|
|
checkTypeOfAttributes: strictTemplates,
|
|
// Even in full template type-checking mode, DOM binding checks are not quite ready yet.
|
|
checkTypeOfDomBindings: false,
|
|
checkTypeOfOutputEvents: strictTemplates,
|
|
checkTypeOfAnimationEvents: strictTemplates,
|
|
// Checking of DOM events currently has an adverse effect on developer experience,
|
|
// e.g. for `<input (blur)="update($event.target.value)">` enabling this check results in:
|
|
// - error TS2531: Object is possibly 'null'.
|
|
// - error TS2339: Property 'value' does not exist on type 'EventTarget'.
|
|
checkTypeOfDomEvents: strictTemplates,
|
|
checkTypeOfDomReferences: strictTemplates,
|
|
// Non-DOM references have the correct type in View Engine so there is no strictness flag.
|
|
checkTypeOfNonDomReferences: true,
|
|
// Pipes are checked in View Engine so there is no strictness flag.
|
|
checkTypeOfPipes: true,
|
|
strictSafeNavigationTypes: strictTemplates,
|
|
useContextGenericType: strictTemplates,
|
|
strictLiteralTypes: true,
|
|
enableTemplateTypeChecker: this.enableTemplateTypeChecker,
|
|
useInlineTypeConstructors,
|
|
// Warnings for suboptimal type inference are only enabled if in Language Service mode
|
|
// (providing the full TemplateTypeChecker API) and if strict mode is not enabled. In strict
|
|
// mode, the user is in full control of type inference.
|
|
suggestionsForSuboptimalTypeInference: this.enableTemplateTypeChecker && !strictTemplates,
|
|
controlFlowPreventingContentProjection: this.options.extendedDiagnostics?.defaultCategory || DiagnosticCategoryLabel.Warning,
|
|
unusedStandaloneImports: this.options.extendedDiagnostics?.defaultCategory || DiagnosticCategoryLabel.Warning,
|
|
allowSignalsInTwoWayBindings,
|
|
checkTwoWayBoundEvents,
|
|
allowDomEventAssertion
|
|
};
|
|
} else {
|
|
typeCheckingConfig = {
|
|
applyTemplateContextGuards: false,
|
|
checkQueries: false,
|
|
checkTemplateBodies: false,
|
|
checkControlFlowBodies: false,
|
|
// Enable deep schema checking in "basic" template type-checking mode only if Closure
|
|
// compilation is requested, which is a good proxy for "only in google3".
|
|
alwaysCheckSchemaInTemplateBodies: this.closureCompilerEnabled,
|
|
checkTypeOfInputBindings: false,
|
|
strictNullInputBindings: false,
|
|
honorAccessModifiersForInputBindings: false,
|
|
checkTypeOfAttributes: false,
|
|
checkTypeOfDomBindings: false,
|
|
checkTypeOfOutputEvents: false,
|
|
checkTypeOfAnimationEvents: false,
|
|
checkTypeOfDomEvents: false,
|
|
checkTypeOfDomReferences: false,
|
|
checkTypeOfNonDomReferences: false,
|
|
checkTypeOfPipes: false,
|
|
strictSafeNavigationTypes: false,
|
|
useContextGenericType: false,
|
|
strictLiteralTypes: false,
|
|
enableTemplateTypeChecker: this.enableTemplateTypeChecker,
|
|
useInlineTypeConstructors,
|
|
// In "basic" template type-checking mode, no warnings are produced since most things are
|
|
// not checked anyways.
|
|
suggestionsForSuboptimalTypeInference: false,
|
|
controlFlowPreventingContentProjection: this.options.extendedDiagnostics?.defaultCategory || DiagnosticCategoryLabel.Warning,
|
|
unusedStandaloneImports: this.options.extendedDiagnostics?.defaultCategory || DiagnosticCategoryLabel.Warning,
|
|
allowSignalsInTwoWayBindings,
|
|
checkTwoWayBoundEvents,
|
|
allowDomEventAssertion
|
|
};
|
|
}
|
|
if (this.options.strictInputTypes !== void 0) {
|
|
typeCheckingConfig.checkTypeOfInputBindings = this.options.strictInputTypes;
|
|
typeCheckingConfig.applyTemplateContextGuards = this.options.strictInputTypes;
|
|
}
|
|
if (this.options.strictInputAccessModifiers !== void 0) {
|
|
typeCheckingConfig.honorAccessModifiersForInputBindings = this.options.strictInputAccessModifiers;
|
|
}
|
|
if (this.options.strictNullInputTypes !== void 0) {
|
|
typeCheckingConfig.strictNullInputBindings = this.options.strictNullInputTypes;
|
|
}
|
|
if (this.options.strictOutputEventTypes !== void 0) {
|
|
typeCheckingConfig.checkTypeOfOutputEvents = this.options.strictOutputEventTypes;
|
|
typeCheckingConfig.checkTypeOfAnimationEvents = this.options.strictOutputEventTypes;
|
|
}
|
|
if (this.options.strictDomEventTypes !== void 0) {
|
|
typeCheckingConfig.checkTypeOfDomEvents = this.options.strictDomEventTypes;
|
|
}
|
|
if (this.options.strictSafeNavigationTypes !== void 0) {
|
|
typeCheckingConfig.strictSafeNavigationTypes = this.options.strictSafeNavigationTypes;
|
|
}
|
|
if (this.options.strictDomLocalRefTypes !== void 0) {
|
|
typeCheckingConfig.checkTypeOfDomReferences = this.options.strictDomLocalRefTypes;
|
|
}
|
|
if (this.options.strictAttributeTypes !== void 0) {
|
|
typeCheckingConfig.checkTypeOfAttributes = this.options.strictAttributeTypes;
|
|
}
|
|
if (this.options.strictContextGenerics !== void 0) {
|
|
typeCheckingConfig.useContextGenericType = this.options.strictContextGenerics;
|
|
}
|
|
if (this.options.strictLiteralTypes !== void 0) {
|
|
typeCheckingConfig.strictLiteralTypes = this.options.strictLiteralTypes;
|
|
}
|
|
if (this.options.extendedDiagnostics?.checks?.controlFlowPreventingContentProjection !== void 0) {
|
|
typeCheckingConfig.controlFlowPreventingContentProjection = this.options.extendedDiagnostics.checks.controlFlowPreventingContentProjection;
|
|
}
|
|
if (this.options.extendedDiagnostics?.checks?.unusedStandaloneImports !== void 0) {
|
|
typeCheckingConfig.unusedStandaloneImports = this.options.extendedDiagnostics.checks.unusedStandaloneImports;
|
|
}
|
|
return typeCheckingConfig;
|
|
}
|
|
getTemplateDiagnostics() {
|
|
const compilation = this.ensureAnalyzed();
|
|
const diagnostics = [];
|
|
for (const sf of this.inputProgram.getSourceFiles()) {
|
|
if (sf.isDeclarationFile || this.adapter.isShim(sf)) {
|
|
continue;
|
|
}
|
|
diagnostics.push(...compilation.templateTypeChecker.getDiagnosticsForFile(sf, OptimizeFor.WholeProgram));
|
|
}
|
|
const program = this.programDriver.getProgram();
|
|
this.incrementalStrategy.setIncrementalState(this.incrementalCompilation.state, program);
|
|
this.currentProgram = program;
|
|
return diagnostics;
|
|
}
|
|
getTemplateDiagnosticsForFile(sf, optimizeFor) {
|
|
const compilation = this.ensureAnalyzed();
|
|
const diagnostics = [];
|
|
if (!sf.isDeclarationFile && !this.adapter.isShim(sf)) {
|
|
diagnostics.push(...compilation.templateTypeChecker.getDiagnosticsForFile(sf, optimizeFor));
|
|
}
|
|
const program = this.programDriver.getProgram();
|
|
this.incrementalStrategy.setIncrementalState(this.incrementalCompilation.state, program);
|
|
this.currentProgram = program;
|
|
return diagnostics;
|
|
}
|
|
getNonTemplateDiagnostics() {
|
|
if (this.nonTemplateDiagnostics === null) {
|
|
const compilation = this.ensureAnalyzed();
|
|
this.nonTemplateDiagnostics = [...compilation.traitCompiler.diagnostics];
|
|
if (this.entryPoint !== null && compilation.exportReferenceGraph !== null) {
|
|
this.nonTemplateDiagnostics.push(...checkForPrivateExports(this.entryPoint, this.inputProgram.getTypeChecker(), compilation.exportReferenceGraph));
|
|
}
|
|
}
|
|
return this.nonTemplateDiagnostics;
|
|
}
|
|
runAdditionalChecks(sf) {
|
|
const diagnostics = [];
|
|
const compilation = this.ensureAnalyzed();
|
|
const { extendedTemplateChecker, templateSemanticsChecker, sourceFileValidator } = compilation;
|
|
const files = sf ? [sf] : this.inputProgram.getSourceFiles();
|
|
for (const sf2 of files) {
|
|
if (sourceFileValidator !== null) {
|
|
const sourceFileDiagnostics = sourceFileValidator.getDiagnosticsForFile(sf2);
|
|
if (sourceFileDiagnostics !== null) {
|
|
diagnostics.push(...sourceFileDiagnostics);
|
|
}
|
|
}
|
|
if (templateSemanticsChecker !== null) {
|
|
diagnostics.push(...compilation.traitCompiler.runAdditionalChecks(sf2, (clazz, handler) => {
|
|
return handler.templateSemanticsCheck?.(clazz, templateSemanticsChecker) || null;
|
|
}));
|
|
}
|
|
if (this.options.strictTemplates && extendedTemplateChecker !== null) {
|
|
diagnostics.push(...compilation.traitCompiler.runAdditionalChecks(sf2, (clazz, handler) => {
|
|
return handler.extendedTemplateCheck?.(clazz, extendedTemplateChecker) || null;
|
|
}));
|
|
}
|
|
}
|
|
return diagnostics;
|
|
}
|
|
makeCompilation() {
|
|
const isCore = this.options._isAngularCoreCompilation ?? isAngularCorePackage(this.inputProgram);
|
|
let compilationMode = CompilationMode.FULL;
|
|
if (!isCore) {
|
|
switch (this.options.compilationMode) {
|
|
case "full":
|
|
compilationMode = CompilationMode.FULL;
|
|
break;
|
|
case "partial":
|
|
compilationMode = CompilationMode.PARTIAL;
|
|
break;
|
|
case "experimental-local":
|
|
compilationMode = CompilationMode.LOCAL;
|
|
break;
|
|
}
|
|
}
|
|
if (this.emitDeclarationOnly) {
|
|
compilationMode = CompilationMode.LOCAL;
|
|
}
|
|
const checker = this.inputProgram.getTypeChecker();
|
|
const reflector = new TypeScriptReflectionHost(checker, compilationMode === CompilationMode.LOCAL);
|
|
let refEmitter;
|
|
let aliasingHost = null;
|
|
if (this.adapter.unifiedModulesHost === null || !this.options["_useHostForImportGeneration"] && !this.options["_useHostForImportAndAliasGeneration"]) {
|
|
let localImportStrategy;
|
|
if (this.options.rootDirs !== void 0 && this.options.rootDirs.length > 0) {
|
|
localImportStrategy = new LogicalProjectStrategy(reflector, new LogicalFileSystem([...this.adapter.rootDirs], this.adapter));
|
|
} else {
|
|
localImportStrategy = new RelativePathStrategy(reflector);
|
|
}
|
|
refEmitter = new ReferenceEmitter([
|
|
// First, try to use local identifiers if available.
|
|
new LocalIdentifierStrategy(),
|
|
// Next, attempt to use an absolute import.
|
|
new AbsoluteModuleStrategy(this.inputProgram, checker, this.moduleResolver, reflector),
|
|
// Finally, check if the reference is being written into a file within the project's .ts
|
|
// sources, and use a relative import if so. If this fails, ReferenceEmitter will throw
|
|
// an error.
|
|
localImportStrategy
|
|
]);
|
|
if (this.entryPoint === null && this.options.generateDeepReexports === true) {
|
|
aliasingHost = new PrivateExportAliasingHost(reflector);
|
|
}
|
|
} else {
|
|
refEmitter = new ReferenceEmitter([
|
|
// First, try to use local identifiers if available.
|
|
new LocalIdentifierStrategy(),
|
|
// Then use aliased references (this is a workaround to StrictDeps checks).
|
|
...this.options["_useHostForImportAndAliasGeneration"] ? [new AliasStrategy()] : [],
|
|
// Then use fileNameToModuleName to emit imports.
|
|
new UnifiedModulesStrategy(reflector, this.adapter.unifiedModulesHost)
|
|
]);
|
|
if (this.options["_useHostForImportAndAliasGeneration"]) {
|
|
aliasingHost = new UnifiedModulesAliasingHost(this.adapter.unifiedModulesHost);
|
|
}
|
|
}
|
|
const evaluator = new PartialEvaluator(reflector, checker, this.incrementalCompilation.depGraph);
|
|
const dtsReader = new DtsMetadataReader(checker, reflector);
|
|
const localMetaRegistry = new LocalMetadataRegistry();
|
|
const localMetaReader = localMetaRegistry;
|
|
const depScopeReader = new MetadataDtsModuleScopeResolver(dtsReader, aliasingHost);
|
|
const metaReader = new CompoundMetadataReader([localMetaReader, dtsReader]);
|
|
const ngModuleIndex = new NgModuleIndexImpl(metaReader, localMetaReader);
|
|
const ngModuleScopeRegistry = new LocalModuleScopeRegistry(localMetaReader, metaReader, depScopeReader, refEmitter, aliasingHost);
|
|
const standaloneScopeReader = new StandaloneComponentScopeReader(metaReader, ngModuleScopeRegistry, depScopeReader);
|
|
const selectorlessScopeReader = new SelectorlessComponentScopeReader(metaReader, reflector);
|
|
const scopeReader = new CompoundComponentScopeReader([
|
|
ngModuleScopeRegistry,
|
|
selectorlessScopeReader,
|
|
standaloneScopeReader
|
|
]);
|
|
const semanticDepGraphUpdater = this.incrementalCompilation.semanticDepGraphUpdater;
|
|
const metaRegistry = new CompoundMetadataRegistry([localMetaRegistry, ngModuleScopeRegistry]);
|
|
const injectableRegistry = new InjectableClassRegistry(reflector, isCore);
|
|
const hostDirectivesResolver = new HostDirectivesResolver(metaReader);
|
|
const exportedProviderStatusResolver = new ExportedProviderStatusResolver(metaReader);
|
|
const importTracker = new ImportedSymbolsTracker();
|
|
const typeCheckScopeRegistry = new TypeCheckScopeRegistry(scopeReader, metaReader, hostDirectivesResolver);
|
|
let referencesRegistry;
|
|
let exportReferenceGraph = null;
|
|
if (this.entryPoint !== null) {
|
|
exportReferenceGraph = new ReferenceGraph();
|
|
referencesRegistry = new ReferenceGraphAdapter(exportReferenceGraph);
|
|
} else {
|
|
referencesRegistry = new NoopReferencesRegistry();
|
|
}
|
|
const dtsTransforms = new DtsTransformRegistry();
|
|
const resourceRegistry = new ResourceRegistry();
|
|
const deferredSymbolsTracker = new DeferredSymbolTracker(this.inputProgram.getTypeChecker(), this.options.onlyExplicitDeferDependencyImports ?? false);
|
|
let localCompilationExtraImportsTracker = null;
|
|
if (compilationMode === CompilationMode.LOCAL && this.options.generateExtraImportsInLocalMode) {
|
|
localCompilationExtraImportsTracker = new LocalCompilationExtraImportsTracker(checker);
|
|
}
|
|
const cycleHandlingStrategy = compilationMode === CompilationMode.PARTIAL ? 1 : 0;
|
|
const strictCtorDeps = this.options.strictInjectionParameters || false;
|
|
const supportJitMode = this.options["supportJitMode"] ?? true;
|
|
const supportTestBed = this.options["supportTestBed"] ?? true;
|
|
const externalRuntimeStyles = this.options["externalRuntimeStyles"] ?? false;
|
|
const typeCheckHostBindings = this.options.typeCheckHostBindings ?? false;
|
|
if (supportTestBed === false && compilationMode === CompilationMode.PARTIAL) {
|
|
throw new Error('TestBed support ("supportTestBed" option) cannot be disabled in partial compilation mode.');
|
|
}
|
|
if (supportJitMode === false && compilationMode === CompilationMode.PARTIAL) {
|
|
throw new Error('JIT mode support ("supportJitMode" option) cannot be disabled in partial compilation mode.');
|
|
}
|
|
if (supportJitMode === false && this.options.forbidOrphanComponents) {
|
|
throw new Error('JIT mode support ("supportJitMode" option) cannot be disabled when forbidOrphanComponents is set to true');
|
|
}
|
|
const jitDeclarationRegistry = new JitDeclarationRegistry();
|
|
const handlers = [
|
|
new ComponentDecoratorHandler(reflector, evaluator, metaRegistry, metaReader, scopeReader, this.adapter, ngModuleScopeRegistry, typeCheckScopeRegistry, resourceRegistry, isCore, strictCtorDeps, this.resourceManager, this.adapter.rootDirs, this.options.preserveWhitespaces || false, this.options.i18nUseExternalIds !== false, this.options.enableI18nLegacyMessageIdFormat !== false, this.usePoisonedData, this.options.i18nNormalizeLineEndingsInICUs === true, this.moduleResolver, this.cycleAnalyzer, cycleHandlingStrategy, refEmitter, referencesRegistry, this.incrementalCompilation.depGraph, injectableRegistry, semanticDepGraphUpdater, this.closureCompilerEnabled, this.delegatingPerfRecorder, hostDirectivesResolver, importTracker, supportTestBed, compilationMode, deferredSymbolsTracker, !!this.options.forbidOrphanComponents, this.enableBlockSyntax, this.enableLetSyntax, externalRuntimeStyles, localCompilationExtraImportsTracker, jitDeclarationRegistry, this.options.i18nPreserveWhitespaceForLegacyExtraction ?? true, !!this.options.strictStandalone, this.enableHmr, this.implicitStandaloneValue, typeCheckHostBindings, this.enableSelectorless, this.emitDeclarationOnly),
|
|
// TODO(alxhub): understand why the cast here is necessary (something to do with `null`
|
|
// not being assignable to `unknown` when wrapped in `Readonly`).
|
|
new DirectiveDecoratorHandler(reflector, evaluator, metaRegistry, ngModuleScopeRegistry, metaReader, injectableRegistry, refEmitter, referencesRegistry, isCore, strictCtorDeps, semanticDepGraphUpdater, this.closureCompilerEnabled, this.delegatingPerfRecorder, importTracker, supportTestBed, typeCheckScopeRegistry, compilationMode, jitDeclarationRegistry, resourceRegistry, !!this.options.strictStandalone, this.implicitStandaloneValue, this.usePoisonedData, typeCheckHostBindings, this.emitDeclarationOnly),
|
|
// Pipe handler must be before injectable handler in list so pipe factories are printed
|
|
// before injectable factories (so injectable factories can delegate to them)
|
|
new PipeDecoratorHandler(reflector, evaluator, metaRegistry, ngModuleScopeRegistry, injectableRegistry, isCore, this.delegatingPerfRecorder, supportTestBed, compilationMode, !!this.options.generateExtraImportsInLocalMode, !!this.options.strictStandalone, this.implicitStandaloneValue),
|
|
new InjectableDecoratorHandler(reflector, evaluator, isCore, strictCtorDeps, injectableRegistry, this.delegatingPerfRecorder, supportTestBed, compilationMode),
|
|
new NgModuleDecoratorHandler(reflector, evaluator, metaReader, metaRegistry, ngModuleScopeRegistry, referencesRegistry, exportedProviderStatusResolver, semanticDepGraphUpdater, isCore, refEmitter, this.closureCompilerEnabled, this.options.onlyPublishPublicTypingsForNgModules ?? false, injectableRegistry, this.delegatingPerfRecorder, supportTestBed, supportJitMode, compilationMode, localCompilationExtraImportsTracker, jitDeclarationRegistry, this.emitDeclarationOnly)
|
|
];
|
|
const traitCompiler = new TraitCompiler(handlers, reflector, this.delegatingPerfRecorder, this.incrementalCompilation, this.options.compileNonExportedClasses !== false, compilationMode, dtsTransforms, semanticDepGraphUpdater, this.adapter, this.emitDeclarationOnly);
|
|
const notifyingDriver = new NotifyingProgramDriverWrapper(this.programDriver, (program) => {
|
|
this.incrementalStrategy.setIncrementalState(this.incrementalCompilation.state, program);
|
|
this.currentProgram = program;
|
|
});
|
|
const typeCheckingConfig = this.getTypeCheckingConfig();
|
|
const templateTypeChecker = new TemplateTypeCheckerImpl(this.inputProgram, notifyingDriver, traitCompiler, typeCheckingConfig, refEmitter, reflector, this.adapter, this.incrementalCompilation, metaReader, localMetaReader, ngModuleIndex, scopeReader, typeCheckScopeRegistry, this.delegatingPerfRecorder);
|
|
const extendedTemplateChecker = this.constructionDiagnostics.length === 0 ? new ExtendedTemplateCheckerImpl(templateTypeChecker, checker, ALL_DIAGNOSTIC_FACTORIES, this.options) : null;
|
|
const templateSemanticsChecker = this.constructionDiagnostics.length === 0 ? new TemplateSemanticsCheckerImpl(templateTypeChecker) : null;
|
|
const sourceFileValidator = this.constructionDiagnostics.length === 0 ? new SourceFileValidator(reflector, importTracker, templateTypeChecker, typeCheckingConfig) : null;
|
|
return {
|
|
isCore,
|
|
traitCompiler,
|
|
reflector,
|
|
scopeRegistry: ngModuleScopeRegistry,
|
|
dtsTransforms,
|
|
exportReferenceGraph,
|
|
metaReader,
|
|
typeCheckScopeRegistry,
|
|
aliasingHost,
|
|
refEmitter,
|
|
templateTypeChecker,
|
|
resourceRegistry,
|
|
extendedTemplateChecker,
|
|
localCompilationExtraImportsTracker,
|
|
jitDeclarationRegistry,
|
|
templateSemanticsChecker,
|
|
sourceFileValidator,
|
|
supportJitMode
|
|
};
|
|
}
|
|
};
|
|
function isAngularCorePackage(program) {
|
|
const r3Symbols = getR3SymbolsFile(program);
|
|
if (r3Symbols === null) {
|
|
return false;
|
|
}
|
|
return r3Symbols.statements.some((stmt) => {
|
|
if (!ts28.isVariableStatement(stmt)) {
|
|
return false;
|
|
}
|
|
const modifiers = ts28.getModifiers(stmt);
|
|
if (modifiers === void 0 || !modifiers.some((mod) => mod.kind === ts28.SyntaxKind.ExportKeyword)) {
|
|
return false;
|
|
}
|
|
return stmt.declarationList.declarations.some((decl) => {
|
|
if (!ts28.isIdentifier(decl.name) || decl.name.text !== "ITS_JUST_ANGULAR") {
|
|
return false;
|
|
}
|
|
if (decl.initializer === void 0 || decl.initializer.kind !== ts28.SyntaxKind.TrueKeyword) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
});
|
|
}
|
|
function getR3SymbolsFile(program) {
|
|
return program.getSourceFiles().find((file) => file.fileName.indexOf("r3_symbols.ts") >= 0) || null;
|
|
}
|
|
function* verifyCompatibleTypeCheckOptions(options) {
|
|
if (options.fullTemplateTypeCheck === false && options.strictTemplates === true) {
|
|
yield makeConfigDiagnostic({
|
|
category: ts28.DiagnosticCategory.Error,
|
|
code: ErrorCode.CONFIG_STRICT_TEMPLATES_IMPLIES_FULL_TEMPLATE_TYPECHECK,
|
|
messageText: `
|
|
Angular compiler option "strictTemplates" is enabled, however "fullTemplateTypeCheck" is disabled.
|
|
|
|
Having the "strictTemplates" flag enabled implies that "fullTemplateTypeCheck" is also enabled, so
|
|
the latter can not be explicitly disabled.
|
|
|
|
One of the following actions is required:
|
|
1. Remove the "fullTemplateTypeCheck" option.
|
|
2. Remove "strictTemplates" or set it to 'false'.
|
|
|
|
More information about the template type checking compiler options can be found in the documentation:
|
|
https://angular.dev/tools/cli/template-typecheck
|
|
`.trim()
|
|
});
|
|
}
|
|
if (options.extendedDiagnostics && options.strictTemplates === false) {
|
|
yield makeConfigDiagnostic({
|
|
category: ts28.DiagnosticCategory.Error,
|
|
code: ErrorCode.CONFIG_EXTENDED_DIAGNOSTICS_IMPLIES_STRICT_TEMPLATES,
|
|
messageText: `
|
|
Angular compiler option "extendedDiagnostics" is configured, however "strictTemplates" is disabled.
|
|
|
|
Using "extendedDiagnostics" requires that "strictTemplates" is also enabled.
|
|
|
|
One of the following actions is required:
|
|
1. Remove "strictTemplates: false" to enable it.
|
|
2. Remove "extendedDiagnostics" configuration to disable them.
|
|
`.trim()
|
|
});
|
|
}
|
|
const allowedCategoryLabels = Array.from(Object.values(DiagnosticCategoryLabel));
|
|
const defaultCategory = options.extendedDiagnostics?.defaultCategory;
|
|
if (defaultCategory && !allowedCategoryLabels.includes(defaultCategory)) {
|
|
yield makeConfigDiagnostic({
|
|
category: ts28.DiagnosticCategory.Error,
|
|
code: ErrorCode.CONFIG_EXTENDED_DIAGNOSTICS_UNKNOWN_CATEGORY_LABEL,
|
|
messageText: `
|
|
Angular compiler option "extendedDiagnostics.defaultCategory" has an unknown diagnostic category: "${defaultCategory}".
|
|
|
|
Allowed diagnostic categories are:
|
|
${allowedCategoryLabels.join("\n")}
|
|
`.trim()
|
|
});
|
|
}
|
|
for (const [checkName, category] of Object.entries(options.extendedDiagnostics?.checks ?? {})) {
|
|
if (!SUPPORTED_DIAGNOSTIC_NAMES.has(checkName)) {
|
|
yield makeConfigDiagnostic({
|
|
category: ts28.DiagnosticCategory.Error,
|
|
code: ErrorCode.CONFIG_EXTENDED_DIAGNOSTICS_UNKNOWN_CHECK,
|
|
messageText: `
|
|
Angular compiler option "extendedDiagnostics.checks" has an unknown check: "${checkName}".
|
|
|
|
Allowed check names are:
|
|
${Array.from(SUPPORTED_DIAGNOSTIC_NAMES).join("\n")}
|
|
`.trim()
|
|
});
|
|
}
|
|
if (!allowedCategoryLabels.includes(category)) {
|
|
yield makeConfigDiagnostic({
|
|
category: ts28.DiagnosticCategory.Error,
|
|
code: ErrorCode.CONFIG_EXTENDED_DIAGNOSTICS_UNKNOWN_CATEGORY_LABEL,
|
|
messageText: `
|
|
Angular compiler option "extendedDiagnostics.checks['${checkName}']" has an unknown diagnostic category: "${category}".
|
|
|
|
Allowed diagnostic categories are:
|
|
${allowedCategoryLabels.join("\n")}
|
|
`.trim()
|
|
});
|
|
}
|
|
}
|
|
}
|
|
function makeConfigDiagnostic({ category, code, messageText }) {
|
|
return {
|
|
category,
|
|
code: ngErrorCode(code),
|
|
file: void 0,
|
|
start: void 0,
|
|
length: void 0,
|
|
messageText
|
|
};
|
|
}
|
|
var ReferenceGraphAdapter = class {
|
|
graph;
|
|
constructor(graph) {
|
|
this.graph = graph;
|
|
}
|
|
add(source, ...references) {
|
|
for (const { node } of references) {
|
|
let sourceFile = node.getSourceFile();
|
|
if (sourceFile === void 0) {
|
|
sourceFile = ts28.getOriginalNode(node).getSourceFile();
|
|
}
|
|
if (sourceFile === void 0 || !isDtsPath(sourceFile.fileName)) {
|
|
this.graph.add(source, node);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
var NotifyingProgramDriverWrapper = class {
|
|
delegate;
|
|
notifyNewProgram;
|
|
getSourceFileVersion;
|
|
constructor(delegate, notifyNewProgram) {
|
|
this.delegate = delegate;
|
|
this.notifyNewProgram = notifyNewProgram;
|
|
this.getSourceFileVersion = this.delegate.getSourceFileVersion?.bind(this);
|
|
}
|
|
get supportsInlineOperations() {
|
|
return this.delegate.supportsInlineOperations;
|
|
}
|
|
getProgram() {
|
|
return this.delegate.getProgram();
|
|
}
|
|
updateFiles(contents, updateMode) {
|
|
this.delegate.updateFiles(contents, updateMode);
|
|
this.notifyNewProgram(this.delegate.getProgram());
|
|
}
|
|
};
|
|
function versionMapFromProgram(program, driver) {
|
|
if (driver.getSourceFileVersion === void 0) {
|
|
return null;
|
|
}
|
|
const versions = /* @__PURE__ */ new Map();
|
|
for (const possiblyRedirectedSourceFile of program.getSourceFiles()) {
|
|
const sf = toUnredirectedSourceFile(possiblyRedirectedSourceFile);
|
|
versions.set(absoluteFromSourceFile(sf), driver.getSourceFileVersion(sf));
|
|
}
|
|
return versions;
|
|
}
|
|
|
|
// packages/compiler-cli/src/ngtsc/core/src/host.js
|
|
import ts29 from "typescript";
|
|
var DelegatingCompilerHost = class {
|
|
delegate;
|
|
createHash;
|
|
directoryExists;
|
|
fileNameToModuleName;
|
|
getCancellationToken;
|
|
getCanonicalFileName;
|
|
getCurrentDirectory;
|
|
getDefaultLibFileName;
|
|
getDefaultLibLocation;
|
|
getDirectories;
|
|
getEnvironmentVariable;
|
|
getModifiedResourceFiles;
|
|
getNewLine;
|
|
getParsedCommandLine;
|
|
getSourceFileByPath;
|
|
readDirectory;
|
|
readFile;
|
|
readResource;
|
|
transformResource;
|
|
realpath;
|
|
resolveModuleNames;
|
|
resolveTypeReferenceDirectives;
|
|
resourceNameToFileName;
|
|
trace;
|
|
useCaseSensitiveFileNames;
|
|
writeFile;
|
|
getModuleResolutionCache;
|
|
hasInvalidatedResolutions;
|
|
resolveModuleNameLiterals;
|
|
resolveTypeReferenceDirectiveReferences;
|
|
// jsDocParsingMode is not a method like the other elements above
|
|
// TODO: ignore usage can be dropped once 5.2 support is dropped
|
|
get jsDocParsingMode() {
|
|
return this.delegate.jsDocParsingMode;
|
|
}
|
|
set jsDocParsingMode(mode) {
|
|
this.delegate.jsDocParsingMode = mode;
|
|
}
|
|
constructor(delegate) {
|
|
this.delegate = delegate;
|
|
this.createHash = this.delegateMethod("createHash");
|
|
this.directoryExists = this.delegateMethod("directoryExists");
|
|
this.fileNameToModuleName = this.delegateMethod("fileNameToModuleName");
|
|
this.getCancellationToken = this.delegateMethod("getCancellationToken");
|
|
this.getCanonicalFileName = this.delegateMethod("getCanonicalFileName");
|
|
this.getCurrentDirectory = this.delegateMethod("getCurrentDirectory");
|
|
this.getDefaultLibFileName = this.delegateMethod("getDefaultLibFileName");
|
|
this.getDefaultLibLocation = this.delegateMethod("getDefaultLibLocation");
|
|
this.getDirectories = this.delegateMethod("getDirectories");
|
|
this.getEnvironmentVariable = this.delegateMethod("getEnvironmentVariable");
|
|
this.getModifiedResourceFiles = this.delegateMethod("getModifiedResourceFiles");
|
|
this.getNewLine = this.delegateMethod("getNewLine");
|
|
this.getParsedCommandLine = this.delegateMethod("getParsedCommandLine");
|
|
this.getSourceFileByPath = this.delegateMethod("getSourceFileByPath");
|
|
this.readDirectory = this.delegateMethod("readDirectory");
|
|
this.readFile = this.delegateMethod("readFile");
|
|
this.readResource = this.delegateMethod("readResource");
|
|
this.transformResource = this.delegateMethod("transformResource");
|
|
this.realpath = this.delegateMethod("realpath");
|
|
this.resolveModuleNames = this.delegateMethod("resolveModuleNames");
|
|
this.resolveTypeReferenceDirectives = this.delegateMethod("resolveTypeReferenceDirectives");
|
|
this.resourceNameToFileName = this.delegateMethod("resourceNameToFileName");
|
|
this.trace = this.delegateMethod("trace");
|
|
this.useCaseSensitiveFileNames = this.delegateMethod("useCaseSensitiveFileNames");
|
|
this.writeFile = this.delegateMethod("writeFile");
|
|
this.getModuleResolutionCache = this.delegateMethod("getModuleResolutionCache");
|
|
this.hasInvalidatedResolutions = this.delegateMethod("hasInvalidatedResolutions");
|
|
this.resolveModuleNameLiterals = this.delegateMethod("resolveModuleNameLiterals");
|
|
this.resolveTypeReferenceDirectiveReferences = this.delegateMethod("resolveTypeReferenceDirectiveReferences");
|
|
}
|
|
delegateMethod(name) {
|
|
return this.delegate[name] !== void 0 ? this.delegate[name].bind(this.delegate) : void 0;
|
|
}
|
|
};
|
|
var NgCompilerHost = class _NgCompilerHost extends DelegatingCompilerHost {
|
|
shimAdapter;
|
|
shimTagger;
|
|
entryPoint = null;
|
|
constructionDiagnostics;
|
|
inputFiles;
|
|
rootDirs;
|
|
constructor(delegate, inputFiles, rootDirs, shimAdapter, shimTagger, entryPoint, diagnostics) {
|
|
super(delegate);
|
|
this.shimAdapter = shimAdapter;
|
|
this.shimTagger = shimTagger;
|
|
this.entryPoint = entryPoint;
|
|
this.constructionDiagnostics = diagnostics;
|
|
this.inputFiles = [...inputFiles, ...shimAdapter.extraInputFiles];
|
|
this.rootDirs = rootDirs;
|
|
if (this.resolveModuleNames === void 0) {
|
|
this.resolveModuleNames = this.createCachedResolveModuleNamesFunction();
|
|
}
|
|
}
|
|
/**
|
|
* Retrieves a set of `ts.SourceFile`s which should not be emitted as JS files.
|
|
*
|
|
* Available after this host is used to create a `ts.Program` (which causes all the files in the
|
|
* program to be enumerated).
|
|
*/
|
|
get ignoreForEmit() {
|
|
return this.shimAdapter.ignoreForEmit;
|
|
}
|
|
/**
|
|
* Retrieve the array of shim extension prefixes for which shims were created for each original
|
|
* file.
|
|
*/
|
|
get shimExtensionPrefixes() {
|
|
return this.shimAdapter.extensionPrefixes;
|
|
}
|
|
/**
|
|
* Performs cleanup that needs to happen after a `ts.Program` has been created using this host.
|
|
*/
|
|
postProgramCreationCleanup() {
|
|
this.shimTagger.finalize();
|
|
}
|
|
/**
|
|
* Create an `NgCompilerHost` from a delegate host, an array of input filenames, and the full set
|
|
* of TypeScript and Angular compiler options.
|
|
*/
|
|
static wrap(delegate, inputFiles, options, oldProgram) {
|
|
const topLevelShimGenerators = [];
|
|
const perFileShimGenerators = [];
|
|
const rootDirs = getRootDirs(delegate, options);
|
|
perFileShimGenerators.push(new TypeCheckShimGenerator());
|
|
let diagnostics = [];
|
|
const normalizedTsInputFiles = [];
|
|
for (const inputFile of inputFiles) {
|
|
if (!isNonDeclarationTsPath(inputFile)) {
|
|
continue;
|
|
}
|
|
normalizedTsInputFiles.push(resolve(inputFile));
|
|
}
|
|
let entryPoint = null;
|
|
if (options.flatModuleOutFile != null && options.flatModuleOutFile !== "") {
|
|
entryPoint = findFlatIndexEntryPoint(normalizedTsInputFiles);
|
|
if (entryPoint === null) {
|
|
diagnostics.push({
|
|
category: ts29.DiagnosticCategory.Error,
|
|
code: ngErrorCode(ErrorCode.CONFIG_FLAT_MODULE_NO_INDEX),
|
|
file: void 0,
|
|
start: void 0,
|
|
length: void 0,
|
|
messageText: 'Angular compiler option "flatModuleOutFile" requires one and only one .ts file in the "files" field.'
|
|
});
|
|
} else {
|
|
const flatModuleId = options.flatModuleId || null;
|
|
const flatModuleOutFile = normalizeSeparators(options.flatModuleOutFile);
|
|
const flatIndexGenerator = new FlatIndexGenerator(entryPoint, flatModuleOutFile, flatModuleId);
|
|
topLevelShimGenerators.push(flatIndexGenerator);
|
|
}
|
|
}
|
|
const shimAdapter = new ShimAdapter(delegate, normalizedTsInputFiles, topLevelShimGenerators, perFileShimGenerators, oldProgram);
|
|
const shimTagger = new ShimReferenceTagger(perFileShimGenerators.map((gen) => gen.extensionPrefix));
|
|
return new _NgCompilerHost(delegate, inputFiles, rootDirs, shimAdapter, shimTagger, entryPoint, diagnostics);
|
|
}
|
|
/**
|
|
* Check whether the given `ts.SourceFile` is a shim file.
|
|
*
|
|
* If this returns false, the file is user-provided.
|
|
*/
|
|
isShim(sf) {
|
|
return isShim(sf);
|
|
}
|
|
/**
|
|
* Check whether the given `ts.SourceFile` is a resource file.
|
|
*
|
|
* This simply returns `false` for the compiler-cli since resource files are not added as root
|
|
* files to the project.
|
|
*/
|
|
isResource(sf) {
|
|
return false;
|
|
}
|
|
getSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) {
|
|
const shimSf = this.shimAdapter.maybeGenerate(resolve(fileName));
|
|
if (shimSf !== null) {
|
|
return shimSf;
|
|
}
|
|
const sf = this.delegate.getSourceFile(fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile);
|
|
if (sf === void 0) {
|
|
return void 0;
|
|
}
|
|
this.shimTagger.tag(sf);
|
|
return sf;
|
|
}
|
|
fileExists(fileName) {
|
|
return this.delegate.fileExists(fileName) || this.shimAdapter.maybeGenerate(resolve(fileName)) != null;
|
|
}
|
|
get unifiedModulesHost() {
|
|
return this.fileNameToModuleName !== void 0 ? this : null;
|
|
}
|
|
createCachedResolveModuleNamesFunction() {
|
|
const moduleResolutionCache = ts29.createModuleResolutionCache(this.getCurrentDirectory(), this.getCanonicalFileName.bind(this));
|
|
return (moduleNames, containingFile, reusedNames, redirectedReference, options) => {
|
|
return moduleNames.map((moduleName) => {
|
|
const module = ts29.resolveModuleName(moduleName, containingFile, options, this, moduleResolutionCache, redirectedReference);
|
|
return module.resolvedModule;
|
|
});
|
|
};
|
|
}
|
|
};
|
|
|
|
// packages/compiler-cli/src/ngtsc/program.js
|
|
var NgtscProgram = class {
|
|
options;
|
|
compiler;
|
|
/**
|
|
* The primary TypeScript program, which is used for analysis and emit.
|
|
*/
|
|
tsProgram;
|
|
host;
|
|
incrementalStrategy;
|
|
constructor(rootNames, options, delegateHost, oldProgram) {
|
|
this.options = options;
|
|
const perfRecorder = ActivePerfRecorder.zeroedToNow();
|
|
perfRecorder.phase(PerfPhase.Setup);
|
|
if (!options.disableTypeScriptVersionCheck) {
|
|
verifySupportedTypeScriptVersion();
|
|
}
|
|
if (options.compilationMode === "experimental-local") {
|
|
options.noEmitOnError = false;
|
|
}
|
|
const reuseProgram = oldProgram?.compiler.getCurrentProgram();
|
|
this.host = NgCompilerHost.wrap(delegateHost, rootNames, options, reuseProgram ?? null);
|
|
if (reuseProgram !== void 0) {
|
|
retagAllTsFiles(reuseProgram);
|
|
}
|
|
this.tsProgram = perfRecorder.inPhase(PerfPhase.TypeScriptProgramCreate, () => ts30.createProgram(this.host.inputFiles, options, this.host, reuseProgram));
|
|
perfRecorder.phase(PerfPhase.Unaccounted);
|
|
perfRecorder.memory(PerfCheckpoint.TypeScriptProgramCreate);
|
|
this.host.postProgramCreationCleanup();
|
|
const programDriver = new TsCreateProgramDriver(this.tsProgram, this.host, this.options, this.host.shimExtensionPrefixes);
|
|
this.incrementalStrategy = oldProgram !== void 0 ? oldProgram.incrementalStrategy.toNextBuildStrategy() : new TrackedIncrementalBuildStrategy();
|
|
const modifiedResourceFiles = /* @__PURE__ */ new Set();
|
|
if (this.host.getModifiedResourceFiles !== void 0) {
|
|
const strings = this.host.getModifiedResourceFiles();
|
|
if (strings !== void 0) {
|
|
for (const fileString of strings) {
|
|
modifiedResourceFiles.add(absoluteFrom(fileString));
|
|
}
|
|
}
|
|
}
|
|
let ticket;
|
|
if (oldProgram === void 0) {
|
|
ticket = freshCompilationTicket(
|
|
this.tsProgram,
|
|
options,
|
|
this.incrementalStrategy,
|
|
programDriver,
|
|
perfRecorder,
|
|
/* enableTemplateTypeChecker */
|
|
false,
|
|
/* usePoisonedData */
|
|
false
|
|
);
|
|
} else {
|
|
ticket = incrementalFromCompilerTicket(oldProgram.compiler, this.tsProgram, this.incrementalStrategy, programDriver, modifiedResourceFiles, perfRecorder);
|
|
}
|
|
this.compiler = NgCompiler.fromTicket(ticket, this.host);
|
|
}
|
|
getTsProgram() {
|
|
return this.tsProgram;
|
|
}
|
|
getReuseTsProgram() {
|
|
return this.compiler.getCurrentProgram();
|
|
}
|
|
getTsOptionDiagnostics(cancellationToken) {
|
|
return this.compiler.perfRecorder.inPhase(PerfPhase.TypeScriptDiagnostics, () => this.tsProgram.getOptionsDiagnostics(cancellationToken));
|
|
}
|
|
getTsSyntacticDiagnostics(sourceFile, cancellationToken) {
|
|
return this.compiler.perfRecorder.inPhase(PerfPhase.TypeScriptDiagnostics, () => {
|
|
const ignoredFiles = this.compiler.ignoreForDiagnostics;
|
|
let res;
|
|
if (sourceFile !== void 0) {
|
|
if (ignoredFiles.has(sourceFile)) {
|
|
return [];
|
|
}
|
|
res = this.tsProgram.getSyntacticDiagnostics(sourceFile, cancellationToken);
|
|
} else {
|
|
const diagnostics = [];
|
|
for (const sf of this.tsProgram.getSourceFiles()) {
|
|
if (!ignoredFiles.has(sf)) {
|
|
diagnostics.push(...this.tsProgram.getSyntacticDiagnostics(sf, cancellationToken));
|
|
}
|
|
}
|
|
res = diagnostics;
|
|
}
|
|
return res;
|
|
});
|
|
}
|
|
getTsSemanticDiagnostics(sourceFile, cancellationToken) {
|
|
if (this.options.compilationMode === "experimental-local") {
|
|
return [];
|
|
}
|
|
return this.compiler.perfRecorder.inPhase(PerfPhase.TypeScriptDiagnostics, () => {
|
|
const ignoredFiles = this.compiler.ignoreForDiagnostics;
|
|
let res;
|
|
if (sourceFile !== void 0) {
|
|
if (ignoredFiles.has(sourceFile)) {
|
|
return [];
|
|
}
|
|
res = this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken);
|
|
} else {
|
|
const diagnostics = [];
|
|
for (const sf of this.tsProgram.getSourceFiles()) {
|
|
if (!ignoredFiles.has(sf)) {
|
|
diagnostics.push(...this.tsProgram.getSemanticDiagnostics(sf, cancellationToken));
|
|
}
|
|
}
|
|
res = diagnostics;
|
|
}
|
|
return res;
|
|
});
|
|
}
|
|
getNgOptionDiagnostics(cancellationToken) {
|
|
return this.compiler.getOptionDiagnostics();
|
|
}
|
|
getNgStructuralDiagnostics(cancellationToken) {
|
|
return [];
|
|
}
|
|
getNgSemanticDiagnostics(fileName, cancellationToken) {
|
|
let sf = void 0;
|
|
if (fileName !== void 0) {
|
|
sf = this.tsProgram.getSourceFile(fileName);
|
|
if (sf === void 0) {
|
|
return [];
|
|
}
|
|
}
|
|
if (sf === void 0) {
|
|
return this.compiler.getDiagnostics();
|
|
} else {
|
|
return this.compiler.getDiagnosticsForFile(sf, OptimizeFor.WholeProgram);
|
|
}
|
|
}
|
|
/**
|
|
* Ensure that the `NgCompiler` has properly analyzed the program, and allow for the asynchronous
|
|
* loading of any resources during the process.
|
|
*
|
|
* This is used by the Angular CLI to allow for spawning (async) child compilations for things
|
|
* like SASS files used in `styleUrls`.
|
|
*/
|
|
loadNgStructureAsync() {
|
|
return this.compiler.analyzeAsync();
|
|
}
|
|
listLazyRoutes(entryRoute) {
|
|
return [];
|
|
}
|
|
emitXi18n() {
|
|
const ctx = new MessageBundle(new HtmlParser(), [], {}, this.options.i18nOutLocale ?? null, this.options.i18nPreserveWhitespaceForLegacyExtraction);
|
|
this.compiler.xi18n(ctx);
|
|
i18nExtract(this.options.i18nOutFormat ?? null, this.options.i18nOutFile ?? null, this.host, this.options, ctx, resolve);
|
|
}
|
|
emit(opts) {
|
|
if (opts !== void 0 && opts.emitFlags !== void 0 && opts.emitFlags & EmitFlags.I18nBundle) {
|
|
this.emitXi18n();
|
|
if (!(opts.emitFlags & EmitFlags.JS)) {
|
|
return {
|
|
diagnostics: [],
|
|
emitSkipped: true,
|
|
emittedFiles: []
|
|
};
|
|
}
|
|
}
|
|
const forceEmit = opts?.forceEmit ?? false;
|
|
this.compiler.perfRecorder.memory(PerfCheckpoint.PreEmit);
|
|
const res = this.compiler.perfRecorder.inPhase(PerfPhase.TypeScriptEmit, () => {
|
|
const { transformers } = this.compiler.prepareEmit();
|
|
const ignoreFiles = this.compiler.ignoreForEmit;
|
|
const emitCallback = opts?.emitCallback ?? defaultEmitCallback;
|
|
const writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => {
|
|
if (sourceFiles !== void 0) {
|
|
for (const writtenSf of sourceFiles) {
|
|
if (writtenSf.isDeclarationFile) {
|
|
continue;
|
|
}
|
|
this.compiler.incrementalCompilation.recordSuccessfulEmit(writtenSf);
|
|
}
|
|
}
|
|
this.host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
|
};
|
|
const customTransforms = opts && opts.customTransformers;
|
|
const beforeTransforms = transformers.before || [];
|
|
const afterDeclarationsTransforms = transformers.afterDeclarations;
|
|
if (customTransforms !== void 0 && customTransforms.beforeTs !== void 0) {
|
|
beforeTransforms.push(...customTransforms.beforeTs);
|
|
}
|
|
const emitResults = [];
|
|
for (const targetSourceFile of this.tsProgram.getSourceFiles()) {
|
|
if (targetSourceFile.isDeclarationFile || ignoreFiles.has(targetSourceFile)) {
|
|
continue;
|
|
}
|
|
if (!forceEmit && this.compiler.incrementalCompilation.safeToSkipEmit(targetSourceFile)) {
|
|
this.compiler.perfRecorder.eventCount(PerfEvent.EmitSkipSourceFile);
|
|
continue;
|
|
}
|
|
this.compiler.perfRecorder.eventCount(PerfEvent.EmitSourceFile);
|
|
emitResults.push(emitCallback({
|
|
targetSourceFile,
|
|
program: this.tsProgram,
|
|
host: this.host,
|
|
options: this.options,
|
|
emitOnlyDtsFiles: false,
|
|
writeFile,
|
|
customTransformers: {
|
|
before: beforeTransforms,
|
|
after: customTransforms && customTransforms.afterTs,
|
|
afterDeclarations: afterDeclarationsTransforms
|
|
}
|
|
}));
|
|
}
|
|
this.compiler.perfRecorder.memory(PerfCheckpoint.Emit);
|
|
return (opts && opts.mergeEmitResultsCallback || mergeEmitResults)(emitResults);
|
|
});
|
|
if (this.options.tracePerformance !== void 0) {
|
|
const perf = this.compiler.perfRecorder.finalize();
|
|
getFileSystem().writeFile(getFileSystem().resolve(this.options.tracePerformance), JSON.stringify(perf, null, 2));
|
|
}
|
|
return res;
|
|
}
|
|
getIndexedComponents() {
|
|
return this.compiler.getIndexedComponents();
|
|
}
|
|
/**
|
|
* Gets information for the current program that may be used to generate API
|
|
* reference documentation. This includes Angular-specific information, such
|
|
* as component inputs and outputs.
|
|
*
|
|
* @param entryPoint Path to the entry point for the package for which API
|
|
* docs should be extracted.
|
|
*/
|
|
getApiDocumentation(entryPoint, privateModules) {
|
|
return this.compiler.getApiDocumentation(entryPoint, privateModules);
|
|
}
|
|
getEmittedSourceFiles() {
|
|
throw new Error("Method not implemented.");
|
|
}
|
|
};
|
|
var defaultEmitCallback = ({ program, targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers }) => program.emit(targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
|
|
function mergeEmitResults(emitResults) {
|
|
const diagnostics = [];
|
|
let emitSkipped = false;
|
|
const emittedFiles = [];
|
|
for (const er of emitResults) {
|
|
diagnostics.push(...er.diagnostics);
|
|
emitSkipped = emitSkipped || er.emitSkipped;
|
|
emittedFiles.push(...er.emittedFiles || []);
|
|
}
|
|
return { diagnostics, emitSkipped, emittedFiles };
|
|
}
|
|
|
|
// packages/compiler-cli/src/transformers/program.js
|
|
function createProgram({ rootNames, options, host, oldProgram }) {
|
|
return new NgtscProgram(rootNames, options, host, oldProgram);
|
|
}
|
|
|
|
// packages/compiler-cli/src/perform_compile.js
|
|
import ts32 from "typescript";
|
|
|
|
// packages/compiler-cli/src/transformers/util.js
|
|
import ts31 from "typescript";
|
|
function createMessageDiagnostic(messageText) {
|
|
return {
|
|
file: void 0,
|
|
start: void 0,
|
|
length: void 0,
|
|
category: ts31.DiagnosticCategory.Message,
|
|
messageText,
|
|
code: DEFAULT_ERROR_CODE,
|
|
source: SOURCE
|
|
};
|
|
}
|
|
|
|
// packages/compiler-cli/src/perform_compile.js
|
|
var defaultFormatHost = {
|
|
getCurrentDirectory: () => ts32.sys.getCurrentDirectory(),
|
|
getCanonicalFileName: (fileName) => fileName,
|
|
getNewLine: () => ts32.sys.newLine
|
|
};
|
|
function formatDiagnostics(diags, host = defaultFormatHost) {
|
|
if (diags && diags.length) {
|
|
return diags.map((diagnostic) => replaceTsWithNgInErrors(ts32.formatDiagnosticsWithColorAndContext([diagnostic], host))).join("");
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
function calcProjectFileAndBasePath(project, host = getFileSystem()) {
|
|
const absProject = host.resolve(project);
|
|
const projectIsDir = host.lstat(absProject).isDirectory();
|
|
const projectFile = projectIsDir ? host.join(absProject, "tsconfig.json") : absProject;
|
|
const projectDir = projectIsDir ? absProject : host.dirname(absProject);
|
|
const basePath = host.resolve(projectDir);
|
|
return { projectFile, basePath };
|
|
}
|
|
function readConfiguration(project, existingOptions, host = getFileSystem()) {
|
|
try {
|
|
const fs = getFileSystem();
|
|
const readConfigFile = (configFile) => ts32.readConfigFile(configFile, (file) => host.readFile(host.resolve(file)));
|
|
const readAngularCompilerOptions = (configFile, parentOptions = {}) => {
|
|
const { config: config2, error: error2 } = readConfigFile(configFile);
|
|
if (error2) {
|
|
return parentOptions;
|
|
}
|
|
const angularCompilerOptions = config2.angularCompilerOptions ?? config2.bazelOptions?.angularCompilerOptions;
|
|
let existingNgCompilerOptions = { ...angularCompilerOptions, ...parentOptions };
|
|
if (!config2.extends) {
|
|
return existingNgCompilerOptions;
|
|
}
|
|
const extendsPaths = typeof config2.extends === "string" ? [config2.extends] : config2.extends;
|
|
return [...extendsPaths].reverse().reduce((prevOptions, extendsPath) => {
|
|
const extendedConfigPath = getExtendedConfigPath(configFile, extendsPath, host, fs);
|
|
return extendedConfigPath === null ? prevOptions : readAngularCompilerOptions(extendedConfigPath, prevOptions);
|
|
}, existingNgCompilerOptions);
|
|
};
|
|
const { projectFile, basePath } = calcProjectFileAndBasePath(project, host);
|
|
const configFileName = host.resolve(host.pwd(), projectFile);
|
|
const { config, error } = readConfigFile(projectFile);
|
|
if (error) {
|
|
return {
|
|
project,
|
|
errors: [error],
|
|
rootNames: [],
|
|
options: {},
|
|
emitFlags: EmitFlags.Default
|
|
};
|
|
}
|
|
const existingCompilerOptions = {
|
|
genDir: basePath,
|
|
basePath,
|
|
...readAngularCompilerOptions(configFileName),
|
|
...existingOptions
|
|
};
|
|
const parseConfigHost = createParseConfigHost(host, fs);
|
|
const { options, errors, fileNames: rootNames, projectReferences } = ts32.parseJsonConfigFileContent(config, parseConfigHost, basePath, existingCompilerOptions, configFileName);
|
|
let emitFlags = EmitFlags.Default;
|
|
if (!(options["skipMetadataEmit"] || options["flatModuleOutFile"])) {
|
|
emitFlags |= EmitFlags.Metadata;
|
|
}
|
|
if (options["skipTemplateCodegen"]) {
|
|
emitFlags = emitFlags & ~EmitFlags.Codegen;
|
|
}
|
|
return { project: projectFile, rootNames, projectReferences, options, errors, emitFlags };
|
|
} catch (e) {
|
|
const errors = [
|
|
{
|
|
category: ts32.DiagnosticCategory.Error,
|
|
messageText: e.stack ?? e.message,
|
|
file: void 0,
|
|
start: void 0,
|
|
length: void 0,
|
|
source: "angular",
|
|
code: UNKNOWN_ERROR_CODE
|
|
}
|
|
];
|
|
return { project: "", errors, rootNames: [], options: {}, emitFlags: EmitFlags.Default };
|
|
}
|
|
}
|
|
function createParseConfigHost(host, fs = getFileSystem()) {
|
|
return {
|
|
fileExists: host.exists.bind(host),
|
|
readDirectory: createFileSystemTsReadDirectoryFn(fs),
|
|
readFile: host.readFile.bind(host),
|
|
useCaseSensitiveFileNames: fs.isCaseSensitive()
|
|
};
|
|
}
|
|
function getExtendedConfigPath(configFile, extendsValue, host, fs) {
|
|
const result = getExtendedConfigPathWorker(configFile, extendsValue, host, fs);
|
|
if (result !== null) {
|
|
return result;
|
|
}
|
|
return getExtendedConfigPathWorker(configFile, `${extendsValue}.json`, host, fs);
|
|
}
|
|
function getExtendedConfigPathWorker(configFile, extendsValue, host, fs) {
|
|
if (extendsValue.startsWith(".") || fs.isRooted(extendsValue)) {
|
|
const extendedConfigPath = host.resolve(host.dirname(configFile), extendsValue);
|
|
if (host.exists(extendedConfigPath)) {
|
|
return extendedConfigPath;
|
|
}
|
|
} else {
|
|
const parseConfigHost = createParseConfigHost(host, fs);
|
|
const { resolvedModule } = ts32.nodeModuleNameResolver(extendsValue, configFile, { moduleResolution: ts32.ModuleResolutionKind.Node10, resolveJsonModule: true }, parseConfigHost);
|
|
if (resolvedModule) {
|
|
return absoluteFrom(resolvedModule.resolvedFileName);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
function exitCodeFromResult(diags) {
|
|
if (!diags)
|
|
return 0;
|
|
if (diags.every((diag) => diag.category !== ts32.DiagnosticCategory.Error)) {
|
|
return 0;
|
|
}
|
|
return diags.some((d) => d.source === "angular" && d.code === UNKNOWN_ERROR_CODE) ? 2 : 1;
|
|
}
|
|
function performCompilation({ rootNames, options, host, oldProgram, emitCallback, mergeEmitResultsCallback, gatherDiagnostics = defaultGatherDiagnostics, customTransformers, emitFlags = EmitFlags.Default, forceEmit = false, modifiedResourceFiles = null }) {
|
|
let program;
|
|
let emitResult;
|
|
let allDiagnostics = [];
|
|
try {
|
|
if (!host) {
|
|
host = createCompilerHost({ options });
|
|
}
|
|
if (modifiedResourceFiles) {
|
|
host.getModifiedResourceFiles = () => modifiedResourceFiles;
|
|
}
|
|
program = createProgram({ rootNames, host, options, oldProgram });
|
|
const beforeDiags = Date.now();
|
|
allDiagnostics.push(...gatherDiagnostics(program));
|
|
if (options.diagnostics) {
|
|
const afterDiags = Date.now();
|
|
allDiagnostics.push(createMessageDiagnostic(`Time for diagnostics: ${afterDiags - beforeDiags}ms.`));
|
|
}
|
|
if (!hasErrors(allDiagnostics)) {
|
|
emitResult = program.emit({
|
|
emitCallback,
|
|
mergeEmitResultsCallback,
|
|
customTransformers,
|
|
emitFlags,
|
|
forceEmit
|
|
});
|
|
allDiagnostics.push(...emitResult.diagnostics);
|
|
return { diagnostics: allDiagnostics, program, emitResult };
|
|
}
|
|
return { diagnostics: allDiagnostics, program };
|
|
} catch (e) {
|
|
program = void 0;
|
|
allDiagnostics.push({
|
|
category: ts32.DiagnosticCategory.Error,
|
|
messageText: e.stack ?? e.message,
|
|
code: UNKNOWN_ERROR_CODE,
|
|
file: void 0,
|
|
start: void 0,
|
|
length: void 0
|
|
});
|
|
return { diagnostics: allDiagnostics, program };
|
|
}
|
|
}
|
|
function defaultGatherDiagnostics(program) {
|
|
const allDiagnostics = [];
|
|
function checkDiagnostics(diags) {
|
|
if (diags) {
|
|
allDiagnostics.push(...diags);
|
|
return !hasErrors(diags);
|
|
}
|
|
return true;
|
|
}
|
|
let checkOtherDiagnostics = true;
|
|
checkOtherDiagnostics = checkOtherDiagnostics && checkDiagnostics([...program.getTsOptionDiagnostics(), ...program.getNgOptionDiagnostics()]);
|
|
checkOtherDiagnostics = checkOtherDiagnostics && checkDiagnostics(program.getTsSyntacticDiagnostics());
|
|
checkOtherDiagnostics = checkOtherDiagnostics && checkDiagnostics([
|
|
...program.getTsSemanticDiagnostics(),
|
|
...program.getNgStructuralDiagnostics()
|
|
]);
|
|
checkOtherDiagnostics = checkOtherDiagnostics && checkDiagnostics(program.getNgSemanticDiagnostics());
|
|
return allDiagnostics;
|
|
}
|
|
function hasErrors(diags) {
|
|
return diags.some((d) => d.category === ts32.DiagnosticCategory.Error);
|
|
}
|
|
|
|
export {
|
|
DEFAULT_ERROR_CODE,
|
|
UNKNOWN_ERROR_CODE,
|
|
SOURCE,
|
|
isTsDiagnostic,
|
|
EmitFlags,
|
|
createCompilerHost,
|
|
EntryType,
|
|
MemberType,
|
|
DecoratorType,
|
|
MemberTags,
|
|
isDocEntryWithSourceInfo,
|
|
DocsExtractor,
|
|
PatchedProgramIncrementalBuildStrategy,
|
|
freshCompilationTicket,
|
|
incrementalFromStateTicket,
|
|
NgCompiler,
|
|
NgCompilerHost,
|
|
NgtscProgram,
|
|
createProgram,
|
|
createMessageDiagnostic,
|
|
formatDiagnostics,
|
|
calcProjectFileAndBasePath,
|
|
readConfiguration,
|
|
exitCodeFromResult,
|
|
performCompilation,
|
|
defaultGatherDiagnostics
|
|
};
|
|
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.dev/license
|
|
*/
|
|
/*!
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.dev/license
|
|
*/
|