@cashscript/utils 0.13.0-next.8 → 0.13.0-next.9
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/dist/artifact.js +54 -26
- package/dist/bitauth-script.js +53 -12
- package/dist/fingerprint.js +1 -1
- package/dist/types.d.ts +3 -1
- package/dist/types.js +3 -2
- package/package.json +2 -2
package/dist/artifact.js
CHANGED
|
@@ -1,37 +1,65 @@
|
|
|
1
1
|
export function formatArtifact(artifact, format) {
|
|
2
2
|
if (format === 'ts') {
|
|
3
|
-
// We remove any undefined values to make the artifact serializable
|
|
3
|
+
// We remove any undefined values to make the artifact serializable
|
|
4
4
|
const normalisedArtifact = JSON.parse(JSON.stringify(artifact));
|
|
5
|
-
return `export default ${
|
|
5
|
+
return `export default ${stringify(normalisedArtifact, 'ts')} as const;\n`;
|
|
6
6
|
}
|
|
7
|
-
return
|
|
7
|
+
return stringify(artifact, 'json');
|
|
8
8
|
}
|
|
9
9
|
const indent = (level) => ' '.repeat(level);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
// Objects with this many or more properties are always expanded onto multiple lines, to keep
|
|
11
|
+
// generated `.artifact.ts` files compliant with the `object-curly-newline` lint rule (minProperties: 4)
|
|
12
|
+
const MAX_INLINE_OBJECT_PROPERTIES = 3;
|
|
13
|
+
// Recursively serialises an artifact to JSON or TS. Small records that are elements of an array
|
|
14
|
+
// (e.g. `requires`, `constructorInputs`, `abi` inputs) are kept on a single line so list-like data
|
|
15
|
+
// stays compact, while standalone objects assigned to a property (e.g. `compiler.options`) keep
|
|
16
|
+
// their values expanded one per line.
|
|
17
|
+
function stringify(obj, format, indentationLevel = 1, isArrayElement = false) {
|
|
18
|
+
if (typeof obj === 'string')
|
|
19
|
+
return formatString(obj, format);
|
|
20
|
+
// Numbers, booleans and null are represented identically in JSON and TS
|
|
21
|
+
if (obj === null || typeof obj === 'number' || typeof obj === 'boolean') {
|
|
18
22
|
return JSON.stringify(obj);
|
|
19
23
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const formattedItems = obj.map((item) => `${indent(indentationLevel)}${stringifyAsTs(item, indentationLevel + 1)}`);
|
|
25
|
-
return `[\n${formattedItems.join(',\n')},\n${indent(indentationLevel - 1)}]`;
|
|
26
|
-
}
|
|
27
|
-
// Objects are recursively formatted with indentation
|
|
28
|
-
if (typeof obj === 'object') {
|
|
29
|
-
const entries = Object.entries(obj);
|
|
30
|
-
if (entries.length === 0)
|
|
31
|
-
return '{}';
|
|
32
|
-
const formattedEntries = entries.map(([key, value]) => (`${indent(indentationLevel)}${key}: ${stringifyAsTs(value, indentationLevel + 1)}`));
|
|
33
|
-
return `{\n${formattedEntries.join(',\n')},\n${indent(indentationLevel - 1)}}`;
|
|
34
|
-
}
|
|
24
|
+
if (Array.isArray(obj))
|
|
25
|
+
return formatArray(obj, format, indentationLevel, isArrayElement);
|
|
26
|
+
if (typeof obj === 'object')
|
|
27
|
+
return formatObject(obj, format, indentationLevel, isArrayElement);
|
|
35
28
|
throw new Error(`Unsupported type: ${typeof obj}`);
|
|
36
29
|
}
|
|
30
|
+
function formatString(value, format) {
|
|
31
|
+
// JSON strings use standard double-quoted escaping
|
|
32
|
+
if (format === 'json')
|
|
33
|
+
return JSON.stringify(value);
|
|
34
|
+
// TS strings use single quotes instead of double quotes around string values, to match regular TS style
|
|
35
|
+
return `'${JSON.stringify(value).replace(/'/g, "\\'").replace(/\\"/g, '"').slice(1, -1)}'`;
|
|
36
|
+
}
|
|
37
|
+
function formatArray(array, format, indentationLevel, isArrayElement) {
|
|
38
|
+
if (array.length === 0)
|
|
39
|
+
return '[]';
|
|
40
|
+
const items = array.map((item) => stringify(item, format, indentationLevel + 1, true));
|
|
41
|
+
if (isArrayElement && canInline(array))
|
|
42
|
+
return `[${items.join(', ')}]`;
|
|
43
|
+
const lines = items.map((item) => `${indent(indentationLevel)}${item}`).join(',\n');
|
|
44
|
+
const trailingComma = format === 'ts' ? ',' : '';
|
|
45
|
+
return `[\n${lines}${trailingComma}\n${indent(indentationLevel - 1)}]`;
|
|
46
|
+
}
|
|
47
|
+
function formatObject(obj, format, indentationLevel, isArrayElement) {
|
|
48
|
+
const entries = Object.entries(obj).filter(([, value]) => value !== undefined);
|
|
49
|
+
if (entries.length === 0)
|
|
50
|
+
return '{}';
|
|
51
|
+
const formatKey = (key) => (format === 'json' ? JSON.stringify(key) : key);
|
|
52
|
+
const formatted = entries.map(([key, value]) => `${formatKey(key)}: ${stringify(value, format, indentationLevel + 1)}`);
|
|
53
|
+
if (isArrayElement && entries.length <= MAX_INLINE_OBJECT_PROPERTIES && canInline(obj)) {
|
|
54
|
+
return `{ ${formatted.join(', ')} }`;
|
|
55
|
+
}
|
|
56
|
+
const lines = formatted.map((entry) => `${indent(indentationLevel)}${entry}`).join(',\n');
|
|
57
|
+
const trailingComma = format === 'ts' ? ',' : '';
|
|
58
|
+
return `{\n${lines}${trailingComma}\n${indent(indentationLevel - 1)}}`;
|
|
59
|
+
}
|
|
60
|
+
// A container can be inlined when none of its values are themselves a non-empty object or array
|
|
61
|
+
function canInline(container) {
|
|
62
|
+
const values = Array.isArray(container) ? container : Object.values(container);
|
|
63
|
+
return values.every((value) => (value === undefined || value === null || typeof value !== 'object' || Object.keys(value).length === 0));
|
|
64
|
+
}
|
|
37
65
|
//# sourceMappingURL=artifact.js.map
|
package/dist/bitauth-script.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { range } from './data.js';
|
|
2
2
|
import { scriptToBitAuthAsm } from './script.js';
|
|
3
3
|
import { parseSourceTags, sourceMapToLocationData } from './source-map.js';
|
|
4
|
-
import { PositionHint } from './types.js';
|
|
4
|
+
import { PositionHint, SourceTagKind } from './types.js';
|
|
5
5
|
export function buildLineToOpcodesMap(bytecode, sourceMapOrLocationData) {
|
|
6
6
|
const locationData = typeof sourceMapOrLocationData === 'string' ? sourceMapToLocationData(sourceMapOrLocationData) : sourceMapOrLocationData;
|
|
7
7
|
return locationData.reduce((lineToOpcodeMap, singleLocation, index) => {
|
|
@@ -43,14 +43,38 @@ function getDisplayLine(singleLocation) {
|
|
|
43
43
|
function escapeCommentChars(text) {
|
|
44
44
|
return text.replaceAll('/*', '\\/*').replaceAll('*/', '*\\/');
|
|
45
45
|
}
|
|
46
|
+
// Compiler-injected prologue checks: emitted before the function body but executing at its top.
|
|
47
|
+
const PROLOGUE_KINDS = [
|
|
48
|
+
SourceTagKind.LOCKTIME_GUARD,
|
|
49
|
+
SourceTagKind.PARAMETER_VALIDATION,
|
|
50
|
+
];
|
|
46
51
|
function buildInsertions(locationData, sourceLines, sourceTags) {
|
|
47
52
|
const tags = (sourceTags ? parseSourceTags(sourceTags) : []);
|
|
48
53
|
return tags.map((tag) => {
|
|
49
|
-
const
|
|
50
|
-
const
|
|
54
|
+
const { insertAfterLine, indent } = deriveAnchor(tag, tags, locationData, sourceLines);
|
|
55
|
+
const annotation = `${indent}${tagDescription(tag, locationData, sourceLines)}`;
|
|
51
56
|
return { insertAfterLine, annotation, startIndex: tag.startIndex, endIndex: tag.endIndex };
|
|
52
57
|
});
|
|
53
58
|
}
|
|
59
|
+
function deriveAnchor(tag, tags, locationData, sourceLines) {
|
|
60
|
+
if (PROLOGUE_KINDS.includes(tag.kind)) {
|
|
61
|
+
const prologueTags = tags.filter((t) => PROLOGUE_KINDS.includes(t.kind));
|
|
62
|
+
// The prologue is one contiguous opcode block at the function's start
|
|
63
|
+
const lastPrologueOpcode = Math.max(...prologueTags.map((t) => t.endIndex));
|
|
64
|
+
const firstBodyOpcode = lastPrologueOpcode + 1;
|
|
65
|
+
const firstBodyLine = getDisplayLine(locationData[firstBodyOpcode]);
|
|
66
|
+
return {
|
|
67
|
+
// `insertAfterLine` splices *after* a line, so `firstBodyLine - 1` lands all the prologue
|
|
68
|
+
// annotations directly above the first body statement, at its indentation.
|
|
69
|
+
insertAfterLine: firstBodyLine - 1,
|
|
70
|
+
indent: lineIndent(sourceLines, firstBodyLine),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
insertAfterLine: getDisplayLine(locationData[Math.max(tag.startIndex - 1, 0)]),
|
|
75
|
+
indent: deriveIndent(getDisplayLine(locationData[tag.startIndex]), sourceLines),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
54
78
|
function spliceSyntheticSourceLines(sourceLines, insertions) {
|
|
55
79
|
return insertions.reduceRight((lines, ins) => [...lines.slice(0, ins.insertAfterLine), ins.annotation, ...lines.slice(ins.insertAfterLine)], sourceLines);
|
|
56
80
|
}
|
|
@@ -80,19 +104,36 @@ const getUpdatedLineNumber = (currentLineNumber, insertion, opcodeIndex) => {
|
|
|
80
104
|
return currentLineNumber + 1;
|
|
81
105
|
return currentLineNumber;
|
|
82
106
|
};
|
|
83
|
-
|
|
84
|
-
function
|
|
107
|
+
// e.g. ">>> for-loop update (i = i + 1)"
|
|
108
|
+
function tagDescription(tag, locationData, sourceLines) {
|
|
109
|
+
switch (tag.kind) {
|
|
110
|
+
case SourceTagKind.LOCKTIME_GUARD:
|
|
111
|
+
return '>>> tx.locktime guard (auto-injected)';
|
|
112
|
+
case SourceTagKind.PARAMETER_VALIDATION: {
|
|
113
|
+
const parameter = deriveSourceText(tag, locationData, sourceLines);
|
|
114
|
+
return `>>> parameter type check${parameter ? ` (${parameter})` : ''}`;
|
|
115
|
+
}
|
|
116
|
+
case SourceTagKind.FOR_UPDATE:
|
|
117
|
+
default:
|
|
118
|
+
return `>>> for-loop update (${deriveSourceText(tag, locationData, sourceLines)})`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function deriveIndent(headerLine, sourceLines) {
|
|
122
|
+
const bodyLine = sourceLines.slice(headerLine).find((l) => l.trim().length > 0);
|
|
123
|
+
return bodyLine?.match(/^(\s*)/)?.[1] ?? '';
|
|
124
|
+
}
|
|
125
|
+
function lineIndent(sourceLines, line) {
|
|
126
|
+
return sourceLines[line - 1]?.match(/^(\s*)/)?.[1] ?? '';
|
|
127
|
+
}
|
|
128
|
+
function deriveSourceText(tag, locationData, sourceLines) {
|
|
85
129
|
const headerLine = getDisplayLine(locationData[tag.startIndex]);
|
|
86
|
-
// Find the column span of the update expression using single-line locations in the tag range
|
|
87
130
|
const singleLineLocations = range(tag.startIndex, tag.endIndex)
|
|
88
131
|
.map((idx) => locationData[idx].location)
|
|
89
|
-
.filter((loc) => loc.start.line === loc.end.line);
|
|
132
|
+
.filter((loc) => loc.start.line === loc.end.line && loc.start.column !== loc.end.column);
|
|
133
|
+
if (singleLineLocations.length === 0)
|
|
134
|
+
return '';
|
|
90
135
|
const startCol = Math.min(...singleLineLocations.map((loc) => loc.start.column));
|
|
91
136
|
const endCol = Math.max(...singleLineLocations.map((loc) => loc.end.column));
|
|
92
|
-
|
|
93
|
-
// Indentation: use first non-empty line after header
|
|
94
|
-
const bodyLine = sourceLines.slice(headerLine).find((l) => l.trim().length > 0);
|
|
95
|
-
const indent = bodyLine?.match(/^(\s*)/)?.[1] ?? '';
|
|
96
|
-
return `${indent}>>> for-loop update (${expression})`;
|
|
137
|
+
return sourceLines[headerLine - 1].substring(startCol, endCol).trim();
|
|
97
138
|
}
|
|
98
139
|
//# sourceMappingURL=bitauth-script.js.map
|
package/dist/fingerprint.js
CHANGED
|
@@ -14,7 +14,7 @@ export function computeBytecodePattern(bytecode) {
|
|
|
14
14
|
function groupByPushRun(opcodes) {
|
|
15
15
|
return opcodes.reduce((opcodeRuns, opcode) => {
|
|
16
16
|
const lastOpcodeRun = opcodeRuns.at(-1);
|
|
17
|
-
// If
|
|
17
|
+
// If there are no runs yet, start a new run with the current opcode
|
|
18
18
|
if (!lastOpcodeRun)
|
|
19
19
|
return [[opcode]];
|
|
20
20
|
// If the last run is the same type of opcode as the current opcode, add the current opcode to the last run
|
package/dist/types.d.ts
CHANGED
package/dist/types.js
CHANGED
|
@@ -191,10 +191,11 @@ export var PositionHint;
|
|
|
191
191
|
PositionHint[PositionHint["START"] = 0] = "START";
|
|
192
192
|
PositionHint[PositionHint["END"] = 1] = "END";
|
|
193
193
|
})(PositionHint || (PositionHint = {}));
|
|
194
|
-
// Semantic tags for opcodes that
|
|
195
|
-
// Currently used for loop constructs where opcode order diverges from source line order.
|
|
194
|
+
// Semantic tags for compiler-injected opcodes that have no (or misleading) user source
|
|
196
195
|
export var SourceTagKind;
|
|
197
196
|
(function (SourceTagKind) {
|
|
198
197
|
SourceTagKind["FOR_UPDATE"] = "fu";
|
|
198
|
+
SourceTagKind["LOCKTIME_GUARD"] = "lg";
|
|
199
|
+
SourceTagKind["PARAMETER_VALIDATION"] = "pv";
|
|
199
200
|
})(SourceTagKind || (SourceTagKind = {}));
|
|
200
201
|
//# sourceMappingURL=types.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cashscript/utils",
|
|
3
|
-
"version": "0.13.0-next.
|
|
3
|
+
"version": "0.13.0-next.9",
|
|
4
4
|
"description": "CashScript utilities and types",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bitcoin cash",
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"typescript": "^5.9.2",
|
|
49
49
|
"vitest": "^4.0.15"
|
|
50
50
|
},
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "1bbda54a000f6744bdb67a6379c7bfb0b784dceb"
|
|
52
52
|
}
|