lit-analyzer
Version:
CLI that type checks bindings in lit-html templates
231 lines (230 loc) • 10.9 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isFacadeModule = exports.visitIndirectImportsFromSourceFile = void 0;
var tsModule = __importStar(require("typescript"));
/**
* Visits all indirect imports from a source file
* Emits them using "emitIndirectImport" callback
* @param sourceFile
* @param context
*/
function visitIndirectImportsFromSourceFile(sourceFile, context) {
var e_1, _a, e_2, _b;
var _c, _d, _e;
var currentDepth = (_c = context.depth) !== null && _c !== void 0 ? _c : 0;
// Emit a visit. If this file has been seen already, the function will return false, and traversal will stop
if (!context.emitIndirectImport(sourceFile)) {
return;
}
var inExternal = context.program.isSourceFileFromExternalLibrary(sourceFile);
// Check if we have traversed too deep
if (inExternal && currentDepth >= ((_d = context.maxExternalDepth) !== null && _d !== void 0 ? _d : Infinity)) {
return;
}
else if (!inExternal && currentDepth >= ((_e = context.maxInternalDepth) !== null && _e !== void 0 ? _e : Infinity)) {
return;
}
// Get all direct imports from the cache
var directImports = context.directImportCache.get(sourceFile);
if (directImports == null) {
// If the cache didn't have all direct imports, build up using the visitor function
directImports = new Set();
var newContext = __assign(__assign({}, context), { emitDirectImport: function (file) {
directImports.add(file);
} });
// Emit all direct imports
visitDirectImports(sourceFile, newContext);
// Cache the result
context.directImportCache.set(sourceFile, directImports);
}
else {
// Updated references to newest source files
var updatedImports = new Set();
try {
for (var directImports_1 = __values(directImports), directImports_1_1 = directImports_1.next(); !directImports_1_1.done; directImports_1_1 = directImports_1.next()) {
var sf = directImports_1_1.value;
var updatedSf = context.program.getSourceFile(sf.fileName);
if (updatedSf != null) {
updatedImports.add(updatedSf);
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (directImports_1_1 && !directImports_1_1.done && (_a = directImports_1.return)) _a.call(directImports_1);
}
finally { if (e_1) throw e_1.error; }
}
directImports = updatedImports;
}
try {
// Call this function recursively on all direct imports from this source file
for (var directImports_2 = __values(directImports), directImports_2_1 = directImports_2.next(); !directImports_2_1.done; directImports_2_1 = directImports_2.next()) {
var file = directImports_2_1.value;
var toExternal = context.program.isSourceFileFromExternalLibrary(file);
var fromProjectToExternal = !inExternal && toExternal;
// It's possible to only follow external dependencies from the source file of interest (depth 0)
/*if (fromProjectToExternal && currentDepth !== 0) {
continue;
}*/
// Calculate new depth. Reset depth to 1 if we go from a project module to an external module.
// This will make sure that we always go X modules deep into external modules
var newDepth = void 0;
if (fromProjectToExternal) {
newDepth = 1;
}
else {
newDepth = currentDepth + 1;
}
if (isFacadeModule(file, context.ts)) {
// Facade modules are ignored when calculating depth
newDepth--;
}
// Visit direct imported source files recursively
visitIndirectImportsFromSourceFile(file, __assign(__assign({}, context), { depth: newDepth }));
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (directImports_2_1 && !directImports_2_1.done && (_b = directImports_2.return)) _b.call(directImports_2);
}
finally { if (e_2) throw e_2.error; }
}
}
exports.visitIndirectImportsFromSourceFile = visitIndirectImportsFromSourceFile;
/**
* Visits all direct imports in an AST.
* Emits them using "emitDirectImport"
* @param node
* @param context
*/
function visitDirectImports(node, context) {
var _a;
if (node == null)
return;
// Handle top level imports/exports: (import "..."), (import { ... } from "..."), (export * from "...")
if ((context.ts.isImportDeclaration(node) && !((_a = node.importClause) === null || _a === void 0 ? void 0 : _a.isTypeOnly)) || (context.ts.isExportDeclaration(node) && !node.isTypeOnly)) {
if (node.moduleSpecifier != null && context.ts.isStringLiteral(node.moduleSpecifier) && context.ts.isSourceFile(node.parent)) {
// Potentially ignore all imports/exports with named imports/exports because importing an interface would not
// necessarily result in the custom element being defined. An even better solution would be to ignore all
// import declarations with only interface-like/type-alias imports.
/*if (("importClause" in node && node.importClause != null) || ("exportClause" in node && node.exportClause != null)) {
return;
}*/
emitDirectModuleImportWithName(node.moduleSpecifier.text, node, context);
}
}
// Handle async imports (await import(...))
else if (context.ts.isCallExpression(node) && node.expression.kind === context.ts.SyntaxKind.ImportKeyword) {
var moduleSpecifier = node.arguments[0];
if (moduleSpecifier != null && context.ts.isStringLiteralLike(moduleSpecifier)) {
emitDirectModuleImportWithName(moduleSpecifier.text, node, context);
}
}
node.forEachChild(function (child) { return visitDirectImports(child, context); });
}
/**
* Resolves and emits a direct imported module
* @param moduleSpecifier
* @param node
* @param context
*/
function emitDirectModuleImportWithName(moduleSpecifier, node, context) {
var _a, _b, _c, _d;
var fromSourceFile = node.getSourceFile();
// Resolve the imported string
var result;
if (context.project && "getResolvedModuleWithFailedLookupLocationsFromCache" in context.project) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result = context.project.getResolvedModuleWithFailedLookupLocationsFromCache(moduleSpecifier, fromSourceFile.fileName);
}
else if ("getResolvedModuleWithFailedLookupLocationsFromCache" in context.program) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result = context.program["getResolvedModuleWithFailedLookupLocationsFromCache"](moduleSpecifier, fromSourceFile.fileName);
}
else {
var cache = (_b = (_a = context.program).getModuleResolutionCache) === null || _b === void 0 ? void 0 : _b.call(_a);
var mode = undefined;
if (context.ts.isImportDeclaration(node) || context.ts.isExportDeclaration(node)) {
if (node.moduleSpecifier != null && context.ts.isStringLiteral(node.moduleSpecifier) && context.ts.isSourceFile(node.parent)) {
mode = tsModule.getModeForUsageLocation(fromSourceFile, node.moduleSpecifier);
}
}
if (cache != null) {
result = context.ts.resolveModuleNameFromCache(moduleSpecifier, node.getSourceFile().fileName, cache, mode);
}
if (result == null) {
// Result could not be found from the cache, try and resolve module without using the
// cache.
result = context.ts.resolveModuleName(moduleSpecifier, node.getSourceFile().fileName, context.program.getCompilerOptions(), context.ts.createCompilerHost(context.program.getCompilerOptions()));
}
}
if (((_c = result === null || result === void 0 ? void 0 : result.resolvedModule) === null || _c === void 0 ? void 0 : _c.resolvedFileName) != null) {
var resolvedModule = result.resolvedModule;
var sourceFile = context.program.getSourceFile(resolvedModule.resolvedFileName);
if (sourceFile != null) {
(_d = context.emitDirectImport) === null || _d === void 0 ? void 0 : _d.call(context, sourceFile);
}
}
}
/**
* Returns whether a SourceFile is a Facade Module.
* A Facade Module only consists of import and export declarations.
* @param sourceFile
* @param ts
*/
function isFacadeModule(sourceFile, ts) {
var statements = sourceFile.statements;
var isFacade = statements.every(function (statement) {
return ts.isImportDeclaration(statement) || ts.isExportDeclaration(statement);
});
return isFacade;
}
exports.isFacadeModule = isFacadeModule;
;