@portll/cobolwork 0.5.0 → 0.7.0
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/README.md +89 -26
- package/STABILITY.md +76 -0
- package/bin/cobolwork.mjs +56 -14
- package/lib/baseline.mjs +9 -0
- package/lib/bms.mjs +21 -7
- package/lib/build.mjs +133 -53
- package/lib/capabilities.mjs +13 -11
- package/lib/cics-commands.mjs +501 -9
- package/lib/compliance.mjs +11 -0
- package/lib/consequence.mjs +17 -0
- package/lib/control-reuse.mjs +113 -0
- package/lib/control-workers.mjs +161 -0
- package/lib/control.mjs +77 -4
- package/lib/dataflow.mjs +402 -152
- package/lib/db2/cursor.mjs +15 -0
- package/lib/db2/read.mjs +170 -0
- package/lib/db2/rules.mjs +77 -0
- package/lib/db2/stmt/alter.mjs +473 -0
- package/lib/db2/stmt/grant.mjs +125 -0
- package/lib/db2/stmt/index.mjs +141 -0
- package/lib/db2/stmt/misc.mjs +325 -0
- package/lib/db2/stmt/routine.mjs +564 -0
- package/lib/db2/stmt/storage.mjs +146 -0
- package/lib/db2/stmt/table.mjs +540 -0
- package/lib/db2/stmt/view.mjs +146 -0
- package/lib/diff.mjs +17 -5
- package/lib/equivalence.mjs +44 -5
- package/lib/evidence/cli.mjs +26 -7
- package/lib/evidence/record.mjs +16 -6
- package/lib/evidence/seal.mjs +17 -0
- package/lib/evidence/store.mjs +44 -15
- package/lib/evidence/timestamp.mjs +83 -0
- package/lib/evidence/verify.mjs +120 -42
- package/lib/exec-reading.mjs +51 -0
- package/lib/execution.mjs +3 -2
- package/lib/explain.mjs +2 -0
- package/lib/exploitability.mjs +11 -2
- package/lib/hlasm/asm/data.mjs +128 -0
- package/lib/hlasm/asm/listing.mjs +43 -0
- package/lib/hlasm/asm/output.mjs +45 -0
- package/lib/hlasm/asm/sections.mjs +161 -0
- package/lib/hlasm/asm/symbols.mjs +78 -0
- package/lib/hlasm/exec.mjs +27 -0
- package/lib/hlasm/expr.mjs +167 -0
- package/lib/hlasm/instr.mjs +73 -0
- package/lib/hlasm/locate.mjs +333 -0
- package/lib/hlasm/macro/authorization.mjs +146 -0
- package/lib/hlasm/macro/datasets.mjs +107 -0
- package/lib/hlasm/macro/io.mjs +162 -0
- package/lib/hlasm/macro/le.mjs +53 -0
- package/lib/hlasm/macro/linkage.mjs +143 -0
- package/lib/hlasm/macro/operator.mjs +77 -0
- package/lib/hlasm/macro/program.mjs +184 -0
- package/lib/hlasm/macro/recovery.mjs +85 -0
- package/lib/hlasm/macro/storage.mjs +167 -0
- package/lib/hlasm/macro/structured.mjs +131 -0
- package/lib/hlasm/model.mjs +97 -0
- package/lib/hlasm/mvs38.mjs +47 -0
- package/lib/hlasm/operands.mjs +59 -0
- package/lib/hlasm/optable.mjs +61 -0
- package/lib/hlasm/read.mjs +130 -0
- package/lib/hlasm.mjs +44 -7
- package/lib/ims/dli.mjs +37 -0
- package/lib/ims/macro/dbd.mjs +299 -0
- package/lib/ims/macro/psb.mjs +286 -0
- package/lib/ims/model.mjs +149 -0
- package/lib/ims/operands.mjs +23 -0
- package/lib/ims/read.mjs +37 -0
- package/lib/ims/rules.mjs +135 -0
- package/lib/inventory.mjs +10 -7
- package/lib/ironwork-ids.mjs +24 -0
- package/lib/ironwork.mjs +20 -13
- package/lib/kernel/pds-archive.mjs +256 -0
- package/lib/kernel/registry.mjs +32 -23
- package/lib/kernel/shared-pass.mjs +163 -0
- package/lib/kernel/source-tree.mjs +104 -36
- package/lib/kernel/version-key.mjs +17 -0
- package/lib/layout.mjs +26 -31
- package/lib/options.mjs +26 -4
- package/lib/parser.mjs +136 -17
- package/lib/pli/cursor.mjs +15 -0
- package/lib/pli/expr.mjs +101 -0
- package/lib/pli/include.mjs +82 -0
- package/lib/pli/layout.mjs +125 -0
- package/lib/pli/lex.mjs +198 -0
- package/lib/pli/program.mjs +280 -0
- package/lib/pli/rules/based.mjs +95 -0
- package/lib/pli/rules/conditions.mjs +68 -0
- package/lib/pli/rules/entry.mjs +130 -0
- package/lib/pli/rules/index.mjs +24 -0
- package/lib/pli/rules/preprocessor.mjs +55 -0
- package/lib/pli/statements.mjs +130 -0
- package/lib/pli/stmt/alloc.mjs +45 -0
- package/lib/pli/stmt/assignment.mjs +56 -0
- package/lib/pli/stmt/call.mjs +104 -0
- package/lib/pli/stmt/conditions.mjs +94 -0
- package/lib/pli/stmt/control.mjs +219 -0
- package/lib/pli/stmt/declare.mjs +149 -0
- package/lib/pli/stmt/exec.mjs +55 -0
- package/lib/pli/stmt/io.mjs +239 -0
- package/lib/pli/stmt/misc.mjs +4 -0
- package/lib/pli/stmt/preprocessor.mjs +242 -0
- package/lib/pli/stmt/procedure.mjs +258 -0
- package/lib/pli/stmt/stream.mjs +283 -0
- package/lib/pli/storage.mjs +129 -0
- package/lib/policy.mjs +6 -0
- package/lib/precompile-check.mjs +124 -0
- package/lib/precompile-cics.mjs +8 -4
- package/lib/reach.mjs +11 -2
- package/lib/revision.json +1 -1
- package/lib/sarif.mjs +44 -3
- package/lib/scan.mjs +28 -12
- package/lib/sets/abend.mjs +82 -8
- package/lib/sets/build.mjs +43 -37
- package/lib/sets/cics.mjs +20 -38
- package/lib/sets/compile.mjs +46 -18
- package/lib/sets/copybook.mjs +5 -3
- package/lib/sets/crypto.mjs +5 -3
- package/lib/sets/ddl.mjs +36 -0
- package/lib/sets/flow.mjs +32 -8
- package/lib/sets/hidden.mjs +5 -3
- package/lib/sets/hlasm.mjs +124 -8
- package/lib/sets/ims.mjs +139 -0
- package/lib/sets/log.mjs +11 -9
- package/lib/sets/opaque.mjs +27 -7
- package/lib/sets/pli.mjs +40 -0
- package/lib/sets/priv.mjs +5 -3
- package/lib/sets/recon.mjs +5 -3
- package/lib/sets/secrets.mjs +112 -0
- package/lib/sets/semantics.mjs +8 -3
- package/lib/sets/web.mjs +39 -23
- package/lib/site.mjs +10 -0
- package/lib/sources.mjs +80 -20
- package/lib/statement-cursor.mjs +67 -0
- package/lib/verify.mjs +3 -2
- package/lib/version.mjs +6 -0
- package/package.json +3 -2
- package/rules/compliance-cobit2019.json +520 -5
- package/rules/compliance-dora.json +509 -5
- package/rules/compliance-ffiec.json +505 -1
- package/rules/compliance-nist80053.json +557 -1
- package/rules/gitleaks-mainframe.toml +46 -15
- package/rules/hlasm-instructions.json +2396 -0
- package/rules/hlasm-optables.json +8024 -0
- package/schema/cobolwork-baseline.schema.json +36 -0
- package/schema/cobolwork-build-provenance.schema.json +187 -0
- package/schema/cobolwork-build.schema.json +382 -0
- package/schema/cobolwork-capabilities.schema.json +239 -0
- package/schema/cobolwork-diff.schema.json +217 -0
- package/schema/cobolwork-evidence.schema.json +161 -0
- package/schema/cobolwork-execution.schema.json +53 -0
- package/schema/cobolwork-explain.schema.json +360 -0
- package/schema/cobolwork-finding.schema.json +465 -0
- package/schema/cobolwork-flow.schema.json +465 -0
- package/schema/cobolwork-gate.schema.json +211 -0
- package/schema/cobolwork-inventory.schema.json +206 -0
- package/schema/cobolwork-parse.schema.json +105 -0
- package/schema/cobolwork-reach.schema.json +74 -0
- package/schema/cobolwork-report.schema.json +559 -0
- package/schema/cobolwork-witness.schema.json +107 -0
- package/schema/cobolwork.baseline.schema.json +101 -0
- package/schema/cobolwork.policy.schema.json +7 -0
- package/schema/cobolwork.site.schema.json +116 -0
|
@@ -36,6 +36,7 @@ import { basename, dirname, relative, resolve, sep } from 'node:path';
|
|
|
36
36
|
import { buildFileIndex, parseSource } from '../parser.mjs';
|
|
37
37
|
import { classifyUnder, decodeSource, diskClassifier, heldClassifier, kindOfBytes, looksEbcdic, readSource, relPath } from '../sources.mjs';
|
|
38
38
|
import { revisionBlobs, treeOf, treePathParts } from './git.mjs';
|
|
39
|
+
import { archiveKind, readArchive } from './pds-archive.mjs';
|
|
39
40
|
|
|
40
41
|
// The shape every adapter answers to. Checked rather than documented, because an adapter missing a
|
|
41
42
|
// method fails at the first rule set that happens to call it rather than at the boundary.
|
|
@@ -70,6 +71,7 @@ export function directoryTree(root, opts = {}) {
|
|
|
70
71
|
const kindOf = diskClassifier();
|
|
71
72
|
classifyUnder(root, kindOf);
|
|
72
73
|
if (top !== root) classifyUnder(top, kindOf);
|
|
74
|
+
const parseSpec = { copyDirs: idx.copyDirs, fileIndex: idx.index, indexRoot: idx.index.root, systemDirs };
|
|
73
75
|
|
|
74
76
|
return {
|
|
75
77
|
kind: 'directory',
|
|
@@ -81,6 +83,7 @@ export function directoryTree(root, opts = {}) {
|
|
|
81
83
|
list: () => [...idx.index.values()].sort(),
|
|
82
84
|
bytes: (p) => readFileSync(p),
|
|
83
85
|
text: (p) => readSource(p),
|
|
86
|
+
decode: decodeSource,
|
|
84
87
|
rel: (p) => relPath(root, p),
|
|
85
88
|
// The same prefix test buildFileIndex applies while walking. It is not the containment
|
|
86
89
|
// mechanism - the walk is, and it resolves symlinks before testing - it is the answer to
|
|
@@ -88,17 +91,26 @@ export function directoryTree(root, opts = {}) {
|
|
|
88
91
|
contains: (p) => { const r = resolve(p); return r === top || r.startsWith(top + sep); },
|
|
89
92
|
// One parse configuration, in one place. `text` is optional: a caller that has already read
|
|
90
93
|
// the source passes it rather than reading twice.
|
|
91
|
-
parse: (file, text) =>
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
fileIndex: idx.index,
|
|
95
|
-
mainDir: dirname(file),
|
|
96
|
-
copyFormat: 'auto',
|
|
97
|
-
systemDirs,
|
|
98
|
-
}),
|
|
94
|
+
parse: (file, text) => parseInDirectory(parseSpec, file, text),
|
|
95
|
+
// The same configuration as data, for a worker thread to parse as this tree does.
|
|
96
|
+
parseSpec,
|
|
99
97
|
};
|
|
100
98
|
}
|
|
101
99
|
|
|
100
|
+
// A directory tree's parse, from its parseSpec. A spec cloned into a worker loses the index's root,
|
|
101
|
+
// which the parser reads to refuse a COPY that leaves the tree, so it is put back.
|
|
102
|
+
export function parseInDirectory(spec, file, text) {
|
|
103
|
+
if (spec.fileIndex.root !== spec.indexRoot) spec.fileIndex.root = spec.indexRoot;
|
|
104
|
+
return parseSource(text ?? readSource(file).text, file, {
|
|
105
|
+
format: 'auto',
|
|
106
|
+
includeDirs: spec.copyDirs,
|
|
107
|
+
fileIndex: spec.fileIndex,
|
|
108
|
+
mainDir: dirname(file),
|
|
109
|
+
copyFormat: 'auto',
|
|
110
|
+
systemDirs: spec.systemDirs,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
102
114
|
// A tree whose files are held rather than on disk. The parser finds copybooks in the index built
|
|
103
115
|
// here and reads them through `readText`, so a COPY resolves to a held file or to a system copy
|
|
104
116
|
// library outside the tree, never to a file on disk that happens to sit under this tree's root.
|
|
@@ -135,6 +147,7 @@ function heldTree({ kind, root, store, rel, systemDirs = [], copyDirs = null, cl
|
|
|
135
147
|
list: () => [...store.keys()].sort(),
|
|
136
148
|
bytes,
|
|
137
149
|
text,
|
|
150
|
+
decode: decodeSource,
|
|
138
151
|
rel,
|
|
139
152
|
// Containment by construction: a path this tree does not hold is a path outside it.
|
|
140
153
|
contains: (p) => store.has(p),
|
|
@@ -206,24 +219,26 @@ export function unframeRecords(buf) {
|
|
|
206
219
|
return looksEbcdic(ebcdic) ? ebcdic : lines(0x0A);
|
|
207
220
|
}
|
|
208
221
|
|
|
209
|
-
// A
|
|
210
|
-
// not a regular one, is refused rather than followed or waited on.
|
|
211
|
-
function
|
|
222
|
+
// A file the walk found, read as it is or its first `limit` bytes. A link put in its place since, or
|
|
223
|
+
// a file that is not a regular one, is refused rather than followed or waited on.
|
|
224
|
+
function readFound(file, limit = Infinity) {
|
|
212
225
|
const fd = openSync(file, constants.O_RDONLY | (constants.O_NOFOLLOW || 0) | (constants.O_NONBLOCK || 0));
|
|
213
226
|
try {
|
|
214
227
|
const st = fstatSync(fd);
|
|
215
228
|
if (!st.isFile()) throw Object.assign(new Error(`not a regular file: ${file}`), { code: 'ENOTFILE' });
|
|
216
|
-
const
|
|
229
|
+
const size = Math.min(st.size, limit);
|
|
230
|
+
const buf = Buffer.alloc(size);
|
|
217
231
|
let n = 0;
|
|
218
|
-
while (n <
|
|
219
|
-
const r = readSync(fd, buf, n,
|
|
232
|
+
while (n < size) {
|
|
233
|
+
const r = readSync(fd, buf, n, size - n, n);
|
|
220
234
|
if (!r) break;
|
|
221
235
|
n += r;
|
|
222
236
|
}
|
|
223
|
-
return
|
|
237
|
+
return buf.subarray(0, n);
|
|
224
238
|
} finally { closeSync(fd); }
|
|
225
239
|
}
|
|
226
240
|
|
|
241
|
+
|
|
227
242
|
const QUALIFIER = /^[A-Z@#$][A-Z0-9@#$-]{0,7}$/;
|
|
228
243
|
const MEMBER = /^([A-Z@#$][A-Z0-9@#$]{0,7})(?:\.[^.]*)?$/i;
|
|
229
244
|
const MAX_LISTED = 8;
|
|
@@ -243,58 +258,111 @@ const MAX_LISTED = 8;
|
|
|
243
258
|
export function pdsExportTree(dir, opts = {}) {
|
|
244
259
|
const idx = buildFileIndex(dir);
|
|
245
260
|
const top = realpathSync(dir);
|
|
246
|
-
const
|
|
261
|
+
const files = [...idx.index.values()].sort().map((file) => {
|
|
262
|
+
let real;
|
|
263
|
+
try { real = realpathSync(file); } catch { real = file; }
|
|
264
|
+
return { rel: relPath(dir, file), escapes: real !== top && !real.startsWith(top + sep), read: (limit) => readFound(real, limit) };
|
|
265
|
+
});
|
|
266
|
+
return exportOf({ dir, root: `${resolve(dir)}@pds`, files, systemDirs: opts.systemDirs, symlinks: idx.symlinks,
|
|
267
|
+
unreadableDirs: idx.unreadableDirs.map((d) => relative(dir, d)) });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// The same export as a git revision holds it, read with plumbing into memory, as gitTree reads one.
|
|
271
|
+
export function pdsExportRevision(repo, ref, opts = {}) {
|
|
272
|
+
const oid = treeOf(repo, ref);
|
|
273
|
+
const { blobs } = revisionBlobs(repo, oid, ref);
|
|
274
|
+
const files = blobs.filter((b) => treePathParts(b.path)).map((b) => ({ rel: b.path, read: (limit = Infinity) => b.bytes.subarray(0, limit) }));
|
|
275
|
+
const at = `${resolve(repo)}@${oid.slice(0, 12)}`;
|
|
276
|
+
return Object.assign(exportOf({ dir: at, root: `${at}@pds`, files, systemDirs: opts.systemDirs }), { ref, oid });
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// An export's files, each { rel, read(limit), escapes }, held as members: a file whose directory is a
|
|
280
|
+
// data set name and whose name a member name, or an XMIT file or IEBCOPY unload read into its data
|
|
281
|
+
// set. `rel` is the file's path from the export's top, with forward slashes.
|
|
282
|
+
function exportOf({ dir, root, files, systemDirs = [], symlinks = { followed: 0, outside: 0, broken: 0 }, unreadableDirs = [] }) {
|
|
247
283
|
const origins = new Map();
|
|
248
284
|
const found = new Map();
|
|
249
285
|
const notMembers = [];
|
|
250
286
|
const duplicates = [];
|
|
251
287
|
const dataSets = new Set();
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const
|
|
288
|
+
const archived = new Map();
|
|
289
|
+
const archives = { files: 0, members: 0, aliases: 0, notRead: [] };
|
|
290
|
+
for (const { rel, read, escapes } of files) {
|
|
291
|
+
const parts = rel.split('/');
|
|
292
|
+
const file = parts.pop();
|
|
293
|
+
const dsn = parts.join('.').toUpperCase();
|
|
294
|
+
let head = null;
|
|
295
|
+
try { head = archiveKind(read(16)); } catch { head = null; }
|
|
296
|
+
if (head) {
|
|
297
|
+
if (escapes) { notMembers.push(rel); continue; }
|
|
298
|
+
let a;
|
|
299
|
+
try { a = readArchive(read()); } catch (e) { a = { why: reasonOf(e) }; }
|
|
300
|
+
const name = a.dataSet || file.replace(/\.[^.]*$/, '').toUpperCase();
|
|
301
|
+
if (!a.why && !(name.length <= 44 && name.split('.').every((q) => QUALIFIER.test(q)))) a.why = `${name} is not a data set name`;
|
|
302
|
+
if (a.why) { archives.notRead.push(`${rel}: ${a.why}`); continue; }
|
|
303
|
+
archives.files++;
|
|
304
|
+
archives.aliases += a.aliases.length;
|
|
305
|
+
if (a.unmatched) archives.notRead.push(`${rel}: ${a.unmatched} member(s) whose blocks no directory entry names`);
|
|
306
|
+
if (a.missing) archives.notRead.push(`${rel}: no data for ${a.missing.join(', ')}`);
|
|
307
|
+
for (const m of a.members) {
|
|
308
|
+
const at = m.name === null ? resolve(root, name) : resolve(root, name, m.name);
|
|
309
|
+
const origin = m.name === null ? rel : `${rel}(${m.name})`;
|
|
310
|
+
if (found.has(at)) { duplicates.push(`${origin}: ${relPath(root, at)} is ${found.get(at)}`); continue; }
|
|
311
|
+
archived.set(at, m.bytes);
|
|
312
|
+
found.set(at, origin);
|
|
313
|
+
dataSets.add(m.name === null ? at : dirname(at));
|
|
314
|
+
archives.members++;
|
|
315
|
+
}
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
const member = MEMBER.exec(file);
|
|
256
319
|
if (!member || dsn.length > 44 || (dsn && !dsn.split('.').every((q) => QUALIFIER.test(q)))) {
|
|
257
|
-
notMembers.push(
|
|
320
|
+
notMembers.push(rel);
|
|
258
321
|
continue;
|
|
259
322
|
}
|
|
260
323
|
const at = resolve(root, ...(dsn ? [dsn] : []), member[1].toUpperCase());
|
|
261
|
-
if (found.has(at)) { duplicates.push(`${
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
origins.set(at, real);
|
|
266
|
-
found.set(at, relPath(dir, file));
|
|
324
|
+
if (found.has(at)) { duplicates.push(`${rel}: ${relPath(root, at)} is ${found.get(at)}`); continue; }
|
|
325
|
+
if (escapes) { notMembers.push(rel); continue; }
|
|
326
|
+
origins.set(at, read);
|
|
327
|
+
found.set(at, rel);
|
|
267
328
|
dataSets.add(dirname(at));
|
|
268
329
|
}
|
|
269
330
|
const kinds = new Map();
|
|
270
331
|
const byKind = {};
|
|
271
332
|
const copyDirs = new Set();
|
|
272
|
-
|
|
333
|
+
const bytesOf = (at) => archived.get(at) ?? unframeRecords(origins.get(at)());
|
|
334
|
+
for (const at of [...origins.keys(), ...archived.keys()]) {
|
|
273
335
|
let kind = null;
|
|
274
|
-
try { kind = kindOfBytes(
|
|
336
|
+
try { kind = kindOfBytes(bytesOf(at)); } catch { kind = null; }
|
|
275
337
|
kinds.set(at, kind);
|
|
276
338
|
byKind[kind || 'other'] = (byKind[kind || 'other'] || 0) + 1;
|
|
277
339
|
if (kind !== 'jcl' && kind !== 'bms') copyDirs.add(dirname(at));
|
|
278
340
|
}
|
|
279
341
|
const store = {
|
|
280
|
-
keys: () => origins.keys(),
|
|
281
|
-
has: (p) => origins.has(p),
|
|
282
|
-
get: (p) => (origins.has(p)
|
|
342
|
+
keys: () => [...origins.keys(), ...archived.keys()].values(),
|
|
343
|
+
has: (p) => origins.has(p) || archived.has(p),
|
|
344
|
+
get: (p) => (origins.has(p) || archived.has(p) ? bytesOf(p) : undefined),
|
|
283
345
|
};
|
|
284
346
|
const tree = heldTree({
|
|
285
|
-
kind: 'pds-export', root, store, rel: (p) => relPath(root, p), systemDirs
|
|
286
|
-
copyDirs: [...copyDirs], classify: () => (p) => kinds.get(p) ?? null, symlinks
|
|
287
|
-
unreadableDirs:
|
|
347
|
+
kind: 'pds-export', root, store, rel: (p) => relPath(root, p), systemDirs,
|
|
348
|
+
copyDirs: [...copyDirs], classify: () => (p) => kinds.get(p) ?? null, symlinks,
|
|
349
|
+
unreadableDirs: unreadableDirs.map((d) => resolve(root, d)),
|
|
288
350
|
});
|
|
289
351
|
return Object.assign(tree, {
|
|
290
352
|
dir,
|
|
291
353
|
origin: (p) => found.get(p) ?? null,
|
|
292
354
|
export: {
|
|
293
355
|
dataSets: dataSets.size,
|
|
294
|
-
members: origins.size,
|
|
356
|
+
members: origins.size + archived.size,
|
|
295
357
|
byKind,
|
|
296
358
|
...(notMembers.length ? { notMembers: notMembers.length, notMembersListed: notMembers.slice(0, MAX_LISTED) } : {}),
|
|
297
359
|
...(duplicates.length ? { duplicates: duplicates.length, duplicatesListed: duplicates.slice(0, MAX_LISTED) } : {}),
|
|
360
|
+
...(archives.files || archives.notRead.length ? {
|
|
361
|
+
archives: {
|
|
362
|
+
files: archives.files, members: archives.members, aliases: archives.aliases,
|
|
363
|
+
...(archives.notRead.length ? { notRead: archives.notRead.length, notReadListed: archives.notRead.slice(0, MAX_LISTED) } : {}),
|
|
364
|
+
},
|
|
365
|
+
} : {}),
|
|
298
366
|
},
|
|
299
367
|
});
|
|
300
368
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// The version key and unknown keys of a document cobolwork reads. A document without the key
|
|
3
|
+
// predates versioning and is read as version 0, which holds the same keys as version 1.
|
|
4
|
+
import { printable } from './printable.mjs';
|
|
5
|
+
|
|
6
|
+
// Why the document cannot be read, as a clause to follow its name, or null when it can.
|
|
7
|
+
export function versionProblem(raw, current) {
|
|
8
|
+
const version = raw.version === undefined ? 0 : raw.version;
|
|
9
|
+
if (!Number.isInteger(version) || version < 0) return `has version ${printable(JSON.stringify(version), 40)}, which is not a whole number from 0`;
|
|
10
|
+
if (version > current) return `is version ${version}, and this cobolwork reads up to version ${current}; upgrade cobolwork`;
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// A key opening with an underscore is a note for people, as diag/propose-site.mjs writes them.
|
|
15
|
+
export const unknownKeys = (raw, known) => Object.keys(raw).filter((k) => !known.has(k) && !k.startsWith('_')).sort();
|
|
16
|
+
|
|
17
|
+
export const ignoredKey = (where, key) => `${where}: ${printable(JSON.stringify(key), 80)} is not a key this cobolwork reads, so it was ignored`;
|
package/lib/layout.mjs
CHANGED
|
@@ -22,6 +22,8 @@ function picInfo(pic, constants) {
|
|
|
22
22
|
if (ch === 'V' || ch === 'P') continue;
|
|
23
23
|
if (ch === '9') { info.digits += n; info.display += n; continue; }
|
|
24
24
|
if (ch === 'N' || ch === 'G') { info.national = true; info.display += 2 * n; continue; }
|
|
25
|
+
// A U is one UTF-8 character, for which IBM reserves four bytes (Defining UTF-8 data items).
|
|
26
|
+
if (ch === 'U') { info.utf8 = true; info.display += 4 * n; continue; }
|
|
25
27
|
if (ch === 'X' || ch === 'A') info.alphanumeric = true;
|
|
26
28
|
info.display += n;
|
|
27
29
|
}
|
|
@@ -45,9 +47,13 @@ function binaryBytes(digits, scheme) {
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
function elementarySize(item, scheme, constants) {
|
|
50
|
+
if (item.byteLength) return item.byteLength;
|
|
48
51
|
if (!item.picture && !item.usage) {
|
|
49
52
|
const constBytes = constants && constants.textBytes;
|
|
50
|
-
const
|
|
53
|
+
const bytes = (v) => (v.t === 'lit' ? literalBytes(v) : v.t === 'word' && constBytes && constBytes.has(v.u) ? constBytes.get(v.u) : 0);
|
|
54
|
+
// A screen item shows a numeric VALUE as written, one position per character.
|
|
55
|
+
const digits = (v) => v.t === 'num' || (v.t === 'word' && /^\d+$/.test(v.v));
|
|
56
|
+
const fromValue = item.values.reduce((n, v) => n + (item.section === 'SCREEN' && digits(v) ? v.v.length : bytes(v)), 0);
|
|
51
57
|
if (item.section === 'SCREEN' && !fromValue && item.screenRefItem) return item.screenRefItem.size || 0;
|
|
52
58
|
if (item.section === 'SCREEN') return Math.max(1, fromValue);
|
|
53
59
|
if (fromValue) return fromValue;
|
|
@@ -147,47 +153,36 @@ export function computeSizes(roots, scheme, constants) {
|
|
|
147
153
|
for (const r of roots) place(r, 0);
|
|
148
154
|
}
|
|
149
155
|
|
|
150
|
-
// A report group is
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
// to, is as large as the largest group.
|
|
156
|
+
// A report group is as large as the larger of its items laid end to end and the column its
|
|
157
|
+
// rightmost elementary item ends in, COLUMN PLUS counting on from the end of the item before; an
|
|
158
|
+
// elementary item keeps its picture's size. Every 01 group of a report, like the file the report is
|
|
159
|
+
// written to, is as large as the largest group. These are GnuCOBOL's sizes: IBM's Report Writer is a
|
|
160
|
+
// precompiler that writes its own records.
|
|
154
161
|
export function layoutReport(rd) {
|
|
155
162
|
const structural = (x) => x.children.filter(c => c.level !== 88 && c.level !== 66 && c.level !== 78);
|
|
156
163
|
const mark = (x) => { x.rd = rd; for (const c of x.children) mark(c); };
|
|
157
164
|
for (const g of rd.groups) mark(g);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const lineWidth = (line) => {
|
|
165
|
+
const sizeOf = (x) => {
|
|
166
|
+
const kids = structural(x);
|
|
167
|
+
if (!kids.length) return x.size || 0;
|
|
162
168
|
let last = 0;
|
|
163
169
|
let right = 0;
|
|
164
170
|
let total = 0;
|
|
165
|
-
const
|
|
166
|
-
const len = c
|
|
171
|
+
for (const c of kids) {
|
|
172
|
+
const len = sizeOf(c) * (c.occurs || 1);
|
|
173
|
+
total += len;
|
|
174
|
+
if (structural(c).length) continue;
|
|
167
175
|
const start = c.rwColumn ? (c.rwColumn.at != null ? c.rwColumn.at : last + c.rwColumn.plus) : last + 1;
|
|
168
176
|
last = start + len - 1;
|
|
169
177
|
right = Math.max(right, last);
|
|
170
|
-
total += len;
|
|
171
|
-
};
|
|
172
|
-
const walk = (x) => { for (const c of structural(x)) { if (structural(c).length) walk(c); else place(c); } };
|
|
173
|
-
if (structural(line).length) walk(line);
|
|
174
|
-
else place(line);
|
|
175
|
-
return Math.max(right, total);
|
|
176
|
-
};
|
|
177
|
-
// The entries that open a line; a group with no LINE clause anywhere in it is one line.
|
|
178
|
-
for (const g of rd.groups) {
|
|
179
|
-
const lines = [];
|
|
180
|
-
const collect = (x) => { if (x.rwLine) { lines.push(x); return; } for (const c of structural(x)) collect(c); };
|
|
181
|
-
collect(g);
|
|
182
|
-
if (!lines.length) lines.push(g);
|
|
183
|
-
let total = 0;
|
|
184
|
-
for (const line of lines) {
|
|
185
|
-
const w = lineWidth(line);
|
|
186
|
-
if (line !== g) { line.size = w; line.contributes = w * (line.occurs || 1); }
|
|
187
|
-
total += w;
|
|
188
178
|
}
|
|
189
|
-
|
|
190
|
-
|
|
179
|
+
const size = Math.max(right, total);
|
|
180
|
+
x.size = size;
|
|
181
|
+
x.contributes = size * (x.occurs || 1);
|
|
182
|
+
return size;
|
|
183
|
+
};
|
|
184
|
+
let width = 0;
|
|
185
|
+
for (const g of rd.groups) width = Math.max(width, sizeOf(g));
|
|
191
186
|
for (const g of rd.groups) { g.size = width; g.contributes = width; }
|
|
192
187
|
rd.width = width;
|
|
193
188
|
}
|
package/lib/options.mjs
CHANGED
|
@@ -485,17 +485,27 @@ export function sourceOperands(args, compiler = 'gnucobol') {
|
|
|
485
485
|
return out;
|
|
486
486
|
}
|
|
487
487
|
|
|
488
|
+
// The first GnuCOBOL release with the -fec option, and with each condition added after it.
|
|
489
|
+
const FEC_SINCE = '3.1';
|
|
490
|
+
const CONDITION_SINCE = { 'EC-PROGRAM-ARG-MISMATCH': '3.2' };
|
|
491
|
+
export const olderThan = (version, since) => {
|
|
492
|
+
const [a, b] = [version, since].map((v) => String(v).split('.').map(Number));
|
|
493
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) if ((a[i] || 0) !== (b[i] || 0)) return (a[i] || 0) < (b[i] || 0);
|
|
494
|
+
return false;
|
|
495
|
+
};
|
|
496
|
+
|
|
488
497
|
// A cobc argument vector. Returns { checks, forbidden, add }: `add` holds the -fec options that
|
|
489
498
|
// supply a required check nothing turned on, and `forbidden` the options the policy's forbid list
|
|
490
499
|
// names. A check turned off is a failed check, not overridden: appending past it would hide a build
|
|
491
|
-
// configured against the policy.
|
|
492
|
-
|
|
500
|
+
// configured against the policy. With `pinned` ({ version, where }), a check that GnuCOBOL version
|
|
501
|
+
// cannot generate fails, however the command asks for it.
|
|
502
|
+
export function cobcChecks(args, { required, forbid = [], pinned = null }) {
|
|
493
503
|
const state = new Map();
|
|
494
504
|
const turnedOff = new Map();
|
|
495
505
|
const set = (name, on, arg) => {
|
|
496
506
|
for (const check of required) {
|
|
497
507
|
if (!covers(name, EXCEPTION_OF_CHECK[check])) continue;
|
|
498
|
-
state.set(check, on);
|
|
508
|
+
state.set(check, on ? arg : false);
|
|
499
509
|
if (on) turnedOff.delete(check); else turnedOff.set(check, arg);
|
|
500
510
|
}
|
|
501
511
|
};
|
|
@@ -509,11 +519,23 @@ export function cobcChecks(args, { required, forbid = [] }) {
|
|
|
509
519
|
if (value === undefined) continue;
|
|
510
520
|
set(exceptionName(value), !m[1], shown);
|
|
511
521
|
}
|
|
522
|
+
const tooOld = (check, by) => {
|
|
523
|
+
if (!pinned) return null;
|
|
524
|
+
const condition = EXCEPTION_OF_CHECK[check];
|
|
525
|
+
const since = CONDITION_SINCE[condition];
|
|
526
|
+
const pin = `${pinned.where} pins ${pinned.version}`;
|
|
527
|
+
if (since && olderThan(pinned.version, since)) return `${check} needs ${condition}, which is in GnuCOBOL ${since} and later; ${pin}`;
|
|
528
|
+
if (!/^-(debug|d)$/.test(by) && olderThan(pinned.version, FEC_SINCE)) return `${check} needs -fec, which is in GnuCOBOL ${FEC_SINCE} and later; ${pin}`;
|
|
529
|
+
return null;
|
|
530
|
+
};
|
|
512
531
|
const add = [];
|
|
513
532
|
const checks = required.map((check) => {
|
|
514
|
-
if (state.get(check) === true) return { check, ok: true, why: 'turned on by the command line' };
|
|
515
533
|
if (turnedOff.has(check)) return { check, ok: false, why: `${turnedOff.get(check)} turns off the ${check} check` };
|
|
516
534
|
const flag = `-fec=${EXCEPTION_OF_CHECK[check]}`;
|
|
535
|
+
const by = state.get(check) || flag;
|
|
536
|
+
const old = tooOld(check, by);
|
|
537
|
+
if (old) return { check, ok: false, why: old };
|
|
538
|
+
if (state.get(check)) return { check, ok: true, why: 'turned on by the command line' };
|
|
517
539
|
if (!add.includes(flag)) add.push(flag);
|
|
518
540
|
return { check, ok: true, added: true, why: `added as ${flag}` };
|
|
519
541
|
});
|