build.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. import * as fs from 'fs';
  2. import * as path from 'path';
  3. import * as prettier from 'prettier';
  4. import * as Prism from 'prismjs';
  5. // tslint:disable-next-line:no-import-side-effect
  6. import 'prismjs/components/prism-typescript';
  7. import { parseDescription } from '../site/utils';
  8. interface RuleJson {
  9. rules: {
  10. [propName: string]: boolean | any[];
  11. };
  12. meta: {
  13. category: keyof typeof Builder.RuleCategoryPriority;
  14. description: string;
  15. reason?: string;
  16. 'ts-only'?: boolean;
  17. 'has-fixer'?: boolean;
  18. 'requires-type-info'?: boolean;
  19. prettier?: boolean;
  20. };
  21. }
  22. class Builder {
  23. public static RuleCategoryPriority = {
  24. 'typescript-specific': 0,
  25. functionality: 1,
  26. maintainability: 2,
  27. style: 3
  28. };
  29. public static RuleCategoryDescription = {
  30. 'typescript-specific': {
  31. title: 'TypeScript 相关',
  32. description: '与 TypeScript 特性相关的规则。'
  33. },
  34. functionality: {
  35. title: '功能性检查',
  36. description: '找出可能的错误,以及可能会产生 bug 的编码习惯。'
  37. },
  38. maintainability: {
  39. title: '可维护性',
  40. description: '增强代码可维护性的规则。'
  41. },
  42. style: {
  43. title: '代码风格',
  44. description: '与代码风格相关的规则。'
  45. }
  46. };
  47. private static CommentMap = {
  48. 'ts-only': '仅支持 ts 文件',
  49. 'has-fixer': '支持自动修复',
  50. 'requires-type-info': '需要提供类型信息',
  51. prettier: '可交由 prettier 控制'
  52. };
  53. private ruleList: RuleJson[] = this.getRuleList();
  54. /**
  55. * 生成 index.js 文件
  56. */
  57. public buildIndex() {
  58. const tslintConfigList = this.ruleList.map((ruleJson) => {
  59. const ruleName = Object.keys(ruleJson.rules)[0];
  60. const comments = Object.keys(ruleJson.meta).map((key) => {
  61. let metaItemValue: string =
  62. Builder.CommentMap[key] || ruleJson.meta[key].toString();
  63. return `@${key} ${metaItemValue
  64. // 去掉 `
  65. .replace(/`/g, '')
  66. // 如果在 meta 种存在手动写的 \n,则会换行并且对其行首
  67. .split('\n')
  68. .map((line, index) => {
  69. if (index === 0) return line;
  70. return ' '.repeat(key.length + 2) + line;
  71. })
  72. .join('\n')}`;
  73. });
  74. if (!ruleJson.meta.category) console.log(ruleJson.rules);
  75. return `/**
  76. ${comments
  77. // 统一添加行首的 *
  78. .join('\n')
  79. .split('\n')
  80. .map((comment) => `* ${comment}`)
  81. .join('\n')}
  82. */
  83. "${ruleName}": ${JSON.stringify(ruleJson.rules[ruleName])}`;
  84. });
  85. const tslintConfig = `
  86. /**
  87. * AlloyTeam TSLint 规则
  88. *
  89. * 作者: xcatliu <xcatliu@gmail.com>
  90. * 仓库: https://github.com/AlloyTeam/tslint-config-alloy
  91. *
  92. * 基于 tslint@5.11.0
  93. * 此文件是由脚本 scripts/build.ts 自动生成
  94. *
  95. * @category 此规则属于哪种分类
  96. * @description 一句话描述此规则
  97. * @reason 为什么要开启(关闭)此规则
  98. * @ts-only 仅支持 ts 文件
  99. * @has-fixer 支持自动修复
  100. * @requires-type-info 需要提供类型信息(需要 --project 参数)
  101. * @prettier 可交由 prettier 控制
  102. */
  103. module.exports = {
  104. "rules": {
  105. ${tslintConfigList.join(',\n')}
  106. }
  107. };
  108. `;
  109. fs.writeFileSync(
  110. path.resolve(__dirname, '../index.js'),
  111. // 使用 prettier 格式化文件内容
  112. prettier.format(tslintConfig, {
  113. ...require('../prettier.config'),
  114. parser: 'babylon'
  115. }),
  116. 'utf-8'
  117. );
  118. }
  119. /**
  120. * 生成 site/tslint.json
  121. */
  122. public buildSiteTSLintRulesJson() {
  123. const tslintJson = {};
  124. this.ruleList.forEach((ruleJson) => {
  125. const ruleName = this.getRuleName(ruleJson);
  126. tslintJson[ruleName] = ruleJson.rules[ruleName];
  127. });
  128. fs.writeFileSync(
  129. path.resolve(__dirname, '../site/tslint-rules.json'),
  130. // 使用 prettier 格式化文件内容
  131. prettier.format(JSON.stringify(tslintJson), {
  132. ...require('../prettier.config'),
  133. parser: 'json'
  134. }),
  135. 'utf-8'
  136. );
  137. }
  138. /**
  139. * 生成 site/tslint-meta.json
  140. */
  141. public buildSiteTSLintMetaJson() {
  142. const tslintMetaJson = {};
  143. this.ruleList.forEach((ruleJson) => {
  144. const ruleName = this.getRuleName(ruleJson);
  145. tslintMetaJson[ruleName] = ruleJson.meta;
  146. });
  147. fs.writeFileSync(
  148. path.resolve(__dirname, '../site/tslint-meta.json'),
  149. // 使用 prettier 格式化文件内容
  150. prettier.format(JSON.stringify(tslintMetaJson), {
  151. ...require('../prettier.config'),
  152. parser: 'json'
  153. }),
  154. 'utf-8'
  155. );
  156. }
  157. /**
  158. * 生成 site/tslint-tests.json
  159. */
  160. public buildSiteTSLintTestsJson() {
  161. const tslintTestsJson = {};
  162. this.ruleList.forEach((ruleJson) => {
  163. const ruleName = this.getRuleName(ruleJson);
  164. const testGoodPath = path.resolve(__dirname, '../test/', ruleName, 'good.ts');
  165. const testBadPath = path.resolve(__dirname, '../test/', ruleName, 'bad.ts');
  166. if (fs.existsSync(testGoodPath)) {
  167. if (!tslintTestsJson[ruleName]) {
  168. tslintTestsJson[ruleName] = {};
  169. }
  170. tslintTestsJson[ruleName].good = Prism.highlight(
  171. fs.readFileSync(testGoodPath, 'utf-8'),
  172. Prism.languages.typescript
  173. );
  174. }
  175. if (fs.existsSync(testBadPath)) {
  176. if (!tslintTestsJson[ruleName]) {
  177. tslintTestsJson[ruleName] = {};
  178. }
  179. tslintTestsJson[ruleName].bad = Prism.highlight(
  180. fs.readFileSync(testBadPath, 'utf-8'),
  181. Prism.languages.typescript
  182. );
  183. }
  184. });
  185. fs.writeFileSync(
  186. path.resolve(__dirname, '../site/tslint-tests.json'),
  187. // 使用 prettier 格式化文件内容
  188. prettier.format(JSON.stringify(tslintTestsJson), {
  189. ...require('../prettier.config'),
  190. parser: 'json'
  191. }),
  192. 'utf-8'
  193. );
  194. }
  195. /**
  196. * 生成 README.md 文件
  197. */
  198. public buildREADME() {
  199. const typescriptSpecificTable = this.renderCategoryTable('typescript-specific');
  200. const functionalityTable = this.renderCategoryTable('functionality');
  201. const maintainabilityTable = this.renderCategoryTable('maintainability');
  202. const styleTable = this.renderCategoryTable('style');
  203. const ruleContent = `
  204. ### ${Builder.RuleCategoryDescription['typescript-specific'].title}
  205. ${Builder.RuleCategoryDescription['typescript-specific'].description}
  206. ${typescriptSpecificTable}
  207. ### ${Builder.RuleCategoryDescription.functionality.title}
  208. ${Builder.RuleCategoryDescription.functionality.description}
  209. ${functionalityTable}
  210. ### ${Builder.RuleCategoryDescription.maintainability.title}
  211. ${Builder.RuleCategoryDescription.maintainability.description}
  212. ${maintainabilityTable}
  213. ### ${Builder.RuleCategoryDescription.style.title}
  214. ${Builder.RuleCategoryDescription.style.description}
  215. ${styleTable}
  216. `;
  217. const READMETemplate = fs.readFileSync(path.resolve(__dirname, '../_README.md'), 'utf-8');
  218. const READMEContent = READMETemplate.replace('RULE_CONTENT', ruleContent);
  219. fs.writeFileSync(
  220. path.resolve(__dirname, '../README.md'),
  221. // 使用 prettier 格式化文件内容
  222. prettier.format(READMEContent, {
  223. ...require('../prettier.config'),
  224. parser: 'markdown'
  225. }),
  226. 'utf-8'
  227. );
  228. }
  229. /**
  230. * 生成某一个分类的列表
  231. */
  232. private renderCategoryTable(category: string) {
  233. const ruleListHTML = this.ruleList
  234. .filter((ruleJson) => ruleJson.meta.category === category)
  235. .map((ruleJson) => {
  236. const ruleName = Object.keys(ruleJson.rules)[0];
  237. return `
  238. <tr>
  239. <td>${this.renderCheckMark(ruleJson.rules[ruleName])}</td>
  240. <td><a href="https://palantir.github.io/tslint/rules/${ruleName}/">${ruleName}</a></td>
  241. <td>${parseDescription(ruleJson.meta.description)}</td>
  242. </tr>`;
  243. })
  244. .join('');
  245. const tableHTML = `
  246. <table>
  247. <thead>
  248. <tr>
  249. <th width="60">开关</th>
  250. <th>名称</th>
  251. <th>描述</th>
  252. </tr>
  253. </thead>
  254. <tbody>${ruleListHTML}
  255. </tbody>
  256. </table>
  257. `;
  258. return tableHTML;
  259. }
  260. /**
  261. * 获取规则列表,根据分类和字母排序
  262. */
  263. private getRuleList() {
  264. const ruleList = fs
  265. .readdirSync(path.resolve(__dirname, '../test'))
  266. .filter((filename) =>
  267. fs.lstatSync(path.resolve(__dirname, '../test', filename)).isDirectory()
  268. )
  269. .map((ruleName) => {
  270. const ruleJson: RuleJson = require(path.resolve(
  271. __dirname,
  272. '../test',
  273. ruleName,
  274. 'tslint.json'
  275. ));
  276. return ruleJson;
  277. })
  278. .sort((aRuleJson, bRuleJson) => {
  279. const aRuleCategory = aRuleJson.meta.category;
  280. const bRuleCategory = bRuleJson.meta.category;
  281. if (
  282. Builder.RuleCategoryPriority[aRuleCategory] >
  283. Builder.RuleCategoryPriority[bRuleCategory]
  284. ) {
  285. return 1;
  286. }
  287. if (
  288. Builder.RuleCategoryPriority[aRuleCategory] <
  289. Builder.RuleCategoryPriority[bRuleCategory]
  290. ) {
  291. return -1;
  292. }
  293. return this.getRuleName(aRuleJson) > this.getRuleName(bRuleJson) ? 1 : -1;
  294. });
  295. return ruleList;
  296. }
  297. /**
  298. * 根据具体的规则对象生成规则的名称
  299. * @param ruleJson 具体的规则对象
  300. */
  301. private getRuleName(ruleJson: RuleJson) {
  302. return Object.keys(ruleJson.rules)[0];
  303. }
  304. private renderCheckMark(isCheck) {
  305. // undefined or null or false
  306. if (!isCheck) {
  307. return '❌';
  308. }
  309. return '✅';
  310. }
  311. }
  312. const builder = new Builder();
  313. builder.buildIndex();
  314. builder.buildREADME();
  315. builder.buildSiteTSLintRulesJson();
  316. builder.buildSiteTSLintMetaJson();
  317. builder.buildSiteTSLintTestsJson();