import * as ts from 'typescript' ; import { Fix , IndividualChange } from '../failure' ; import { debugLog } from './ast_tools' ; /**
 * A Fixer turns Nodes (that are supposed to have been matched before) into a
 * Fix. This is meant to be implemented by Rule implementers (or
 * ban-preset-pattern users). See also `buildReplacementFixer` for a simpler way
 * of implementing a Fixer.
 */ export interface Fixer { getFixForFlaggedNode ( node ) : Fix | undefined ; } /**
 * A simple Fixer builder based on a function that looks at a node, and
 * output either nothing, or a replacement. If this is too limiting, implement
 * Fixer instead.
 */ export function buildReplacementFixer ( potentialReplacementGenerator ) { return { getFixForFlaggedNode : ( n ) : Fix | undefined => { const partialFix = potentialReplacementGenerator ( n ) ; if ( ! partialFix ) { return ; } return { changes : [ { sourceFile : n . getSourceFile ( ) , start : n . getStart ( ) , end : n . getEnd ( ) , replacement : partialFix . replaceWith } ] } ; } } ; } /**
 * Builds an IndividualChange that imports the required symbol from the given
 * file under the given name. This might reimport the same thing twice in some
 * cases, but it will always make it available under the right name (though
 * its name might collide with other imports, as we don't currently check for
 * that).
 */ export function maybeAddNamedImport ( source , importWhat , fromFile , importAs ? , tazeComment ? ) { const importStatements = source . statements . filter ( ts . isImportDeclaration ) ; const importSpecifier = importAs ? `${ importWhat } as ${ importAs }` : importWhat ; for ( const iDecl of importStatements ) { const parsedDecl = maybeParseImportNode ( iDecl ) ; if ( ! parsedDecl || parsedDecl . fromFile !== fromFile ) { continue ; } if ( ts . isNamespaceImport ( parsedDecl . namedBindings ) ) { debugLog ( ( ) => `... but it's a wildcard import` ) ; continue ; } const foundRightImport = parsedDecl . namedBindings . elements . some ( iSpec => iSpec . propertyName ? iSpec . name . getText ( ) === importAs && iSpec . propertyName . getText ( ) === importWhat : iSpec . name . getText ( ) === importWhat ) ; if ( foundRightImport ) { debugLog ( ( ) => `"${ iDecl . getFullText ( ) }" imports ${ importWhat } as we want.` ) ; return ; } debugLog ( ( ) => `No named imports from that file, generating new fix` ) ; return { start : parsedDecl . namedBindings . elements [ 0 ] . getStart ( ) , end : parsedDecl . namedBindings . elements [ 0 ] . getStart ( ) , sourceFile : source , replacement : `${ importSpecifier }, ` } ; } const newImportStatement = `import {${ importSpecifier }} from '${ fromFile }';` + ( tazeComment ? `  ${ tazeComment }\n` : `\n` ) ; const insertionPosition = importStatements . length ? importStatements [ importStatements . length - 1 ] . getEnd ( ) + 1 : 0 ; return { start : insertionPosition , end : insertionPosition , sourceFile : source , replacement : newImportStatement } ; } /**
 * Builds an IndividualChange that imports the required namespace from the given
 * file under the given name. This might reimport the same thing twice in some
 * cases, but it will always make it available under the right name (though
 * its name might collide with other imports, as we don't currently check for
 * that).
 */ export function maybeAddNamespaceImport ( source , fromFile , importAs , tazeComment ? ) { const importStatements = source . statements . filter ( ts . isImportDeclaration ) ; const hasTheRightImport = importStatements . some ( iDecl => { const parsedDecl = maybeParseImportNode ( iDecl ) ; if ( ! parsedDecl || parsedDecl . fromFile !== fromFile ) { return false ; } debugLog ( ( ) => `"${ iDecl . getFullText ( ) }" is an import from the right file` ) ; if ( ts . isNamedImports ( parsedDecl . namedBindings ) ) { debugLog ( ( ) => `... but it's a named import` ) ; return false ; } if ( parsedDecl . namedBindings . name . getText ( ) !== importAs ) { debugLog ( ( ) => `... but not the right name, we need to reimport` ) ; return false ; } debugLog ( ( ) => `... and the right name, no need to reimport` ) ; return true ; } ) ; if ( ! hasTheRightImport ) { const insertionPosition = importStatements . length ? importStatements [ importStatements . length - 1 ] . getEnd ( ) + 1 : 0 ; return { start : insertionPosition , end : insertionPosition , sourceFile : source , replacement : tazeComment ? `import * as ${ importAs } from '${ fromFile }';  ${ tazeComment }\n` : `import * as ${ importAs } from '${ fromFile }';\n` } ; } return ; } /**
 * This tries to make sense of an ImportDeclaration, and returns the interesting
 * parts, undefined if the import declaration is valid but not understandable by
 * the checker.
 */ function maybeParseImportNode ( iDecl ) { if ( ! iDecl . importClause ) { debugLog ( ( ) => `Ignoring import without imported symbol: ${ iDecl . getFullText ( ) }` ) ; return ; } if ( iDecl . importClause . name || ! iDecl . importClause . namedBindings ) { debugLog ( ( ) => `Ignoring import: ${ iDecl . getFullText ( ) }` ) ; return ; } if ( ! ts . isStringLiteral ( iDecl . moduleSpecifier ) ) { debugLog ( ( ) => `Ignoring import whose module specifier is not literal` ) ; return ; } return { namedBindings : iDecl . importClause . namedBindings , fromFile : iDecl . moduleSpecifier . text } ; }