@contractkit/openapi-to-ck 0.9.1 → 0.9.3
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.
- package/.turbo/turbo-build$colon$ci.log +5 -5
- package/.turbo/turbo-test$colon$ci.log +12 -12
- package/CHANGELOG.md +15 -0
- package/dist/ast-to-ck.d.ts +14 -0
- package/dist/ast-to-ck.d.ts.map +1 -1
- package/dist/{chunk-FMEMOXYD.js → chunk-76CPOLZK.js} +22 -10
- package/dist/chunk-76CPOLZK.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/plugin.js +1 -1
- package/package.json +4 -4
- package/src/ast-to-ck.ts +50 -9
- package/tests/ast-to-ck.test.ts +77 -0
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/clover.xml +0 -879
- package/coverage/coverage-final.json +0 -10
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -131
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/coverage/src/ast-to-ck.ts.html +0 -1363
- package/coverage/src/circular-refs.ts.html +0 -298
- package/coverage/src/convert.ts.html +0 -535
- package/coverage/src/index.html +0 -221
- package/coverage/src/normalize.ts.html +0 -1162
- package/coverage/src/paths-to-ast.ts.html +0 -1129
- package/coverage/src/schema-to-ast.ts.html +0 -1378
- package/coverage/src/tag-splitter.ts.html +0 -685
- package/coverage/src/warnings.ts.html +0 -154
- package/coverage/tests/helpers.ts.html +0 -559
- package/coverage/tests/index.html +0 -116
- package/dist/chunk-FMEMOXYD.js.map +0 -1
package/src/ast-to-ck.ts
CHANGED
|
@@ -16,13 +16,48 @@ import type {
|
|
|
16
16
|
|
|
17
17
|
const INDENT = ' '; // 4 spaces
|
|
18
18
|
|
|
19
|
+
/** Matches a bare identifier that needs no quoting (mirrors serializeDefault). */
|
|
20
|
+
const IDENT_RE = /^[a-zA-Z_$][a-zA-Z0-9_$\-.]*$/;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Flatten a description to a single line so it is safe to embed in a trailing
|
|
24
|
+
* `# ...` comment. OpenAPI descriptions routinely contain newlines; an embedded
|
|
25
|
+
* newline would terminate the comment and dump the rest as raw `.ck` source.
|
|
26
|
+
*/
|
|
27
|
+
function singleLineComment(text: string): string {
|
|
28
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Serialize an enum value as a `.ck` enum argument. Bare identifiers are emitted
|
|
33
|
+
* as-is; anything else (spaces, punctuation) is wrapped in a string literal.
|
|
34
|
+
* `.ck` string literals have no escape sequences, so when the value contains a
|
|
35
|
+
* double quote we fall back to single quotes (and vice versa).
|
|
36
|
+
*/
|
|
37
|
+
function quoteEnumValue(value: string): string {
|
|
38
|
+
if (IDENT_RE.test(value)) return value;
|
|
39
|
+
if (!value.includes('"')) return `"${value}"`;
|
|
40
|
+
if (!value.includes("'")) return `'${value}'`;
|
|
41
|
+
// Value contains both quote styles; `.ck` cannot escape, so keep double
|
|
42
|
+
// quotes and preserve the inner ones as best we can.
|
|
43
|
+
return `"${value}"`;
|
|
44
|
+
}
|
|
45
|
+
|
|
19
46
|
// ─── Public API ───────────────────────────────────────────────────────────
|
|
20
47
|
|
|
48
|
+
/** Options controlling how a {@link CkRootNode} is rendered to `.ck` source. */
|
|
21
49
|
export interface SerializeOptions {
|
|
22
50
|
/** Emit descriptions as inline # comments. Default: true. */
|
|
23
51
|
includeComments?: boolean;
|
|
24
52
|
}
|
|
25
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Serialize a `.ck` AST back to formatted `.ck` source text. Emits the options
|
|
56
|
+
* block first, then every model, then every route, separated by blank lines and
|
|
57
|
+
* terminated with a trailing newline. Descriptions become trailing `# ...`
|
|
58
|
+
* comments unless `options.includeComments` is `false`. The output is designed
|
|
59
|
+
* to re-parse cleanly via `parseCk` (see round-trip tests).
|
|
60
|
+
*/
|
|
26
61
|
export function astToCk(root: CkRootNode, options: SerializeOptions = {}): string {
|
|
27
62
|
const { includeComments = true } = options;
|
|
28
63
|
const ctx: Ctx = { includeComments };
|
|
@@ -114,7 +149,7 @@ function serializeModel(model: ModelNode, ctx: Ctx): string {
|
|
|
114
149
|
}
|
|
115
150
|
|
|
116
151
|
const prefix = prefixes.length > 0 ? prefixes.join(' ') + ' ' : '';
|
|
117
|
-
const comment = ctx.includeComments && model.description ? ` # ${model.description}` : '';
|
|
152
|
+
const comment = ctx.includeComments && model.description ? ` # ${singleLineComment(model.description)}` : '';
|
|
118
153
|
|
|
119
154
|
// Type alias: contract Name: typeExpression
|
|
120
155
|
if (model.type) {
|
|
@@ -151,7 +186,7 @@ function serializeField(field: FieldNode, depth: number, ctx: Ctx): string {
|
|
|
151
186
|
}
|
|
152
187
|
|
|
153
188
|
const defaultVal = field.default !== undefined ? ` = ${serializeDefault(field.default)}` : '';
|
|
154
|
-
const comment = ctx.includeComments && field.description ? ` # ${field.description}` : '';
|
|
189
|
+
const comment = ctx.includeComments && field.description ? ` # ${singleLineComment(field.description)}` : '';
|
|
155
190
|
|
|
156
191
|
return `${indent}${field.name}${optional}: ${deprecated}${visibility}${typeStr}${defaultVal}${comment}`;
|
|
157
192
|
}
|
|
@@ -173,6 +208,12 @@ function serializeDefault(value: string | number | boolean): string {
|
|
|
173
208
|
|
|
174
209
|
// ─── Types ────────────────────────────────────────────────────────────────
|
|
175
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Serialize a single {@link ContractTypeNode} to its inline `.ck` type
|
|
213
|
+
* expression (e.g. `array(User, min=1)`, `string | int`, `enum(asc, desc)`).
|
|
214
|
+
* Recurses through composite types; enum values are quoted as needed via
|
|
215
|
+
* {@link quoteEnumValue}.
|
|
216
|
+
*/
|
|
176
217
|
export function serializeType(type: ContractTypeNode): string {
|
|
177
218
|
switch (type.kind) {
|
|
178
219
|
case 'scalar':
|
|
@@ -184,7 +225,7 @@ export function serializeType(type: ContractTypeNode): string {
|
|
|
184
225
|
case 'record':
|
|
185
226
|
return `record(${serializeType(type.key)}, ${serializeType(type.value)})`;
|
|
186
227
|
case 'enum':
|
|
187
|
-
return `enum(${type.values.join(', ')})`;
|
|
228
|
+
return `enum(${type.values.map(quoteEnumValue).join(', ')})`;
|
|
188
229
|
case 'literal':
|
|
189
230
|
return serializeLiteral(type);
|
|
190
231
|
case 'union':
|
|
@@ -251,7 +292,7 @@ function serializeRoute(route: OpRouteNode, ctx: Ctx): string {
|
|
|
251
292
|
const lines: string[] = [];
|
|
252
293
|
|
|
253
294
|
const modStr = serializeModifiers(route.modifiers);
|
|
254
|
-
const comment = ctx.includeComments && route.description ? ` # ${route.description}` : '';
|
|
295
|
+
const comment = ctx.includeComments && route.description ? ` # ${singleLineComment(route.description)}` : '';
|
|
255
296
|
lines.push(`operation${modStr} ${route.path}: {${comment}`);
|
|
256
297
|
|
|
257
298
|
// Route-level params
|
|
@@ -276,7 +317,7 @@ function serializeRoute(route: OpRouteNode, ctx: Ctx): string {
|
|
|
276
317
|
function serializeOperation(lines: string[], op: OpOperationNode, depth: number, ctx: Ctx): string[] {
|
|
277
318
|
const indent = INDENT.repeat(depth);
|
|
278
319
|
const modStr = serializeModifiers(op.modifiers);
|
|
279
|
-
const comment = ctx.includeComments && op.description ? ` # ${op.description}` : '';
|
|
320
|
+
const comment = ctx.includeComments && op.description ? ` # ${singleLineComment(op.description)}` : '';
|
|
280
321
|
lines.push(`${indent}${op.method}${modStr}: {${comment}`);
|
|
281
322
|
|
|
282
323
|
const inner = INDENT.repeat(depth + 1);
|
|
@@ -293,7 +334,7 @@ function serializeOperation(lines: string[], op: OpOperationNode, depth: number,
|
|
|
293
334
|
|
|
294
335
|
// Signature
|
|
295
336
|
if (op.signature) {
|
|
296
|
-
const sigComment = ctx.includeComments && op.signatureDescription ? ` # ${op.signatureDescription}` : '';
|
|
337
|
+
const sigComment = ctx.includeComments && op.signatureDescription ? ` # ${singleLineComment(op.signatureDescription)}` : '';
|
|
297
338
|
if (op.signaturePolicy) {
|
|
298
339
|
lines.push(`${inner}signature: {`);
|
|
299
340
|
lines.push(`${inner} options: ${op.signature}${sigComment}`);
|
|
@@ -363,7 +404,7 @@ function serializeParamSource(lines: string[], keyword: string, source: ParamSou
|
|
|
363
404
|
typeStr = `${typeStr} | null`;
|
|
364
405
|
}
|
|
365
406
|
const defaultVal = param.default !== undefined ? ` = ${serializeDefault(param.default)}` : '';
|
|
366
|
-
const comment = ctx.includeComments && param.description ? ` # ${param.description}` : '';
|
|
407
|
+
const comment = ctx.includeComments && param.description ? ` # ${singleLineComment(param.description)}` : '';
|
|
367
408
|
lines.push(`${INDENT.repeat(depth + 1)}${param.name}${optional}: ${typeStr}${defaultVal}${comment}`);
|
|
368
409
|
}
|
|
369
410
|
lines.push(`${indent}}`);
|
|
@@ -393,7 +434,7 @@ function serializeResponses(lines: string[], responses: OpResponseNode[], depth:
|
|
|
393
434
|
lines.push(`${INDENT.repeat(depth + 2)}headers: {`);
|
|
394
435
|
for (const h of resp.headers!) {
|
|
395
436
|
const opt = h.optional ? '?' : '';
|
|
396
|
-
const trail = h.description ? ` # ${h.description}` : '';
|
|
437
|
+
const trail = h.description ? ` # ${singleLineComment(h.description)}` : '';
|
|
397
438
|
lines.push(`${INDENT.repeat(depth + 3)}${h.name}${opt}: ${serializeType(h.type)}${trail}`);
|
|
398
439
|
}
|
|
399
440
|
lines.push(`${INDENT.repeat(depth + 2)}}`);
|
|
@@ -415,7 +456,7 @@ function serializeSecurityBlock(lines: string[], security: SecurityNode, depth:
|
|
|
415
456
|
|
|
416
457
|
const sec = security as SecurityFields;
|
|
417
458
|
if (sec.policy !== undefined) {
|
|
418
|
-
const comment = ctx.includeComments && sec.policyDescription ? ` # ${sec.policyDescription}` : '';
|
|
459
|
+
const comment = ctx.includeComments && sec.policyDescription ? ` # ${singleLineComment(sec.policyDescription)}` : '';
|
|
419
460
|
const value = sec.policy === false ? 'none' : sec.policy;
|
|
420
461
|
lines.push(`${indent}security: {`);
|
|
421
462
|
lines.push(`${INDENT.repeat(depth + 1)}policy: ${value}${comment}`);
|
package/tests/ast-to-ck.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { parseCk, DiagnosticCollector } from '@contractkit/core';
|
|
2
3
|
import { astToCk, serializeType } from '../src/ast-to-ck.js';
|
|
3
4
|
import {
|
|
4
5
|
ckRoot,
|
|
@@ -449,3 +450,79 @@ describe('full document', () => {
|
|
|
449
450
|
expect(result).toContain(' service: LedgerService.listAccounts');
|
|
450
451
|
});
|
|
451
452
|
});
|
|
453
|
+
|
|
454
|
+
// ─── Round-trip regressions ─────────────────────────────────────────────────
|
|
455
|
+
|
|
456
|
+
/** Parse `.ck` source and return true when it re-parses with no errors. */
|
|
457
|
+
function parsesCleanly(source: string): boolean {
|
|
458
|
+
const diag = new DiagnosticCollector();
|
|
459
|
+
parseCk(source, 'roundtrip.ck', diag);
|
|
460
|
+
return !diag.hasErrors();
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
describe('round-trip: multi-line descriptions', () => {
|
|
464
|
+
it('flattens a multi-line model description into a single-line trailing comment', () => {
|
|
465
|
+
const root = ckRoot({
|
|
466
|
+
models: [model('User', [field('id', scalarType('uuid'))], { description: 'A user.\nSpans multiple\nlines.' })],
|
|
467
|
+
});
|
|
468
|
+
const result = astToCk(root);
|
|
469
|
+
expect(result).toContain('contract User: { # A user. Spans multiple lines.');
|
|
470
|
+
// The comment line must not contain an embedded newline that leaks source.
|
|
471
|
+
const commentLine = result.split('\n').find(l => l.includes('contract User'))!;
|
|
472
|
+
expect(commentLine).toBe('contract User: { # A user. Spans multiple lines.');
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('flattens a multi-line field description', () => {
|
|
476
|
+
const root = ckRoot({
|
|
477
|
+
models: [model('User', [field('id', scalarType('uuid'), { description: 'The\nuser\nID' })])],
|
|
478
|
+
});
|
|
479
|
+
const result = astToCk(root);
|
|
480
|
+
expect(result).toContain(' id: uuid # The user ID');
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('re-parses cleanly with multi-line descriptions on model and field', () => {
|
|
484
|
+
const root = ckRoot({
|
|
485
|
+
models: [
|
|
486
|
+
model(
|
|
487
|
+
'User',
|
|
488
|
+
[
|
|
489
|
+
field('id', scalarType('uuid'), { visibility: 'readonly', description: 'The user ID.\nGenerated server-side.' }),
|
|
490
|
+
field('name', scalarType('string'), { description: 'Display name.\r\n\r\nCan contain\ttabs.' }),
|
|
491
|
+
],
|
|
492
|
+
{ description: 'A user record.\nMultiple paragraphs here.\n\nEven blank lines.' },
|
|
493
|
+
),
|
|
494
|
+
],
|
|
495
|
+
});
|
|
496
|
+
const result = astToCk(root);
|
|
497
|
+
expect(parsesCleanly(result)).toBe(true);
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
describe('round-trip: enum values needing quotes', () => {
|
|
502
|
+
it('quotes enum values that contain spaces', () => {
|
|
503
|
+
expect(serializeType(enumType('Not Started', 'In Progress', 'Done'))).toBe('enum("Not Started", "In Progress", Done)');
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('leaves bare-identifier enum values unquoted', () => {
|
|
507
|
+
expect(serializeType(enumType('asc', 'desc'))).toBe('enum(asc, desc)');
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('falls back to single quotes when the value contains a double quote', () => {
|
|
511
|
+
expect(serializeType(enumType('a "quoted" value'))).toBe(`enum('a "quoted" value')`);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it('keeps double quotes when the value contains both quote styles', () => {
|
|
515
|
+
// `.ck` string literals have no escape sequences, so a value with both
|
|
516
|
+
// quote styles cannot round-trip; the serializer keeps double quotes.
|
|
517
|
+
expect(serializeType(enumType(`a "b" 'c'`))).toBe(`enum("a "b" 'c'")`);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('re-parses cleanly with space-containing enum values', () => {
|
|
521
|
+
const root = ckRoot({
|
|
522
|
+
models: [model('Task', [field('status', enumType('Not Started', 'in progress', 'Done'))])],
|
|
523
|
+
});
|
|
524
|
+
const result = astToCk(root);
|
|
525
|
+
expect(result).toContain('status: enum("Not Started", "in progress", Done)');
|
|
526
|
+
expect(parsesCleanly(result)).toBe(true);
|
|
527
|
+
});
|
|
528
|
+
});
|
package/coverage/base.css
DELETED
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
body, html {
|
|
2
|
-
margin:0; padding: 0;
|
|
3
|
-
height: 100%;
|
|
4
|
-
}
|
|
5
|
-
body {
|
|
6
|
-
font-family: Helvetica Neue, Helvetica, Arial;
|
|
7
|
-
font-size: 14px;
|
|
8
|
-
color:#333;
|
|
9
|
-
}
|
|
10
|
-
.small { font-size: 12px; }
|
|
11
|
-
*, *:after, *:before {
|
|
12
|
-
-webkit-box-sizing:border-box;
|
|
13
|
-
-moz-box-sizing:border-box;
|
|
14
|
-
box-sizing:border-box;
|
|
15
|
-
}
|
|
16
|
-
h1 { font-size: 20px; margin: 0;}
|
|
17
|
-
h2 { font-size: 14px; }
|
|
18
|
-
pre {
|
|
19
|
-
font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
|
20
|
-
margin: 0;
|
|
21
|
-
padding: 0;
|
|
22
|
-
-moz-tab-size: 2;
|
|
23
|
-
-o-tab-size: 2;
|
|
24
|
-
tab-size: 2;
|
|
25
|
-
}
|
|
26
|
-
a { color:#0074D9; text-decoration:none; }
|
|
27
|
-
a:hover { text-decoration:underline; }
|
|
28
|
-
.strong { font-weight: bold; }
|
|
29
|
-
.space-top1 { padding: 10px 0 0 0; }
|
|
30
|
-
.pad2y { padding: 20px 0; }
|
|
31
|
-
.pad1y { padding: 10px 0; }
|
|
32
|
-
.pad2x { padding: 0 20px; }
|
|
33
|
-
.pad2 { padding: 20px; }
|
|
34
|
-
.pad1 { padding: 10px; }
|
|
35
|
-
.space-left2 { padding-left:55px; }
|
|
36
|
-
.space-right2 { padding-right:20px; }
|
|
37
|
-
.center { text-align:center; }
|
|
38
|
-
.clearfix { display:block; }
|
|
39
|
-
.clearfix:after {
|
|
40
|
-
content:'';
|
|
41
|
-
display:block;
|
|
42
|
-
height:0;
|
|
43
|
-
clear:both;
|
|
44
|
-
visibility:hidden;
|
|
45
|
-
}
|
|
46
|
-
.fl { float: left; }
|
|
47
|
-
@media only screen and (max-width:640px) {
|
|
48
|
-
.col3 { width:100%; max-width:100%; }
|
|
49
|
-
.hide-mobile { display:none!important; }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.quiet {
|
|
53
|
-
color: #7f7f7f;
|
|
54
|
-
color: rgba(0,0,0,0.5);
|
|
55
|
-
}
|
|
56
|
-
.quiet a { opacity: 0.7; }
|
|
57
|
-
|
|
58
|
-
.fraction {
|
|
59
|
-
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
|
|
60
|
-
font-size: 10px;
|
|
61
|
-
color: #555;
|
|
62
|
-
background: #E8E8E8;
|
|
63
|
-
padding: 4px 5px;
|
|
64
|
-
border-radius: 3px;
|
|
65
|
-
vertical-align: middle;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
div.path a:link, div.path a:visited { color: #333; }
|
|
69
|
-
table.coverage {
|
|
70
|
-
border-collapse: collapse;
|
|
71
|
-
margin: 10px 0 0 0;
|
|
72
|
-
padding: 0;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
table.coverage td {
|
|
76
|
-
margin: 0;
|
|
77
|
-
padding: 0;
|
|
78
|
-
vertical-align: top;
|
|
79
|
-
}
|
|
80
|
-
table.coverage td.line-count {
|
|
81
|
-
text-align: right;
|
|
82
|
-
padding: 0 5px 0 20px;
|
|
83
|
-
}
|
|
84
|
-
table.coverage td.line-coverage {
|
|
85
|
-
text-align: right;
|
|
86
|
-
padding-right: 10px;
|
|
87
|
-
min-width:20px;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
table.coverage td span.cline-any {
|
|
91
|
-
display: inline-block;
|
|
92
|
-
padding: 0 5px;
|
|
93
|
-
width: 100%;
|
|
94
|
-
}
|
|
95
|
-
.missing-if-branch {
|
|
96
|
-
display: inline-block;
|
|
97
|
-
margin-right: 5px;
|
|
98
|
-
border-radius: 3px;
|
|
99
|
-
position: relative;
|
|
100
|
-
padding: 0 4px;
|
|
101
|
-
background: #333;
|
|
102
|
-
color: yellow;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.skip-if-branch {
|
|
106
|
-
display: none;
|
|
107
|
-
margin-right: 10px;
|
|
108
|
-
position: relative;
|
|
109
|
-
padding: 0 4px;
|
|
110
|
-
background: #ccc;
|
|
111
|
-
color: white;
|
|
112
|
-
}
|
|
113
|
-
.missing-if-branch .typ, .skip-if-branch .typ {
|
|
114
|
-
color: inherit !important;
|
|
115
|
-
}
|
|
116
|
-
.coverage-summary {
|
|
117
|
-
border-collapse: collapse;
|
|
118
|
-
width: 100%;
|
|
119
|
-
}
|
|
120
|
-
.coverage-summary tr { border-bottom: 1px solid #bbb; }
|
|
121
|
-
.keyline-all { border: 1px solid #ddd; }
|
|
122
|
-
.coverage-summary td, .coverage-summary th { padding: 10px; }
|
|
123
|
-
.coverage-summary tbody { border: 1px solid #bbb; }
|
|
124
|
-
.coverage-summary td { border-right: 1px solid #bbb; }
|
|
125
|
-
.coverage-summary td:last-child { border-right: none; }
|
|
126
|
-
.coverage-summary th {
|
|
127
|
-
text-align: left;
|
|
128
|
-
font-weight: normal;
|
|
129
|
-
white-space: nowrap;
|
|
130
|
-
}
|
|
131
|
-
.coverage-summary th.file { border-right: none !important; }
|
|
132
|
-
.coverage-summary th.pct { }
|
|
133
|
-
.coverage-summary th.pic,
|
|
134
|
-
.coverage-summary th.abs,
|
|
135
|
-
.coverage-summary td.pct,
|
|
136
|
-
.coverage-summary td.abs { text-align: right; }
|
|
137
|
-
.coverage-summary td.file { white-space: nowrap; }
|
|
138
|
-
.coverage-summary td.pic { min-width: 120px !important; }
|
|
139
|
-
.coverage-summary tfoot td { }
|
|
140
|
-
|
|
141
|
-
.coverage-summary .sorter {
|
|
142
|
-
height: 10px;
|
|
143
|
-
width: 7px;
|
|
144
|
-
display: inline-block;
|
|
145
|
-
margin-left: 0.5em;
|
|
146
|
-
background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
|
|
147
|
-
}
|
|
148
|
-
.coverage-summary .sorted .sorter {
|
|
149
|
-
background-position: 0 -20px;
|
|
150
|
-
}
|
|
151
|
-
.coverage-summary .sorted-desc .sorter {
|
|
152
|
-
background-position: 0 -10px;
|
|
153
|
-
}
|
|
154
|
-
.status-line { height: 10px; }
|
|
155
|
-
/* yellow */
|
|
156
|
-
.cbranch-no { background: yellow !important; color: #111; }
|
|
157
|
-
/* dark red */
|
|
158
|
-
.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
|
|
159
|
-
.low .chart { border:1px solid #C21F39 }
|
|
160
|
-
.highlighted,
|
|
161
|
-
.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
|
|
162
|
-
background: #C21F39 !important;
|
|
163
|
-
}
|
|
164
|
-
/* medium red */
|
|
165
|
-
.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
|
|
166
|
-
/* light red */
|
|
167
|
-
.low, .cline-no { background:#FCE1E5 }
|
|
168
|
-
/* light green */
|
|
169
|
-
.high, .cline-yes { background:rgb(230,245,208) }
|
|
170
|
-
/* medium green */
|
|
171
|
-
.cstat-yes { background:rgb(161,215,106) }
|
|
172
|
-
/* dark green */
|
|
173
|
-
.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
|
|
174
|
-
.high .chart { border:1px solid rgb(77,146,33) }
|
|
175
|
-
/* dark yellow (gold) */
|
|
176
|
-
.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
|
|
177
|
-
.medium .chart { border:1px solid #f9cd0b; }
|
|
178
|
-
/* light yellow */
|
|
179
|
-
.medium { background: #fff4c2; }
|
|
180
|
-
|
|
181
|
-
.cstat-skip { background: #ddd; color: #111; }
|
|
182
|
-
.fstat-skip { background: #ddd; color: #111 !important; }
|
|
183
|
-
.cbranch-skip { background: #ddd !important; color: #111; }
|
|
184
|
-
|
|
185
|
-
span.cline-neutral { background: #eaeaea; }
|
|
186
|
-
|
|
187
|
-
.coverage-summary td.empty {
|
|
188
|
-
opacity: .5;
|
|
189
|
-
padding-top: 4px;
|
|
190
|
-
padding-bottom: 4px;
|
|
191
|
-
line-height: 1;
|
|
192
|
-
color: #888;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.cover-fill, .cover-empty {
|
|
196
|
-
display:inline-block;
|
|
197
|
-
height: 12px;
|
|
198
|
-
}
|
|
199
|
-
.chart {
|
|
200
|
-
line-height: 0;
|
|
201
|
-
}
|
|
202
|
-
.cover-empty {
|
|
203
|
-
background: white;
|
|
204
|
-
}
|
|
205
|
-
.cover-full {
|
|
206
|
-
border-right: none !important;
|
|
207
|
-
}
|
|
208
|
-
pre.prettyprint {
|
|
209
|
-
border: none !important;
|
|
210
|
-
padding: 0 !important;
|
|
211
|
-
margin: 0 !important;
|
|
212
|
-
}
|
|
213
|
-
.com { color: #999 !important; }
|
|
214
|
-
.ignore-none { color: #999; font-weight: normal; }
|
|
215
|
-
|
|
216
|
-
.wrapper {
|
|
217
|
-
min-height: 100%;
|
|
218
|
-
height: auto !important;
|
|
219
|
-
height: 100%;
|
|
220
|
-
margin: 0 auto -48px;
|
|
221
|
-
}
|
|
222
|
-
.footer, .push {
|
|
223
|
-
height: 48px;
|
|
224
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
|
-
var jumpToCode = (function init() {
|
|
3
|
-
// Classes of code we would like to highlight in the file view
|
|
4
|
-
var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
|
|
5
|
-
|
|
6
|
-
// Elements to highlight in the file listing view
|
|
7
|
-
var fileListingElements = ['td.pct.low'];
|
|
8
|
-
|
|
9
|
-
// We don't want to select elements that are direct descendants of another match
|
|
10
|
-
var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
|
|
11
|
-
|
|
12
|
-
// Selector that finds elements on the page to which we can jump
|
|
13
|
-
var selector =
|
|
14
|
-
fileListingElements.join(', ') +
|
|
15
|
-
', ' +
|
|
16
|
-
notSelector +
|
|
17
|
-
missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
|
|
18
|
-
|
|
19
|
-
// The NodeList of matching elements
|
|
20
|
-
var missingCoverageElements = document.querySelectorAll(selector);
|
|
21
|
-
|
|
22
|
-
var currentIndex;
|
|
23
|
-
|
|
24
|
-
function toggleClass(index) {
|
|
25
|
-
missingCoverageElements
|
|
26
|
-
.item(currentIndex)
|
|
27
|
-
.classList.remove('highlighted');
|
|
28
|
-
missingCoverageElements.item(index).classList.add('highlighted');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function makeCurrent(index) {
|
|
32
|
-
toggleClass(index);
|
|
33
|
-
currentIndex = index;
|
|
34
|
-
missingCoverageElements.item(index).scrollIntoView({
|
|
35
|
-
behavior: 'smooth',
|
|
36
|
-
block: 'center',
|
|
37
|
-
inline: 'center'
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function goToPrevious() {
|
|
42
|
-
var nextIndex = 0;
|
|
43
|
-
if (typeof currentIndex !== 'number' || currentIndex === 0) {
|
|
44
|
-
nextIndex = missingCoverageElements.length - 1;
|
|
45
|
-
} else if (missingCoverageElements.length > 1) {
|
|
46
|
-
nextIndex = currentIndex - 1;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
makeCurrent(nextIndex);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function goToNext() {
|
|
53
|
-
var nextIndex = 0;
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
typeof currentIndex === 'number' &&
|
|
57
|
-
currentIndex < missingCoverageElements.length - 1
|
|
58
|
-
) {
|
|
59
|
-
nextIndex = currentIndex + 1;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
makeCurrent(nextIndex);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return function jump(event) {
|
|
66
|
-
if (
|
|
67
|
-
document.getElementById('fileSearch') === document.activeElement &&
|
|
68
|
-
document.activeElement != null
|
|
69
|
-
) {
|
|
70
|
-
// if we're currently focused on the search input, we don't want to navigate
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
switch (event.which) {
|
|
75
|
-
case 78: // n
|
|
76
|
-
case 74: // j
|
|
77
|
-
goToNext();
|
|
78
|
-
break;
|
|
79
|
-
case 66: // b
|
|
80
|
-
case 75: // k
|
|
81
|
-
case 80: // p
|
|
82
|
-
goToPrevious();
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
})();
|
|
87
|
-
window.addEventListener('keydown', jumpToCode);
|