5c: the build, the freeze audit and the release pipeline
This commit is contained in:
@@ -1,12 +1,24 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import type { JsonValue } from '../json-value.ts'
|
||||
import { adfDocumentFault, isAdfDocument } from './document.ts'
|
||||
import { largestNesting } from '../nesting.ts'
|
||||
|
||||
function fault(value: unknown): string {
|
||||
return adfDocumentFault(value) ?? 'accepted'
|
||||
}
|
||||
|
||||
function nested(levels: number): JsonValue {
|
||||
let value: JsonValue = 1
|
||||
for (let level = 0; level < levels; level += 1) value = [value]
|
||||
return value
|
||||
}
|
||||
|
||||
function withAttribute(value: JsonValue): unknown {
|
||||
return { content: [{ attrs: { a: value }, type: 'paragraph' }], type: 'doc', version: 1 }
|
||||
}
|
||||
|
||||
test('accepts an editor-normal document', () => {
|
||||
assert.equal(isAdfDocument({ content: [{ content: [{ text: 'x', type: 'text' }], type: 'paragraph' }], type: 'doc', version: 1 }), true)
|
||||
assert.equal(isAdfDocument({ type: 'doc', version: 1 }), true)
|
||||
@@ -44,6 +56,15 @@ test('rejects a node whose shape ProseMirror JSON cannot hold', () => {
|
||||
assert.equal(isAdfDocument({ content: [{ attrs: [], type: 'paragraph' }], type: 'doc', version: 1 }), false)
|
||||
})
|
||||
|
||||
test('holds an attribute value to the levels the parser reads one at, the attrs object costing none', () => {
|
||||
assert.equal(isAdfDocument(withAttribute(nested(largestNesting))), true)
|
||||
assert.equal(isAdfDocument(withAttribute(nested(largestNesting + 1))), false)
|
||||
assert.equal(adfDocumentFault(withAttribute(nested(largestNesting + 1)), Number.POSITIVE_INFINITY), undefined)
|
||||
const marked = { content: [{ marks: [{ attrs: { a: nested(largestNesting + 1) }, type: 'link' }], text: 'x', type: 'text' }], type: 'doc', version: 1 }
|
||||
assert.equal(isAdfDocument(marked), false)
|
||||
assert.equal(adfDocumentFault(marked, Number.POSITIVE_INFINITY), undefined)
|
||||
})
|
||||
|
||||
test('accepts the JSON values an attribute may hold', () => {
|
||||
assert.equal(isAdfDocument({ content: [{ attrs: { a: [1, 'x', null, true, { b: 2 }] }, type: 'paragraph' }], type: 'doc', version: 1 }), true)
|
||||
assert.equal(isAdfDocument({ content: [{ attrs: { a: [() => 1] }, type: 'paragraph' }], type: 'doc', version: 1 }), false)
|
||||
|
||||
+12
-10
@@ -1,4 +1,5 @@
|
||||
import { isJsonValue, type JsonValue } from '../json-value.ts'
|
||||
import { largestNesting } from '../nesting.ts'
|
||||
|
||||
export type AdfAttributes = { [key: string]: JsonValue }
|
||||
|
||||
@@ -25,7 +26,7 @@ const documentKeys = ['content', 'type', 'version']
|
||||
const markKeys = ['attrs', 'type']
|
||||
const nodeKeys = ['attrs', 'content', 'marks', 'text', 'type']
|
||||
|
||||
export function adfDocumentFault(value: unknown): string | undefined {
|
||||
export function adfDocumentFault(value: unknown, levels: number = largestNesting): string | undefined {
|
||||
if (!isRecord(value)) return `an ADF document is an object: found ${describe(value)}`
|
||||
const extra = extraKey(value, documentKeys)
|
||||
if (extra !== undefined) return `an ADF document holds content, type and version alone: found the key ${extra}`
|
||||
@@ -37,7 +38,7 @@ export function adfDocumentFault(value: unknown): string | undefined {
|
||||
if (!('content' in value)) return undefined
|
||||
const content = value['content']
|
||||
if (!Array.isArray(content)) return `an ADF document's content is an array: found ${describe(content)}`
|
||||
return isNodeArray(content) ? undefined : "an ADF document's content holds ADF nodes: one of them is not"
|
||||
return isNodeArray(content, levels) ? undefined : "an ADF document's content holds ADF nodes: one of them is not"
|
||||
}
|
||||
|
||||
export function carriesOnly(node: AdfNode, attributes: readonly string[]): boolean {
|
||||
@@ -50,23 +51,23 @@ export function isAdfDocument(value: unknown): value is AdfDocument {
|
||||
}
|
||||
|
||||
export function isAdfNode(value: unknown): value is AdfNode {
|
||||
return isNodeArray([value])
|
||||
return isNodeArray([value], largestNesting)
|
||||
}
|
||||
|
||||
export function isAdfMark(value: unknown): value is AdfMark {
|
||||
export function isAdfMark(value: unknown, levels: number = largestNesting): value is AdfMark {
|
||||
if (!isRecord(value) || !holdsOnly(value, markKeys)) return false
|
||||
if (typeof value['type'] !== 'string') return false
|
||||
return !('attrs' in value) || isAttributes(value['attrs'])
|
||||
return !('attrs' in value) || isAttributes(value['attrs'], levels)
|
||||
}
|
||||
|
||||
function isNodeArray(value: readonly unknown[]): boolean {
|
||||
function isNodeArray(value: readonly unknown[], levels: number): boolean {
|
||||
const pending: unknown[] = [...value]
|
||||
while (pending.length > 0) {
|
||||
const node = pending.pop()
|
||||
if (!isRecord(node) || !holdsOnly(node, nodeKeys)) return false
|
||||
if (typeof node['type'] !== 'string') return false
|
||||
if ('attrs' in node && !isAttributes(node['attrs'])) return false
|
||||
if ('marks' in node && !isArrayOf(node['marks'], isAdfMark)) return false
|
||||
if ('attrs' in node && !isAttributes(node['attrs'], levels)) return false
|
||||
if ('marks' in node && !isArrayOf(node['marks'], (mark): mark is AdfMark => isAdfMark(mark, levels))) return false
|
||||
if ('text' in node && typeof node['text'] !== 'string') return false
|
||||
if ('content' in node) {
|
||||
const content = node['content']
|
||||
@@ -81,8 +82,9 @@ function isArrayOf<T>(value: unknown, guard: (item: unknown) => item is T): valu
|
||||
return Array.isArray(value) && [...value].every(guard)
|
||||
}
|
||||
|
||||
function isAttributes(value: unknown): value is AdfAttributes {
|
||||
return isRecord(value) && isJsonValue(value)
|
||||
// Per value, so an attribute reaches the same 500 levels the parser reads one at (AGENTS.md §11).
|
||||
function isAttributes(value: unknown, levels: number): value is AdfAttributes {
|
||||
return isRecord(value) && Object.values(value).every((held) => isJsonValue(held, levels))
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
|
||||
Reference in New Issue
Block a user