customRules.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. import { RuleParams, RuleOnError } from "markdownlint"
  2. import * as yaml from "js-yaml"
  3. export const enforceHeaderStructure = {
  4. names: ["enforce-header-structure"],
  5. description: "Proposal header structure should follow template",
  6. tags: ["structure"],
  7. function: function rule(params: RuleParams, onError: RuleOnError) {
  8. const string = params.frontMatterLines
  9. .join("\n")
  10. .trim()
  11. .replace(/^-*$/gm, "")
  12. const frontMatter: any = yaml.load(string)
  13. if (!frontMatter) return
  14. const category: string = frontMatter.category
  15. if (!category) return
  16. if (["Meta"].includes(category)) return
  17. const filtered = params.tokens.filter(function filterToken(token) {
  18. return (
  19. token.type === "heading_open" &&
  20. (token.tag === "h1" || token.tag === "h2")
  21. )
  22. })
  23. let index = 0
  24. let tempHeadings = expectedHeadings;
  25. while (index < filtered.length) {
  26. let token = filtered[index]
  27. tempHeadings = tempHeadings.filter(item => item !== token.line)
  28. if (index >= filtered.length) {
  29. onError({
  30. lineNumber: 1,
  31. detail: `Expected heading \`${tempHeadings[0]}\` and none exists. Please follow the structure outlined in the Proposal Template.`,
  32. })
  33. return
  34. } else {
  35. index++
  36. }
  37. }
  38. tempHeadings.forEach(item => {
  39. onError({
  40. lineNumber: 1,
  41. detail: `Expected heading \`${item}\` and none exists. Please follow the structure outlined in the Proposal Template.`,
  42. })
  43. })
  44. if (tempHeadings.length >= 0) {
  45. return
  46. }
  47. },
  48. }
  49. const expectedHeadings = [
  50. "## Summary",
  51. "## Motivation",
  52. "## Alternatives Considered",
  53. "## New Terminology",
  54. "## Detailed Design",
  55. "## Impact",
  56. "## Security Considerations",
  57. ]
  58. export const enforceMetadataStructure = {
  59. names: ["enforce-front-matter-structure"],
  60. description:
  61. "Proposal front matter should be YAML following template structure",
  62. tags: ["front-matter"],
  63. function: function rule(params: RuleParams, onError: RuleOnError) {
  64. const string = params.frontMatterLines
  65. .join("\n")
  66. .trim()
  67. .replace(/^-*$/gm, "")
  68. const frontMatter: any = yaml.load(string)
  69. if (!frontMatter) {
  70. onError({
  71. lineNumber: 1,
  72. detail: `Missing front matter metadata formatted as YAML`,
  73. })
  74. return
  75. }
  76. Object.keys(requiredMetadata).forEach((meta) => {
  77. if (!frontMatter[meta]) {
  78. onError({
  79. lineNumber: 1,
  80. detail: `Front matter metadata either doesn't contain \`${meta}\` or isn't formatted correctly`,
  81. })
  82. }
  83. })
  84. Object.keys(frontMatter).forEach((key) => {
  85. if (!(requiredMetadata as any)[key] && !optionalMetadata.includes(key)) {
  86. onError({
  87. lineNumber: 1,
  88. detail: `Front matter contains invalid metadata \`${key}\``,
  89. })
  90. }
  91. })
  92. },
  93. }
  94. const requiredMetadata = {
  95. simd: {},
  96. title: {},
  97. authors: {},
  98. category: {},
  99. type: {},
  100. status: {},
  101. created: {},
  102. }
  103. const optionalMetadata = [
  104. "feature",
  105. "supersedes",
  106. "superseded-by",
  107. "extends",
  108. "development",
  109. ]
  110. export const metadataSimdIsValid = {
  111. names: ["front-matter-has-simd"],
  112. description: "Metadata `simd` is a 4 digit numerical string",
  113. tags: ["front-matter"],
  114. function: function rule(params: RuleParams, onError: RuleOnError) {
  115. const string = params.frontMatterLines
  116. .join("\n")
  117. .trim()
  118. .replace(/^-*$/gm, "")
  119. const frontMatter: any = yaml.load(string)
  120. if (!frontMatter) return
  121. const simd: string = frontMatter.simd
  122. if (!simd) return
  123. if (isNaN(Number(simd))) {
  124. onError({
  125. lineNumber: 1,
  126. detail: "Front matter `simd` must be a numerical string",
  127. })
  128. }
  129. if (simd.length !== 4) {
  130. onError({
  131. lineNumber: 1,
  132. detail: "Front matter `simd` must be 4 digits",
  133. })
  134. }
  135. },
  136. }
  137. export const metadataTitleIsValid = {
  138. names: ["front-matter-has-title"],
  139. description:
  140. "Proposal front matter should include a title no longer than 45 characters",
  141. tags: ["front-matter"],
  142. function: function rule(params: RuleParams, onError: RuleOnError) {
  143. const string = params.frontMatterLines
  144. .join("\n")
  145. .trim()
  146. .replace(/^-*$/gm, "")
  147. const frontMatter: any = yaml.load(string)
  148. if (!frontMatter) return
  149. const title: string = frontMatter.title
  150. if (!title) return
  151. if (title.length > 45) {
  152. onError({
  153. lineNumber: 1,
  154. detail: "Metadata `title` should be no longer than 45 characters",
  155. })
  156. }
  157. },
  158. }
  159. export const metadataAuthorsIsValid = {
  160. names: ["front-matter-has-authors"],
  161. description: "Proposal front matter should include authors",
  162. tags: ["front-matter"],
  163. function: function rule(params: RuleParams, onError: RuleOnError) {
  164. const string = params.frontMatterLines
  165. .join("\n")
  166. .trim()
  167. .replace(/^-*$/gm, "")
  168. const frontMatter: any = yaml.load(string)
  169. if (!frontMatter) return
  170. const authors: string = frontMatter.authors
  171. if (!authors) return
  172. if (authors.length == 0) {
  173. onError({
  174. lineNumber: 1,
  175. detail: "Metadata `authors` exists but doesn't include any values",
  176. })
  177. }
  178. },
  179. }
  180. export const metadataCategoryIsValid = {
  181. names: ["front-matter-has-valid-category"],
  182. description: "Proposal front matter should have a valid category",
  183. tags: ["front-matter"],
  184. function: function rule(params: RuleParams, onError: RuleOnError) {
  185. const string = params.frontMatterLines
  186. .join("\n")
  187. .trim()
  188. .replace(/^-*$/gm, "")
  189. const frontMatter: any = yaml.load(string)
  190. if (!frontMatter) return
  191. const category: string = frontMatter.category
  192. if (!category) return
  193. if (!["Meta", "Standard"].includes(category)) {
  194. onError({
  195. lineNumber: 1,
  196. detail: `\`${category}\` is not supported as a value for category`,
  197. })
  198. }
  199. },
  200. }
  201. export const metadataTypeIsValid = {
  202. names: ["front-matter-has-valid-type"],
  203. description: "Proposal front matter should have a valid type",
  204. tags: ["front-matter"],
  205. function: function rule(params: RuleParams, onError: RuleOnError) {
  206. const string = params.frontMatterLines
  207. .join("\n")
  208. .trim()
  209. .replace(/^-*$/gm, "")
  210. const frontMatter: any = yaml.load(string)
  211. if (!frontMatter) return
  212. const type: string = frontMatter.type
  213. if (!type) return
  214. const validTypes = ["Core", "Networking", "Interface", "Meta"]
  215. if (!validTypes.some((validType) => type.includes(validType))) {
  216. onError({
  217. lineNumber: 1,
  218. detail: `\`${type}\` is not supported as a value for type. Valid values for type are: ${validTypes.join(
  219. ", "
  220. )}`,
  221. })
  222. }
  223. },
  224. }
  225. export const metadataStatusIsValid = {
  226. names: ["front-matter-has-valid-status"],
  227. description: "Proposal front matter should have a valid status",
  228. tags: ["front-matter"],
  229. function: function rule(params: RuleParams, onError: RuleOnError) {
  230. const string = params.frontMatterLines
  231. .join("\n")
  232. .trim()
  233. .replace(/^-*$/gm, "")
  234. const frontMatter: any = yaml.load(string)
  235. if (!frontMatter) return
  236. const status: string = frontMatter.status
  237. if (!status) return
  238. const validStatus = [
  239. "Idea",
  240. "Review",
  241. "Accepted",
  242. "Stagnant",
  243. "Withdrawn",
  244. "Implemented",
  245. "Activated",
  246. "Living"
  247. ]
  248. if (!validStatus.includes(status)) {
  249. onError({
  250. lineNumber: 1,
  251. detail: `\`${status}\` is not supported as a value for status. Valid values for status are: ${validStatus.join(
  252. ", "
  253. )}`,
  254. })
  255. }
  256. },
  257. }