@contractkit/plugin-python 0.11.6 → 0.11.7
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 +4 -4
- package/.turbo/turbo-test$colon$ci.log +9 -9
- package/CHANGELOG.md +8 -0
- package/dist/codegen-client.d.ts.map +1 -1
- package/dist/codegen-models.d.ts +9 -0
- package/dist/codegen-models.d.ts.map +1 -1
- package/dist/index.js +24 -8
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/codegen-client.ts +17 -2
- package/src/codegen-models.ts +34 -8
- package/tests/codegen-client.test.ts +64 -0
- package/tests/codegen-models.test.ts +49 -0
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/clover.xml +0 -559
- package/coverage/coverage-final.json +0 -4
- 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/codegen-client.ts.html +0 -2071
- package/coverage/src/codegen-models.ts.html +0 -1360
- package/coverage/src/index.html +0 -131
- package/coverage/tests/helpers.ts.html +0 -667
- package/coverage/tests/index.html +0 -116
package/src/codegen-models.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContractRootNode, ModelNode, FieldNode, ContractTypeNode } from '@contractkit/core';
|
|
1
|
+
import type { ContractRootNode, ModelNode, FieldNode, ContractTypeNode, ScalarTypeNode } from '@contractkit/core';
|
|
2
2
|
import { computeModelsWithInput, topoSortModels, collectExternalRefs, collectExternalInputRefs } from '@contractkit/core';
|
|
3
3
|
|
|
4
4
|
// ─── Public entry point ────────────────────────────────────────────────────
|
|
@@ -173,6 +173,15 @@ function scanTypeImports(type: ContractTypeNode, imports: ImportTracker): void {
|
|
|
173
173
|
|
|
174
174
|
// ─── Type rendering ───────────────────────────────────────────────────────
|
|
175
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Render a ContractKit type node as its Python type annotation (Pydantic v2 conventions).
|
|
178
|
+
*
|
|
179
|
+
* @param modelsWithInput - Model names that have a distinct `Input` variant; a `ref` to one of
|
|
180
|
+
* these renders as `<Name>Input` when `forInput` is set.
|
|
181
|
+
* @param forInput - When true, request-side refs use their `Input` variant.
|
|
182
|
+
* @throws {Error} Via the scalar renderer, if a scalar name has no Python mapping (guards against
|
|
183
|
+
* a scalar being added to core without updating this plugin).
|
|
184
|
+
*/
|
|
176
185
|
export function renderPyType(type: ContractTypeNode, modelsWithInput?: Set<string>, forInput = false): string {
|
|
177
186
|
switch (type.kind) {
|
|
178
187
|
case 'scalar':
|
|
@@ -209,7 +218,7 @@ export function renderPyType(type: ContractTypeNode, modelsWithInput?: Set<strin
|
|
|
209
218
|
}
|
|
210
219
|
}
|
|
211
220
|
|
|
212
|
-
function renderScalar(name:
|
|
221
|
+
function renderScalar(name: ScalarTypeNode['name']): string {
|
|
213
222
|
switch (name) {
|
|
214
223
|
case 'string':
|
|
215
224
|
case 'email':
|
|
@@ -231,6 +240,8 @@ function renderScalar(name: string): string {
|
|
|
231
240
|
return 'datetime';
|
|
232
241
|
case 'duration':
|
|
233
242
|
return 'timedelta';
|
|
243
|
+
case 'interval':
|
|
244
|
+
return 'str';
|
|
234
245
|
case 'uuid':
|
|
235
246
|
return 'UUID';
|
|
236
247
|
case 'null':
|
|
@@ -241,8 +252,10 @@ function renderScalar(name: string): string {
|
|
|
241
252
|
case 'json':
|
|
242
253
|
case 'object':
|
|
243
254
|
return 'Any';
|
|
244
|
-
default:
|
|
245
|
-
|
|
255
|
+
default: {
|
|
256
|
+
const _exhaustive: never = name;
|
|
257
|
+
throw new Error(`plugin-python: unmapped scalar '${String(_exhaustive)}' — add a case`);
|
|
258
|
+
}
|
|
246
259
|
}
|
|
247
260
|
}
|
|
248
261
|
|
|
@@ -261,6 +274,19 @@ export function toPythonFieldName(name: string): string {
|
|
|
261
274
|
return result;
|
|
262
275
|
}
|
|
263
276
|
|
|
277
|
+
// ─── Comment emission ─────────────────────────────────────────────────────
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Render `text` as one `#`-prefixed Python comment line per source line.
|
|
281
|
+
* Descriptions come from `#` doc comments and may contain embedded newlines;
|
|
282
|
+
* emitting them verbatim would leave every line after the first uncommented
|
|
283
|
+
* (a SyntaxError in the generated module). `indent` is the whitespace prefix
|
|
284
|
+
* that precedes `# ` at the call site.
|
|
285
|
+
*/
|
|
286
|
+
function commentLines(text: string, indent: string): string[] {
|
|
287
|
+
return text.split('\n').map(line => `${indent}# ${line}`);
|
|
288
|
+
}
|
|
289
|
+
|
|
264
290
|
// ─── Model generation ─────────────────────────────────────────────────────
|
|
265
291
|
|
|
266
292
|
function generateModel(model: ModelNode, allModelsWithInput: Set<string>, imports: ImportTracker): string[] {
|
|
@@ -278,7 +304,7 @@ function generateModel(model: ModelNode, allModelsWithInput: Set<string>, import
|
|
|
278
304
|
|
|
279
305
|
function generateTypeAlias(model: ModelNode, allModelsWithInput: Set<string>, _: ImportTracker): string[] {
|
|
280
306
|
const lines: string[] = [];
|
|
281
|
-
if (model.description) lines.push(
|
|
307
|
+
if (model.description) lines.push(...commentLines(model.description, ''));
|
|
282
308
|
if (model.deprecated) lines.push('# @deprecated');
|
|
283
309
|
lines.push(`${model.name} = ${renderPyType(model.type!, allModelsWithInput)}`);
|
|
284
310
|
if (allModelsWithInput.has(model.name)) {
|
|
@@ -289,7 +315,7 @@ function generateTypeAlias(model: ModelNode, allModelsWithInput: Set<string>, _:
|
|
|
289
315
|
|
|
290
316
|
function generateSimpleModel(model: ModelNode, allModelsWithInput: Set<string>, imports: ImportTracker): string[] {
|
|
291
317
|
const lines: string[] = [];
|
|
292
|
-
if (model.description) lines.push(
|
|
318
|
+
if (model.description) lines.push(...commentLines(model.description, ''));
|
|
293
319
|
if (model.deprecated) lines.push('# @deprecated');
|
|
294
320
|
|
|
295
321
|
const baseList = model.bases && model.bases.length > 0 ? model.bases.join(', ') : 'BaseModel';
|
|
@@ -317,7 +343,7 @@ function generateSplitModel(model: ModelNode, allModelsWithInput: Set<string>, i
|
|
|
317
343
|
|
|
318
344
|
// Read model — omit writeonly fields
|
|
319
345
|
const readFields = model.fields.filter(f => f.visibility !== 'writeonly');
|
|
320
|
-
if (model.description) lines.push(
|
|
346
|
+
if (model.description) lines.push(...commentLines(model.description, ''));
|
|
321
347
|
if (model.deprecated) lines.push('# @deprecated');
|
|
322
348
|
|
|
323
349
|
const readBaseList = model.bases && model.bases.length > 0 ? model.bases.join(', ') : 'BaseModel';
|
|
@@ -387,7 +413,7 @@ function renderField(field: FieldNode, allModelsWithInput: Set<string>, imports:
|
|
|
387
413
|
}
|
|
388
414
|
|
|
389
415
|
if (field.deprecated) lines.push(` # @deprecated`);
|
|
390
|
-
if (field.description) lines.push(
|
|
416
|
+
if (field.description) lines.push(...commentLines(field.description, ' '));
|
|
391
417
|
|
|
392
418
|
let rhs: string;
|
|
393
419
|
if (fieldAnnotations.length > 0) {
|
|
@@ -358,4 +358,68 @@ describe('generatePythonClient', () => {
|
|
|
358
358
|
expect(output).not.toContain('_fetch_with_headers');
|
|
359
359
|
});
|
|
360
360
|
});
|
|
361
|
+
|
|
362
|
+
// ─── Docstring injection (regression) ─────────────────────────────────
|
|
363
|
+
|
|
364
|
+
describe('docstring safety', () => {
|
|
365
|
+
it('escapes """ in op name/description so it cannot close the docstring early', () => {
|
|
366
|
+
const root = opRoot([
|
|
367
|
+
opRoute('/payments', [
|
|
368
|
+
opOperation('get', {
|
|
369
|
+
sdk: 'getPayment',
|
|
370
|
+
name: 'Bad """ name',
|
|
371
|
+
description: 'desc with """ triple quote',
|
|
372
|
+
responses: [opResponse(200, 'Payment')],
|
|
373
|
+
}),
|
|
374
|
+
]),
|
|
375
|
+
]);
|
|
376
|
+
const output = generatePythonClient(root);
|
|
377
|
+
|
|
378
|
+
// The escaped form is emitted...
|
|
379
|
+
expect(output).toContain('Bad \\"\\"\\" name');
|
|
380
|
+
expect(output).toContain('desc with \\"\\"\\" triple quote');
|
|
381
|
+
|
|
382
|
+
// ...and the docstring body is only closed by the real delimiter, not the
|
|
383
|
+
// injected one. Between the two `"""` fences there must be exactly the two
|
|
384
|
+
// sanitized body lines and nothing that terminates early.
|
|
385
|
+
const idx = output.indexOf('async def get_payment');
|
|
386
|
+
const body = output.slice(idx);
|
|
387
|
+
const open = body.indexOf(' """');
|
|
388
|
+
const close = body.indexOf(' """', open + 1);
|
|
389
|
+
const between = body.slice(open + ' """'.length, close);
|
|
390
|
+
expect(between).not.toMatch(/"""/); // no unescaped triple-quote inside the docstring
|
|
391
|
+
// The method body after the docstring is intact.
|
|
392
|
+
expect(body.slice(close)).toContain('await self._fetch');
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('guards a trailing backslash in a description', () => {
|
|
396
|
+
const root = opRoot([
|
|
397
|
+
opRoute('/payments', [
|
|
398
|
+
opOperation('get', {
|
|
399
|
+
sdk: 'getPayment',
|
|
400
|
+
description: 'ends with backslash\\',
|
|
401
|
+
responses: [opResponse(200, 'Payment')],
|
|
402
|
+
}),
|
|
403
|
+
]),
|
|
404
|
+
]);
|
|
405
|
+
const output = generatePythonClient(root);
|
|
406
|
+
// Trailing backslash is neutralized (space appended) so it can't escape the delimiter.
|
|
407
|
+
expect(output).toContain(' ends with backslash\\ \n');
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('keeps each line of a multi-line description indented in the docstring', () => {
|
|
411
|
+
const root = opRoot([
|
|
412
|
+
opRoute('/payments', [
|
|
413
|
+
opOperation('get', {
|
|
414
|
+
sdk: 'getPayment',
|
|
415
|
+
description: 'first line\nsecond line',
|
|
416
|
+
responses: [opResponse(200, 'Payment')],
|
|
417
|
+
}),
|
|
418
|
+
]),
|
|
419
|
+
]);
|
|
420
|
+
const output = generatePythonClient(root);
|
|
421
|
+
expect(output).toContain(' first line');
|
|
422
|
+
expect(output).toContain(' second line');
|
|
423
|
+
});
|
|
424
|
+
});
|
|
361
425
|
});
|
|
@@ -27,6 +27,11 @@ describe('renderPyType', () => {
|
|
|
27
27
|
expect(renderPyType(scalarType('unknown'))).toBe('Any');
|
|
28
28
|
expect(renderPyType(scalarType('json'))).toBe('Any');
|
|
29
29
|
expect(renderPyType(scalarType('object'))).toBe('Any');
|
|
30
|
+
expect(renderPyType(scalarType('interval'))).toBe('str');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('throws on an unmapped scalar name', () => {
|
|
34
|
+
expect(() => renderPyType({ kind: 'scalar', name: 'decimal' } as any)).toThrow(/unmapped scalar 'decimal'/);
|
|
30
35
|
});
|
|
31
36
|
|
|
32
37
|
it('renders enum', () => {
|
|
@@ -293,3 +298,47 @@ describe('generatePydanticModels', () => {
|
|
|
293
298
|
expect(output).toContain('meta: dict[str, str]');
|
|
294
299
|
});
|
|
295
300
|
});
|
|
301
|
+
|
|
302
|
+
// ─── Multi-line descriptions (regression) ─────────────────────────────────
|
|
303
|
+
|
|
304
|
+
describe('multi-line descriptions', () => {
|
|
305
|
+
it('comments every line of a multi-line model description', () => {
|
|
306
|
+
const root = contractRoot([
|
|
307
|
+
model('Payment', [field('amount', scalarType('number'))], {
|
|
308
|
+
description: 'A payment record.\nSecond line of docs.',
|
|
309
|
+
}),
|
|
310
|
+
]);
|
|
311
|
+
const output = generatePydanticModels(root);
|
|
312
|
+
expect(output).toContain('# A payment record.');
|
|
313
|
+
expect(output).toContain('# Second line of docs.');
|
|
314
|
+
// No physical line of a description may be left uncommented (would be a SyntaxError).
|
|
315
|
+
expect(output).not.toMatch(/^Second line of docs\.$/m);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('comments every line of a multi-line field description with field indent', () => {
|
|
319
|
+
const root = contractRoot([
|
|
320
|
+
model('Payment', [
|
|
321
|
+
field('amount', scalarType('number'), {
|
|
322
|
+
description: 'Amount in cents.\nMust be non-negative.',
|
|
323
|
+
}),
|
|
324
|
+
]),
|
|
325
|
+
]);
|
|
326
|
+
const output = generatePydanticModels(root);
|
|
327
|
+
expect(output).toContain(' # Amount in cents.');
|
|
328
|
+
expect(output).toContain(' # Must be non-negative.');
|
|
329
|
+
expect(output).not.toMatch(/^\s*Must be non-negative\.$/m);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('comments every line of a multi-line type-alias description', () => {
|
|
333
|
+
const root = contractRoot([
|
|
334
|
+
model('Status', [], {
|
|
335
|
+
type: enumType('pending', 'done'),
|
|
336
|
+
description: 'Lifecycle status.\nExtra detail line.',
|
|
337
|
+
}),
|
|
338
|
+
]);
|
|
339
|
+
const output = generatePydanticModels(root);
|
|
340
|
+
expect(output).toContain('# Lifecycle status.');
|
|
341
|
+
expect(output).toContain('# Extra detail line.');
|
|
342
|
+
expect(output).not.toMatch(/^Extra detail line\.$/m);
|
|
343
|
+
});
|
|
344
|
+
});
|
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);
|