No flags found
Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.
e.g., #unittest #integration
#production #enterprise
#frontend #backend
d43f3ef
... +24 ...
c5c6819
Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.
e.g., #unittest #integration
#production #enterprise
#frontend #backend
1 | + | /** |
|
2 | + | * @typedef {import('vfile').VFile} VFile |
|
3 | + | * @typedef {import('property-information').Schema} Schema |
|
4 | + | * @typedef {import('unist').Position} Position |
|
5 | + | * @typedef {import('unist').Point} Point |
|
6 | + | * @typedef {import('hast').Parent} Parent |
|
7 | + | * @typedef {import('hast').Element} Element |
|
8 | + | * @typedef {import('hast').Root} Root |
|
9 | + | * @typedef {import('hast').Text} Text |
|
10 | + | * @typedef {import('hast').Comment} Comment |
|
11 | + | * @typedef {import('hast').DocType} Doctype |
|
12 | + | * @typedef {Parent['children'][number]} Child |
|
13 | + | * @typedef {Element['children'][number]} ElementChild |
|
14 | + | * @typedef {Child|Root} Node |
|
15 | + | * @typedef {import('parse5').Document} P5Document |
|
16 | + | * @typedef {import('parse5').DocumentType} P5Doctype |
|
17 | + | * @typedef {import('parse5').CommentNode} P5Comment |
|
18 | + | * @typedef {import('parse5').TextNode} P5Text |
|
19 | + | * @typedef {import('parse5').Element} P5Element |
|
20 | + | * @typedef {import('parse5').ElementLocation} P5ElementLocation |
|
21 | + | * @typedef {import('parse5').Location} P5Location |
|
22 | + | * @typedef {import('parse5').Attribute} P5Attribute |
|
23 | + | * @typedef {import('parse5').Node} P5Node |
|
24 | + | * |
|
25 | + | * @typedef {'html'|'svg'} Space |
|
26 | + | * |
|
27 | + | * @callback Handler |
|
28 | + | * @param {Context} ctx |
|
29 | + | * @param {P5Node} node |
|
30 | + | * @param {Array.<Child>} [children] |
|
31 | + | * @returns {Node} |
|
32 | + | * |
|
33 | + | * @typedef Options |
|
34 | + | * @property {Space} [space='html'] Whether the root of the tree is in the `'html'` or `'svg'` space. If an element in with the SVG namespace is found in `ast`, `fromParse5` automatically switches to the SVG space when entering the element, and switches back when leaving |
|
35 | + | * @property {VFile} [file] `VFile`, used to add positional information to nodes. If given, the file should have the original HTML source as its contents |
|
36 | + | * @property {boolean} [verbose=false] Whether to add extra positional information about starting tags, closing tags, and attributes to elements. Note: not used without `file` |
|
37 | + | * |
|
38 | + | * @typedef Context |
|
39 | + | * @property {Schema} schema |
|
40 | + | * @property {VFile|undefined} file |
|
41 | + | * @property {boolean|undefined} verbose |
|
42 | + | * @property {boolean} location |
|
43 | + | */ |
|
44 | + | ||
45 | + | import {h, s} from 'hastscript' |
|
46 | + | import {html, svg, find} from 'property-information' |
|
47 | + | import {location} from 'vfile-location' |
|
48 | + | import {webNamespaces} from 'web-namespaces' |
|
49 | + | ||
50 | + | const own = {}.hasOwnProperty |
|
51 | + | ||
52 | + | // Handlers. |
|
53 | + | const map = { |
|
54 | + | '#document': root, |
|
55 | + | '#document-fragment': root, |
|
56 | + | '#text': text, |
|
57 | + | '#comment': comment, |
|
58 | + | '#documentType': doctype |
|
59 | + | } |
|
60 | + | ||
61 | + | /** |
|
62 | + | * Transform Parse5’s AST to a hast tree. |
|
63 | + | * |
|
64 | + | * @param {P5Node} ast |
|
65 | + | * @param {Options|VFile} [options] |
|
66 | + | */ |
|
67 | + | export function fromParse5(ast, options = {}) { |
|
68 | + | /** @type {Options} */ |
|
69 | + | let settings |
|
70 | + | /** @type {VFile|undefined} */ |
|
71 | + | let file |
|
72 | + | ||
73 | + | if (isFile(options)) { |
|
74 | + | file = options |
|
75 | + | settings = {} |
|
76 | + | } else { |
|
77 | + | file = options.file |
|
78 | + | settings = options |
|
79 | + | } |
|
80 | + | ||
81 | + | return transform( |
|
82 | + | { |
|
83 | + | schema: settings.space === 'svg' ? svg : html, |
|
84 | + | file, |
|
85 | + | verbose: settings.verbose, |
|
86 | + | location: false |
|
87 | + | }, |
|
88 | + | ast |
|
89 | + | ) |
|
90 | + | } |
|
91 | + | ||
92 | + | /** |
|
93 | + | * Transform children. |
|
94 | + | * |
|
95 | + | * @param {Context} ctx |
|
96 | + | * @param {P5Node} ast |
|
97 | + | * @returns {Node} |
|
98 | + | */ |
|
99 | + | function transform(ctx, ast) { |
|
100 | + | const schema = ctx.schema |
|
101 | + | /** @type {Handler} */ |
|
102 | + | // @ts-expect-error: index is fine. |
|
103 | + | const fn = own.call(map, ast.nodeName) ? map[ast.nodeName] : element |
|
104 | + | /** @type {Array.<Child>|undefined} */ |
|
105 | + | let children |
|
106 | + | ||
107 | + | // Element. |
|
108 | + | if ('tagName' in ast) { |
|
109 | + | ctx.schema = ast.namespaceURI === webNamespaces.svg ? svg : html |
|
110 | + | } |
|
111 | + | ||
112 | + | if ('childNodes' in ast) { |
|
113 | + | children = nodes(ctx, ast.childNodes) |
|
114 | + | } |
|
115 | + | ||
116 | + | const result = fn(ctx, ast, children) |
|
117 | + | ||
118 | + | if ('sourceCodeLocation' in ast && ast.sourceCodeLocation && ctx.file) { |
|
119 | + | // @ts-expect-error It’s fine. |
|
120 | + | const position = createLocation(ctx, result, ast.sourceCodeLocation) |
|
121 | + | ||
122 | + | if (position) { |
|
123 | + | ctx.location = true |
|
124 | + | result.position = position |
|
125 | + | } |
|
126 | + | } |
|
127 | + | ||
128 | + | ctx.schema = schema |
|
129 | + | ||
130 | + | return result |
|
131 | + | } |
|
132 | + | ||
133 | + | /** |
|
134 | + | * Transform children. |
|
135 | + | * |
|
136 | + | * @param {Context} ctx |
|
137 | + | * @param {Array.<P5Node>} children |
|
138 | + | * @returns {Array.<Child>} |
|
139 | + | */ |
|
140 | + | function nodes(ctx, children) { |
|
141 | + | let index = -1 |
|
142 | + | /** @type {Array.<Child>} */ |
|
143 | + | const result = [] |
|
144 | + | ||
145 | + | while (++index < children.length) { |
|
146 | + | // @ts-expect-error Assume no roots in children. |
|
147 | + | result[index] = transform(ctx, children[index]) |
|
148 | + | } |
|
149 | + | ||
150 | + | return result |
|
151 | + | } |
|
152 | + | ||
153 | + | /** |
|
154 | + | * Transform a document. |
|
155 | + | * Stores `ast.quirksMode` in `node.data.quirksMode`. |
|
156 | + | * |
|
157 | + | * @type {Handler} |
|
158 | + | * @param {P5Document} ast |
|
159 | + | * @param {Array.<Child>} children |
|
160 | + | * @returns {Root} |
|
161 | + | */ |
|
162 | + | function root(ctx, ast, children) { |
|
163 | + | /** @type {Root} */ |
|
164 | + | const result = { |
|
165 | + | type: 'root', |
|
166 | + | children, |
|
167 | + | data: {quirksMode: ast.mode === 'quirks' || ast.mode === 'limited-quirks'} |
|
168 | + | } |
|
169 | + | ||
170 | + | if (ctx.file && ctx.location) { |
|
171 | + | const doc = String(ctx.file) |
|
172 | + | const loc = location(doc) |
|
173 | + | result.position = { |
|
174 | + | start: loc.toPoint(0), |
|
175 | + | end: loc.toPoint(doc.length) |
|
176 | + | } |
|
177 | + | } |
|
178 | + | ||
179 | + | return result |
|
180 | + | } |
|
181 | + | ||
182 | + | /** |
|
183 | + | * Transform a doctype. |
|
184 | + | * |
|
185 | + | * @type {Handler} |
|
186 | + | * @returns {Doctype} |
|
187 | + | */ |
|
188 | + | function doctype() { |
|
189 | + | // @ts-expect-error Types are out of date. |
|
190 | + | return {type: 'doctype'} |
|
191 | + | } |
|
192 | + | ||
193 | + | /** |
|
194 | + | * Transform a text. |
|
195 | + | * |
|
196 | + | * @type {Handler} |
|
197 | + | * @param {P5Text} ast |
|
198 | + | * @returns {Text} |
|
199 | + | */ |
|
200 | + | function text(_, ast) { |
|
201 | + | return {type: 'text', value: ast.value} |
|
202 | + | } |
|
203 | + | ||
204 | + | /** |
|
205 | + | * Transform a comment. |
|
206 | + | * |
|
207 | + | * @type {Handler} |
|
208 | + | * @param {P5Comment} ast |
|
209 | + | * @returns {Comment} |
|
210 | + | */ |
|
211 | + | function comment(_, ast) { |
|
212 | + | return {type: 'comment', value: ast.data} |
|
213 | + | } |
|
214 | + | ||
215 | + | /** |
|
216 | + | * Transform an element. |
|
217 | + | * |
|
218 | + | * @type {Handler} |
|
219 | + | * @param {P5Element} ast |
|
220 | + | * @param {Array.<ElementChild>} children |
|
221 | + | * @returns {Element} |
|
222 | + | */ |
|
223 | + | function element(ctx, ast, children) { |
|
224 | + | const fn = ctx.schema.space === 'svg' ? s : h |
|
225 | + | let index = -1 |
|
226 | + | /** @type {Object.<string, string>} */ |
|
227 | + | const props = {} |
|
228 | + | ||
229 | + | while (++index < ast.attrs.length) { |
|
230 | + | const attribute = ast.attrs[index] |
|
231 | + | props[(attribute.prefix ? attribute.prefix + ':' : '') + attribute.name] = |
|
232 | + | attribute.value |
|
233 | + | } |
|
234 | + | ||
235 | + | const result = fn(ast.tagName, props, children) |
|
236 | + | ||
237 | + | if (result.tagName === 'template' && 'content' in ast) { |
|
238 | + | const pos = ast.sourceCodeLocation |
|
239 | + | const startTag = pos && pos.startTag && position(pos.startTag) |
|
240 | + | const endTag = pos && pos.endTag && position(pos.endTag) |
|
241 | + | ||
242 | + | /** @type {Root} */ |
|
243 | + | // @ts-expect-error Types are wrong. |
|
244 | + | const content = transform(ctx, ast.content) |
|
245 | + | ||
246 | + | if (startTag && endTag && ctx.file) { |
|
247 | + | content.position = {start: startTag.end, end: endTag.start} |
|
248 | + | } |
|
249 | + | ||
250 | + | result.content = content |
|
251 | + | } |
|
252 | + | ||
253 | + | return result |
|
254 | + | } |
|
255 | + | ||
256 | + | /** |
|
257 | + | * Create clean positional information. |
|
258 | + | * |
|
259 | + | * @param {Context} ctx |
|
260 | + | * @param {Node} node |
|
261 | + | * @param {P5ElementLocation} location |
|
262 | + | * @returns {Position|null} |
|
263 | + | */ |
|
264 | + | function createLocation(ctx, node, location) { |
|
265 | + | const result = position(location) |
|
266 | + | ||
267 | + | if (node.type === 'element') { |
|
268 | + | const tail = node.children[node.children.length - 1] |
|
269 | + | ||
270 | + | // Bug for unclosed with children. |
|
271 | + | // See: <https://github.com/inikulin/parse5/issues/109>. |
|
272 | + | if ( |
|
273 | + | result && |
|
274 | + | !location.endTag && |
|
275 | + | tail && |
|
276 | + | tail.position && |
|
277 | + | tail.position.end |
|
278 | + | ) { |
|
279 | + | result.end = Object.assign({}, tail.position.end) |
|
280 | + | } |
|
281 | + | ||
282 | + | if (ctx.verbose) { |
|
283 | + | /** @type {Object.<string, Position|null>} */ |
|
284 | + | const props = {} |
|
285 | + | /** @type {string} */ |
|
286 | + | let key |
|
287 | + | ||
288 | + | for (key in location.attrs) { |
|
289 | + | if (own.call(location.attrs, key)) { |
|
290 | + | props[find(ctx.schema, key).property] = position(location.attrs[key]) |
|
291 | + | } |
|
292 | + | } |
|
293 | + | ||
294 | + | node.data = { |
|
295 | + | position: { |
|
296 | + | opening: position(location.startTag), |
|
297 | + | closing: location.endTag ? position(location.endTag) : null, |
|
298 | + | properties: props |
|
299 | + | } |
|
300 | + | } |
|
301 | + | } |
|
302 | + | } |
|
303 | + | ||
304 | + | return result |
|
305 | + | } |
|
306 | + | ||
307 | + | /** |
|
308 | + | * @param {P5Location} loc |
|
309 | + | * @returns {Position|null} |
|
310 | + | */ |
|
311 | + | function position(loc) { |
|
312 | + | const start = point({ |
|
313 | + | line: loc.startLine, |
|
314 | + | column: loc.startCol, |
|
315 | + | offset: loc.startOffset |
|
316 | + | }) |
|
317 | + | const end = point({ |
|
318 | + | line: loc.endLine, |
|
319 | + | column: loc.endCol, |
|
320 | + | offset: loc.endOffset |
|
321 | + | }) |
|
322 | + | // @ts-expect-error `null` is fine. |
|
323 | + | return start || end ? {start, end} : null |
|
324 | + | } |
|
325 | + | ||
326 | + | /** |
|
327 | + | * @param {Point} point |
|
328 | + | * @returns {Point|null} |
|
329 | + | */ |
|
330 | + | function point(point) { |
|
331 | + | return point.line && point.column ? point : null |
|
332 | + | } |
|
333 | + | ||
334 | + | /** |
|
335 | + | * @param {VFile|Options} value |
|
336 | + | * @returns {value is VFile} |
|
337 | + | */ |
|
338 | + | function isFile(value) { |
|
339 | + | return 'messages' in value |
|
340 | + | } |
1 | - | 'use strict' |
|
1 | + | /** |
|
2 | + | * @typedef {import('./lib/index.js').Options} Options |
|
3 | + | */ |
|
2 | 4 | ||
3 | - | var html = require('property-information/html') |
|
4 | - | var svg = require('property-information/svg') |
|
5 | - | var find = require('property-information/find') |
|
6 | - | var ns = require('web-namespaces') |
|
7 | - | var s = require('hastscript/svg') |
|
8 | - | var h = require('hastscript') |
|
9 | - | var count = require('ccount') |
|
10 | - | ||
11 | - | module.exports = wrapper |
|
12 | - | ||
13 | - | var own = {}.hasOwnProperty |
|
14 | - | ||
15 | - | // Handlers. |
|
16 | - | var map = { |
|
17 | - | '#document': root, |
|
18 | - | '#document-fragment': root, |
|
19 | - | '#text': text, |
|
20 | - | '#comment': comment, |
|
21 | - | '#documentType': doctype |
|
22 | - | } |
|
23 | - | ||
24 | - | // Wrapper to normalise options. |
|
25 | - | function wrapper(ast, options) { |
|
26 | - | var settings = options || {} |
|
27 | - | var file |
|
28 | - | ||
29 | - | if (settings.messages) { |
|
30 | - | file = settings |
|
31 | - | settings = {} |
|
32 | - | } else { |
|
33 | - | file = settings.file |
|
34 | - | } |
|
35 | - | ||
36 | - | return transform(ast, { |
|
37 | - | schema: settings.space === 'svg' ? svg : html, |
|
38 | - | file: file, |
|
39 | - | verbose: settings.verbose, |
|
40 | - | location: false |
|
41 | - | }) |
|
42 | - | } |
|
43 | - | ||
44 | - | // Transform a node. |
|
45 | - | function transform(ast, config) { |
|
46 | - | var schema = config.schema |
|
47 | - | var fn = own.call(map, ast.nodeName) ? map[ast.nodeName] : element |
|
48 | - | var children |
|
49 | - | var node |
|
50 | - | var pos |
|
51 | - | ||
52 | - | if (fn === element) { |
|
53 | - | config.schema = ast.namespaceURI === ns.svg ? svg : html |
|
54 | - | } |
|
55 | - | ||
56 | - | if (ast.childNodes) { |
|
57 | - | children = nodes(ast.childNodes, config) |
|
58 | - | } |
|
59 | - | ||
60 | - | node = fn(ast, children, config) |
|
61 | - | ||
62 | - | if (ast.sourceCodeLocation && config.file) { |
|
63 | - | pos = location(node, ast.sourceCodeLocation, config) |
|
64 | - | ||
65 | - | if (pos) { |
|
66 | - | config.location = true |
|
67 | - | node.position = pos |
|
68 | - | } |
|
69 | - | } |
|
70 | - | ||
71 | - | config.schema = schema |
|
72 | - | ||
73 | - | return node |
|
74 | - | } |
|
75 | - | ||
76 | - | // Transform children. |
|
77 | - | function nodes(children, config) { |
|
78 | - | var length = children.length |
|
79 | - | var index = -1 |
|
80 | - | var result = [] |
|
81 | - | ||
82 | - | while (++index < length) { |
|
83 | - | result[index] = transform(children[index], config) |
|
84 | - | } |
|
85 | - | ||
86 | - | return result |
|
87 | - | } |
|
88 | - | ||
89 | - | // Transform a document. |
|
90 | - | // Stores `ast.quirksMode` in `node.data.quirksMode`. |
|
91 | - | function root(ast, children, config) { |
|
92 | - | var node = {type: 'root', children: children, data: {}} |
|
93 | - | var doc |
|
94 | - | ||
95 | - | node.data.quirksMode = ast.mode === 'quirks' || ast.mode === 'limited-quirks' |
|
96 | - | ||
97 | - | if (config.file && config.location) { |
|
98 | - | doc = String(config.file) |
|
99 | - | ||
100 | - | node.position = { |
|
101 | - | start: {line: 1, column: 1, offset: 0}, |
|
102 | - | end: { |
|
103 | - | line: count(doc, '\n') + 1, |
|
104 | - | column: doc.length - doc.lastIndexOf('\n'), |
|
105 | - | offset: doc.length |
|
106 | - | } |
|
107 | - | } |
|
108 | - | } |
|
109 | - | ||
110 | - | return node |
|
111 | - | } |
|
112 | - | ||
113 | - | // Transform a doctype. |
|
114 | - | function doctype(ast) { |
|
115 | - | return { |
|
116 | - | type: 'doctype', |
|
117 | - | name: ast.name || '', |
|
118 | - | public: ast.publicId || null, |
|
119 | - | system: ast.systemId || null |
|
120 | - | } |
|
121 | - | } |
|
122 | - | ||
123 | - | // Transform a text. |
|
124 | - | function text(ast) { |
|
125 | - | return {type: 'text', value: ast.value} |
|
126 | - | } |
|
127 | - | ||
128 | - | // Transform a comment. |
|
129 | - | function comment(ast) { |
|
130 | - | return {type: 'comment', value: ast.data} |
|
131 | - | } |
|
132 | - | ||
133 | - | // Transform an element. |
|
134 | - | function element(ast, children, config) { |
|
135 | - | var fn = config.schema.space === 'svg' ? s : h |
|
136 | - | var name = ast.tagName |
|
137 | - | var attributes = ast.attrs |
|
138 | - | var length = attributes.length |
|
139 | - | var props = {} |
|
140 | - | var index = -1 |
|
141 | - | var attribute |
|
142 | - | var prop |
|
143 | - | var node |
|
144 | - | var pos |
|
145 | - | var start |
|
146 | - | var end |
|
147 | - | ||
148 | - | while (++index < length) { |
|
149 | - | attribute = attributes[index] |
|
150 | - | prop = (attribute.prefix ? attribute.prefix + ':' : '') + attribute.name |
|
151 | - | props[prop] = attribute.value |
|
152 | - | } |
|
153 | - | ||
154 | - | node = fn(name, props, children) |
|
155 | - | ||
156 | - | if (name === 'template' && 'content' in ast) { |
|
157 | - | pos = ast.sourceCodeLocation |
|
158 | - | start = pos && pos.startTag && position(pos.startTag).end |
|
159 | - | end = pos && pos.endTag && position(pos.endTag).start |
|
160 | - | ||
161 | - | node.content = transform(ast.content, config) |
|
162 | - | ||
163 | - | if ((start || end) && config.file) { |
|
164 | - | node.content.position = {start: start, end: end} |
|
165 | - | } |
|
166 | - | } |
|
167 | - | ||
168 | - | return node |
|
169 | - | } |
|
170 | - | ||
171 | - | // Create clean positional information. |
|
172 | - | function location(node, location, config) { |
|
173 | - | var schema = config.schema |
|
174 | - | var verbose = config.verbose |
|
175 | - | var pos = position(location) |
|
176 | - | var reference |
|
177 | - | var attributes |
|
178 | - | var attribute |
|
179 | - | var props |
|
180 | - | var prop |
|
181 | - | ||
182 | - | if (node.type === 'element') { |
|
183 | - | reference = node.children[node.children.length - 1] |
|
184 | - | ||
185 | - | // Bug for unclosed with children. |
|
186 | - | // See: <https://github.com/inikulin/parse5/issues/109>. |
|
187 | - | if ( |
|
188 | - | !location.endTag && |
|
189 | - | reference && |
|
190 | - | reference.position && |
|
191 | - | reference.position.end |
|
192 | - | ) { |
|
193 | - | pos.end = Object.assign({}, reference.position.end) |
|
194 | - | } |
|
195 | - | ||
196 | - | if (verbose) { |
|
197 | - | attributes = location.attrs |
|
198 | - | props = {} |
|
199 | - | ||
200 | - | for (attribute in attributes) { |
|
201 | - | prop = find(schema, attribute).property |
|
202 | - | props[prop] = position(attributes[attribute]) |
|
203 | - | } |
|
204 | - | ||
205 | - | node.data = { |
|
206 | - | position: { |
|
207 | - | opening: position(location.startTag), |
|
208 | - | closing: location.endTag ? position(location.endTag) : null, |
|
209 | - | properties: props |
|
210 | - | } |
|
211 | - | } |
|
212 | - | } |
|
213 | - | } |
|
214 | - | ||
215 | - | return pos |
|
216 | - | } |
|
217 | - | ||
218 | - | function position(loc) { |
|
219 | - | var start = point({ |
|
220 | - | line: loc.startLine, |
|
221 | - | column: loc.startCol, |
|
222 | - | offset: loc.startOffset |
|
223 | - | }) |
|
224 | - | var end = point({ |
|
225 | - | line: loc.endLine, |
|
226 | - | column: loc.endCol, |
|
227 | - | offset: loc.endOffset |
|
228 | - | }) |
|
229 | - | return start || end ? {start: start, end: end} : null |
|
230 | - | } |
|
231 | - | ||
232 | - | function point(point) { |
|
233 | - | return point.line && point.column ? point : null |
|
234 | - | } |
|
5 | + | export {fromParse5} from './lib/index.js' |
Learn more Showing 1 files with coverage changes found.
lib/index.js
Files | Coverage |
---|---|
index.js | 100.00% |
lib/index.js | 100.00% |
Project Totals (2 files) | 100.00% |
c5c6819
3a0adc3
762ba62
8df84b7
a57a0bb
0977456
3a594c0
1584594
7fd0848
21e9223
20e9416
8e6ee62
c8170b1
2736de8
05b63c2
2085ef4
cea54a6
7a3a3c9
e75a23b
4f3e76b
cb5d14e
d3bbec1
2103471
c9b1e27
4c1e5c5
d43f3ef