circuitjson-toolkit 1.0.3 → 1.0.16
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/AGENTS.md +5 -3
- package/README.md +21 -2
- package/docs/api.md +50 -4
- package/docs/model-format.md +21 -3
- package/package.json +3 -2
- package/spec/library-scope.md +4 -1
- package/src/core/CircuitJsonBomBuilder.mjs +143 -0
- package/src/core/CircuitJsonDocument.mjs +46 -13
- package/src/core/CircuitJsonElementValidator.mjs +990 -0
- package/src/core/CircuitJsonIndexer.mjs +773 -4
- package/src/core/CircuitJsonManufacturingBuilder.mjs +898 -0
- package/src/core/CircuitJsonManufacturingDownloadBuilder.mjs +196 -0
- package/src/core/CircuitJsonParser.mjs +22 -6
- package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +329 -0
- package/src/core/CircuitJsonPcbCopperGeometry.mjs +503 -0
- package/src/core/CircuitJsonPcbDrawingStyle.mjs +88 -0
- package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +172 -0
- package/src/core/CircuitJsonPcbNetMetadata.mjs +247 -0
- package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +70 -0
- package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +992 -0
- package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +872 -0
- package/src/core/CircuitJsonPcbPrimitiveFields.mjs +233 -0
- package/src/core/CircuitJsonPcbPrimitiveGeometry.mjs +142 -0
- package/src/core/CircuitJsonPcbPrimitiveGroups.mjs +305 -0
- package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +65 -0
- package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +895 -0
- package/src/core/CircuitJsonPcbTraceLengthModel.mjs +257 -0
- package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +683 -0
- package/src/core/CircuitJsonSourceMetadata.mjs +233 -0
- package/src/core/CircuitJsonSupportMatrixBuilder.mjs +481 -0
- package/src/core/CircuitJsonUnits.mjs +133 -8
- package/src/core/PcbBoundsSelectionModel.mjs +250 -0
- package/src/core/PcbCandidateSelectionModel.mjs +77 -0
- package/src/core/PcbDiagnosticFocusModel.mjs +423 -0
- package/src/core/PcbInteractionPrimitiveModel.mjs +560 -0
- package/src/core/SelectedPartCircuitJsonExportAdapter.mjs +335 -0
- package/src/core/spice/SpiceCompatibilityPreprocessor.mjs +139 -0
- package/src/core/spice/SpiceDirectiveParser.mjs +231 -0
- package/src/core/spice/SpiceFallbackSimulationEngine.mjs +168 -0
- package/src/core/spice/SpiceSimulationDiagnostics.mjs +234 -0
- package/src/core/spice/SpiceSimulationGraphBuilder.mjs +421 -0
- package/src/core/spice/SpiceSimulationGraphSummary.mjs +90 -0
- package/src/core/spice/SpiceSimulationService.mjs +92 -0
- package/src/core/spice/SpiceTimeSeriesNormalizer.mjs +132 -0
- package/src/index.mjs +8 -0
- package/src/renderers.mjs +29 -0
- package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +128 -0
- package/src/ui/CircuitJsonPcbSvgRenderer.mjs +964 -0
- package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +168 -0
- package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +138 -0
- package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +114 -0
- package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +130 -0
- package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +994 -0
- package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +439 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { SpiceDirectiveParser } from './SpiceDirectiveParser.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Provides a deterministic local fallback for simple transient examples.
|
|
5
|
+
*/
|
|
6
|
+
export class SpiceFallbackSimulationEngine {
|
|
7
|
+
/**
|
|
8
|
+
* Simulates constant independent sources from a SPICE netlist.
|
|
9
|
+
* @param {string} spiceString SPICE netlist text.
|
|
10
|
+
* @returns {Promise<object>}
|
|
11
|
+
*/
|
|
12
|
+
async simulate(spiceString) {
|
|
13
|
+
const timeValues =
|
|
14
|
+
SpiceFallbackSimulationEngine.#buildTimeValues(spiceString)
|
|
15
|
+
const voltageRows =
|
|
16
|
+
SpiceFallbackSimulationEngine.#buildVoltageRows(spiceString)
|
|
17
|
+
const currentRows =
|
|
18
|
+
SpiceFallbackSimulationEngine.#buildCurrentRows(spiceString)
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
dataType: 'real',
|
|
22
|
+
data: [
|
|
23
|
+
{ name: 'time', type: 'time', values: timeValues },
|
|
24
|
+
...voltageRows.map((row) => ({
|
|
25
|
+
name: row.name,
|
|
26
|
+
type: 'voltage',
|
|
27
|
+
values: timeValues.map(() => row.value)
|
|
28
|
+
})),
|
|
29
|
+
...currentRows.map((row) => ({
|
|
30
|
+
name: row.name,
|
|
31
|
+
type: 'current',
|
|
32
|
+
values: timeValues.map(() => row.value)
|
|
33
|
+
}))
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Builds transient timestamps from the netlist TRAN directive.
|
|
40
|
+
* @param {string} spiceString SPICE netlist text.
|
|
41
|
+
* @returns {number[]}
|
|
42
|
+
*/
|
|
43
|
+
static #buildTimeValues(spiceString) {
|
|
44
|
+
const tran = SpiceDirectiveParser.parseTransient(spiceString)
|
|
45
|
+
const tstep = tran?.tstep && tran.tstep > 0 ? tran.tstep : 0.001
|
|
46
|
+
const tstart = tran?.tstart ?? 0
|
|
47
|
+
const tstop = tran?.tstop && tran.tstop >= tstart ? tran.tstop : tstart
|
|
48
|
+
const stepCount = Math.max(0, Math.round((tstop - tstart) / tstep))
|
|
49
|
+
|
|
50
|
+
return Array.from({ length: stepCount + 1 }, (_, index) =>
|
|
51
|
+
SpiceFallbackSimulationEngine.#round(tstart + index * tstep)
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Builds constant voltage result rows from independent DC voltage sources.
|
|
57
|
+
* @param {string} spiceString SPICE netlist text.
|
|
58
|
+
* @returns {{ name: string, value: number }[]}
|
|
59
|
+
*/
|
|
60
|
+
static #buildVoltageRows(spiceString) {
|
|
61
|
+
const nodeVoltages = new Map([['0', 0]])
|
|
62
|
+
|
|
63
|
+
for (const line of SpiceFallbackSimulationEngine.#componentLines(
|
|
64
|
+
spiceString
|
|
65
|
+
)) {
|
|
66
|
+
const tokens = line.split(/\s+/).filter(Boolean)
|
|
67
|
+
const name = tokens[0] || ''
|
|
68
|
+
if (!/^v/i.test(name) || tokens.length < 4) continue
|
|
69
|
+
|
|
70
|
+
const positiveNode = tokens[1]
|
|
71
|
+
const negativeNode = tokens[2]
|
|
72
|
+
const value =
|
|
73
|
+
SpiceFallbackSimulationEngine.#parseDcValue(tokens.slice(3)) ??
|
|
74
|
+
0
|
|
75
|
+
const negativeValue =
|
|
76
|
+
SpiceFallbackSimulationEngine.#lookupNodeValue(
|
|
77
|
+
nodeVoltages,
|
|
78
|
+
negativeNode
|
|
79
|
+
) ?? 0
|
|
80
|
+
|
|
81
|
+
nodeVoltages.set(
|
|
82
|
+
String(positiveNode).toLowerCase(),
|
|
83
|
+
negativeValue + value
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return [...nodeVoltages.entries()]
|
|
88
|
+
.filter(([node]) => node !== '0')
|
|
89
|
+
.map(([node, value]) => ({
|
|
90
|
+
name: `v(${node})`,
|
|
91
|
+
value: SpiceFallbackSimulationEngine.#round(value)
|
|
92
|
+
}))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Builds constant current result rows from independent DC current sources.
|
|
97
|
+
* @param {string} spiceString SPICE netlist text.
|
|
98
|
+
* @returns {{ name: string, value: number }[]}
|
|
99
|
+
*/
|
|
100
|
+
static #buildCurrentRows(spiceString) {
|
|
101
|
+
const rows = []
|
|
102
|
+
|
|
103
|
+
for (const line of SpiceFallbackSimulationEngine.#componentLines(
|
|
104
|
+
spiceString
|
|
105
|
+
)) {
|
|
106
|
+
const tokens = line.split(/\s+/).filter(Boolean)
|
|
107
|
+
const name = tokens[0] || ''
|
|
108
|
+
if (!/^i/i.test(name) || tokens.length < 4) continue
|
|
109
|
+
|
|
110
|
+
rows.push({
|
|
111
|
+
name: `i(${name})`,
|
|
112
|
+
value:
|
|
113
|
+
SpiceFallbackSimulationEngine.#parseDcValue(
|
|
114
|
+
tokens.slice(3)
|
|
115
|
+
) ?? 0
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return rows
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Returns non-comment component lines from a netlist.
|
|
124
|
+
* @param {string} spiceString SPICE netlist text.
|
|
125
|
+
* @returns {string[]}
|
|
126
|
+
*/
|
|
127
|
+
static #componentLines(spiceString) {
|
|
128
|
+
return String(spiceString || '')
|
|
129
|
+
.split(/\r?\n/)
|
|
130
|
+
.map((line) => line.trim())
|
|
131
|
+
.filter(
|
|
132
|
+
(line) => line && !line.startsWith('*') && !line.startsWith('.')
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Parses a DC source value from component tail tokens.
|
|
138
|
+
* @param {string[]} tokens Component tokens after node names.
|
|
139
|
+
* @returns {number | undefined}
|
|
140
|
+
*/
|
|
141
|
+
static #parseDcValue(tokens) {
|
|
142
|
+
const dcIndex = tokens.findIndex(
|
|
143
|
+
(token) => token.toLowerCase() === 'dc'
|
|
144
|
+
)
|
|
145
|
+
const valueToken = dcIndex >= 0 ? tokens[dcIndex + 1] : tokens[0]
|
|
146
|
+
|
|
147
|
+
return SpiceDirectiveParser.parseNumber(valueToken)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Looks up a node voltage by normalized node name.
|
|
152
|
+
* @param {Map<string, number>} nodeVoltages Node voltage map.
|
|
153
|
+
* @param {string} nodeName Node name.
|
|
154
|
+
* @returns {number | undefined}
|
|
155
|
+
*/
|
|
156
|
+
static #lookupNodeValue(nodeVoltages, nodeName) {
|
|
157
|
+
return nodeVoltages.get(String(nodeName || '').toLowerCase())
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Rounds numeric output to a stable precision.
|
|
162
|
+
* @param {number} value Numeric value.
|
|
163
|
+
* @returns {number}
|
|
164
|
+
*/
|
|
165
|
+
static #round(value) {
|
|
166
|
+
return Number(Number(value).toPrecision(12))
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
const PROBE_METADATA_COMMENT_PATTERNS = [
|
|
2
|
+
{
|
|
3
|
+
probeType: 'voltage',
|
|
4
|
+
pattern:
|
|
5
|
+
/^\s*\*\s*(?:ecadforge_voltage_probe|circuitjson_voltage_probe|simulation_voltage_probe)\s+(.+)\s*$/,
|
|
6
|
+
requiredFields: [
|
|
7
|
+
'simulation_voltage_probe_id',
|
|
8
|
+
'spice_vector',
|
|
9
|
+
'source_node_name'
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
probeType: 'current',
|
|
14
|
+
pattern:
|
|
15
|
+
/^\s*\*\s*(?:ecadforge_current_probe|circuitjson_current_probe|simulation_current_probe)\s+(.+)\s*$/,
|
|
16
|
+
requiredFields: ['simulation_current_probe_id', 'spice_vector']
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Builds diagnostics for SPICE syntax that local helpers cannot resolve.
|
|
22
|
+
*/
|
|
23
|
+
export class SpiceSimulationDiagnostics {
|
|
24
|
+
/**
|
|
25
|
+
* Returns syntax diagnostics for a netlist.
|
|
26
|
+
* @param {string} spiceString SPICE netlist text.
|
|
27
|
+
* @returns {object[]}
|
|
28
|
+
*/
|
|
29
|
+
static analyze(spiceString) {
|
|
30
|
+
const diagnostics = []
|
|
31
|
+
const lines = String(spiceString || '').split(/\r?\n/)
|
|
32
|
+
|
|
33
|
+
lines.forEach((line, index) => {
|
|
34
|
+
const lineNumber = index + 1
|
|
35
|
+
diagnostics.push(
|
|
36
|
+
...SpiceSimulationDiagnostics.#probeMetadataDiagnostics(
|
|
37
|
+
line,
|
|
38
|
+
lineNumber
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
if (/^\s*\.lib\b/i.test(line)) {
|
|
43
|
+
diagnostics.push(
|
|
44
|
+
SpiceSimulationDiagnostics.#diagnostic({
|
|
45
|
+
code: 'spice_external_library_unsupported',
|
|
46
|
+
lineNumber,
|
|
47
|
+
message:
|
|
48
|
+
'External .lib directives are not resolved by the local simulator fallback.'
|
|
49
|
+
})
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (/^\s*\.include\b/i.test(line)) {
|
|
54
|
+
diagnostics.push(
|
|
55
|
+
SpiceSimulationDiagnostics.#diagnostic({
|
|
56
|
+
code: 'spice_external_include_unsupported',
|
|
57
|
+
lineNumber,
|
|
58
|
+
message:
|
|
59
|
+
'External .include directives are not resolved by the local simulator fallback.'
|
|
60
|
+
})
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (/\bpwl\s+repeat\b/i.test(line)) {
|
|
65
|
+
diagnostics.push(
|
|
66
|
+
SpiceSimulationDiagnostics.#diagnostic({
|
|
67
|
+
code: 'spice_pwl_repeat_unsupported',
|
|
68
|
+
lineNumber,
|
|
69
|
+
message:
|
|
70
|
+
'PWL REPEAT source syntax is reported for callers that need a full simulator.'
|
|
71
|
+
})
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (/^\s*\.model\b.*\bako\s*:/i.test(line)) {
|
|
76
|
+
diagnostics.push(
|
|
77
|
+
SpiceSimulationDiagnostics.#diagnostic({
|
|
78
|
+
code: 'spice_pspice_ako_model_unsupported',
|
|
79
|
+
lineNumber,
|
|
80
|
+
message:
|
|
81
|
+
'PSPICE AKO model aliases are not resolved by the local simulator fallback.'
|
|
82
|
+
})
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (/^\s*d\S*\s+\S+\s+\S+\s+\S+\s+[+-]?\d/i.test(line)) {
|
|
87
|
+
diagnostics.push(
|
|
88
|
+
SpiceSimulationDiagnostics.#diagnostic({
|
|
89
|
+
code: 'spice_pspice_diode_area_factor_unsupported',
|
|
90
|
+
lineNumber,
|
|
91
|
+
message:
|
|
92
|
+
'PSPICE diode area factor syntax is not evaluated by the local simulator fallback.'
|
|
93
|
+
})
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (/\btable\s*\{/i.test(line)) {
|
|
98
|
+
diagnostics.push(
|
|
99
|
+
SpiceSimulationDiagnostics.#diagnostic({
|
|
100
|
+
code: 'spice_pspice_table_source_unsupported',
|
|
101
|
+
lineNumber,
|
|
102
|
+
message:
|
|
103
|
+
'PSPICE TABLE source syntax is not evaluated by the local simulator fallback.'
|
|
104
|
+
})
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (/^\s*\.model\b.*\bvswitch\s*\(/i.test(line)) {
|
|
109
|
+
diagnostics.push(
|
|
110
|
+
SpiceSimulationDiagnostics.#diagnostic({
|
|
111
|
+
code: 'spice_pspice_vswitch_model_unsupported',
|
|
112
|
+
lineNumber,
|
|
113
|
+
message:
|
|
114
|
+
'PSPICE VSWITCH model syntax is not evaluated by the local simulator fallback.'
|
|
115
|
+
})
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
return diagnostics
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Returns diagnostics for requested plots that did not produce graphs.
|
|
125
|
+
* @param {{ normalizedToken: string, originalToken: string }[]} missingPlots Missing requested plot tokens.
|
|
126
|
+
* @returns {object[]}
|
|
127
|
+
*/
|
|
128
|
+
static requestedPlotDiagnostics(missingPlots) {
|
|
129
|
+
return missingPlots.map((plot) => ({
|
|
130
|
+
severity: 'warning',
|
|
131
|
+
code: 'spice_requested_plot_missing',
|
|
132
|
+
plot: plot.originalToken,
|
|
133
|
+
normalizedPlot: plot.normalizedToken,
|
|
134
|
+
message:
|
|
135
|
+
'Requested transient plot did not match simulator output: ' +
|
|
136
|
+
plot.originalToken
|
|
137
|
+
}))
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Returns diagnostics for malformed probe metadata comments on one line.
|
|
142
|
+
* @param {string} line SPICE netlist line.
|
|
143
|
+
* @param {number} lineNumber One-based source line number.
|
|
144
|
+
* @returns {object[]}
|
|
145
|
+
*/
|
|
146
|
+
static #probeMetadataDiagnostics(line, lineNumber) {
|
|
147
|
+
const diagnostics = []
|
|
148
|
+
|
|
149
|
+
for (const definition of PROBE_METADATA_COMMENT_PATTERNS) {
|
|
150
|
+
const match = line.match(definition.pattern)
|
|
151
|
+
if (!match?.[1]) continue
|
|
152
|
+
|
|
153
|
+
let parsed
|
|
154
|
+
try {
|
|
155
|
+
parsed = JSON.parse(match[1])
|
|
156
|
+
} catch {
|
|
157
|
+
diagnostics.push(
|
|
158
|
+
SpiceSimulationDiagnostics.#diagnostic({
|
|
159
|
+
code: 'spice_probe_metadata_invalid_json',
|
|
160
|
+
lineNumber,
|
|
161
|
+
probeType: definition.probeType,
|
|
162
|
+
message:
|
|
163
|
+
SpiceSimulationDiagnostics.#probeTypeLabel(
|
|
164
|
+
definition.probeType
|
|
165
|
+
) + ' probe metadata comment contains invalid JSON.'
|
|
166
|
+
})
|
|
167
|
+
)
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (
|
|
172
|
+
!SpiceSimulationDiagnostics.#hasRequiredStringFields(
|
|
173
|
+
parsed,
|
|
174
|
+
definition.requiredFields
|
|
175
|
+
)
|
|
176
|
+
) {
|
|
177
|
+
diagnostics.push(
|
|
178
|
+
SpiceSimulationDiagnostics.#diagnostic({
|
|
179
|
+
code: 'spice_probe_metadata_invalid_shape',
|
|
180
|
+
lineNumber,
|
|
181
|
+
probeType: definition.probeType,
|
|
182
|
+
message:
|
|
183
|
+
SpiceSimulationDiagnostics.#probeTypeLabel(
|
|
184
|
+
definition.probeType
|
|
185
|
+
) +
|
|
186
|
+
' probe metadata comment is missing required string fields.'
|
|
187
|
+
})
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return diagnostics
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Returns true when parsed metadata contains all required string fields.
|
|
197
|
+
* @param {unknown} parsed Parsed metadata value.
|
|
198
|
+
* @param {string[]} requiredFields Required field names.
|
|
199
|
+
* @returns {boolean}
|
|
200
|
+
*/
|
|
201
|
+
static #hasRequiredStringFields(parsed, requiredFields) {
|
|
202
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
203
|
+
return false
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return requiredFields.every(
|
|
207
|
+
(field) => typeof parsed[field] === 'string'
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Returns a display label for a probe type.
|
|
213
|
+
* @param {string} probeType Probe metadata type.
|
|
214
|
+
* @returns {string}
|
|
215
|
+
*/
|
|
216
|
+
static #probeTypeLabel(probeType) {
|
|
217
|
+
return probeType === 'voltage' ? 'Voltage' : 'Current'
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Builds a warning diagnostic object.
|
|
222
|
+
* @param {{ code: string, lineNumber: number, message: string, probeType?: string }} options Diagnostic options.
|
|
223
|
+
* @returns {object}
|
|
224
|
+
*/
|
|
225
|
+
static #diagnostic(options) {
|
|
226
|
+
return {
|
|
227
|
+
severity: 'warning',
|
|
228
|
+
code: options.code,
|
|
229
|
+
lineNumber: options.lineNumber,
|
|
230
|
+
...(options.probeType ? { probeType: options.probeType } : {}),
|
|
231
|
+
message: options.message
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|