fileoverview author James Henry <https://github.com/JamesHenry>
 *  copyright import ts from 'typescript' ; import unescape from 'lodash.unescape' ; import { ESTreeNodeLoc , ESTreeNode , ESTreeToken } from './temp-types-based-on-js-source' ; import { AST_NODE_TYPES } from './ast-node-types' ; const SyntaxKind = ts . SyntaxKind ; const ASSIGNMENT_OPERATORS = [ SyntaxKind . EqualsToken , SyntaxKind . PlusEqualsToken , SyntaxKind . MinusEqualsToken , SyntaxKind . AsteriskEqualsToken , SyntaxKind . AsteriskAsteriskEqualsToken , SyntaxKind . SlashEqualsToken , SyntaxKind . PercentEqualsToken , SyntaxKind . LessThanLessThanEqualsToken , SyntaxKind . GreaterThanGreaterThanEqualsToken , SyntaxKind . GreaterThanGreaterThanGreaterThanEqualsToken , SyntaxKind . AmpersandEqualsToken , SyntaxKind . BarEqualsToken , SyntaxKind . CaretEqualsToken ] ; const LOGICAL_OPERATORS = [ SyntaxKind . BarBarToken , SyntaxKind . AmpersandAmpersandToken ] ; const TOKEN_TO_TEXT = { [ SyntaxKind . OpenBraceToken ] : '{' , [ SyntaxKind . CloseBraceToken ] : '}' , [ SyntaxKind . OpenParenToken ] : '(' , [ SyntaxKind . CloseParenToken ] : ')' , [ SyntaxKind . OpenBracketToken ] : '[' , [ SyntaxKind . CloseBracketToken ] : ']' , [ SyntaxKind . DotToken ] : '.' , [ SyntaxKind . DotDotDotToken ] : '...' , [ SyntaxKind . SemicolonToken ] : ',' , [ SyntaxKind . CommaToken ] : ',' , [ SyntaxKind . LessThanToken ] : '<' , [ SyntaxKind . GreaterThanToken ] : '>' , [ SyntaxKind . LessThanEqualsToken ] : '<=' , [ SyntaxKind . GreaterThanEqualsToken ] : '>=' , [ SyntaxKind . EqualsEqualsToken ] : '==' , [ SyntaxKind . ExclamationEqualsToken ] : '!=' , [ SyntaxKind . EqualsEqualsEqualsToken ] : '===' , [ SyntaxKind . InstanceOfKeyword ] : 'instanceof' , [ SyntaxKind . ExclamationEqualsEqualsToken ] : '!==' , [ SyntaxKind . EqualsGreaterThanToken ] : '=>' , [ SyntaxKind . PlusToken ] : '+' , [ SyntaxKind . MinusToken ] : '-' , [ SyntaxKind . AsteriskToken ] : '*' , [ SyntaxKind . AsteriskAsteriskToken ] : '**' , [ SyntaxKind . SlashToken ] : '/' , [ SyntaxKind . PercentToken ] : '%' , [ SyntaxKind . PlusPlusToken ] : '++' , [ SyntaxKind . MinusMinusToken ] : '--' , [ SyntaxKind . LessThanLessThanToken ] : '<<' , [ SyntaxKind . LessThanSlashToken ] : '</' , [ SyntaxKind . GreaterThanGreaterThanToken ] : '>>' , [ SyntaxKind . GreaterThanGreaterThanGreaterThanToken ] : '>>>' , [ SyntaxKind . AmpersandToken ] : '&' , [ SyntaxKind . BarToken ] : '|' , [ SyntaxKind . CaretToken ] : '^' , [ SyntaxKind . ExclamationToken ] : '!' , [ SyntaxKind . TildeToken ] : '~' , [ SyntaxKind . AmpersandAmpersandToken ] : '&&' , [ SyntaxKind . BarBarToken ] : '||' , [ SyntaxKind . QuestionToken ] : '?' , [ SyntaxKind . ColonToken ] : ':' , [ SyntaxKind . EqualsToken ] : '=' , [ SyntaxKind . PlusEqualsToken ] : '+=' , [ SyntaxKind . MinusEqualsToken ] : '-=' , [ SyntaxKind . AsteriskEqualsToken ] : '*=' , [ SyntaxKind . AsteriskAsteriskEqualsToken ] : '**=' , [ SyntaxKind . SlashEqualsToken ] : '/=' , [ SyntaxKind . PercentEqualsToken ] : '%=' , [ SyntaxKind . LessThanLessThanEqualsToken ] : '<<=' , [ SyntaxKind . GreaterThanGreaterThanEqualsToken ] : '>>=' , [ SyntaxKind . GreaterThanGreaterThanGreaterThanEqualsToken ] : '>>>=' , [ SyntaxKind . AmpersandEqualsToken ] : '&=' , [ SyntaxKind . BarEqualsToken ] : '|=' , [ SyntaxKind . CaretEqualsToken ] : '^=' , [ SyntaxKind . AtToken ] : '@' , [ SyntaxKind . InKeyword ] : 'in' , [ SyntaxKind . UniqueKeyword ] : 'unique' , [ SyntaxKind . KeyOfKeyword ] : 'keyof' , [ SyntaxKind . NewKeyword ] : 'new' , [ SyntaxKind . ImportKeyword ] : 'import' } ; param { ts . Token } operator returns { boolean } export function isAssignmentOperator ( operator ) { return ASSIGNMENT_OPERATORS . indexOf ( operator . kind ) > - 1 ; } param { ts . Token } operator returns { boolean } export function isLogicalOperator ( operator ) { return LOGICAL_OPERATORS . indexOf ( operator . kind ) > - 1 ; } param { number } kind returns { string } export function getTextForTokenKind ( kind ) { return TOKEN_TO_TEXT [ kind ] ; } param { ts . Node } node returns { boolean } export function isESTreeClassMember ( node ) { return node . kind !== SyntaxKind . SemicolonClassElement ; } param { ts . KeywordSyntaxKind } modifierKind param { ts . Node } node returns { boolean } export function hasModifier ( modifierKind , node ) { return ( ! ! node . modifiers && ! ! node . modifiers . length && node . modifiers . some ( modifier => modifier . kind === modifierKind ) ) ; } param node returns export function getLastModifier ( node ) { return ( ( ! ! node . modifiers && ! ! node . modifiers . length && node . modifiers [ node . modifiers . length - 1 ] ) || null ) ; } param { ts . Node } token returns { boolean } export function isComma ( token ) { return token . kind === SyntaxKind . CommaToken ; } param { ts . Node } node returns { boolean } export function isComment ( node ) { return ( node . kind === SyntaxKind . SingleLineCommentTrivia || node . kind === SyntaxKind . MultiLineCommentTrivia ) ; } param { ts . Node } node returns { boolean } export function isJSDocComment ( node ) { return node . kind === SyntaxKind . JSDocComment ; } param { ts . Token } operator returns { string } export function getBinaryExpressionType ( operator ) { if ( isAssignmentOperator ( operator ) ) { return AST_NODE_TYPES . AssignmentExpression ; } else if ( isLogicalOperator ( operator ) ) { return AST_NODE_TYPES . LogicalExpression ; } return AST_NODE_TYPES . BinaryExpression ; } param { number } start param { number } end param { ts . SourceFile } ast returns { ESTreeNodeLoc } export function getLocFor ( start , end , ast ) { const startLoc = ast . getLineAndCharacterOfPosition ( start ) , endLoc = ast . getLineAndCharacterOfPosition ( end ) ; return { start : { line : startLoc . line + 1 , column : startLoc . character } , end : { line : endLoc . line + 1 , column : endLoc . character } } ; } param { ts . Node } node returns { boolean } export function canContainDirective ( node ) { switch ( node . kind ) { case ts . SyntaxKind . SourceFile : case ts . SyntaxKind . ModuleBlock : return true ; case ts . SyntaxKind . Block : switch ( node . parent . kind ) { case ts . SyntaxKind . Constructor : case ts . SyntaxKind . GetAccessor : case ts . SyntaxKind . SetAccessor : case ts . SyntaxKind . ArrowFunction : case ts . SyntaxKind . FunctionExpression : case ts . SyntaxKind . FunctionDeclaration : case ts . SyntaxKind . MethodDeclaration : return true ; default : return false ; } default : return false ; } } param { ts . Node } nodeOrToken param { ts . SourceFile } ast returns { ESTreeLoc } export function getLoc ( nodeOrToken , ast ) { return getLocFor ( nodeOrToken . getStart ( ast ) , nodeOrToken . end , ast ) ; } param { ts . Node } node returns { boolean } export function isToken ( node ) { return ( node . kind >= SyntaxKind . FirstToken && node . kind <= SyntaxKind . LastToken ) ; } param { ts . Node } node returns { boolean } export function isJSXToken ( node ) { return ( node . kind >= SyntaxKind . JsxElement && node . kind <= SyntaxKind . JsxAttribute ) ; } param { ts . VariableDeclarationList } node returns { string } export function getDeclarationKind ( node ) { if ( node . flags & ts . NodeFlags . Let ) { return 'let' ; } if ( node . flags & ts . NodeFlags . Const ) { return 'const' ; } return 'var' ; } param { ts . Node } node returns { string | null } export function getTSNodeAccessibility ( node ) { const modifiers = node . modifiers ; if ( ! modifiers ) { return null ; } for ( let i = 0 ; i < modifiers . length ; i ++ ) { const modifier = modifiers [ i ] ; switch ( modifier . kind ) { case SyntaxKind . PublicKeyword : return 'public' ; case SyntaxKind . ProtectedKeyword : return 'protected' ; case SyntaxKind . PrivateKeyword : return 'private' ; default : break ; } } return null ; } param { ts . Node } previousToken param { ts . Node } parent param { ts . SourceFile } ast returns { ts . Node | undefined } export function findNextToken ( previousToken , parent , ast ) { return find ( parent ) ; function find ( n ) { if ( ts . isToken ( n ) && n . pos === previousToken . end ) { return n ; } return firstDefined ( n . getChildren ( ast ) , ( child ) => { const shouldDiveInChildNode = ( child . pos <= previousToken . pos && child . end > previousToken . end ) || child . pos === previousToken . end ; return shouldDiveInChildNode && nodeHasTokens ( child , ast ) ? find ( child ) : undefined ; } ) ; } } param { ts . Node } previousToken param { ts . Node } parent param { Function } predicate param { ts . SourceFile } ast returns { ts . Node | undefined } export function findFirstMatchingToken ( previousToken , parent , predicate , ast ) { while ( previousToken ) { if ( predicate ( previousToken ) ) { return previousToken ; } previousToken = findNextToken ( previousToken , parent , ast ) ; } return undefined ; } param { ts . Node } node param { Function } predicate returns { ts . Node | undefined } export function findFirstMatchingAncestor ( node , predicate ) { while ( node ) { if ( predicate ( node ) ) { return node ; } node = node . parent ; } return undefined ; } param { ts . Node } node returns { boolean } export function hasJSXAncestor ( node ) { return ! ! findFirstMatchingAncestor ( node , isJSXToken ) ; } param { string } text returns { string } export function unescapeStringLiteralText ( text ) { return unescape ( text ) ; } param { ts . Node } node returns { boolean } export function isComputedProperty ( node ) { return node . kind === SyntaxKind . ComputedPropertyName ; } param { ts . Node } node returns { boolean } export function isOptional ( node ) { return node . questionToken ? node . questionToken . kind === SyntaxKind . QuestionToken : false ; } param { ts . Node } node param { ESTreeNode } result param { ts . SourceFile } ast returns { ESTreeNode } export function fixExports ( node , result , ast ) { if ( node . modifiers && node . modifiers [ 0 ] . kind === SyntaxKind . ExportKeyword ) { const exportKeyword = node . modifiers [ 0 ] , nextModifier = node . modifiers [ 1 ] , lastModifier = node . modifiers [ node . modifiers . length - 1 ] , declarationIsDefault = nextModifier && nextModifier . kind === SyntaxKind . DefaultKeyword , varToken = findNextToken ( lastModifier , ast , ast ) ; result . range [ 0 ] = varToken ! . getStart ( ast ) ; result . loc = getLocFor ( result . range [ 0 ] , result . range [ 1 ] , ast ) ; const declarationType = declarationIsDefault ? AST_NODE_TYPES . ExportDefaultDeclaration : AST_NODE_TYPES . ExportNamedDeclaration ; const newResult = { type : declarationType , declaration : result , range : [ exportKeyword . getStart ( ast ) , result . range [ 1 ] ] , loc : getLocFor ( exportKeyword . getStart ( ast ) , result . range [ 1 ] , ast ) } ; if ( ! declarationIsDefault ) { newResult . specifiers = [ ] ; newResult . source = null ; } return newResult ; } return result ; } param { ts . Token } token returns { string } export function getTokenType ( token ) { if ( token . originalKeywordKind ) { switch ( token . originalKeywordKind ) { case SyntaxKind . NullKeyword : return 'Null' ; case SyntaxKind . GetKeyword : case SyntaxKind . SetKeyword : case SyntaxKind . TypeKeyword : case SyntaxKind . ModuleKeyword : return 'Identifier' ; default : return 'Keyword' ; } } if ( token . kind >= SyntaxKind . FirstKeyword && token . kind <= SyntaxKind . LastFutureReservedWord ) { if ( token . kind === SyntaxKind . FalseKeyword || token . kind === SyntaxKind . TrueKeyword ) { return 'Boolean' ; } return 'Keyword' ; } if ( token . kind >= SyntaxKind . FirstPunctuation && token . kind <= SyntaxKind . LastBinaryOperator ) { return 'Punctuator' ; } if ( token . kind >= SyntaxKind . NoSubstitutionTemplateLiteral && token . kind <= SyntaxKind . TemplateTail ) { return 'Template' ; } switch ( token . kind ) { case SyntaxKind . NumericLiteral : return 'Numeric' ; case SyntaxKind . JsxText : return 'JSXText' ; case SyntaxKind . StringLiteral : if ( token . parent && ( token . parent . kind === SyntaxKind . JsxAttribute || token . parent . kind === SyntaxKind . JsxElement ) ) { return 'JSXText' ; } return 'String' ; case SyntaxKind . RegularExpressionLiteral : return 'RegularExpression' ; case SyntaxKind . Identifier : case SyntaxKind . ConstructorKeyword : case SyntaxKind . GetKeyword : case SyntaxKind . SetKeyword : default : } if ( token . parent ) { if ( token . kind === SyntaxKind . Identifier && token . parent . kind === SyntaxKind . PropertyAccessExpression && hasJSXAncestor ( token ) ) { return 'JSXIdentifier' ; } if ( isJSXToken ( token . parent ) ) { if ( token . kind === SyntaxKind . PropertyAccessExpression ) { return 'JSXMemberExpression' ; } if ( token . kind === SyntaxKind . Identifier ) { return 'JSXIdentifier' ; } } } return 'Identifier' ; } param { ts . Node } token param { ts . SourceFile } ast returns { ESTreeToken } export function convertToken ( token , ast ) { const start = token . kind === SyntaxKind . JsxText ? token . getFullStart ( ) : token . getStart ( ast ) , end = token . getEnd ( ) , value = ast . text . slice ( start , end ) , newToken = { type : getTokenType ( token ) , value , range : [ start , end ] , loc : getLocFor ( start , end , ast ) } ; if ( newToken . type === 'RegularExpression' ) { newToken . regex = { pattern : value . slice ( 1 , value . lastIndexOf ( '/' ) ) , flags : value . slice ( value . lastIndexOf ( '/' ) + 1 ) } ; } return newToken ; } param { ts . SourceFile } ast returns { ESTreeToken [ ] } export function convertTokens ( ast ) { const result = [ ] ; param { ts . Node } node returns { void } function walk ( node ) { if ( isComment ( node ) || isJSDocComment ( node ) ) { return ; } if ( isToken ( node ) && node . kind !== SyntaxKind . EndOfFileToken ) { const converted = convertToken ( node , ast ) ; if ( converted ) { result . push ( converted ) ; } } else { node . getChildren ( ast ) . forEach ( walk ) ; } } walk ( ast ) ; return result ; } param { ts . SourceFile } ast param { number } start param { number } end returns { ts . Node } private export function getNodeContainer ( ast , start , end ) { let container = null ; param { ts . Node } node returns { void } function walk ( node ) { const nodeStart = node . pos ; const nodeEnd = node . end ; if ( start >= nodeStart && end <= nodeEnd ) { if ( isToken ( node ) ) { container = node ; } else { node . getChildren ( ) . forEach ( walk ) ; } } } walk ( ast ) ; return container ! ; } param { ts . SourceFile } ast param { number } start param { string } message returns { Object } export function createError ( ast , start , message ) { const loc = ast . getLineAndCharacterOfPosition ( start ) ; return { index : start , lineNumber : loc . line + 1 , column : loc . character , message } ; } param { ts . Node } n param { ts . SourceFile } ast export function nodeHasTokens ( n , ast ) { return n . kind === SyntaxKind . EndOfFileToken ? ! ! ( n as any ) . jsDoc : n . getWidth ( ast ) !== 0 ; } template T template U param { ReadonlyArray < T > | undefined } array param { ( element : T , index : number ) => ( U | undefined ) } callback returns { U | undefined } export function firstDefined < T , U > ( array , callback ) { if ( array === undefined ) { return undefined ; } for ( let i = 0 ; i < array . length ; i ++ ) { const result = callback ( array [ i ] , i ) ; if ( result !== undefined ) { return result ; } } return undefined ; }