import { parse } from '@babel/babylon' ; import generate from '@babel/generator' ; import traverse , { Node , Visitor } from '@babel/traverse' ; import { File } from '@babel/types' ; import { sync } from 'glob' ; import { dropWhile , pullAt } from 'lodash' ; import { EOL } from 'os' ; import { relative , resolve } from 'path' ; type Warning = [ string , string , number , number ] ; type Rule = ( warnings : Warning [ ] ) => Visitor < Node > ; let rules = new Map < string , Rule > ( ) ; export function addRule ( ruleName , rule ) { if ( rules . has ( ruleName ) ) { throw `A rule with the name "${ ruleName }" is already defined` ; } rules . set ( ruleName , rule ) ; } export async function compile ( code , filename ) { let [ warnings , ast ] = await convert ( parse ( code , { plugins : [ 'classProperties' , 'flow' , 'objectRestSpread' ] , sourceType : 'module' } ) ) ; warnings . forEach ( ( [ message , issueURL , line , column ] ) => { console . log ( `Warning: ${ message } (at ${ relative ( __dirname , filename ) }: line ${ line }, column ${ column }). See ${ issueURL }` ) ; } ) ; return addTrailingSpace ( trimLeadingNewlines ( generate ( stripAtFlowAnnotation ( ast ) ) . code ) ) ; } internal export async function convert < T extends Node > ( ast ) { await Promise . all ( sync ( resolve ( __dirname , './rules/*.js' ) ) . map ( _ => import ( _ ) ) ) ; let warnings = [ ] ; rules . forEach ( visitor => traverse ( ast , visitor ( warnings ) ) ) ; return [ warnings , ast ] ; } function stripAtFlowAnnotation ( ast ) { let { leadingComments } = ast . program . body [ 0 ] ; if ( leadingComments ) { let index = leadingComments . findIndex ( _ => _ . value . trim ( ) === '@flow' ) ; if ( index > - 1 ) { pullAt ( leadingComments , index ) ; } } return ast ; } function addTrailingSpace ( file ) { if ( file . endsWith ( EOL ) ) { return file ; } return file + EOL ; } function trimLeadingNewlines ( file ) { return dropWhile ( file . split ( EOL ) , _ => ! _ ) . join ( EOL ) ; }