import * as ts from 'typescript' ; import { debugLog } from './ast_tools' ; import { TrustedTypesConfig } from './trusted_types_configuration' ; /**
 * Returns true if the AST expression is Trusted Types compliant and can be
 * safely used in the sink.
 *
 * This function is only called if the rule is configured to allow specific
 * Trusted Type value in the assignment.
 */ export function isExpressionOfAllowedTrustedType ( tc , expr , allowedType ) { if ( isIntersectionOfTrustedType ( tc , expr , allowedType ) ) return true ; if ( isTrustedTypeCastToUnknownToString ( tc , expr , allowedType ) ) return true ; if ( isTrustedTypeUnionWithStringCastToString ( tc , expr , allowedType ) ) return true ; if ( isTrustedTypeUnwrapperFunction ( tc , expr , allowedType ) ) return true ; return false ; } /**
 * Helper function which checks if given TS Symbol is allowed (matches
 * configured Trusted Type).
 */ function isAllowedSymbol ( tc , symbol , allowedType ) { debugLog ( ( ) => `isAllowedSymbol called with symbol ${ symbol ?. getName ( ) }` ) ; if ( ! symbol ) return false ; const fqn = tc . getFullyQualifiedName ( symbol ) ; debugLog ( ( ) => `fully qualified name is ${ fqn }` ) ; if ( ! fqn . endsWith ( '.' + allowedType . typeName ) ) return false ; const declarations = symbol . getDeclarations ( ) ; if ( ! declarations ) return false ; const declarationFileNames = declarations . map ( d => d . getSourceFile ( ) ?. fileName ) ; debugLog ( ( ) => `got declaration filenames ${ declarationFileNames }` ) ; return declarationFileNames . some ( fileName => fileName . includes ( allowedType . modulePathMatcher ) ) ; } /**
 * Returns true if the expression matches the following format:
 * "AllowedTrustedType as unknown as string"
 */ function isTrustedTypeCastToUnknownToString ( tc , expr , allowedType ) { if ( ! ts . isAsExpression ( expr ) || expr . type . kind !== ts . SyntaxKind . StringKeyword ) { return false ; } const innerExpr = expr . expression ; if ( ! ts . isAsExpression ( innerExpr ) || innerExpr . type . kind !== ts . SyntaxKind . UnknownKeyword ) { return false ; } const castSource = innerExpr . expression ; debugLog ( ( ) => `looking at cast source ${ castSource . getText ( ) }` ) ; return isAllowedSymbol ( tc , tc . getTypeAtLocation ( castSource ) . getSymbol ( ) , allowedType ) ; } /**
 * Returns true if the expression matches the following format:
 * "(AllowedTrustedType | string) as string"
 */ function isTrustedTypeUnionWithStringCastToString ( tc , expr , allowedType ) { if ( ! ts . isAsExpression ( expr ) || expr . type . kind !== ts . SyntaxKind . StringKeyword ) { return false ; } const innerExprType = tc . getTypeAtLocation ( expr . expression ) ; return innerExprType . isUnion ( ) && innerExprType . types . some ( type => isAllowedSymbol ( tc , type . getSymbol ( ) , allowedType ) ) ; } /**
 * Returns true if the expression is a function call of the following signature:
 * "(TypeCompatibleWithTrustedType): string"
 *
 * where `TypeCompatibleWithTrustedType` can be either the Trusted Type itself
 * or a TS union. We only require the first argument of the call site to be
 * exact Trusted Type. Pattern like `unwrap('err msg', TT)` will not work.
 * We intentionally want make the unwrapper pattern more apparent by forcing the
 * TT value in the first argument.
 */ function isTrustedTypeUnwrapperFunction ( tc , expr , allowedType ) { if ( ! ts . isCallExpression ( expr ) ) return false ; return expr . arguments . length > 0 && isAllowedSymbol ( tc , tc . getTypeAtLocation ( expr . arguments [ 0 ] ) . getSymbol ( ) , allowedType ) ; } /**
 * Returns true if the expression is a value of a type that is the
 * intersection of Trusted Types and other types.
 */ function isIntersectionOfTrustedType ( tc , expr , allowedType ) { const type = tc . getTypeAtLocation ( expr ) ; if ( ! type . isIntersection ( ) ) return false ; return type . types . some ( t => isAllowedSymbol ( tc , t . getSymbol ( ) , allowedType ) ) ; }