dsh-standards 1.0.3 → 1.2.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/40-checker/README.md +15 -0
- package/40-checker/bin/dsh-flow-collect.mjs +3 -0
- package/40-checker/bin/dsh-levels.mjs +13 -2
- package/40-checker/fixtures/bundle-patch-only/cordis.patch.yml +3 -0
- package/40-checker/fixtures/bundle-patch-only/package.json +12 -0
- package/40-checker/fixtures/c12-command-arg/index.mjs +7 -0
- package/40-checker/fixtures/c12-command-arg/package.json +10 -0
- package/40-checker/fixtures/c12-comment/index.mjs +3 -0
- package/40-checker/fixtures/c12-comment/package.json +10 -0
- package/40-checker/fixtures/c12-compare/index.mjs +3 -0
- package/40-checker/fixtures/c12-compare/package.json +10 -0
- package/40-checker/fixtures/c12-doc-string/index.mjs +13 -0
- package/40-checker/fixtures/c12-doc-string/package.json +10 -0
- package/40-checker/fixtures/c12-grant/index.mjs +3 -0
- package/40-checker/fixtures/c12-grant/package.json +10 -0
- package/40-checker/fixtures/c12-string/index.mjs +3 -0
- package/40-checker/fixtures/c12-string/package.json +10 -0
- package/40-checker/fixtures/declaration-file/index.d.ts +1 -0
- package/40-checker/fixtures/declaration-file/index.mjs +9 -0
- package/40-checker/fixtures/declaration-file/package.json +10 -0
- package/40-checker/fixtures/import-local-side-effect/index.mjs +11 -0
- package/40-checker/fixtures/import-local-side-effect/package.json +21 -0
- package/40-checker/fixtures/import-local-side-effect/side-effectful.mjs +5 -0
- package/40-checker/fixtures/import-open-readonly/index.mjs +14 -0
- package/40-checker/fixtures/import-open-readonly/package.json +19 -0
- package/40-checker/fixtures/import-package-only/index.mjs +11 -0
- package/40-checker/fixtures/import-package-only/package.json +21 -0
- package/40-checker/fixtures/import-parse-error/index.mjs +5 -0
- package/40-checker/fixtures/import-parse-error/package.json +10 -0
- package/40-checker/fixtures/import-side-effect/index.mjs +14 -0
- package/40-checker/fixtures/import-side-effect/package.json +19 -0
- package/40-checker/fixtures/import-side-effect-nonentry/index.mjs +9 -0
- package/40-checker/fixtures/import-side-effect-nonentry/package.json +19 -0
- package/40-checker/fixtures/import-side-effect-nonentry/selfcheck.mjs +8 -0
- package/40-checker/fixtures/import-void-main/index.mjs +16 -0
- package/40-checker/fixtures/import-void-main/package.json +19 -0
- package/40-checker/fixtures/no-source/package.json +19 -0
- package/40-checker/fixtures/parse-error-nonentry/index.mjs +2 -0
- package/40-checker/fixtures/parse-error-nonentry/package.json +10 -0
- package/40-checker/fixtures/parse-error-nonentry/references/broken.js +1 -0
- package/40-checker/scripts/bundle-smoke.mjs +3 -0
- package/40-checker/scripts/entry18-ci.mjs +4 -3
- package/40-checker/scripts/entry18-web.mjs +4 -1
- package/40-checker/scripts/flow-collect.mjs +494 -0
- package/40-checker/scripts/flow-sample-ci.mjs +4 -4
- package/40-checker/scripts/host-farm.mjs +2 -1
- package/40-checker/scripts/journey-config-observe.mjs +5 -3
- package/40-checker/scripts/journey-runtime.mjs +208 -83
- package/40-checker/scripts/level-audit.mjs +91 -19
- package/40-checker/scripts/m2-runner.mjs +4 -3
- package/40-checker/scripts/runtime-child.mjs +4 -0
- package/40-checker/src/cli.mjs +11 -3
- package/40-checker/src/declarations.mjs +514 -19
- package/40-checker/src/flow-evidence.mjs +431 -0
- package/40-checker/src/index.mjs +3 -0
- package/40-checker/src/manifest.mjs +5 -0
- package/40-checker/src/remediation.mjs +132 -0
- package/40-checker/src/report.mjs +19 -2
- package/40-checker/src/sandbox-env.mjs +78 -0
- package/40-checker/test/checks.test.mjs +243 -1
- package/40-checker/test/flow-evidence.test.mjs +295 -0
- package/40-checker/test/remediation.test.mjs +300 -0
- package/40-checker/vendor/babel/README.md +10 -0
- package/40-checker/vendor/babel/index.cjs +3 -0
- package/40-checker/vendor/babel/node_modules/@babel/helper-string-parser/LICENSE +22 -0
- package/40-checker/vendor/babel/node_modules/@babel/helper-string-parser/README.md +19 -0
- package/40-checker/vendor/babel/node_modules/@babel/helper-string-parser/lib/index.d.ts +41 -0
- package/40-checker/vendor/babel/node_modules/@babel/helper-string-parser/lib/index.js +286 -0
- package/40-checker/vendor/babel/node_modules/@babel/helper-string-parser/package.json +31 -0
- package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/LICENSE +22 -0
- package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/README.md +19 -0
- package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/lib/identifier-browser.js +62 -0
- package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/lib/identifier.js +60 -0
- package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/lib/index.d.ts +28 -0
- package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/lib/index.js +27 -0
- package/40-checker/vendor/babel/node_modules/@babel/helper-validator-identifier/package.json +45 -0
- package/40-checker/vendor/babel/node_modules/@babel/parser/CHANGELOG.md +1073 -0
- package/40-checker/vendor/babel/node_modules/@babel/parser/LICENSE +19 -0
- package/40-checker/vendor/babel/node_modules/@babel/parser/README.md +19 -0
- package/40-checker/vendor/babel/node_modules/@babel/parser/bin/babel-parser.js +14 -0
- package/40-checker/vendor/babel/node_modules/@babel/parser/lib/index.d.ts +1047 -0
- package/40-checker/vendor/babel/node_modules/@babel/parser/lib/index.js +14612 -0
- package/40-checker/vendor/babel/node_modules/@babel/parser/package.json +57 -0
- package/40-checker/vendor/babel/node_modules/@babel/parser/typings/babel-parser.d.ts +636 -0
- package/40-checker/vendor/babel/node_modules/@babel/types/LICENSE +22 -0
- package/40-checker/vendor/babel/node_modules/@babel/types/README.md +19 -0
- package/40-checker/vendor/babel/node_modules/@babel/types/lib/index-legacy.d.ts +2765 -0
- package/40-checker/vendor/babel/node_modules/@babel/types/lib/index.d.ts +3668 -0
- package/40-checker/vendor/babel/node_modules/@babel/types/lib/index.js +10758 -0
- package/40-checker/vendor/babel/node_modules/@babel/types/lib/index.js.flow +2618 -0
- package/40-checker/vendor/babel/node_modules/@babel/types/package.json +38 -0
- package/CHANGELOG.md +66 -0
- package/bin/dsh-flow-collect.mjs +4 -0
- package/package.json +2 -1
- package/spec/README.md +2 -0
- package/spec/flow/README.md +23 -2
- package/spec/flow-evidence.schema.json +217 -0
- package/spec/levels.json +2 -0
- package/spec/remediation.json +478 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import remediationSpec from '../../spec/remediation.json' with { type: 'json' }
|
|
7
|
+
import packageJson from '../../package.json' with { type: 'json' }
|
|
8
|
+
import { DOCS_BASE, joinAdvice } from '../src/remediation.mjs'
|
|
9
|
+
import { buildReport } from '../src/report.mjs'
|
|
10
|
+
import { runManifestChecks } from '../src/manifest.mjs'
|
|
11
|
+
import { checkCapabilities, checkCredentials, checkImportTimeAdvisory, checkLabels } from '../src/declarations.mjs'
|
|
12
|
+
import { readSources } from '../src/util.mjs'
|
|
13
|
+
|
|
14
|
+
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
15
|
+
const srcDir = path.join(here, '..', 'src')
|
|
16
|
+
const fixturesDir = path.join(here, '..', 'fixtures')
|
|
17
|
+
const guideDocs = process.env.DSH_GUIDE_DOCS
|
|
18
|
+
? path.resolve(process.env.DSH_GUIDE_DOCS)
|
|
19
|
+
: path.resolve(here, '..', '..', '..', 'dsh-plugin-guide', 'docs')
|
|
20
|
+
const RULES = remediationSpec
|
|
21
|
+
|
|
22
|
+
function slugify(text) {
|
|
23
|
+
return String(text)
|
|
24
|
+
.trim()
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.replace(/[^\p{L}\p{N}\s_-]/gu, '')
|
|
27
|
+
.replace(/\s+/g, '-')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function headingSlugs(text) {
|
|
31
|
+
const slugs = new Set()
|
|
32
|
+
let fence = null
|
|
33
|
+
for (const line of text.split(/\r?\n/)) {
|
|
34
|
+
const fenceMatch = /^\s*(```+|~~~+)/.exec(line)
|
|
35
|
+
if (fenceMatch) {
|
|
36
|
+
if (fence === null) fence = fenceMatch[1][0]
|
|
37
|
+
else if (fence === fenceMatch[1][0]) fence = null
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
if (fence !== null) continue
|
|
41
|
+
// Explicit HTML anchors are valid targets too (older docs predate heading-only slugs).
|
|
42
|
+
const explicit = /<a\s+id="([^"]+)"/.exec(line)
|
|
43
|
+
if (explicit) slugs.add(explicit[1])
|
|
44
|
+
const match = /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(line)
|
|
45
|
+
if (match) slugs.add(slugify(match[2]))
|
|
46
|
+
}
|
|
47
|
+
return slugs
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The key set the checker can emit: C-x[.y] literals, clause values, G0-G4. */
|
|
51
|
+
function checkerUniverse() {
|
|
52
|
+
const keys = new Set()
|
|
53
|
+
for (const file of fs.readdirSync(srcDir).filter(name => name.endsWith('.mjs'))) {
|
|
54
|
+
const text = fs.readFileSync(path.join(srcDir, file), 'utf8')
|
|
55
|
+
for (const match of text.matchAll(/'((?:C-\d+)(?:\.\d+)*)'/g)) keys.add(match[1])
|
|
56
|
+
for (const match of text.matchAll(/item:\s*'(G[0-4])'/g)) keys.add(match[1])
|
|
57
|
+
}
|
|
58
|
+
for (const gate of ['G0', 'G1', 'G2', 'G3', 'G4']) keys.add(gate)
|
|
59
|
+
return keys
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function runtimeNotRun() {
|
|
63
|
+
const meta = {
|
|
64
|
+
'DSC-04': ['C-4', 'L1'], 'DSC-05': ['C-5', 'L1'], 'DSC-06': ['C-6', 'L1+L2'],
|
|
65
|
+
'DSC-07': ['C-7', 'L2'], 'DSC-08': ['C-8', 'L3'], 'DSC-09': ['C-9', 'L1'],
|
|
66
|
+
}
|
|
67
|
+
return Object.entries(meta).map(([id, [clause, layer]]) => ({
|
|
68
|
+
id, clause, layer, method: 'M', advisory: false,
|
|
69
|
+
result: 'ERROR', coverage: 'not-run', reason: 'test: runtime not run', details: [], evidence: [],
|
|
70
|
+
}))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function staticReportFor(dir) {
|
|
74
|
+
const result = runManifestChecks(dir)
|
|
75
|
+
const sources = readSources(dir)
|
|
76
|
+
const declaration = result.manifest
|
|
77
|
+
? [
|
|
78
|
+
checkCapabilities(result.manifest, sources, result.patch),
|
|
79
|
+
checkCredentials(result.manifest, sources),
|
|
80
|
+
checkLabels(result.manifest, sources),
|
|
81
|
+
].map(item => (item.result === 'WARN' && ['DSC-11', 'DSC-13'].includes(item.id) ? { ...item, advisory: true } : item))
|
|
82
|
+
: [
|
|
83
|
+
{ id: 'DSC-11', clause: 'C-11', layer: 'L0', method: 'M', advisory: false, result: 'ERROR', coverage: 'not-run', reason: 'manifest unavailable', details: [], evidence: [] },
|
|
84
|
+
{ id: 'DSC-12', clause: 'C-12', layer: 'L0', method: 'M', advisory: false, result: 'ERROR', coverage: 'not-run', reason: 'manifest unavailable', details: [], evidence: [] },
|
|
85
|
+
{ id: 'DSC-13', clause: 'C-13', layer: 'L0', method: 'M', advisory: false, result: 'ERROR', coverage: 'not-run', reason: 'manifest unavailable', details: [], evidence: [] },
|
|
86
|
+
]
|
|
87
|
+
const checks = [
|
|
88
|
+
...result.checks,
|
|
89
|
+
...runtimeNotRun(),
|
|
90
|
+
{ id: 'DSC-14', clause: 'C-14', layer: 'L1', method: 'M', advisory: false, result: 'ERROR', coverage: 'not-run', reason: 'test: runtime not run', details: [], evidence: [] },
|
|
91
|
+
checkImportTimeAdvisory(sources, result.entry?.relative),
|
|
92
|
+
...declaration,
|
|
93
|
+
]
|
|
94
|
+
if (result.g4?.result === 'FAIL') {
|
|
95
|
+
const dsc01 = checks.find(item => item.id === 'DSC-01')
|
|
96
|
+
if (dsc01 && dsc01.result !== 'FAIL') {
|
|
97
|
+
dsc01.result = 'FAIL'
|
|
98
|
+
dsc01.reason = dsc01.reason ? `${dsc01.reason}; ${result.g4.reason}` : result.g4.reason
|
|
99
|
+
dsc01.details = [...(dsc01.details ?? []), { item: 'G4', result: 'FAIL', reason: result.g4.reason }]
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return buildReport({
|
|
103
|
+
subject: {
|
|
104
|
+
mode: 'package', profile: 'package', kind: result.kind, kindInferred: true,
|
|
105
|
+
name: result.manifest?.name ?? null, version: result.manifest?.version ?? null, path: dir,
|
|
106
|
+
},
|
|
107
|
+
anchor: { repo: 'test', commit: '0'.repeat(40), scanner: 'test', dsh: '0.2.2-alpha.2', cordis: '4.0.6-alpha.1', runtime: null },
|
|
108
|
+
checks,
|
|
109
|
+
g4: result.g4,
|
|
110
|
+
scope: 'ds-c',
|
|
111
|
+
profile: 'package',
|
|
112
|
+
kind: result.kind,
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
test('remediation table covers every checker key exactly', () => {
|
|
117
|
+
const universe = checkerUniverse()
|
|
118
|
+
const keys = new Set(Object.keys(RULES))
|
|
119
|
+
const missing = [...universe].filter(key => !keys.has(key)).sort()
|
|
120
|
+
const extra = [...keys].filter(key => !universe.has(key)).sort()
|
|
121
|
+
assert.deepEqual(missing, [], `missing remediation rules: ${missing.join(', ')}`)
|
|
122
|
+
assert.deepEqual(extra, [], `stale remediation rules: ${extra.join(', ')}`)
|
|
123
|
+
assert.ok(universe.size > 0)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('remediation rules have the required shape', () => {
|
|
127
|
+
assert.equal(DOCS_BASE, 'https://github.com/PerryLink/dsh-plugin-guide/blob/v0.4.4/')
|
|
128
|
+
assert.equal(packageJson.version, '1.2.0')
|
|
129
|
+
assert.ok(Object.keys(RULES).length > 0)
|
|
130
|
+
for (const [key, rule] of Object.entries(RULES)) {
|
|
131
|
+
assert.match(key, /^(?:C-\d+(?:\.\d+)*|G[0-4])$/)
|
|
132
|
+
for (const field of ['title', 'why', 'fix']) {
|
|
133
|
+
assert.equal(typeof rule[field], 'string', `${key}.${field}`)
|
|
134
|
+
assert.ok(rule[field].trim().length > 0, `${key}.${field} is empty`)
|
|
135
|
+
}
|
|
136
|
+
assert.match(rule.docs, /^docs\/[a-z]+\.md#[a-z0-9-]+$/, `${key}.docs`)
|
|
137
|
+
assert.ok(['fail', 'warn', 'advisory'].includes(rule.severityHint), `${key}.severityHint`)
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test('advice on every fixture has a rule (no ruleMissing)', () => {
|
|
142
|
+
const dirs = fs.readdirSync(fixturesDir, { withFileTypes: true })
|
|
143
|
+
.filter(entry => entry.isDirectory())
|
|
144
|
+
.map(entry => path.join(fixturesDir, entry.name))
|
|
145
|
+
.filter(dir => fs.existsSync(path.join(dir, 'package.json')))
|
|
146
|
+
assert.ok(dirs.length >= 20, `expected fixture packages, got ${dirs.length}`)
|
|
147
|
+
let total = 0
|
|
148
|
+
for (const dir of dirs) {
|
|
149
|
+
const report = staticReportFor(dir)
|
|
150
|
+
assert.ok(Array.isArray(report.advice), `${path.basename(dir)}: advice is not an array`)
|
|
151
|
+
const missing = report.advice.filter(item => item.ruleMissing)
|
|
152
|
+
assert.deepEqual(missing.map(item => `${item.source}:${item.key}`), [], `${path.basename(dir)}: ruleMissing`)
|
|
153
|
+
for (const item of report.advice) {
|
|
154
|
+
assert.ok(item.docsUrl, `${path.basename(dir)}: ${item.key} has no docsUrl`)
|
|
155
|
+
assert.equal(item.docsUrl, `${DOCS_BASE}${RULES[item.key].docs}`, `${path.basename(dir)}: ${item.key} docsUrl`)
|
|
156
|
+
assert.equal(item.version, packageJson.version)
|
|
157
|
+
assert.ok(['FAIL', 'WARN'].includes(item.severity))
|
|
158
|
+
}
|
|
159
|
+
total += report.advice.length
|
|
160
|
+
}
|
|
161
|
+
assert.ok(total > 0, 'fixtures produced no advice; the test would be vacuous')
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('runtime FAIL without details falls back to check.clause', () => {
|
|
165
|
+
const checks = [
|
|
166
|
+
{
|
|
167
|
+
id: 'DSC-04', clause: 'C-4', layer: 'L1', result: 'FAIL', coverage: 'full',
|
|
168
|
+
reason: 'load failed: boom', details: [], evidence: ['index.mjs:1'], method: 'M', advisory: false,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: 'DSC-14', clause: 'C-14', layer: 'L1', result: 'FAIL', coverage: 'full',
|
|
172
|
+
reason: 'runtime failed', details: [], evidence: ['index.mjs:1'], method: 'M', advisory: false,
|
|
173
|
+
},
|
|
174
|
+
]
|
|
175
|
+
const report = buildReport({
|
|
176
|
+
subject: { mode: 'package', profile: 'package', kind: 'plugin', kindInferred: false, name: 'x', version: '1.0.0', path: here },
|
|
177
|
+
anchor: { repo: 'x', commit: '0'.repeat(40), scanner: 'x', dsh: '0.2.2-alpha.2', cordis: '4.0.6-alpha.1', runtime: null },
|
|
178
|
+
checks,
|
|
179
|
+
scope: 'ds-c',
|
|
180
|
+
profile: 'package',
|
|
181
|
+
kind: 'plugin',
|
|
182
|
+
})
|
|
183
|
+
const c4 = report.advice.find(item => item.source === 'DSC-04')
|
|
184
|
+
const c14 = report.advice.find(item => item.source === 'DSC-14')
|
|
185
|
+
assert.equal(c4.key, 'C-4')
|
|
186
|
+
assert.equal(c4.severity, 'FAIL')
|
|
187
|
+
assert.equal(c4.version, '1.2.0')
|
|
188
|
+
assert.deepEqual(c4.evidence, ['index.mjs:1'])
|
|
189
|
+
assert.equal(c4.docsUrl, `${DOCS_BASE}docs/levels.md#entry`)
|
|
190
|
+
assert.equal(c14.key, 'C-14')
|
|
191
|
+
assert.equal(c14.docsUrl, `${DOCS_BASE}docs/entry.md#the-two-claims`)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
test('advice uses detail severity even when the check was adjusted to WARN', () => {
|
|
195
|
+
const checks = [
|
|
196
|
+
{ id: 'DSC-01', clause: 'C-1', layer: 'L0', result: 'PASS', coverage: 'full', reason: '', details: [], evidence: [] },
|
|
197
|
+
{
|
|
198
|
+
id: 'DSC-02', clause: 'C-2', layer: 'L0', result: 'FAIL', coverage: 'full', reason: '',
|
|
199
|
+
details: [{ item: 'C-2.1', result: 'FAIL', reason: 'no engines', evidence: ['package.json#engines'] }],
|
|
200
|
+
evidence: [], advisory: false,
|
|
201
|
+
},
|
|
202
|
+
{ id: 'DSC-11', clause: 'C-11', layer: 'L0', result: 'PASS', coverage: 'full', reason: '', details: [], evidence: [] },
|
|
203
|
+
{ id: 'DSC-12', clause: 'C-12', layer: 'L0', result: 'PASS', coverage: 'full', reason: '', details: [], evidence: [] },
|
|
204
|
+
{ id: 'DSC-13', clause: 'C-13', layer: 'L0', result: 'PASS', coverage: 'full', reason: '', details: [], evidence: [] },
|
|
205
|
+
{ id: 'DSC-14', clause: 'C-14', layer: 'L1', result: 'PASS', coverage: 'full', reason: '', details: [], evidence: [] },
|
|
206
|
+
]
|
|
207
|
+
const report = buildReport({
|
|
208
|
+
subject: { mode: 'package', profile: 'package', kind: 'plugin', kindInferred: false, name: 'x', version: '1.0.0', path: here },
|
|
209
|
+
anchor: { repo: 'x', commit: '0'.repeat(40), scanner: 'x', dsh: '0.2.2-alpha.2', cordis: '4.0.6-alpha.1', runtime: null },
|
|
210
|
+
checks,
|
|
211
|
+
scope: 'entry',
|
|
212
|
+
profile: 'package',
|
|
213
|
+
kind: 'plugin',
|
|
214
|
+
})
|
|
215
|
+
assert.equal(report.checks.find(item => item.id === 'DSC-02').result, 'WARN')
|
|
216
|
+
const item = report.advice.find(entry => entry.key === 'C-2.1')
|
|
217
|
+
assert.equal(item.severity, 'FAIL')
|
|
218
|
+
assert.equal(item.source, 'DSC-02')
|
|
219
|
+
assert.match(item.reason, /no engines/)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test('duplicate details merge evidence and count once per check and key', () => {
|
|
223
|
+
const checks = [{
|
|
224
|
+
id: 'DSC-11', clause: 'C-11', layer: 'L0', result: 'WARN', coverage: 'full', reason: '',
|
|
225
|
+
details: [
|
|
226
|
+
{ item: 'C-11.2', result: 'WARN', reason: 'trigger tools', evidence: ['a.mjs:1'] },
|
|
227
|
+
{ item: 'C-11.1', result: 'WARN', reason: 'missing capabilities', evidence: ['package.json#dsh.capabilities'] },
|
|
228
|
+
{ item: 'C-11.2', result: 'WARN', reason: 'trigger web', evidence: ['b.mjs:2'] },
|
|
229
|
+
],
|
|
230
|
+
evidence: [], advisory: true,
|
|
231
|
+
}]
|
|
232
|
+
const report = buildReport({
|
|
233
|
+
subject: { mode: 'package', profile: 'package', kind: 'plugin', kindInferred: false, name: 'x', version: '1.0.0', path: here },
|
|
234
|
+
anchor: { repo: 'x', commit: '0'.repeat(40), scanner: 'x', dsh: '0.2.2-alpha.2', cordis: '4.0.6-alpha.1', runtime: null },
|
|
235
|
+
checks,
|
|
236
|
+
scope: 'ds-c',
|
|
237
|
+
profile: 'package',
|
|
238
|
+
kind: 'plugin',
|
|
239
|
+
})
|
|
240
|
+
const merged = report.advice.find(item => item.key === 'C-11.2')
|
|
241
|
+
assert.ok(merged)
|
|
242
|
+
assert.equal(merged.occurrences, 2)
|
|
243
|
+
assert.deepEqual(merged.evidence, ['a.mjs:1', 'b.mjs:2'])
|
|
244
|
+
assert.equal(merged.advisory, true)
|
|
245
|
+
assert.equal(report.advice.filter(item => item.source === 'DSC-11').length, 2)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
test('buildReport includes advice by default and omits it on advice:false', () => {
|
|
249
|
+
const base = {
|
|
250
|
+
subject: { mode: 'package', profile: 'package', kind: 'plugin', kindInferred: false, name: 'x', version: '1.0.0', path: here },
|
|
251
|
+
anchor: { repo: 'x', commit: '0'.repeat(40), scanner: 'x', dsh: '0.2.2-alpha.2', cordis: '4.0.6-alpha.1', runtime: null },
|
|
252
|
+
checks: [],
|
|
253
|
+
scope: 'ds-c',
|
|
254
|
+
profile: 'package',
|
|
255
|
+
kind: 'plugin',
|
|
256
|
+
}
|
|
257
|
+
const on = buildReport(base)
|
|
258
|
+
const off = buildReport({ ...base, advice: false })
|
|
259
|
+
assert.ok(Array.isArray(on.advice))
|
|
260
|
+
assert.equal(on.advice.length, 0)
|
|
261
|
+
assert.equal(Object.hasOwn(off, 'advice'), false)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
test('every rule docs anchor exists in the local guide repository', t => {
|
|
265
|
+
if (!fs.existsSync(guideDocs)) {
|
|
266
|
+
t.skip(`guide docs not checked out at ${guideDocs}`)
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
const cache = new Map()
|
|
270
|
+
for (const [key, rule] of Object.entries(RULES)) {
|
|
271
|
+
const hash = rule.docs.indexOf('#')
|
|
272
|
+
const file = rule.docs.slice(0, hash)
|
|
273
|
+
const anchor = rule.docs.slice(hash + 1)
|
|
274
|
+
assert.ok(hash > 0 && !anchor.includes('#'), `${key}: docs must be <file>#<anchor>`)
|
|
275
|
+
const relative = file.replace(/^docs\//, '')
|
|
276
|
+
const absolute = path.join(guideDocs, relative)
|
|
277
|
+
assert.ok(fs.existsSync(absolute), `${key}: missing guide file ${file}`)
|
|
278
|
+
if (!cache.has(file)) cache.set(file, headingSlugs(fs.readFileSync(absolute, 'utf8')))
|
|
279
|
+
assert.ok(cache.get(file).has(anchor), `${key}: missing anchor ${rule.docs}`)
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
test('joinAdvice is pure and leaves the report untouched', () => {
|
|
284
|
+
const checks = [{
|
|
285
|
+
id: 'DSC-04', clause: 'C-4', layer: 'L1', result: 'FAIL', coverage: 'full',
|
|
286
|
+
reason: 'load failed', details: [], evidence: ['index.mjs'], method: 'M', advisory: false,
|
|
287
|
+
}]
|
|
288
|
+
const report = {
|
|
289
|
+
checks,
|
|
290
|
+
standard: { id: 'DS-C', version: '1.0.0' },
|
|
291
|
+
}
|
|
292
|
+
const snapshot = JSON.stringify(report)
|
|
293
|
+
const advice = joinAdvice(report, remediationSpec, DOCS_BASE)
|
|
294
|
+
assert.equal(JSON.stringify(report), snapshot)
|
|
295
|
+
assert.equal(advice.length, 1)
|
|
296
|
+
assert.equal(advice[0].key, 'C-4')
|
|
297
|
+
// A flat rule map (no version metadata) still gets the package version.
|
|
298
|
+
const bare = joinAdvice(report, RULES, DOCS_BASE)
|
|
299
|
+
assert.equal(bare[0].version, packageJson.version)
|
|
300
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Vendored @babel/parser
|
|
2
|
+
|
|
3
|
+
- Package: `@babel/parser` 8.1.0 (MIT), with `@babel/types`,
|
|
4
|
+
`@babel/helper-string-parser`, `@babel/helper-validator-identifier`.
|
|
5
|
+
- Why: the checker needs a real AST for TypeScript/JSX as well as JS. acorn
|
|
6
|
+
alone cannot parse `.ts`/`.tsx`; Babel can.
|
|
7
|
+
- Layout: `node_modules/@babel/*` so `@babel/parser` resolves its own deps
|
|
8
|
+
without network access. `index.cjs` is the only entry the checker imports.
|
|
9
|
+
- Licenses: `node_modules/@babel/parser/LICENSE` and the sibling packages.
|
|
10
|
+
- Update: copy the four packages from a fresh `npm install @babel/parser@8.1.0`.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2014-present Sebastian McKenzie and other contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @babel/helper-string-parser
|
|
2
|
+
|
|
3
|
+
> A utility package to parse strings
|
|
4
|
+
|
|
5
|
+
See our website [@babel/helper-string-parser](https://babeljs.io/docs/babel-helper-string-parser) for more information.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Using npm:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install --save @babel/helper-string-parser
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
or using yarn:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
yarn add @babel/helper-string-parser
|
|
19
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
type StringContentsErrorHandlers = EscapedCharErrorHandlers & {
|
|
2
|
+
unterminated(initialPos: number, initialLineStart: number, initialCurLine: number): void;
|
|
3
|
+
};
|
|
4
|
+
declare function readStringContents(type: "single" | "double" | "template", input: string, pos: number, lineStart: number, curLine: number, errors: StringContentsErrorHandlers): {
|
|
5
|
+
pos: number;
|
|
6
|
+
str: string;
|
|
7
|
+
firstInvalidLoc: {
|
|
8
|
+
pos: number;
|
|
9
|
+
lineStart: number;
|
|
10
|
+
curLine: number;
|
|
11
|
+
} | null;
|
|
12
|
+
lineStart: number;
|
|
13
|
+
curLine: number;
|
|
14
|
+
};
|
|
15
|
+
type EscapedCharErrorHandlers = HexCharErrorHandlers & CodePointErrorHandlers & {
|
|
16
|
+
strictNumericEscape(pos: number, lineStart: number, curLine: number): void;
|
|
17
|
+
};
|
|
18
|
+
type HexCharErrorHandlers = IntErrorHandlers & {
|
|
19
|
+
invalidEscapeSequence(pos: number, lineStart: number, curLine: number): void;
|
|
20
|
+
};
|
|
21
|
+
type IntErrorHandlers = {
|
|
22
|
+
numericSeparatorInEscapeSequence(pos: number, lineStart: number, curLine: number): void;
|
|
23
|
+
unexpectedNumericSeparator(pos: number, lineStart: number, curLine: number): void;
|
|
24
|
+
invalidDigit(pos: number, lineStart: number, curLine: number, radix: number): void;
|
|
25
|
+
};
|
|
26
|
+
declare function readInt(input: string, pos: number, lineStart: number, curLine: number, radix: number, len: number | undefined, forceLen: boolean, allowNumSeparator: boolean | "bail", errors: IntErrorHandlers, bailOnError: boolean): {
|
|
27
|
+
n: null;
|
|
28
|
+
pos: number;
|
|
29
|
+
} | {
|
|
30
|
+
n: number;
|
|
31
|
+
pos: number;
|
|
32
|
+
};
|
|
33
|
+
type CodePointErrorHandlers = HexCharErrorHandlers & {
|
|
34
|
+
invalidCodePoint(pos: number, lineStart: number, curLine: number): void;
|
|
35
|
+
};
|
|
36
|
+
declare function readCodePoint(input: string, pos: number, lineStart: number, curLine: number, throwOnInvalid: boolean, errors: CodePointErrorHandlers): {
|
|
37
|
+
code: number | null;
|
|
38
|
+
pos: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export { type CodePointErrorHandlers, type IntErrorHandlers, type StringContentsErrorHandlers, readCodePoint, readInt, readStringContents };
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
var _isDigit = function isDigit(code) {
|
|
2
|
+
return code >= 48 && code <= 57;
|
|
3
|
+
};
|
|
4
|
+
const forbiddenNumericSeparatorSiblings = {
|
|
5
|
+
decBinOct: new Set([46, 66, 69, 79, 95, 98, 101, 111, 43, 45]),
|
|
6
|
+
hex: new Set([46, 88, 95, 120])
|
|
7
|
+
};
|
|
8
|
+
const isAllowedNumericSeparatorSibling = {
|
|
9
|
+
bin: ch => ch === 48 || ch === 49,
|
|
10
|
+
oct: ch => ch >= 48 && ch <= 55,
|
|
11
|
+
dec: ch => ch >= 48 && ch <= 57,
|
|
12
|
+
hex: ch => ch >= 48 && ch <= 57 || ch >= 65 && ch <= 70 || ch >= 97 && ch <= 102
|
|
13
|
+
};
|
|
14
|
+
function readStringContents(type, input, pos, lineStart, curLine, errors) {
|
|
15
|
+
const initialPos = pos;
|
|
16
|
+
const initialLineStart = lineStart;
|
|
17
|
+
const initialCurLine = curLine;
|
|
18
|
+
let out = "";
|
|
19
|
+
let firstInvalidLoc = null;
|
|
20
|
+
let chunkStart = pos;
|
|
21
|
+
const {
|
|
22
|
+
length
|
|
23
|
+
} = input;
|
|
24
|
+
for (;;) {
|
|
25
|
+
if (pos >= length) {
|
|
26
|
+
errors.unterminated(initialPos, initialLineStart, initialCurLine);
|
|
27
|
+
out += input.slice(chunkStart, pos);
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
const ch = input.charCodeAt(pos);
|
|
31
|
+
if (isStringEnd(type, ch, input, pos)) {
|
|
32
|
+
out += input.slice(chunkStart, pos);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
if (ch === 92) {
|
|
36
|
+
out += input.slice(chunkStart, pos);
|
|
37
|
+
const res = readEscapedChar(input, pos, lineStart, curLine, type === "template", errors);
|
|
38
|
+
if (res.ch === null && !firstInvalidLoc) {
|
|
39
|
+
firstInvalidLoc = {
|
|
40
|
+
pos,
|
|
41
|
+
lineStart,
|
|
42
|
+
curLine
|
|
43
|
+
};
|
|
44
|
+
} else {
|
|
45
|
+
out += res.ch;
|
|
46
|
+
}
|
|
47
|
+
({
|
|
48
|
+
pos,
|
|
49
|
+
lineStart,
|
|
50
|
+
curLine
|
|
51
|
+
} = res);
|
|
52
|
+
chunkStart = pos;
|
|
53
|
+
} else if (ch === 8232 || ch === 8233) {
|
|
54
|
+
++pos;
|
|
55
|
+
++curLine;
|
|
56
|
+
lineStart = pos;
|
|
57
|
+
} else if (ch === 10 || ch === 13) {
|
|
58
|
+
if (type === "template") {
|
|
59
|
+
out += input.slice(chunkStart, pos) + "\n";
|
|
60
|
+
++pos;
|
|
61
|
+
if (ch === 13 && input.charCodeAt(pos) === 10) {
|
|
62
|
+
++pos;
|
|
63
|
+
}
|
|
64
|
+
++curLine;
|
|
65
|
+
chunkStart = lineStart = pos;
|
|
66
|
+
} else {
|
|
67
|
+
errors.unterminated(initialPos, initialLineStart, initialCurLine);
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
++pos;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
pos,
|
|
75
|
+
str: out,
|
|
76
|
+
firstInvalidLoc,
|
|
77
|
+
lineStart,
|
|
78
|
+
curLine
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function isStringEnd(type, ch, input, pos) {
|
|
82
|
+
if (type === "template") {
|
|
83
|
+
return ch === 96 || ch === 36 && input.charCodeAt(pos + 1) === 123;
|
|
84
|
+
}
|
|
85
|
+
return ch === (type === "double" ? 34 : 39);
|
|
86
|
+
}
|
|
87
|
+
function readEscapedChar(input, pos, lineStart, curLine, inTemplate, errors) {
|
|
88
|
+
const throwOnInvalid = !inTemplate;
|
|
89
|
+
pos++;
|
|
90
|
+
const res = ch => ({
|
|
91
|
+
pos,
|
|
92
|
+
ch,
|
|
93
|
+
lineStart,
|
|
94
|
+
curLine
|
|
95
|
+
});
|
|
96
|
+
const ch = input.charCodeAt(pos++);
|
|
97
|
+
switch (ch) {
|
|
98
|
+
case 110:
|
|
99
|
+
return res("\n");
|
|
100
|
+
case 114:
|
|
101
|
+
return res("\r");
|
|
102
|
+
case 120:
|
|
103
|
+
{
|
|
104
|
+
let code;
|
|
105
|
+
({
|
|
106
|
+
code,
|
|
107
|
+
pos
|
|
108
|
+
} = readHexChar(input, pos, lineStart, curLine, 2, false, throwOnInvalid, errors));
|
|
109
|
+
return res(code === null ? null : String.fromCharCode(code));
|
|
110
|
+
}
|
|
111
|
+
case 117:
|
|
112
|
+
{
|
|
113
|
+
let code;
|
|
114
|
+
({
|
|
115
|
+
code,
|
|
116
|
+
pos
|
|
117
|
+
} = readCodePoint(input, pos, lineStart, curLine, throwOnInvalid, errors));
|
|
118
|
+
return res(code === null ? null : String.fromCodePoint(code));
|
|
119
|
+
}
|
|
120
|
+
case 116:
|
|
121
|
+
return res("\t");
|
|
122
|
+
case 98:
|
|
123
|
+
return res("\b");
|
|
124
|
+
case 118:
|
|
125
|
+
return res("\u000b");
|
|
126
|
+
case 102:
|
|
127
|
+
return res("\f");
|
|
128
|
+
case 13:
|
|
129
|
+
if (input.charCodeAt(pos) === 10) {
|
|
130
|
+
++pos;
|
|
131
|
+
}
|
|
132
|
+
case 10:
|
|
133
|
+
lineStart = pos;
|
|
134
|
+
++curLine;
|
|
135
|
+
case 8232:
|
|
136
|
+
case 8233:
|
|
137
|
+
return res("");
|
|
138
|
+
case 56:
|
|
139
|
+
case 57:
|
|
140
|
+
if (inTemplate) {
|
|
141
|
+
return res(null);
|
|
142
|
+
} else {
|
|
143
|
+
errors.strictNumericEscape(pos - 1, lineStart, curLine);
|
|
144
|
+
}
|
|
145
|
+
default:
|
|
146
|
+
if (ch >= 48 && ch <= 55) {
|
|
147
|
+
const startPos = pos - 1;
|
|
148
|
+
const match = /^[0-7]+/.exec(input.slice(startPos, pos + 2));
|
|
149
|
+
let octalStr = match[0];
|
|
150
|
+
let octal = parseInt(octalStr, 8);
|
|
151
|
+
if (octal > 255) {
|
|
152
|
+
octalStr = octalStr.slice(0, -1);
|
|
153
|
+
octal = parseInt(octalStr, 8);
|
|
154
|
+
}
|
|
155
|
+
pos += octalStr.length - 1;
|
|
156
|
+
const next = input.charCodeAt(pos);
|
|
157
|
+
if (octalStr !== "0" || next === 56 || next === 57) {
|
|
158
|
+
if (inTemplate) {
|
|
159
|
+
return res(null);
|
|
160
|
+
} else {
|
|
161
|
+
errors.strictNumericEscape(startPos, lineStart, curLine);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return res(String.fromCharCode(octal));
|
|
165
|
+
}
|
|
166
|
+
return res(String.fromCharCode(ch));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function readHexChar(input, pos, lineStart, curLine, len, forceLen, throwOnInvalid, errors) {
|
|
170
|
+
const initialPos = pos;
|
|
171
|
+
let n;
|
|
172
|
+
({
|
|
173
|
+
n,
|
|
174
|
+
pos
|
|
175
|
+
} = readInt(input, pos, lineStart, curLine, 16, len, forceLen, false, errors, !throwOnInvalid));
|
|
176
|
+
if (n === null) {
|
|
177
|
+
if (throwOnInvalid) {
|
|
178
|
+
errors.invalidEscapeSequence(initialPos, lineStart, curLine);
|
|
179
|
+
} else {
|
|
180
|
+
pos = initialPos - 1;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
code: n,
|
|
185
|
+
pos
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function readInt(input, pos, lineStart, curLine, radix, len, forceLen, allowNumSeparator, errors, bailOnError) {
|
|
189
|
+
const start = pos;
|
|
190
|
+
const forbiddenSiblings = radix === 16 ? forbiddenNumericSeparatorSiblings.hex : forbiddenNumericSeparatorSiblings.decBinOct;
|
|
191
|
+
const isAllowedSibling = radix === 16 ? isAllowedNumericSeparatorSibling.hex : radix === 10 ? isAllowedNumericSeparatorSibling.dec : radix === 8 ? isAllowedNumericSeparatorSibling.oct : isAllowedNumericSeparatorSibling.bin;
|
|
192
|
+
let invalid = false;
|
|
193
|
+
let total = 0;
|
|
194
|
+
for (let i = 0, e = len == null ? Infinity : len; i < e; ++i) {
|
|
195
|
+
const code = input.charCodeAt(pos);
|
|
196
|
+
let val;
|
|
197
|
+
if (code === 95 && allowNumSeparator !== "bail") {
|
|
198
|
+
const prev = input.charCodeAt(pos - 1);
|
|
199
|
+
const next = input.charCodeAt(pos + 1);
|
|
200
|
+
if (!allowNumSeparator) {
|
|
201
|
+
if (bailOnError) return {
|
|
202
|
+
n: null,
|
|
203
|
+
pos
|
|
204
|
+
};
|
|
205
|
+
errors.numericSeparatorInEscapeSequence(pos, lineStart, curLine);
|
|
206
|
+
} else if (Number.isNaN(next) || !isAllowedSibling(next) || forbiddenSiblings.has(prev)) {
|
|
207
|
+
if (bailOnError) return {
|
|
208
|
+
n: null,
|
|
209
|
+
pos
|
|
210
|
+
};
|
|
211
|
+
errors.unexpectedNumericSeparator(pos, lineStart, curLine);
|
|
212
|
+
}
|
|
213
|
+
++pos;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (code >= 97) {
|
|
217
|
+
val = code - 97 + 10;
|
|
218
|
+
} else if (code >= 65) {
|
|
219
|
+
val = code - 65 + 10;
|
|
220
|
+
} else if (_isDigit(code)) {
|
|
221
|
+
val = code - 48;
|
|
222
|
+
} else {
|
|
223
|
+
val = Infinity;
|
|
224
|
+
}
|
|
225
|
+
if (val >= radix) {
|
|
226
|
+
if (val <= 9) {
|
|
227
|
+
if (bailOnError) return {
|
|
228
|
+
n: null,
|
|
229
|
+
pos
|
|
230
|
+
};
|
|
231
|
+
errors.invalidDigit(pos, lineStart, curLine, radix);
|
|
232
|
+
val = 0;
|
|
233
|
+
} else if (forceLen) {
|
|
234
|
+
val = 0;
|
|
235
|
+
invalid = true;
|
|
236
|
+
} else {
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
++pos;
|
|
241
|
+
total = total * radix + val;
|
|
242
|
+
}
|
|
243
|
+
if (pos === start || len != null && pos - start !== len || invalid) {
|
|
244
|
+
return {
|
|
245
|
+
n: null,
|
|
246
|
+
pos
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
n: total,
|
|
251
|
+
pos
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function readCodePoint(input, pos, lineStart, curLine, throwOnInvalid, errors) {
|
|
255
|
+
const ch = input.charCodeAt(pos);
|
|
256
|
+
let code;
|
|
257
|
+
if (ch === 123) {
|
|
258
|
+
++pos;
|
|
259
|
+
({
|
|
260
|
+
code,
|
|
261
|
+
pos
|
|
262
|
+
} = readHexChar(input, pos, lineStart, curLine, input.indexOf("}", pos) - pos, true, throwOnInvalid, errors));
|
|
263
|
+
++pos;
|
|
264
|
+
if (code !== null && code > 0x10ffff) {
|
|
265
|
+
if (throwOnInvalid) {
|
|
266
|
+
errors.invalidCodePoint(pos, lineStart, curLine);
|
|
267
|
+
} else {
|
|
268
|
+
return {
|
|
269
|
+
code: null,
|
|
270
|
+
pos
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
} else {
|
|
275
|
+
({
|
|
276
|
+
code,
|
|
277
|
+
pos
|
|
278
|
+
} = readHexChar(input, pos, lineStart, curLine, 4, false, throwOnInvalid, errors));
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
code,
|
|
282
|
+
pos
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export { readCodePoint, readInt, readStringContents };
|