UNPKG

@bazel/typescript

Version:
103 lines (102 loc) 4.39 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const ts = require("typescript"); const ast_tools_1 = require("./ast_tools"); /** * Determines if the given ts.Node is literal enough for security purposes. This * is true when the value is built from compile-time constants, with a certain * tolerance for indirection in order to make this more user-friendly. * * This considers a few different things. We accept * - What TS deems literal (literal strings, literal numbers, literal booleans, * enum literals), * - Binary operations of two expressions that we accept (including * concatenation), * - Template interpolations of what we accept, * - `x?y:z` constructions, if we accept `y` and `z` * - Variables that are const, and initialized with an expression we accept * * And to prevent bypasses, expressions that include casts are not accepted, and * this checker does not follow imports. */ function isLiteral(typeChecker, node) { if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken) { // Concatenation is fine, if the parts are literals. return (isLiteral(typeChecker, node.left) && isLiteral(typeChecker, node.right)); } else if (ts.isTemplateExpression(node)) { // Same for template expressions. return node.templateSpans.every(span => { return isLiteral(typeChecker, span.expression); }); } else if (ts.isTemplateLiteral(node)) { // and literals (in that order). return true; } else if (ts.isConditionalExpression(node)) { return isLiteral(typeChecker, node.whenTrue) && isLiteral(typeChecker, node.whenFalse); } else if (ts.isIdentifier(node)) { return isUnderlyingValueAStringLiteral(node, typeChecker); } const hasCasts = ast_tools_1.findInChildren(node, ts.isAsExpression); return !hasCasts && isLiteralAccordingToItsType(typeChecker, node); } exports.isLiteral = isLiteral; /** * Given an identifier, this function goes around the AST to determine * whether we should consider it a string literal, on a best-effort basis. It * is an approximation, but should never have false positives. */ function isUnderlyingValueAStringLiteral(identifier, tc) { // The identifier references a value, and we try to follow the trail: if we // find a variable declaration for the identifier, and it was declared as a // const (so we know it wasn't altered along the way), then the value used // in the declaration is the value our identifier references. That means we // should look at the value used in its initialization (by applying the same // rules as before). // Since we're best-effort, if a part of that operation failed due to lack // of support (for instance, the identifier was imported), then we fail // closed and don't consider the value a literal. // TODO(rjamet): This doesn't follow imports, which is a feature that we need // in a fair amount of cases. return getVariableDeclarationsInSameFile(identifier, tc) .filter(isConst) .some(d => d.initializer !== undefined && isLiteral(tc, d.initializer)); } /** * Returns whether this thing is a literal based on TS's understanding. This is * only looking at the local type, so there's no magic in that function. */ function isLiteralAccordingToItsType(typeChecker, node) { const nodeType = typeChecker.getTypeAtLocation(node); return (nodeType.flags & (ts.TypeFlags.StringLiteral | ts.TypeFlags.NumberLiteral | ts.TypeFlags.BooleanLiteral | ts.TypeFlags.EnumLiteral)) !== 0; } /** * Follows the symbol behind the given identifier, assuming it is a variable, * and return all the variable declarations we can find that match it in the * same file. */ function getVariableDeclarationsInSameFile(node, tc) { const symbol = tc.getSymbolAtLocation(node); if (!symbol) { return []; } const decls = symbol.getDeclarations(); if (!decls) { return []; } return decls.filter(ts.isVariableDeclaration); } // Tests whether the given variable declaration is Const. function isConst(varDecl) { return Boolean(varDecl && varDecl.parent && ts.isVariableDeclarationList(varDecl.parent) && varDecl.parent.flags & ts.NodeFlags.Const); }