| 
									
										
										
										
											2018-07-30 23:07:17 +08:00
										 |  |  | const path = require("path"); | 
					
						
							|  |  |  | const fs = require("fs"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // When --write is set, files will be written in place
 | 
					
						
							|  |  |  | // Elsewise it only prints outdated files
 | 
					
						
							|  |  |  | const doWrite = process.argv.includes("--write"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const allFiles = new Set(); | 
					
						
							|  |  |  | const findFiles = p => { | 
					
						
							|  |  |  | 	const s = fs.statSync(p); | 
					
						
							|  |  |  | 	if (s.isDirectory()) { | 
					
						
							|  |  |  | 		for (const name of fs.readdirSync(p)) { | 
					
						
							|  |  |  | 			if (name.startsWith(".")) continue; | 
					
						
							|  |  |  | 			findFiles(path.join(p, name)); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else if (s.isFile()) { | 
					
						
							|  |  |  | 		allFiles.add(p); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | findFiles(path.resolve(__dirname, "../lib")); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | let canUpdateWithWrite = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const sortImport = (a, b) => { | 
					
						
							|  |  |  | 	if (!a.key.startsWith(".") && b.key.startsWith(".")) return -1; | 
					
						
							|  |  |  | 	if (a.key.startsWith(".") && !b.key.startsWith(".")) return 1; | 
					
						
							|  |  |  | 	if (a.key < b.key) return -1; | 
					
						
							|  |  |  | 	if (a.key > b.key) return 1; | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const execToArray = (content, regexp) => { | 
					
						
							|  |  |  | 	const items = []; | 
					
						
							|  |  |  | 	let match = regexp.exec(content); | 
					
						
							|  |  |  | 	while (match) { | 
					
						
							|  |  |  | 		items.push({ | 
					
						
							|  |  |  | 			content: match[0], | 
					
						
							|  |  |  | 			key: match[1] + match[2] | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 		match = regexp.exec(content); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return items; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const schema = [ | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		title: "license comment", | 
					
						
							|  |  |  | 		regexp: /\/\*\n\s*MIT License http:\/\/www\.opensource\.org\/licenses\/mit-license\.php\n\s*(?:(Authors? .+)\n)?\s*\*\/\n/g, | 
					
						
							|  |  |  | 		updateMessage: "update the license comment", | 
					
						
							|  |  |  | 		update(content, author) { | 
					
						
							|  |  |  | 			return ( | 
					
						
							|  |  |  | 				[ | 
					
						
							|  |  |  | 					"/*", | 
					
						
							|  |  |  | 					"\tMIT License http://www.opensource.org/licenses/mit-license.php", | 
					
						
							|  |  |  | 					author && `\t${author}`, | 
					
						
							|  |  |  | 					"*/" | 
					
						
							|  |  |  | 				] | 
					
						
							|  |  |  | 					.filter(Boolean) | 
					
						
							|  |  |  | 					.join("\n") + "\n" | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		title: "new line after license comment", | 
					
						
							|  |  |  | 		regexp: /\n?/g, | 
					
						
							|  |  |  | 		updateMessage: "insert a new line after the license comment", | 
					
						
							|  |  |  | 		update() { | 
					
						
							|  |  |  | 			return "\n"; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		title: "strict mode", | 
					
						
							|  |  |  | 		regexp: /"use strict";\n/g | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		title: "new line after strict mode", | 
					
						
							|  |  |  | 		regexp: /\n?/g, | 
					
						
							|  |  |  | 		updateMessage: 'insert a new line after "use strict"', | 
					
						
							|  |  |  | 		update() { | 
					
						
							|  |  |  | 			return "\n"; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		title: "imports", | 
					
						
							| 
									
										
										
										
											2018-10-30 05:16:40 +08:00
										 |  |  | 		regexp: /(const (\{\s+\w+(,\s+\w+)*\s+\}|\w+) = (\/\*\* @type \{TODO\} \*\/\s\()?require\("[^"]+"\)\)?(\.\w+)*;\n)+\n/g, | 
					
						
							| 
									
										
										
										
											2018-07-30 23:07:17 +08:00
										 |  |  | 		updateMessage: "sort imports alphabetically", | 
					
						
							|  |  |  | 		update(content) { | 
					
						
							|  |  |  | 			const items = execToArray( | 
					
						
							|  |  |  | 				content, | 
					
						
							| 
									
										
										
										
											2018-10-30 05:16:40 +08:00
										 |  |  | 				/const (?:\{\s+\w+(?:,\s+\w+)*\s+\}|\w+) = (?:\/\*\* @type \{TODO\} \*\/\s\()?require\("([^"]+)"\)\)?((?:\.\w+)*);\n/g | 
					
						
							| 
									
										
										
										
											2018-07-30 23:07:17 +08:00
										 |  |  | 			); | 
					
						
							|  |  |  | 			items.sort(sortImport); | 
					
						
							|  |  |  | 			return items.map(item => item.content).join("") + "\n"; | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		optional: true, | 
					
						
							|  |  |  | 		repeat: true | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		title: "type imports", | 
					
						
							| 
									
										
										
										
											2018-08-14 17:18:06 +08:00
										 |  |  | 		regexp: /(\/\*\* (?:@template \w+ )*@typedef \{import\("[^"]+"\)(\.\w+)*(?:<(?:(?:\w\.)*\w+, )*(?:\w\.)*\w+>)?\} \w+(?:<(?:(?:\w\.)*\w+, )*(?:\w\.)*\w+>)? \*\/\n)+\n/g, | 
					
						
							| 
									
										
										
										
											2018-07-30 23:07:17 +08:00
										 |  |  | 		updateMessage: "sort type imports alphabetically", | 
					
						
							|  |  |  | 		update(content) { | 
					
						
							|  |  |  | 			const items = execToArray( | 
					
						
							|  |  |  | 				content, | 
					
						
							| 
									
										
										
										
											2018-08-14 17:18:06 +08:00
										 |  |  | 				/\/\*\* (?:@template \w+ )*@typedef \{import\("([^"]+)"\)((?:\.\w+)*(?:<(?:(?:\w\.)*\w+, )*(?:\w\.)*\w+>)?)\} \w+(?:<(?:(?:\w\.)*\w+, )*(?:\w\.)*\w+>)? \*\/\n/g | 
					
						
							| 
									
										
										
										
											2018-07-30 23:07:17 +08:00
										 |  |  | 			); | 
					
						
							|  |  |  | 			items.sort(sortImport); | 
					
						
							|  |  |  | 			return items.map(item => item.content).join("") + "\n"; | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		optional: true, | 
					
						
							|  |  |  | 		repeat: true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | for (const filePath of allFiles) { | 
					
						
							|  |  |  | 	let content = fs.readFileSync(filePath, "utf-8"); | 
					
						
							|  |  |  | 	const nl = /(\r?\n)/.exec(content); | 
					
						
							|  |  |  | 	content = content.replace(/\r?\n/g, "\n"); | 
					
						
							|  |  |  | 	let newContent = content; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	let state = 0; | 
					
						
							|  |  |  | 	let pos = 0; | 
					
						
							|  |  |  | 	// eslint-disable-next-line no-constant-condition
 | 
					
						
							|  |  |  | 	while (true) { | 
					
						
							|  |  |  | 		const current = schema[state]; | 
					
						
							|  |  |  | 		if (!current) break; | 
					
						
							|  |  |  | 		current.regexp.lastIndex = pos; | 
					
						
							|  |  |  | 		const match = current.regexp.exec(newContent); | 
					
						
							|  |  |  | 		if (!match) { | 
					
						
							|  |  |  | 			if (current.optional) { | 
					
						
							|  |  |  | 				state++; | 
					
						
							|  |  |  | 				continue; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			console.log(`${filePath}: Missing ${current.title} at ${pos}`); | 
					
						
							|  |  |  | 			process.exitCode = 1; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (match.index !== pos) { | 
					
						
							|  |  |  | 			console.log( | 
					
						
							| 
									
										
										
										
											2019-06-13 16:51:12 +08:00
										 |  |  | 				`${filePath}: Unexpected code at ${pos}-${match.index}, expected ${current.title}` | 
					
						
							| 
									
										
										
										
											2018-07-30 23:07:17 +08:00
										 |  |  | 			); | 
					
						
							|  |  |  | 			process.exitCode = 1; | 
					
						
							|  |  |  | 			pos = match.index; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (!current.repeat) { | 
					
						
							|  |  |  | 			state++; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (current.update) { | 
					
						
							|  |  |  | 			const update = current.update(...match); | 
					
						
							|  |  |  | 			if (update !== match[0]) { | 
					
						
							|  |  |  | 				newContent = | 
					
						
							|  |  |  | 					newContent.substr(0, pos) + | 
					
						
							|  |  |  | 					update + | 
					
						
							|  |  |  | 					newContent.slice(pos + match[0].length); | 
					
						
							|  |  |  | 				pos += update.length; | 
					
						
							|  |  |  | 				if (!doWrite) { | 
					
						
							|  |  |  | 					const updateMessage = | 
					
						
							|  |  |  | 						current.updateMessage || `${current.title} need to be updated`; | 
					
						
							|  |  |  | 					console.log(`${filePath}: ${updateMessage}`); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				continue; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		pos += match[0].length; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (newContent !== content) { | 
					
						
							|  |  |  | 		if (doWrite) { | 
					
						
							|  |  |  | 			if (nl) { | 
					
						
							|  |  |  | 				newContent = newContent.replace(/\n/g, nl[0]); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			fs.writeFileSync(filePath, newContent, "utf-8"); | 
					
						
							|  |  |  | 			console.log(filePath); | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			canUpdateWithWrite = true; | 
					
						
							|  |  |  | 			process.exitCode = 1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if (canUpdateWithWrite) { | 
					
						
							|  |  |  | 	console.log("Run 'yarn fix' to try to fix the problem automatically"); | 
					
						
							|  |  |  | } |