1+ import fs from 'node:fs/promises' ;
2+ import path from 'node:path' ;
3+ import { globSync } from 'glob' ;
4+ import chalk from 'chalk' ;
5+ import * as prettier from 'prettier' ;
6+ import motokoPlugin from 'prettier-plugin-motoko' ;
7+
8+ import { getRootDir } from '../mops.js' ;
9+ import { absToRel } from './test/utils.js' ;
10+ import { parallel } from '../parallel.js' ;
11+
12+ let ignore = [
13+ '**/node_modules/**' ,
14+ '**/.mops/**' ,
15+ '**/.vessel/**' ,
16+ '**/.git/**' ,
17+ '**/dist/**' ,
18+ ] ;
19+
20+ let globConfig = {
21+ nocase : true ,
22+ ignore : ignore ,
23+ } ;
24+
25+ type FormatOptions = {
26+ check : boolean ,
27+ silent : boolean ,
28+ } ;
29+
30+ export type FormatResult = {
31+ ok : boolean ,
32+ total : number ,
33+ checked : number ,
34+ valid : number ,
35+ invalid : number ,
36+ formatted : number ,
37+ } ;
38+
39+ export async function format ( filter : string , options : Partial < FormatOptions > = { } , signal ?: AbortSignal , onProgress ?: ( result : FormatResult ) => void ) : Promise < FormatResult > {
40+ let startTime = Date . now ( ) ;
41+
42+ let rootDir = getRootDir ( ) ;
43+ let globStr = '**/*.mo' ;
44+ if ( filter ) {
45+ globStr = `**/*${ filter } *.mo` ;
46+ }
47+
48+ let files = globSync ( path . join ( rootDir , globStr ) , {
49+ ...globConfig ,
50+ cwd : rootDir ,
51+ } ) ;
52+ let invalidFiles = 0 ;
53+ let checkedFiles = 0 ;
54+
55+ let getResult = ( ok : boolean ) => {
56+ let result : FormatResult = {
57+ ok,
58+ total : files . length ,
59+ checked : checkedFiles ,
60+ valid : files . length - invalidFiles ,
61+ invalid : invalidFiles ,
62+ formatted : invalidFiles ,
63+ } ;
64+ onProgress ?.( result ) ;
65+ return result ;
66+ } ;
67+
68+ if ( ! files . length ) {
69+ if ( filter ) {
70+ options . silent || console . log ( `No files found for filter '${ filter } '` ) ;
71+ return getResult ( false ) ;
72+ }
73+ if ( ! options . silent ) {
74+ console . log ( 'No *.mo files found' ) ;
75+ }
76+ return getResult ( false ) ;
77+ }
78+
79+ if ( signal ?. aborted ) {
80+ return getResult ( false ) ;
81+ }
82+
83+ // get prettier config from .prettierrc
84+ let prettierConfigFile = await prettier . resolveConfigFile ( ) ;
85+
86+ await parallel ( 4 , files , async ( file ) => {
87+ if ( signal ?. aborted ) {
88+ return ;
89+ }
90+
91+ let conf = await prettier . resolveConfig ( file , { editorconfig : true } ) ;
92+ let prettierConfig : prettier . Options = { } ;
93+ if ( prettierConfigFile ) {
94+ if ( conf ) {
95+ prettierConfig = conf ;
96+ }
97+ }
98+
99+ // merge config from mops.toml [format]
100+ // disabled, because we lose vscode extension support
101+ // if (config.format) {
102+ // Object.assign(prettierConfig, config.format);
103+ // }
104+
105+ // add motoko parser plugin
106+ Object . assign ( prettierConfig , {
107+ parser : 'motoko-tt-parse' ,
108+ plugins : [ motokoPlugin ] ,
109+ filepath : file ,
110+ } ) ;
111+
112+ // check file
113+ let code = await fs . readFile ( file , 'utf8' ) ;
114+ let formatted = await prettier . format ( code , prettierConfig ) ;
115+ let ok = formatted === code ;
116+ invalidFiles += Number ( ! ok ) ;
117+
118+ if ( options . check ) {
119+ if ( ok ) {
120+ options . silent || console . log ( `${ chalk . green ( '✓' ) } ${ absToRel ( file ) } ${ chalk . gray ( 'valid' ) } ` ) ;
121+ }
122+ else {
123+ options . silent || console . log ( `${ chalk . red ( '✖' ) } ${ absToRel ( file ) } ${ chalk . gray ( 'invalid' ) } ` ) ;
124+ }
125+ }
126+ else {
127+ if ( ok ) {
128+ options . silent || console . log ( `${ chalk . green ( '✓' ) } ${ absToRel ( file ) } ${ chalk . gray ( 'valid' ) } ` ) ;
129+ }
130+ else {
131+ await fs . writeFile ( file , formatted ) ;
132+ options . silent || console . log ( `${ chalk . yellow ( '*' ) } ${ absToRel ( file ) } ${ chalk . gray ( 'formatted' ) } ` ) ;
133+ }
134+ }
135+
136+ checkedFiles += 1 ;
137+
138+ // trigger onProgress
139+ getResult ( false ) ;
140+ } ) ;
141+
142+ if ( signal ?. aborted ) {
143+ return getResult ( false ) ;
144+ }
145+
146+ if ( ! options . silent ) {
147+ console . log ( '-' . repeat ( 50 ) ) ;
148+
149+ let plural = ( n : number ) => n === 1 ? '' : 's' ;
150+ let str = `Checked ${ chalk . gray ( files . length ) } file${ plural ( files . length ) } in ${ chalk . gray ( ( ( Date . now ( ) - startTime ) / 1000 ) . toFixed ( 2 ) + 's' ) } ` ;
151+ if ( invalidFiles ) {
152+ str += options . check
153+ ? `, invalid ${ chalk . redBright ( invalidFiles ) } file${ plural ( invalidFiles ) } `
154+ : `, formatted ${ chalk . yellowBright ( invalidFiles ) } file${ plural ( invalidFiles ) } ` ;
155+ }
156+ console . log ( str ) ;
157+
158+ if ( ! invalidFiles ) {
159+ console . log ( chalk . green ( '✓ All files have valid formatting' ) ) ;
160+ }
161+ }
162+
163+ if ( options . check && invalidFiles && ! options . silent ) {
164+ console . log ( `${ ( `Run '${ chalk . yellow ( 'mops format' + ( filter ? ` ${ filter } ` : '' ) ) } ' to format your code` ) } ` ) ;
165+ return getResult ( false ) ;
166+ }
167+
168+ return getResult ( true ) ;
169+ }
0 commit comments