@plugjs/plug 0.0.27 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/dist/{failure.cjs → asserts.cjs} +29 -9
  2. package/dist/asserts.cjs.map +6 -0
  3. package/dist/{failure.d.ts → asserts.d.ts} +4 -1
  4. package/dist/asserts.mjs +51 -0
  5. package/dist/asserts.mjs.map +6 -0
  6. package/dist/async.cjs +2 -2
  7. package/dist/async.cjs.map +1 -1
  8. package/dist/async.mjs +2 -2
  9. package/dist/async.mjs.map +1 -1
  10. package/dist/build.cjs +21 -30
  11. package/dist/build.cjs.map +1 -1
  12. package/dist/build.d.ts +2 -13
  13. package/dist/build.mjs +16 -24
  14. package/dist/build.mjs.map +1 -1
  15. package/dist/files.cjs +3 -3
  16. package/dist/files.cjs.map +1 -1
  17. package/dist/files.mjs +1 -1
  18. package/dist/files.mjs.map +1 -1
  19. package/dist/fork.cjs +35 -30
  20. package/dist/fork.cjs.map +1 -1
  21. package/dist/fork.d.ts +6 -3
  22. package/dist/fork.mjs +18 -13
  23. package/dist/fork.mjs.map +1 -1
  24. package/dist/{utils/asyncfs.cjs → fs.cjs} +6 -6
  25. package/dist/{utils/asyncfs.cjs.map → fs.cjs.map} +2 -2
  26. package/dist/{utils/asyncfs.d.ts → fs.d.ts} +1 -1
  27. package/dist/{utils/asyncfs.mjs → fs.mjs} +3 -3
  28. package/dist/{utils/asyncfs.mjs.map → fs.mjs.map} +2 -2
  29. package/dist/helpers.cjs +21 -14
  30. package/dist/helpers.cjs.map +1 -1
  31. package/dist/helpers.d.ts +24 -1
  32. package/dist/helpers.mjs +13 -7
  33. package/dist/helpers.mjs.map +1 -1
  34. package/dist/index.cjs +36 -6
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.ts +25 -8
  37. package/dist/index.mjs +20 -5
  38. package/dist/index.mjs.map +1 -1
  39. package/dist/{log → logging}/colors.cjs +1 -1
  40. package/dist/{log → logging}/colors.cjs.map +1 -1
  41. package/dist/{log → logging}/colors.d.ts +0 -0
  42. package/dist/{log → logging}/colors.mjs +1 -1
  43. package/dist/{log → logging}/colors.mjs.map +1 -1
  44. package/dist/{log → logging}/emit.cjs +1 -2
  45. package/dist/{log → logging}/emit.cjs.map +2 -2
  46. package/dist/{log → logging}/emit.d.ts +0 -0
  47. package/dist/{log → logging}/emit.mjs +1 -2
  48. package/dist/{log → logging}/emit.mjs.map +2 -2
  49. package/dist/{log → logging}/levels.cjs +1 -1
  50. package/dist/{log → logging}/levels.cjs.map +1 -1
  51. package/dist/{log → logging}/levels.d.ts +0 -0
  52. package/dist/{log → logging}/levels.mjs +1 -1
  53. package/dist/{log → logging}/levels.mjs.map +1 -1
  54. package/dist/{log → logging}/logger.cjs +13 -7
  55. package/dist/logging/logger.cjs.map +6 -0
  56. package/dist/{log → logging}/logger.d.ts +0 -0
  57. package/dist/{log → logging}/logger.mjs +11 -5
  58. package/dist/logging/logger.mjs.map +6 -0
  59. package/dist/{log → logging}/options.cjs +12 -6
  60. package/dist/logging/options.cjs.map +6 -0
  61. package/dist/{log → logging}/options.d.ts +1 -1
  62. package/dist/{log → logging}/options.mjs +12 -6
  63. package/dist/logging/options.mjs.map +6 -0
  64. package/dist/{log → logging}/report.cjs +10 -9
  65. package/dist/{log → logging}/report.cjs.map +2 -2
  66. package/dist/{log → logging}/report.d.ts +0 -0
  67. package/dist/{log → logging}/report.mjs +7 -6
  68. package/dist/{log → logging}/report.mjs.map +2 -2
  69. package/dist/{log → logging}/spinner.cjs +1 -1
  70. package/dist/{log → logging}/spinner.cjs.map +1 -1
  71. package/dist/{log → logging}/spinner.d.ts +0 -0
  72. package/dist/{log → logging}/spinner.mjs +1 -1
  73. package/dist/{log → logging}/spinner.mjs.map +1 -1
  74. package/dist/{log.cjs → logging.cjs} +12 -12
  75. package/dist/{log.cjs.map → logging.cjs.map} +2 -2
  76. package/dist/{log.d.ts → logging.d.ts} +6 -6
  77. package/dist/{log.mjs → logging.mjs} +9 -9
  78. package/dist/{log.mjs.map → logging.mjs.map} +1 -1
  79. package/dist/paths.cjs +5 -5
  80. package/dist/paths.cjs.map +1 -1
  81. package/dist/paths.mjs +1 -1
  82. package/dist/pipe.cjs +10 -13
  83. package/dist/pipe.cjs.map +1 -1
  84. package/dist/pipe.d.ts +6 -12
  85. package/dist/pipe.mjs +6 -9
  86. package/dist/pipe.mjs.map +1 -1
  87. package/dist/plugs/copy.cjs +14 -14
  88. package/dist/plugs/copy.cjs.map +1 -1
  89. package/dist/plugs/copy.d.ts +1 -1
  90. package/dist/plugs/copy.mjs +3 -3
  91. package/dist/plugs/copy.mjs.map +1 -1
  92. package/dist/plugs/debug.cjs +7 -7
  93. package/dist/plugs/debug.cjs.map +1 -1
  94. package/dist/plugs/debug.d.ts +1 -1
  95. package/dist/plugs/debug.mjs +1 -1
  96. package/dist/plugs/edit.cjs +21 -0
  97. package/dist/plugs/edit.cjs.map +6 -0
  98. package/dist/plugs/edit.d.ts +7 -0
  99. package/dist/plugs/edit.mjs +29 -0
  100. package/dist/plugs/edit.mjs.map +6 -0
  101. package/dist/plugs/esbuild/fix-extensions.cjs +4 -4
  102. package/dist/plugs/esbuild/fix-extensions.cjs.map +1 -1
  103. package/dist/plugs/esbuild/fix-extensions.mjs +1 -1
  104. package/dist/plugs/esbuild/fix-extensions.mjs.map +1 -1
  105. package/dist/plugs/esbuild.cjs +19 -14
  106. package/dist/plugs/esbuild.cjs.map +1 -1
  107. package/dist/plugs/esbuild.d.ts +1 -1
  108. package/dist/plugs/esbuild.mjs +8 -3
  109. package/dist/plugs/esbuild.mjs.map +1 -1
  110. package/dist/plugs/exec.cjs +2 -82
  111. package/dist/plugs/exec.cjs.map +2 -2
  112. package/dist/plugs/exec.d.ts +6 -32
  113. package/dist/plugs/exec.mjs +2 -68
  114. package/dist/plugs/exec.mjs.map +1 -1
  115. package/dist/plugs/filter.d.ts +1 -1
  116. package/dist/plugs/rmf.cjs +5 -5
  117. package/dist/plugs/rmf.cjs.map +1 -1
  118. package/dist/plugs/rmf.d.ts +1 -1
  119. package/dist/plugs/rmf.mjs +2 -2
  120. package/dist/plugs/rmf.mjs.map +1 -1
  121. package/dist/plugs.cjs +1 -4
  122. package/dist/plugs.cjs.map +1 -1
  123. package/dist/plugs.d.ts +1 -4
  124. package/dist/plugs.mjs +1 -4
  125. package/dist/plugs.mjs.map +1 -1
  126. package/dist/types.d.ts +1 -1
  127. package/dist/utils/caller.cjs +8 -5
  128. package/dist/utils/caller.cjs.map +1 -1
  129. package/dist/utils/caller.mjs +6 -3
  130. package/dist/utils/caller.mjs.map +1 -1
  131. package/dist/utils/exec.cjs +102 -0
  132. package/dist/utils/exec.cjs.map +6 -0
  133. package/dist/utils/exec.d.ts +15 -0
  134. package/dist/utils/exec.mjs +71 -0
  135. package/dist/utils/exec.mjs.map +6 -0
  136. package/dist/utils/walk.cjs +7 -7
  137. package/dist/utils/walk.cjs.map +1 -1
  138. package/dist/utils/walk.mjs +2 -2
  139. package/dist/utils/walk.mjs.map +1 -1
  140. package/dist/{plugs/tsc.cjs → utils.cjs} +9 -7
  141. package/dist/utils.cjs.map +6 -0
  142. package/dist/utils.d.ts +4 -0
  143. package/dist/utils.mjs +6 -0
  144. package/dist/utils.mjs.map +6 -0
  145. package/extra/cli.mjs +26 -5
  146. package/extra/ts-loader.mjs +2 -2
  147. package/package.json +90 -15
  148. package/src/{assert.ts → asserts.ts} +36 -1
  149. package/src/async.ts +3 -1
  150. package/src/files.ts +1 -1
  151. package/src/fork.ts +19 -11
  152. package/src/{utils/asyncfs.ts → fs.ts} +1 -1
  153. package/src/helpers.ts +40 -8
  154. package/src/index.ts +30 -8
  155. package/src/{log → logging}/colors.ts +0 -0
  156. package/src/{log → logging}/emit.ts +0 -1
  157. package/src/{log → logging}/levels.ts +0 -0
  158. package/src/{log → logging}/logger.ts +13 -4
  159. package/src/{log → logging}/options.ts +11 -7
  160. package/src/{log → logging}/report.ts +10 -9
  161. package/src/{log → logging}/spinner.ts +0 -0
  162. package/src/{log.ts → logging.ts} +7 -7
  163. package/src/paths.ts +1 -1
  164. package/src/pipe.ts +13 -20
  165. package/src/plugs/copy.ts +4 -4
  166. package/src/plugs/debug.ts +2 -2
  167. package/src/plugs/edit.ts +34 -0
  168. package/src/plugs/esbuild/fix-extensions.ts +2 -2
  169. package/src/plugs/esbuild.ts +13 -7
  170. package/src/plugs/exec.ts +8 -129
  171. package/src/plugs/filter.ts +1 -1
  172. package/src/plugs/rmf.ts +3 -3
  173. package/src/plugs.ts +1 -13
  174. package/src/types.ts +1 -1
  175. package/src/utils/caller.ts +6 -3
  176. package/src/utils/exec.ts +112 -0
  177. package/src/utils/walk.ts +3 -3
  178. package/src/utils.ts +4 -0
  179. package/types/{webassembly.d.ts → plugjs.d.ts} +7 -1
  180. package/dist/assert.cjs +0 -52
  181. package/dist/assert.cjs.map +0 -6
  182. package/dist/assert.d.ts +0 -4
  183. package/dist/assert.mjs +0 -26
  184. package/dist/assert.mjs.map +0 -6
  185. package/dist/failure.cjs.map +0 -6
  186. package/dist/failure.mjs +0 -33
  187. package/dist/failure.mjs.map +0 -6
  188. package/dist/log/logger.cjs.map +0 -6
  189. package/dist/log/logger.mjs.map +0 -6
  190. package/dist/log/options.cjs.map +0 -6
  191. package/dist/log/options.mjs.map +0 -6
  192. package/dist/plugs/coverage/analysis.cjs +0 -234
  193. package/dist/plugs/coverage/analysis.cjs.map +0 -6
  194. package/dist/plugs/coverage/analysis.d.ts +0 -104
  195. package/dist/plugs/coverage/analysis.mjs +0 -207
  196. package/dist/plugs/coverage/analysis.mjs.map +0 -6
  197. package/dist/plugs/coverage/report.cjs +0 -235
  198. package/dist/plugs/coverage/report.cjs.map +0 -6
  199. package/dist/plugs/coverage/report.d.ts +0 -59
  200. package/dist/plugs/coverage/report.mjs +0 -220
  201. package/dist/plugs/coverage/report.mjs.map +0 -6
  202. package/dist/plugs/coverage.cjs +0 -140
  203. package/dist/plugs/coverage.cjs.map +0 -6
  204. package/dist/plugs/coverage.d.ts +0 -49
  205. package/dist/plugs/coverage.mjs +0 -123
  206. package/dist/plugs/coverage.mjs.map +0 -6
  207. package/dist/plugs/eslint/runner.cjs +0 -91
  208. package/dist/plugs/eslint/runner.cjs.map +0 -6
  209. package/dist/plugs/eslint/runner.d.ts +0 -8
  210. package/dist/plugs/eslint/runner.mjs +0 -68
  211. package/dist/plugs/eslint/runner.mjs.map +0 -6
  212. package/dist/plugs/eslint.cjs +0 -22
  213. package/dist/plugs/eslint.cjs.map +0 -6
  214. package/dist/plugs/eslint.d.ts +0 -34
  215. package/dist/plugs/eslint.mjs +0 -5
  216. package/dist/plugs/eslint.mjs.map +0 -6
  217. package/dist/plugs/mocha/reporter.cjs +0 -159
  218. package/dist/plugs/mocha/reporter.cjs.map +0 -6
  219. package/dist/plugs/mocha/reporter.d.ts +0 -6
  220. package/dist/plugs/mocha/reporter.mjs +0 -127
  221. package/dist/plugs/mocha/reporter.mjs.map +0 -6
  222. package/dist/plugs/mocha/runner.cjs +0 -82
  223. package/dist/plugs/mocha/runner.cjs.map +0 -6
  224. package/dist/plugs/mocha/runner.d.ts +0 -8
  225. package/dist/plugs/mocha/runner.mjs +0 -53
  226. package/dist/plugs/mocha/runner.mjs.map +0 -6
  227. package/dist/plugs/mocha.cjs +0 -22
  228. package/dist/plugs/mocha.cjs.map +0 -6
  229. package/dist/plugs/mocha.d.ts +0 -37
  230. package/dist/plugs/mocha.mjs +0 -5
  231. package/dist/plugs/mocha.mjs.map +0 -6
  232. package/dist/plugs/tsc/compiler.cjs +0 -74
  233. package/dist/plugs/tsc/compiler.cjs.map +0 -6
  234. package/dist/plugs/tsc/compiler.d.ts +0 -24
  235. package/dist/plugs/tsc/compiler.mjs +0 -43
  236. package/dist/plugs/tsc/compiler.mjs.map +0 -6
  237. package/dist/plugs/tsc/options.cjs +0 -82
  238. package/dist/plugs/tsc/options.cjs.map +0 -6
  239. package/dist/plugs/tsc/options.d.ts +0 -8
  240. package/dist/plugs/tsc/options.mjs +0 -51
  241. package/dist/plugs/tsc/options.mjs.map +0 -6
  242. package/dist/plugs/tsc/report.cjs +0 -90
  243. package/dist/plugs/tsc/report.cjs.map +0 -6
  244. package/dist/plugs/tsc/report.d.ts +0 -5
  245. package/dist/plugs/tsc/report.mjs +0 -59
  246. package/dist/plugs/tsc/report.mjs.map +0 -6
  247. package/dist/plugs/tsc/runner.cjs +0 -124
  248. package/dist/plugs/tsc/runner.cjs.map +0 -6
  249. package/dist/plugs/tsc/runner.d.ts +0 -8
  250. package/dist/plugs/tsc/runner.mjs +0 -95
  251. package/dist/plugs/tsc/runner.mjs.map +0 -6
  252. package/dist/plugs/tsc.cjs.map +0 -6
  253. package/dist/plugs/tsc.d.ts +0 -48
  254. package/dist/plugs/tsc.mjs +0 -5
  255. package/dist/plugs/tsc.mjs.map +0 -6
  256. package/src/failure.ts +0 -43
  257. package/src/plugs/coverage/analysis.ts +0 -400
  258. package/src/plugs/coverage/report.ts +0 -368
  259. package/src/plugs/coverage.ts +0 -216
  260. package/src/plugs/eslint/runner.ts +0 -100
  261. package/src/plugs/eslint.ts +0 -42
  262. package/src/plugs/mocha/reporter.ts +0 -178
  263. package/src/plugs/mocha/runner.ts +0 -66
  264. package/src/plugs/mocha.ts +0 -43
  265. package/src/plugs/tsc/compiler.ts +0 -68
  266. package/src/plugs/tsc/options.ts +0 -100
  267. package/src/plugs/tsc/report.ts +0 -77
  268. package/src/plugs/tsc/runner.ts +0 -133
  269. package/src/plugs/tsc.ts +0 -58
  270. package/types/globals.d.ts +0 -15
@@ -1,368 +0,0 @@
1
- import { pathToFileURL } from 'node:url'
2
-
3
- import {
4
- isDeclaration,
5
- isExportDeclaration,
6
- isFile,
7
- isIfStatement,
8
- isImportDeclaration,
9
- isProgram,
10
- isTryStatement,
11
- isTSDeclareMethod,
12
- isTSTypeReference,
13
- isTypeScript,
14
- VISITOR_KEYS,
15
- } from '@babel/types'
16
- import { parse } from '@babel/parser'
17
-
18
- import { $p } from '../../log'
19
- import { readFile } from '../../utils/asyncfs'
20
-
21
- import type { Logger } from '../../log'
22
- import type {
23
- Comment,
24
- Node } from '@babel/types'
25
- import type { AbsolutePath } from '../../paths'
26
- import type { CoverageAnalyser } from './analysis'
27
-
28
- /* ========================================================================== *
29
- * EXPORTED CONSTANTS AND TYPES *
30
- * ========================================================================== */
31
-
32
- /**
33
- * A constant indicating that coverage was skipped (is irrelevant, for e.g.
34
- * comment or typescript definition nodes)
35
- */
36
- export const COVERAGE_SKIPPED = -2
37
-
38
- /**
39
- * A constant indicating that coverage was intentionally ignored because of a
40
- * specific "coverage ignore ..." comment
41
- */
42
- export const COVERAGE_IGNORED = -1
43
-
44
- /** Node coverage summary */
45
- export interface NodeCoverageResult {
46
- /** Number of _covered_ nodes (good!) */
47
- coveredNodes: number,
48
- /** Number of nodes with _no coverage_ (bad!) */
49
- missingNodes: number,
50
- /** Number of nodes ignored by comments like `coverage ignore xxx` */
51
- ignoredNodes: number,
52
- /** Total number of nodes (sum of `covered`, `missing` and `ignored`) */
53
- totalNodes: number,
54
- /**
55
- * Percentage of code coverage (covered as a % of total - ignored nodes)
56
- *
57
- * A `null` value for this field indicates that no coverage data was generated
58
- * either because the source was all ignored or skipped (e.g. when using
59
- * `coverage ignore file` or when covering a TS source only with types).
60
- */
61
- coverage: number | null,
62
- }
63
-
64
- /** Per-file coverage result */
65
- export interface CoverageResult {
66
- /** The actual code this coverage is for */
67
- code: string,
68
- /**
69
- * Per _character_ coverage report:
70
- * - `-2`: coverage skipped (comments, typescript declarations, ...)
71
- * - `-1`: coverage ignored (when using `coverage ignore xxx`)
72
- * - `0`: no coverage collected for this character
73
- * - _any number greater than zero_: number of times this was covered
74
- */
75
- codeCoverage: number[],
76
- /** Node coverage summary */
77
- nodeCoverage: NodeCoverageResult,
78
- }
79
-
80
- /** Aggregation of {@link CoverageResult} over all files */
81
- export type CoverageResults = Record<AbsolutePath, CoverageResult>
82
-
83
- /** Our coverage report, per file */
84
- export interface CoverageReport {
85
- results: CoverageResults,
86
- nodes: NodeCoverageResult,
87
- }
88
-
89
- /* ========================================================================== *
90
- * EXPORTED CONSTANTS AND TYPES *
91
- * ========================================================================== */
92
-
93
- /** Tokens for `coverage ignore xxx`, this should be self-explanatory */
94
- type IgnoreCoverage = 'test' | 'if' | 'else' | 'try' | 'catch' | 'finally' | 'next' | 'file'
95
-
96
- /** Regular expression matching strings like `coverage ignore xxx` */
97
- const ignoreRegexp = /(coverage|istanbul)\s+ignore\s+(test|if|else|try|catch|finally|next|file)(\s|$)/g
98
-
99
- /* ========================================================================== *
100
- * EXPORTED CONSTANTS AND TYPES *
101
- * ========================================================================== */
102
-
103
- /**
104
- * Analyse coverage for the specified source files, using the data from the
105
- * specified coverage files and produce a {@link CoverageReport}.
106
- */
107
- export async function coverageReport(
108
- analyser: CoverageAnalyser,
109
- sourceFiles: AbsolutePath[],
110
- log: Logger,
111
- ): Promise<CoverageReport> {
112
- /* Some of our results */
113
- const results: CoverageResults = {}
114
- const nodes: NodeCoverageResult = {
115
- coveredNodes: 0,
116
- missingNodes: 0,
117
- ignoredNodes: 0,
118
- totalNodes: 0,
119
- coverage: 0,
120
- }
121
-
122
- /*
123
- * Here comes the interesting part: we need to parse the original soruces,
124
- * walk their ASTs and see whether each node has been covered or not.
125
- * We look up every node's position, (for sitemaps, map this to the position
126
- * in the resulting file) then look at the coverage.
127
- */
128
- for (const file of sourceFiles) {
129
- /* Read up the file and parse the tree in the most liberal way possible */
130
- const url = pathToFileURL(file).toString()
131
- const code = await readFile(file, 'utf-8')
132
-
133
- const tree = parse(code, {
134
- allowImportExportEverywhere: true,
135
- allowAwaitOutsideFunction: true,
136
- allowReturnOutsideFunction: true,
137
- allowSuperOutsideMethod: true,
138
- allowUndeclaredExports: true,
139
- attachComment: true,
140
- errorRecovery: false,
141
- sourceType: 'unambiguous',
142
- sourceFilename: file,
143
- startLine: 1,
144
- startColumn: 0,
145
- plugins: [ 'typescript' ],
146
- strictMode: false,
147
- ranges: false,
148
- tokens: false,
149
- createParenthesizedExpressions: true,
150
- })
151
-
152
- const codeCoverage: number[] = new Array(code.length).fill(0)
153
- const nodeCoverage: NodeCoverageResult = {
154
- coveredNodes: 0,
155
- missingNodes: 0,
156
- ignoredNodes: 0,
157
- totalNodes: 0,
158
- coverage: 0,
159
- }
160
-
161
- /* Set the code coverage for the specified node and (optionally) its children */
162
- const setCodeCoverage = (
163
- node: (Node | Comment)[] | Node | Comment | undefined | null,
164
- coverage: number,
165
- recursive: boolean,
166
- ): void => {
167
- if (! node) return
168
-
169
- if (Array.isArray(node)) {
170
- for (const n of node) setCodeCoverage(n, coverage, recursive)
171
- return
172
- }
173
-
174
- if ((node.start != null) && (node.end != null)) {
175
- for (let i = node.start; i < node.end; i ++) {
176
- codeCoverage[i] = coverage
177
- }
178
- }
179
-
180
- if (coverage == COVERAGE_IGNORED) {
181
- nodeCoverage.ignoredNodes++
182
- } else if (coverage === 0) {
183
- nodeCoverage.missingNodes++
184
- } else if (coverage > 0) {
185
- nodeCoverage.coveredNodes++
186
- }
187
-
188
- if (! recursive) return
189
-
190
- const keys = VISITOR_KEYS[node.type] || []
191
- for (const key of keys) {
192
- const value: Node | Node[] = (<any> node)[key]
193
- if (Array.isArray(value)) {
194
- for (const child of value) {
195
- setCodeCoverage(child, coverage, true)
196
- }
197
- } else if (value) {
198
- setCodeCoverage(value, coverage, true)
199
- }
200
- }
201
- }
202
-
203
- /* Recursively invoke "visitNode" on the children of a given node */
204
- const visitChildren = (node: Node, depth: number): void => {
205
- const keys = VISITOR_KEYS[node.type] || []
206
- for (const key of keys) {
207
- const children: Node | null | (Node | null)[] = (<any> node)[key]
208
- if (Array.isArray(children)) {
209
- for (const child of children) {
210
- if (child) visitNode(child, depth + 1)
211
- }
212
- } else if (children) {
213
- visitNode(children, depth + 1)
214
- }
215
- }
216
- }
217
-
218
- /* Visit a node or ignore it depending on a condition */
219
- const maybeIgnoreNode = (
220
- condition: boolean,
221
- node: Node | null | undefined,
222
- depth: number,
223
- ): void => {
224
- if (condition) {
225
- setCodeCoverage(node, COVERAGE_IGNORED, true)
226
- } else if (node) {
227
- visitNode(node, depth)
228
- }
229
- }
230
-
231
- /* Visit a node and evaluate its coverage */
232
- const visitNode = (node: Node, depth: number): void => {
233
- /* See what we're doing here... */
234
- log.trace('-'.padStart((depth * 2) + 1, ' '), node.type, `${node.loc?.start.line}:${node.loc?.start.column}`)
235
-
236
- /* Root nodes (file and program) simply go to their children */
237
- if (isFile(node)) return visitChildren(node, depth)
238
- if (isProgram(node)) return visitChildren(node, depth)
239
-
240
- /* Figure out if we have some "coverage ignore xxxx" in comments */
241
- const ignores: IgnoreCoverage[] = []
242
- for (const comment of node.leadingComments || []) {
243
- for (const match of comment.value.matchAll(ignoreRegexp)) {
244
- ignores.push(match[2] as IgnoreCoverage)
245
- }
246
- }
247
-
248
- /* Skip this node if we have a "coverage ignore next" comment */
249
- if (ignores.includes('next')) return setCodeCoverage(node, COVERAGE_IGNORED, true)
250
-
251
- /* Typescript nodes are skipped, but children aren't in some cases */
252
- if (isTypeScript(node)) {
253
- if (isTSDeclareMethod(node)) return setCodeCoverage(node, COVERAGE_SKIPPED, true)
254
- if (isTSTypeReference(node)) return setCodeCoverage(node, COVERAGE_SKIPPED, true)
255
- if (isDeclaration(node)) return setCodeCoverage(node, COVERAGE_SKIPPED, true)
256
-
257
- /* For things like "X as Y": the "as" node wraps the expression */
258
- setCodeCoverage(node, COVERAGE_SKIPPED, false) // not recursive
259
- return visitChildren(node, depth) // visit all children normally...
260
- }
261
-
262
- // Typescript "import type" or "export type" get skipped all together
263
- if (isExportDeclaration(node) && (node.exportKind === 'type')) {
264
- return setCodeCoverage(node, COVERAGE_SKIPPED, true)
265
- }
266
-
267
- if (isImportDeclaration(node) && (node.importKind === 'type')) {
268
- return setCodeCoverage(node, COVERAGE_SKIPPED, true)
269
- }
270
-
271
- /* Ok, from here we calculate the coverage */
272
- let coverage = 0
273
- if (node.loc) {
274
- const { line, column } = node.loc.start
275
- const c = analyser.coverage(url, line, column)
276
- if (c == null) log.warn(`No coverage for ${node.type} at ${$p(file)}:${line}:${column}`)
277
- else coverage = c
278
- }
279
-
280
- /* Record the code coverage, and set up our variables */
281
- setCodeCoverage(node, coverage, false)
282
-
283
- /* The "if" node might have some ignores... */
284
- if (isIfStatement(node)) {
285
- maybeIgnoreNode(ignores.includes('test'), node.test, depth + 1)
286
- maybeIgnoreNode(ignores.includes('if'), node.consequent, depth + 1)
287
- maybeIgnoreNode(ignores.includes('else'), node.alternate, depth + 1)
288
- return
289
- }
290
-
291
- /* The "try" node might have some ignores... */
292
- if (isTryStatement(node)) {
293
- maybeIgnoreNode(ignores.includes('try'), node.block, depth + 1)
294
- maybeIgnoreNode(ignores.includes('catch'), node.handler, depth + 1)
295
- maybeIgnoreNode(ignores.includes('finally'), node.finalizer, depth + 1)
296
- return
297
- }
298
-
299
- /* All other nodes simply gets visited recursively*/
300
- visitChildren(node, depth)
301
- }
302
-
303
- /* Start by setting the scope of our coverage in the code */
304
- codeCoverage.fill(COVERAGE_SKIPPED) // by default, everything is skipped
305
- setCodeCoverage(tree.program.directives, 0, true) // directives must be covered
306
- setCodeCoverage(tree.program.body, 0, true) // program body must be covered
307
-
308
- /* Cleanup our node statistics */
309
- nodeCoverage.coveredNodes = 0
310
- nodeCoverage.missingNodes = 0
311
- nodeCoverage.ignoredNodes = 0
312
-
313
- /* Check if one of the first comments is "coverage ignore file" */
314
- let ignoreFileCoverage = false
315
- for (const comment of tree.program.body[0]?.leadingComments || []) {
316
- for (const match of comment.value.matchAll(ignoreRegexp)) {
317
- if (match[2] === 'file') {
318
- ignoreFileCoverage = true
319
- break
320
- }
321
- }
322
- /* Already matched "coverage ignore file", skip the rest */
323
- if (ignoreFileCoverage) break
324
- }
325
-
326
- /* If we found a "coverage ignore file" at the beginning, ignore the file */
327
- if (ignoreFileCoverage) {
328
- setCodeCoverage(tree.program, COVERAGE_IGNORED, true)
329
- } else {
330
- visitChildren(tree.program, -1)
331
- }
332
-
333
- /*
334
- * As comments are mixed within codes (and do not get visited in the
335
- * tree) we force-skip them _AFTER_ the tree is visited, otherwise (for
336
- * example) a comment within a block will be shown with the coverage of
337
- * the block itself.
338
- */
339
- setCodeCoverage(tree.comments, COVERAGE_SKIPPED, false)
340
-
341
- /* Update nodes coverage results */
342
- updateNodeCoverageResult(nodeCoverage)
343
-
344
- nodes.coveredNodes += nodeCoverage.coveredNodes
345
- nodes.missingNodes += nodeCoverage.missingNodes
346
- nodes.ignoredNodes += nodeCoverage.ignoredNodes
347
- nodes.totalNodes += nodeCoverage.totalNodes
348
-
349
- /* This file is done, add it to the report */
350
- results[file] = { code, codeCoverage, nodeCoverage }
351
- }
352
-
353
- /* All done, return the report */
354
- updateNodeCoverageResult(nodes)
355
- return { results, nodes }
356
- }
357
-
358
- function updateNodeCoverageResult(result: NodeCoverageResult): void {
359
- const { coveredNodes, missingNodes, ignoredNodes } = result
360
- const totalNodes = result.totalNodes = coveredNodes + missingNodes + ignoredNodes
361
- if (totalNodes === 0) {
362
- result.coverage = null // No "total" nodes, means all ignored
363
- } else if (totalNodes === ignoredNodes) {
364
- result.coverage = null // All nodes were ignored (e.g. coverage ignore file)
365
- } else {
366
- result.coverage = Math.floor((100 * coveredNodes) / (totalNodes - ignoredNodes))
367
- }
368
- }
@@ -1,216 +0,0 @@
1
- import { sep } from 'node:path'
2
-
3
- import { html, initFunction } from '@plugjs/cov8-html'
4
-
5
- import { Files } from '../files'
6
- import { $gry, $ms, $p, $red, $ylw, ERROR, NOTICE, WARN } from '../log'
7
- import { resolveAbsolutePath } from '../paths'
8
- import { install } from '../pipe'
9
- import { walk } from '../utils/walk'
10
- import { createAnalyser } from './coverage/analysis'
11
- import { coverageReport } from './coverage/report'
12
-
13
- import type { AbsolutePath } from '../paths'
14
- import type { Context, PipeParameters, Plug } from '../pipe'
15
- import type { SourceMapBias } from './coverage/analysis'
16
- import type { CoverageResult } from './coverage/report'
17
-
18
- /** Options to analyse coverage reports */
19
- export interface CoverageOptions {
20
- /** The bias for source map analisys (defaults to `greatest_lower_bound`) */
21
- sourceMapBias?: SourceMapBias
22
- /** Minimum _overall_ coverage (as a percentage, defaults to 50) */
23
- minimumCoverage?: number,
24
- /** Optimal _overall_ coverage (as a percentage, defaults to 50) */
25
- optimalCoverage?: number,
26
- /** Minimum _per-file_ coverage (as a percentage, defaults to 75) */
27
- minimumFileCoverage?: number,
28
- /** Optimal _per-file_ coverage (as a percentage, defaults to 75) */
29
- optimalFileCoverage?: number,
30
- }
31
-
32
- export interface CoverageReportOptions extends CoverageOptions {
33
- /** If specified, a JSON and HTML report will be written to this directory */
34
- reportDir: string,
35
- }
36
-
37
- declare module '../pipe' {
38
- export interface Pipe {
39
- /**
40
- * Analyse coverage using files generated by V8/NodeJS.
41
- *
42
- * @param coverageDir The directory where the `coverage-XXX.json` files
43
- * generated by V8/NodeJS can be found.
44
- */
45
- coverage(coverageDir: string): Promise<undefined>
46
- /**
47
- * Analyse coverage using files generated by V8/NodeJS.
48
- *
49
- * @param coverageDir The directory where the `coverage-XXX.json` files
50
- * generated by V8/NodeJS can be found.
51
- * @param options Extra {@link CoverageOptions | options} allowing to
52
- * specify coverage thresholds.
53
- */
54
- coverage(coverageDir: string, options: CoverageOptions): Promise<undefined>
55
- /**
56
- * Analyse coverage using files generated by V8/NodeJS and produce an HTML
57
- * report in the directory specified in `options`.
58
- *
59
- * @param coverageDir The directory where the `coverage-XXX.json` files
60
- * generated by V8/NodeJS can be found.
61
- * @param options Extra {@link CoverageOptions | options} allowing to
62
- * specify coverage thresholds where the HTML report should
63
- * be written to.
64
- */
65
- coverage(coverageDir: string, options: CoverageReportOptions): Pipe
66
- }
67
- }
68
-
69
- /* ========================================================================== *
70
- * INSTALLATION / IMPLEMENTATION *
71
- * ========================================================================== */
72
-
73
- install('coverage', class Coverage implements Plug<Files | undefined> {
74
- constructor(...args: PipeParameters<'coverage'>)
75
- constructor(
76
- private readonly _coverageDir: string,
77
- private readonly _options: Partial<CoverageReportOptions> = {},
78
- ) {}
79
-
80
- async pipe(files: Files, context: Context): Promise<Files | undefined> {
81
- const coverageDir = context.resolve(this._coverageDir)
82
- const coverageFiles: AbsolutePath[] = []
83
- for await (const file of walk(coverageDir, [ 'coverage-*.json' ])) {
84
- coverageFiles.push(resolveAbsolutePath(coverageDir, file))
85
- }
86
-
87
- if (coverageFiles.length === 0) {
88
- throw context.log.fail(`No coverage files found in ${$p(coverageDir)}`)
89
- }
90
-
91
- const sourceFiles = [ ...files.absolutePaths() ]
92
-
93
- const ms1 = Date.now()
94
- const analyser = await createAnalyser(
95
- sourceFiles,
96
- coverageFiles,
97
- this._options.sourceMapBias || 'least_upper_bound',
98
- context.log,
99
- )
100
- context.log.info('Parsed', coverageFiles.length, 'coverage files', $ms(Date.now() - ms1))
101
-
102
- const ms2 = Date.now()
103
- const report = await coverageReport(analyser, sourceFiles, context.log)
104
- context.log.info('Analysed', sourceFiles.length, 'source files', $ms(Date.now() - ms2))
105
-
106
- analyser.destroy()
107
-
108
- const {
109
- minimumCoverage = 50,
110
- minimumFileCoverage = minimumCoverage,
111
- optimalCoverage = Math.round((100 + minimumCoverage) / 2),
112
- optimalFileCoverage = Math.round((100 + minimumFileCoverage) / 2),
113
- } = this._options
114
-
115
- let max = 0
116
- for (const file in report) {
117
- if (file.length > max) max = file.length
118
- }
119
-
120
- let maxLength = 0
121
- for (const file in report.results) {
122
- if (file.length > maxLength) maxLength = file.length
123
- }
124
-
125
- let fileErrors = 0
126
- let fileWarnings = 0
127
- const _report = context.log.report('Coverage report')
128
-
129
- for (const [ _file, result ] of Object.entries(report.results)) {
130
- const { coverage } = result.nodeCoverage
131
- const file = _file as AbsolutePath
132
-
133
- if (coverage == null) {
134
- _report.annotate(NOTICE, file, 'n/a')
135
- } else if (coverage < minimumFileCoverage) {
136
- _report.annotate(ERROR, file, `${coverage} %`)
137
- fileErrors ++
138
- } else if (coverage < optimalFileCoverage) {
139
- _report.annotate(WARN, file, `${coverage} %`)
140
- fileWarnings ++
141
- } else {
142
- _report.annotate(NOTICE, file, `${coverage} %`)
143
- }
144
- }
145
-
146
- if (report.nodes.coverage == null) {
147
- const message = 'No coverage data collected'
148
- _report.add({ level: WARN, message })
149
- } else if (report.nodes.coverage < minimumCoverage) {
150
- const message = `${$red(`${report.nodes.coverage}%`)} does not meet minimum coverage ${$gry(`(${minimumCoverage}%)`)}`
151
- _report.add({ level: ERROR, message })
152
- } else if (report.nodes.coverage < optimalCoverage) {
153
- const message = `${$ylw(`${report.nodes.coverage}%`)} does not meet optimal coverage ${$gry(`(${optimalCoverage}%)`)}`
154
- _report.add({ level: WARN, message })
155
- }
156
-
157
- if (fileErrors) {
158
- const message = `${$red(fileErrors)} files do not meet minimum file coverage ${$gry(`(${minimumFileCoverage}%)`)}`
159
- _report.add({ level: ERROR, message })
160
- }
161
- if (fileWarnings) {
162
- const message = `${$ylw(fileWarnings)} files do not meet optimal file coverage ${$gry(`(${optimalFileCoverage}%)`)}`
163
- _report.add({ level: WARN, message })
164
- }
165
-
166
- /* If we don't have to write a report, pass-through the coverage files */
167
- if (this._options.reportDir == null) return _report.done(false) as any
168
-
169
- /* Create a builder to emit our reports */
170
- const reportDir = context.resolve(this._options.reportDir)
171
- const builder = Files.builder(reportDir)
172
-
173
- /* Thresholds to inject in the report */
174
- const date = new Date().toISOString()
175
- const thresholds = {
176
- minimumCoverage,
177
- minimumFileCoverage,
178
- optimalCoverage,
179
- optimalFileCoverage,
180
- }
181
-
182
- /* The JSON file in the report has *absolute* file paths */
183
- await builder.write('report.json', JSON.stringify({ ...report, thresholds, date }))
184
-
185
- /* The HTML file rendering our report */
186
- await builder.write('index.html', html)
187
-
188
- /* The JSONP file (for our HTML report) has relative files and a tree */
189
- const results: Record<string, CoverageResult> = {}
190
- for (const [ rel, abs ] of files.pathMappings()) {
191
- results[rel] = report.results[abs]
192
- }
193
-
194
- const tree: Record<string, any> = {}
195
- for (const relative of Object.keys(results)) {
196
- const directories = relative.split(sep)
197
- const file = directories.pop()!
198
-
199
- let node = tree
200
- for (const dir of directories) {
201
- node = node[dir] = node[dir] || {}
202
- }
203
-
204
- node[file] = relative
205
- }
206
-
207
- const jsonp = JSON.stringify({ ...report, results, thresholds, tree, date })
208
- await builder.write('report.js', `${initFunction}(${jsonp});`)
209
-
210
- /* Emit our coverage report */
211
- _report.done(false)
212
-
213
- /* Return emitted files */
214
- return builder.build() as any
215
- }
216
- })
@@ -1,100 +0,0 @@
1
- import { ESLint as RealESLint } from 'eslint'
2
-
3
- import { assert } from '../../assert'
4
- import { BuildFailure } from '../../failure'
5
- import { $p, ERROR, NOTICE, WARN } from '../../log'
6
- import { getCurrentWorkingDirectory, resolveAbsolutePath, resolveDirectory, resolveFile } from '../../paths'
7
- import { readFile } from '../../utils/asyncfs'
8
-
9
- import type { Files } from '../../files'
10
- import type { Context, PipeParameters, Plug } from '../../pipe'
11
- import type { ESLintOptions } from '../eslint'
12
-
13
- /** Runner implementation for the `ESLint` plug. */
14
- export default class ESLint implements Plug<void> {
15
- private readonly _options: Readonly<ESLintOptions>
16
-
17
- constructor(...arg: PipeParameters<'eslint'>)
18
- constructor(arg: string | ESLintOptions = {}) {
19
- this._options = typeof arg === 'string' ? { configFile: arg } : arg
20
- }
21
-
22
- async pipe(files: Files, context: Context): Promise<void> {
23
- const { directory, configFile } = this._options
24
-
25
- const cwd = directory ? context.resolve(directory) : getCurrentWorkingDirectory()
26
- assert(resolveDirectory(cwd), `ESLint directory ${$p(cwd)} does not exist`)
27
-
28
- const overrideConfigFile = configFile ? context.resolve(configFile) : undefined
29
- if (overrideConfigFile) {
30
- assert(resolveFile(overrideConfigFile), `ESLint configuration ${$p(overrideConfigFile)} does not exist`)
31
- }
32
-
33
- /* Create our ESLint instance */
34
- const eslint = new RealESLint({ overrideConfigFile, cwd })
35
-
36
- /* Lint all files in parallel */
37
- const paths = [ ...files.absolutePaths() ]
38
- const promises = paths.map(async (filePath) => {
39
- const code = await readFile(filePath, 'utf-8')
40
- return eslint.lintText(code, { filePath })
41
- })
42
-
43
- /* Await for all promises to be settled */
44
- const settlements = await Promise.allSettled(promises)
45
-
46
- /* Run through all promises settlements */
47
- const summary = settlements.reduce((summary, settlement, i) => {
48
- /* Promise rejected, meaining hard failure */
49
- if (settlement.status === 'rejected') {
50
- context.log.error('Error linting', $p(paths[i]), settlement.reason)
51
- summary.failures ++
52
- return summary
53
- }
54
-
55
- /* Push all our results in the summary */
56
- summary.results.push(...settlement.value)
57
- return summary
58
- }, {
59
- results: [] as RealESLint.LintResult[],
60
- failures: 0,
61
- })
62
-
63
- /* In case of failures from promises, fail! */
64
- const { results, failures } = summary
65
- if (failures) throw BuildFailure.fail()
66
-
67
- /* Create our report */
68
- const report = context.log.report('ESLint Report')
69
-
70
- /* Convert ESLint results into our report records */
71
- for (const result of results) {
72
- const { filePath, source, messages } = result
73
- const file = resolveAbsolutePath(getCurrentWorkingDirectory(), filePath)
74
-
75
- for (const record of messages) {
76
- const {
77
- severity,
78
- message,
79
- ruleId: tags,
80
- line,
81
- column,
82
- endLine = line,
83
- endColumn = column + 1,
84
- } = record
85
-
86
- /* Severity becomes our "kind" */
87
- const level = severity === 0 ? NOTICE : severity === 1 ? WARN : ERROR
88
-
89
- /* Characters */
90
- const length = endLine === line ? endColumn - column : endLine > line ? -1 : 1
91
-
92
- /* Add our report */
93
- report.add({ level, message, tags, line, column, length, file, source })
94
- }
95
- }
96
-
97
- /* Emit our report and fail on errors */
98
- report.done(this._options.showSources)
99
- }
100
- }