@jasonbelmonti/markdown-engine 3.5.0 → 3.6.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/CHANGELOG.md +13 -0
- package/README.md +10 -11
- package/dist/declarative-validation/assertions/id-targets.d.ts +2 -0
- package/dist/declarative-validation/assertions/id-targets.d.ts.map +1 -1
- package/dist/declarative-validation/assertions/id-targets.js.map +1 -1
- package/dist/declarative-validation/assertions/table-column-coverage.d.ts.map +1 -1
- package/dist/declarative-validation/assertions/table-column-coverage.js +8 -10
- package/dist/declarative-validation/assertions/table-column-coverage.js.map +1 -1
- package/dist/declarative-validation/assertions/text.d.ts.map +1 -1
- package/dist/declarative-validation/assertions/text.js +5 -0
- package/dist/declarative-validation/assertions/text.js.map +1 -1
- package/dist/declarative-validation/assertions/visible-text.d.ts +5 -0
- package/dist/declarative-validation/assertions/visible-text.d.ts.map +1 -0
- package/dist/declarative-validation/assertions/visible-text.js +42 -0
- package/dist/declarative-validation/assertions/visible-text.js.map +1 -0
- package/dist/declarative-validation/compiler/assertion-builders.js +11 -2
- package/dist/declarative-validation/compiler/assertion-builders.js.map +1 -1
- package/dist/declarative-validation/compiler/assertion-shapes.d.ts.map +1 -1
- package/dist/declarative-validation/compiler/assertion-shapes.js +20 -4
- package/dist/declarative-validation/compiler/assertion-shapes.js.map +1 -1
- package/dist/declarative-validation/compiler/plan.d.ts +4 -1
- package/dist/declarative-validation/compiler/plan.d.ts.map +1 -1
- package/dist/declarative-validation/profile/assertion-schema.d.ts.map +1 -1
- package/dist/declarative-validation/profile/assertion-schema.js +21 -6
- package/dist/declarative-validation/profile/assertion-schema.js.map +1 -1
- package/dist/declarative-validation/profile/cell-predicate-schema.d.ts +4 -0
- package/dist/declarative-validation/profile/cell-predicate-schema.d.ts.map +1 -0
- package/dist/declarative-validation/profile/cell-predicate-schema.js +29 -0
- package/dist/declarative-validation/profile/cell-predicate-schema.js.map +1 -0
- package/dist/declarative-validation/profile/index.d.ts +3 -0
- package/dist/declarative-validation/profile/index.d.ts.map +1 -1
- package/dist/declarative-validation/profile/selector-schema.d.ts.map +1 -1
- package/dist/declarative-validation/profile/selector-schema.js +1 -23
- package/dist/declarative-validation/profile/selector-schema.js.map +1 -1
- package/dist/declarative-validation/selectors/table-targets.d.ts +2 -1
- package/dist/declarative-validation/selectors/table-targets.d.ts.map +1 -1
- package/dist/declarative-validation/selectors/table-targets.js +13 -5
- package/dist/declarative-validation/selectors/table-targets.js.map +1 -1
- package/dist/internal/package-version.d.ts +1 -1
- package/dist/internal/package-version.js +1 -1
- package/dist-bundled/markdown-engine-cli.mjs +179 -67
- package/docs/contracts/declarative-validation.md +32 -1
- package/package.json +2 -2
- package/scripts/install-markdown-engine-cli.sh +2 -2
|
@@ -8047,7 +8047,8 @@ function tableColumnTargets(document3, source) {
|
|
|
8047
8047
|
}
|
|
8048
8048
|
const resolvedColumns = tableResolution.tables.flatMap((table) => {
|
|
8049
8049
|
const columnIndex = columnIndexForHeader(table, source.column);
|
|
8050
|
-
|
|
8050
|
+
const predicateColumnMissing = source.rowWhere !== void 0 && columnIndexForHeader(table, source.rowWhere.column) === void 0;
|
|
8051
|
+
return columnIndex === void 0 || predicateColumnMissing ? [] : [{ table, columnIndex }];
|
|
8051
8052
|
});
|
|
8052
8053
|
if (resolvedColumns.length === 0) {
|
|
8053
8054
|
return {
|
|
@@ -8060,7 +8061,7 @@ function tableColumnTargets(document3, source) {
|
|
|
8060
8061
|
status: "resolved",
|
|
8061
8062
|
source,
|
|
8062
8063
|
targets: resolvedColumns.flatMap(
|
|
8063
|
-
({ table, columnIndex }) => tableCellTargetsForColumn(document3, table, columnIndex)
|
|
8064
|
+
({ table, columnIndex }) => tableCellTargetsForColumn(document3, table, columnIndex, source.rowWhere)
|
|
8064
8065
|
)
|
|
8065
8066
|
};
|
|
8066
8067
|
}
|
|
@@ -8110,9 +8111,11 @@ function rowMatchesPredicate(table, cells, predicate) {
|
|
|
8110
8111
|
function columnIndexForHeader(table, column) {
|
|
8111
8112
|
return headerCells(table).find((cell) => cell.text === column)?.columnIndex;
|
|
8112
8113
|
}
|
|
8113
|
-
function tableCellTargetsForColumn(document3, table, columnIndex) {
|
|
8114
|
-
return dataRows(table).flatMap((row) => {
|
|
8115
|
-
const cell = row.cells.find(
|
|
8114
|
+
function tableCellTargetsForColumn(document3, table, columnIndex, rowWhere) {
|
|
8115
|
+
return dataRows(table).filter((row) => rowMatchesPredicate(table, row.cells, rowWhere)).flatMap((row) => {
|
|
8116
|
+
const cell = row.cells.find(
|
|
8117
|
+
(candidate) => candidate.columnIndex === columnIndex
|
|
8118
|
+
);
|
|
8116
8119
|
return cell === void 0 ? [] : [tableCellSelectionTarget(document3, table, cell)];
|
|
8117
8120
|
});
|
|
8118
8121
|
}
|
|
@@ -9603,8 +9606,16 @@ function sourceLengthRequirement(assertion) {
|
|
|
9603
9606
|
// src/declarative-validation/assertions/table-column-coverage.ts
|
|
9604
9607
|
function evaluateTableColumnCoverage(assertion, context) {
|
|
9605
9608
|
const options = idTokenOptions(assertion);
|
|
9606
|
-
const
|
|
9609
|
+
const sourceResolution = resolveTableColumnIdTokens(
|
|
9610
|
+
context.selection.document,
|
|
9611
|
+
assertion.source,
|
|
9612
|
+
options
|
|
9613
|
+
);
|
|
9614
|
+
const sourceIds = sourceResolution.status === "resolved" ? uniqueSourceIds2(sourceResolution.tokens) : [];
|
|
9607
9615
|
if (sourceIds.length === 0) {
|
|
9616
|
+
if (assertion.allowEmptySource === true && sourceResolution.status === "resolved") {
|
|
9617
|
+
return [];
|
|
9618
|
+
}
|
|
9608
9619
|
return [emptySelectionDiagnostic(context.rule, context.assertionIndex)];
|
|
9609
9620
|
}
|
|
9610
9621
|
const targetResolution = resolveTableColumnIdTokens(
|
|
@@ -9614,12 +9625,22 @@ function evaluateTableColumnCoverage(assertion, context) {
|
|
|
9614
9625
|
);
|
|
9615
9626
|
if (targetResolution.status === "missingSection") {
|
|
9616
9627
|
return sourceIds.map(
|
|
9617
|
-
(sourceId, sourceIdOrder) => missingTargetSectionDiagnostic(
|
|
9628
|
+
(sourceId, sourceIdOrder) => missingTargetSectionDiagnostic(
|
|
9629
|
+
assertion,
|
|
9630
|
+
context,
|
|
9631
|
+
sourceId,
|
|
9632
|
+
sourceIdOrder
|
|
9633
|
+
)
|
|
9618
9634
|
);
|
|
9619
9635
|
}
|
|
9620
9636
|
if (targetResolution.status === "missingColumn") {
|
|
9621
9637
|
return sourceIds.map(
|
|
9622
|
-
(sourceId, sourceIdOrder) => missingTargetColumnDiagnostic(
|
|
9638
|
+
(sourceId, sourceIdOrder) => missingTargetColumnDiagnostic(
|
|
9639
|
+
assertion,
|
|
9640
|
+
context,
|
|
9641
|
+
sourceId,
|
|
9642
|
+
sourceIdOrder
|
|
9643
|
+
)
|
|
9623
9644
|
);
|
|
9624
9645
|
}
|
|
9625
9646
|
const targetComparisonValues = new Set(
|
|
@@ -9641,17 +9662,6 @@ function evaluateTableColumnCoverage(assertion, context) {
|
|
|
9641
9662
|
}
|
|
9642
9663
|
return diagnostics;
|
|
9643
9664
|
}
|
|
9644
|
-
function sourceIdsForCoverage(assertion, context, options) {
|
|
9645
|
-
const sourceResolution = resolveTableColumnIdTokens(
|
|
9646
|
-
context.selection.document,
|
|
9647
|
-
{
|
|
9648
|
-
section: assertion.source.section,
|
|
9649
|
-
column: assertion.source.column
|
|
9650
|
-
},
|
|
9651
|
-
options
|
|
9652
|
-
);
|
|
9653
|
-
return sourceResolution.status === "resolved" ? uniqueSourceIds2(sourceResolution.tokens) : [];
|
|
9654
|
-
}
|
|
9655
9665
|
function uniqueSourceIds2(tokens) {
|
|
9656
9666
|
const sourceIdsByComparisonValue = /* @__PURE__ */ new Map();
|
|
9657
9667
|
for (const token of tokens) {
|
|
@@ -9824,6 +9834,40 @@ function tableColumnSortKey(table, diagnosticOrder, column) {
|
|
|
9824
9834
|
return `table:${table.target.id}:column:${paddedDiagnosticOrder}:${column}`;
|
|
9825
9835
|
}
|
|
9826
9836
|
|
|
9837
|
+
// src/declarative-validation/assertions/visible-text.ts
|
|
9838
|
+
function visibleText(document3, target) {
|
|
9839
|
+
const textAt = (nodeTarget) => documentQueries.nodes(document3, { targetId: nodeTarget.id }).map(nodeText2).join("");
|
|
9840
|
+
switch (target.kind) {
|
|
9841
|
+
case "document":
|
|
9842
|
+
return document3.children.map(nodeText2).join("\n");
|
|
9843
|
+
case "section":
|
|
9844
|
+
return [
|
|
9845
|
+
textAt(target.section.headingTarget),
|
|
9846
|
+
...target.section.bodyTargets.map(textAt)
|
|
9847
|
+
].join("\n");
|
|
9848
|
+
case "heading":
|
|
9849
|
+
return textAt(target.section.headingTarget);
|
|
9850
|
+
case "table":
|
|
9851
|
+
return textAt(target.table.target);
|
|
9852
|
+
case "tableRow":
|
|
9853
|
+
return target.cells.map((cell) => textAt(cell.target)).join(" ");
|
|
9854
|
+
case "tableCell":
|
|
9855
|
+
return textAt(target.cell.target);
|
|
9856
|
+
case "textSpan":
|
|
9857
|
+
return textAt(target.span.target);
|
|
9858
|
+
case "link":
|
|
9859
|
+
return textAt(target.link.target);
|
|
9860
|
+
case "list":
|
|
9861
|
+
return textAt(target.list.target);
|
|
9862
|
+
}
|
|
9863
|
+
}
|
|
9864
|
+
function nodeText2(node2) {
|
|
9865
|
+
if (node2.type === "html" || node2.type === "definition" || node2.type === "yaml") {
|
|
9866
|
+
return "";
|
|
9867
|
+
}
|
|
9868
|
+
return node2.children === void 0 ? node2.text ?? "" : node2.children.map(nodeText2).join("");
|
|
9869
|
+
}
|
|
9870
|
+
|
|
9827
9871
|
// src/declarative-validation/assertions/literal-text.ts
|
|
9828
9872
|
function countNonOverlappingLiteralOccurrences(text5, literal) {
|
|
9829
9873
|
let count = 0;
|
|
@@ -9850,6 +9894,16 @@ function evaluateText(assertion, context) {
|
|
|
9850
9894
|
}
|
|
9851
9895
|
function evaluateTextTarget(assertion, context, target, targetOrder) {
|
|
9852
9896
|
const diagnostics = [];
|
|
9897
|
+
if (assertion.nonBlank === true && visibleText(context.selection.document, target).trim().length === 0) {
|
|
9898
|
+
diagnostics.push(
|
|
9899
|
+
validationDiagnostic(
|
|
9900
|
+
"profile.validation.textBlank",
|
|
9901
|
+
`Selected ${target.kind} must contain non-whitespace text outside raw HTML.`,
|
|
9902
|
+
context.rule,
|
|
9903
|
+
{ assertionIndex: context.assertionIndex, target, targetOrder }
|
|
9904
|
+
)
|
|
9905
|
+
);
|
|
9906
|
+
}
|
|
9853
9907
|
if (assertion.contains !== void 0 && !target.text.includes(assertion.contains)) {
|
|
9854
9908
|
diagnostics.push(
|
|
9855
9909
|
validationDiagnostic(
|
|
@@ -10625,6 +10679,48 @@ function stringArray(value) {
|
|
|
10625
10679
|
return values;
|
|
10626
10680
|
}
|
|
10627
10681
|
|
|
10682
|
+
// src/declarative-validation/profile/cell-predicate-schema.ts
|
|
10683
|
+
function cellPredicateFromValue(value, key, diagnostics) {
|
|
10684
|
+
if (!isPlainRecord(value)) {
|
|
10685
|
+
diagnostics.push(invalidShape(`Selector ${key} must be an object.`));
|
|
10686
|
+
return void 0;
|
|
10687
|
+
}
|
|
10688
|
+
unsupportedKeys(value, ["column", "equals", "includes"], diagnostics);
|
|
10689
|
+
const column = requiredNonEmptyStringField(
|
|
10690
|
+
value.column,
|
|
10691
|
+
`Selector ${key}.column`,
|
|
10692
|
+
diagnostics,
|
|
10693
|
+
(field) => `${field} must be a non-empty string.`
|
|
10694
|
+
);
|
|
10695
|
+
if (column === void 0) {
|
|
10696
|
+
return void 0;
|
|
10697
|
+
}
|
|
10698
|
+
const predicate = {
|
|
10699
|
+
...optionalPredicateString(value, "equals", diagnostics),
|
|
10700
|
+
...optionalPredicateString(value, "includes", diagnostics)
|
|
10701
|
+
};
|
|
10702
|
+
if (predicate.equals === void 0 && predicate.includes === void 0) {
|
|
10703
|
+
diagnostics.push(
|
|
10704
|
+
invalidShape(
|
|
10705
|
+
`Selector ${key} must include at least one of equals or includes.`
|
|
10706
|
+
)
|
|
10707
|
+
);
|
|
10708
|
+
return void 0;
|
|
10709
|
+
}
|
|
10710
|
+
return {
|
|
10711
|
+
column,
|
|
10712
|
+
...predicate
|
|
10713
|
+
};
|
|
10714
|
+
}
|
|
10715
|
+
function optionalPredicateString(record, key, diagnostics) {
|
|
10716
|
+
return optionalStringField(
|
|
10717
|
+
record,
|
|
10718
|
+
key,
|
|
10719
|
+
diagnostics,
|
|
10720
|
+
(field) => `Selector ${field} must be a non-empty string when provided.`
|
|
10721
|
+
);
|
|
10722
|
+
}
|
|
10723
|
+
|
|
10628
10724
|
// src/declarative-validation/profile/selector-schema.ts
|
|
10629
10725
|
function selectorFromValue(value, diagnostics) {
|
|
10630
10726
|
if (!isPlainRecord(value)) {
|
|
@@ -10746,33 +10842,6 @@ function optionalCellPredicate(record, key, diagnostics) {
|
|
|
10746
10842
|
const predicate = cellPredicateFromValue(value, key, diagnostics);
|
|
10747
10843
|
return predicate === void 0 ? {} : { [key]: predicate };
|
|
10748
10844
|
}
|
|
10749
|
-
function cellPredicateFromValue(value, key, diagnostics) {
|
|
10750
|
-
if (!isPlainRecord(value)) {
|
|
10751
|
-
diagnostics.push(invalidShape(`Selector ${key} must be an object.`));
|
|
10752
|
-
return void 0;
|
|
10753
|
-
}
|
|
10754
|
-
unsupportedKeys(value, ["column", "equals", "includes"], diagnostics);
|
|
10755
|
-
const column = requiredSelectorString(value.column, `${key}.column`, diagnostics);
|
|
10756
|
-
if (column === void 0) {
|
|
10757
|
-
return void 0;
|
|
10758
|
-
}
|
|
10759
|
-
const predicate = {
|
|
10760
|
-
...optionalSelectorString(value, "equals", diagnostics),
|
|
10761
|
-
...optionalSelectorString(value, "includes", diagnostics)
|
|
10762
|
-
};
|
|
10763
|
-
if (predicate.equals === void 0 && predicate.includes === void 0) {
|
|
10764
|
-
diagnostics.push(
|
|
10765
|
-
invalidShape(
|
|
10766
|
-
`Selector ${key} must include at least one of equals or includes.`
|
|
10767
|
-
)
|
|
10768
|
-
);
|
|
10769
|
-
return void 0;
|
|
10770
|
-
}
|
|
10771
|
-
return {
|
|
10772
|
-
column,
|
|
10773
|
-
...predicate
|
|
10774
|
-
};
|
|
10775
|
-
}
|
|
10776
10845
|
function optionalBoolean(record, key, diagnostics) {
|
|
10777
10846
|
return optionalBooleanField(
|
|
10778
10847
|
record,
|
|
@@ -11122,7 +11191,22 @@ function pushObjectDiagnostic(fieldName, value, ruleId, diagnostics) {
|
|
|
11122
11191
|
}
|
|
11123
11192
|
function pushTextShapeDiagnostics(assertion, ruleId, diagnostics) {
|
|
11124
11193
|
let valid = true;
|
|
11125
|
-
if (assertion?.
|
|
11194
|
+
if (assertion?.nonBlank !== void 0 && assertion.nonBlank !== true) {
|
|
11195
|
+
diagnostics.push(
|
|
11196
|
+
compileDiagnostic(
|
|
11197
|
+
"profile.config.invalidShape",
|
|
11198
|
+
"text.nonBlank must be true when provided.",
|
|
11199
|
+
ruleId
|
|
11200
|
+
)
|
|
11201
|
+
);
|
|
11202
|
+
valid = false;
|
|
11203
|
+
}
|
|
11204
|
+
if (assertion?.contains !== void 0 && !pushNonEmptyStringDiagnostic(
|
|
11205
|
+
"contains",
|
|
11206
|
+
assertion.contains,
|
|
11207
|
+
ruleId,
|
|
11208
|
+
diagnostics
|
|
11209
|
+
)) {
|
|
11126
11210
|
valid = false;
|
|
11127
11211
|
}
|
|
11128
11212
|
if (assertion?.excludes !== void 0 && closedStringArray("excludes", assertion.excludes, ruleId, diagnostics) === void 0) {
|
|
@@ -11191,7 +11275,13 @@ function closedTableColumnCoverage(value, ruleId, diagnostics) {
|
|
|
11191
11275
|
const record = value;
|
|
11192
11276
|
const hasSupportedKeys = pushUnsupportedKeyDiagnostics(
|
|
11193
11277
|
record,
|
|
11194
|
-
["source", "target", "require"],
|
|
11278
|
+
["source", "target", "require", "allowEmptySource"],
|
|
11279
|
+
diagnostics
|
|
11280
|
+
);
|
|
11281
|
+
pushOptionalBooleanDiagnostic(
|
|
11282
|
+
"tableColumnCoverage.allowEmptySource",
|
|
11283
|
+
record.allowEmptySource,
|
|
11284
|
+
ruleId,
|
|
11195
11285
|
diagnostics
|
|
11196
11286
|
);
|
|
11197
11287
|
const source = closedTableColumnCoverageSource(
|
|
@@ -11217,7 +11307,8 @@ function closedTableColumnCoverage(value, ruleId, diagnostics) {
|
|
|
11217
11307
|
return hasSupportedKeys && diagnostics.length === diagnosticCountBefore && source !== void 0 && target !== void 0 && require2 !== void 0 ? {
|
|
11218
11308
|
source,
|
|
11219
11309
|
target,
|
|
11220
|
-
require: require2
|
|
11310
|
+
require: require2,
|
|
11311
|
+
...record.allowEmptySource === void 0 ? {} : { allowEmptySource: record.allowEmptySource }
|
|
11221
11312
|
} : void 0;
|
|
11222
11313
|
}
|
|
11223
11314
|
function closedTableColumnCoverageSource(value, ruleId, diagnostics) {
|
|
@@ -11232,7 +11323,7 @@ function closedTableColumnCoverageSource(value, ruleId, diagnostics) {
|
|
|
11232
11323
|
const source = value;
|
|
11233
11324
|
const hasSupportedKeys = pushUnsupportedKeyDiagnostics(
|
|
11234
11325
|
source,
|
|
11235
|
-
["section", "column", "prefix", "caseSensitive"],
|
|
11326
|
+
["section", "column", "prefix", "caseSensitive", "rowWhere"],
|
|
11236
11327
|
diagnostics
|
|
11237
11328
|
);
|
|
11238
11329
|
const hasValidFields = pushNonEmptyStringDiagnostic(
|
|
@@ -11256,7 +11347,9 @@ function closedTableColumnCoverageSource(value, ruleId, diagnostics) {
|
|
|
11256
11347
|
ruleId,
|
|
11257
11348
|
diagnostics
|
|
11258
11349
|
);
|
|
11259
|
-
|
|
11350
|
+
const rowWhere = source.rowWhere === void 0 ? void 0 : cellPredicateFromValue(source.rowWhere, "rowWhere", diagnostics);
|
|
11351
|
+
return hasSupportedKeys && hasValidFields && (source.rowWhere === void 0 || rowWhere !== void 0) ? {
|
|
11352
|
+
...rowWhere === void 0 ? {} : { rowWhere },
|
|
11260
11353
|
section: source.section,
|
|
11261
11354
|
column: source.column,
|
|
11262
11355
|
...optionalString("prefix", source.prefix),
|
|
@@ -11431,7 +11524,7 @@ function optionalNumber2(key, value) {
|
|
|
11431
11524
|
return value === void 0 ? {} : { [key]: value };
|
|
11432
11525
|
}
|
|
11433
11526
|
function hasTextPredicate(assertion) {
|
|
11434
|
-
return assertion?.contains !== void 0 || assertion?.excludes !== void 0 && assertion.excludes.length > 0;
|
|
11527
|
+
return assertion?.nonBlank === true || assertion?.contains !== void 0 || assertion?.excludes !== void 0 && assertion.excludes.length > 0;
|
|
11435
11528
|
}
|
|
11436
11529
|
|
|
11437
11530
|
// src/declarative-validation/profile/ids-assertion-contract.ts
|
|
@@ -11856,7 +11949,9 @@ function buildTableColumnCoverageAssertion(assertion, selector, ruleId, syntaxVe
|
|
|
11856
11949
|
)) {
|
|
11857
11950
|
return {
|
|
11858
11951
|
kind: "tableColumnCoverage",
|
|
11952
|
+
...tableColumnCoverage.allowEmptySource === void 0 ? {} : { allowEmptySource: tableColumnCoverage.allowEmptySource },
|
|
11859
11953
|
source: {
|
|
11954
|
+
...tableColumnCoverage.source.rowWhere === void 0 ? {} : { rowWhere: tableColumnCoverage.source.rowWhere },
|
|
11860
11955
|
section: tableColumnCoverage.source.section,
|
|
11861
11956
|
column: tableColumnCoverage.source.column,
|
|
11862
11957
|
caseSensitive: tableColumnCoverage.source.caseSensitive ?? true,
|
|
@@ -11895,13 +11990,13 @@ function buildFrontmatterShapeAssertion(assertion, selector, ruleId, syntaxVersi
|
|
|
11895
11990
|
}
|
|
11896
11991
|
return void 0;
|
|
11897
11992
|
}
|
|
11898
|
-
function buildTextAssertion(assertion, selector, ruleId,
|
|
11993
|
+
function buildTextAssertion(assertion, selector, ruleId, syntaxVersion, diagnostics) {
|
|
11899
11994
|
if (assertion.text === void 0) {
|
|
11900
11995
|
return void 0;
|
|
11901
11996
|
}
|
|
11902
11997
|
if (!pushObjectDiagnostic("text", assertion.text, ruleId, diagnostics) || !pushUnsupportedKeyDiagnostics(
|
|
11903
11998
|
assertion.text,
|
|
11904
|
-
["contains", "excludes"],
|
|
11999
|
+
syntaxVersion === PROFILE_SYNTAX_VERSION_V2 ? ["contains", "excludes", "nonBlank"] : ["contains", "excludes"],
|
|
11905
12000
|
diagnostics
|
|
11906
12001
|
) || !pushTextShapeDiagnostics(assertion.text, ruleId, diagnostics)) {
|
|
11907
12002
|
return void 0;
|
|
@@ -11919,6 +12014,7 @@ function buildTextAssertion(assertion, selector, ruleId, _syntaxVersion, diagnos
|
|
|
11919
12014
|
if (pushCompatibilityDiagnostic("text", selector, ruleId, diagnostics)) {
|
|
11920
12015
|
return {
|
|
11921
12016
|
kind: "text",
|
|
12017
|
+
...assertion.text.nonBlank === true ? { nonBlank: true } : {},
|
|
11922
12018
|
...optionalString("contains", assertion.text.contains),
|
|
11923
12019
|
...optionalStringArray("excludes", assertion.text.excludes)
|
|
11924
12020
|
};
|
|
@@ -12817,7 +12913,7 @@ function assertionFromValue(value, syntaxVersion, diagnostics) {
|
|
|
12817
12913
|
...tableColumnsRequiredFromValue(value.tableColumnsRequired, diagnostics),
|
|
12818
12914
|
...idsFromValue(value.ids, syntaxVersion, diagnostics),
|
|
12819
12915
|
...referencesFromValue(value.references, diagnostics),
|
|
12820
|
-
...textAssertionFromValue(value.text, diagnostics),
|
|
12916
|
+
...textAssertionFromValue(value.text, syntaxVersion, diagnostics),
|
|
12821
12917
|
...textOccurrenceCountFromValue(value.textOccurrenceCount, diagnostics),
|
|
12822
12918
|
...textLengthFromValue(value.textLength, diagnostics),
|
|
12823
12919
|
...supportsV2AssertionSurface(syntaxVersion) ? selectionCountFromValue(value.selectionCount, diagnostics) : {},
|
|
@@ -12833,7 +12929,9 @@ function assertionFromValue(value, syntaxVersion, diagnostics) {
|
|
|
12833
12929
|
return void 0;
|
|
12834
12930
|
}
|
|
12835
12931
|
diagnostics.push(
|
|
12836
|
-
invalidShape(
|
|
12932
|
+
invalidShape(
|
|
12933
|
+
"Rule assert must include at least one supported assertion."
|
|
12934
|
+
)
|
|
12837
12935
|
);
|
|
12838
12936
|
return void 0;
|
|
12839
12937
|
}
|
|
@@ -13014,7 +13112,12 @@ function tableColumnCoverageFromValue(value, diagnostics) {
|
|
|
13014
13112
|
diagnostics.push(invalidShape("tableColumnCoverage must be an object."));
|
|
13015
13113
|
return {};
|
|
13016
13114
|
}
|
|
13017
|
-
unsupportedKeys(
|
|
13115
|
+
unsupportedKeys(
|
|
13116
|
+
value,
|
|
13117
|
+
["source", "target", "require", "allowEmptySource"],
|
|
13118
|
+
diagnostics
|
|
13119
|
+
);
|
|
13120
|
+
const emptyPolicy = optionalBoolean2(value, "allowEmptySource", diagnostics);
|
|
13018
13121
|
const source = tableColumnCoverageSourceFromValue(value.source, diagnostics);
|
|
13019
13122
|
const target = tableColumnCoverageTargetFromValue(value.target, diagnostics);
|
|
13020
13123
|
const require2 = value.require === "everySourceId" ? "everySourceId" : void 0;
|
|
@@ -13025,6 +13128,7 @@ function tableColumnCoverageFromValue(value, diagnostics) {
|
|
|
13025
13128
|
}
|
|
13026
13129
|
return source === void 0 || target === void 0 || require2 === void 0 ? {} : {
|
|
13027
13130
|
tableColumnCoverage: {
|
|
13131
|
+
...emptyPolicy,
|
|
13028
13132
|
source,
|
|
13029
13133
|
target,
|
|
13030
13134
|
require: require2
|
|
@@ -13041,7 +13145,7 @@ function tableColumnCoverageSourceFromValue(value, diagnostics) {
|
|
|
13041
13145
|
}
|
|
13042
13146
|
unsupportedKeys(
|
|
13043
13147
|
value,
|
|
13044
|
-
["section", "column", "prefix", "caseSensitive"],
|
|
13148
|
+
["section", "column", "prefix", "caseSensitive", "rowWhere"],
|
|
13045
13149
|
diagnostics
|
|
13046
13150
|
);
|
|
13047
13151
|
const section = requiredAssertionString(
|
|
@@ -13054,7 +13158,9 @@ function tableColumnCoverageSourceFromValue(value, diagnostics) {
|
|
|
13054
13158
|
"tableColumnCoverage.source.column",
|
|
13055
13159
|
diagnostics
|
|
13056
13160
|
);
|
|
13161
|
+
const rowWhere = value.rowWhere === void 0 ? void 0 : cellPredicateFromValue(value.rowWhere, "rowWhere", diagnostics);
|
|
13057
13162
|
const source = {
|
|
13163
|
+
...rowWhere === void 0 ? {} : { rowWhere },
|
|
13058
13164
|
...optionalTableColumnCoverageSourceString(value, "prefix", diagnostics),
|
|
13059
13165
|
...optionalTableColumnCoverageSourceBoolean(
|
|
13060
13166
|
value,
|
|
@@ -13137,7 +13243,7 @@ function frontmatterShapeFromValue2(value, diagnostics) {
|
|
|
13137
13243
|
}
|
|
13138
13244
|
return result.shape === void 0 ? {} : { frontmatterShape: result.shape };
|
|
13139
13245
|
}
|
|
13140
|
-
function textAssertionFromValue(value, diagnostics) {
|
|
13246
|
+
function textAssertionFromValue(value, syntaxVersion, diagnostics) {
|
|
13141
13247
|
if (value === void 0) {
|
|
13142
13248
|
return {};
|
|
13143
13249
|
}
|
|
@@ -13145,16 +13251,22 @@ function textAssertionFromValue(value, diagnostics) {
|
|
|
13145
13251
|
diagnostics.push(invalidShape("text must be an object."));
|
|
13146
13252
|
return {};
|
|
13147
13253
|
}
|
|
13148
|
-
unsupportedKeys(
|
|
13254
|
+
unsupportedKeys(
|
|
13255
|
+
value,
|
|
13256
|
+
syntaxVersion === PROFILE_SYNTAX_VERSION_V2 ? ["contains", "excludes", "nonBlank"] : ["contains", "excludes"],
|
|
13257
|
+
diagnostics
|
|
13258
|
+
);
|
|
13259
|
+
if (value.nonBlank !== void 0 && value.nonBlank !== true) {
|
|
13260
|
+
diagnostics.push(invalidShape("text.nonBlank must be true when provided."));
|
|
13261
|
+
}
|
|
13149
13262
|
const text5 = {
|
|
13263
|
+
...value.nonBlank === true ? { nonBlank: true } : {},
|
|
13150
13264
|
...optionalAssertionString(value, "contains", diagnostics),
|
|
13151
13265
|
...optionalStringArray2(value, "excludes", diagnostics)
|
|
13152
13266
|
};
|
|
13153
13267
|
if (!hasTextPredicate2(text5)) {
|
|
13154
13268
|
diagnostics.push(
|
|
13155
|
-
invalidShape(
|
|
13156
|
-
"text must include contains or a non-empty excludes array."
|
|
13157
|
-
)
|
|
13269
|
+
invalidShape("text must include contains or a non-empty excludes array.")
|
|
13158
13270
|
);
|
|
13159
13271
|
return {};
|
|
13160
13272
|
}
|
|
@@ -13299,7 +13411,7 @@ function requiredAssertionString(value, field, diagnostics) {
|
|
|
13299
13411
|
);
|
|
13300
13412
|
}
|
|
13301
13413
|
function hasTextPredicate2(text5) {
|
|
13302
|
-
return text5?.contains !== void 0 || text5?.excludes !== void 0;
|
|
13414
|
+
return text5?.nonBlank === true || text5?.contains !== void 0 || text5?.excludes !== void 0;
|
|
13303
13415
|
}
|
|
13304
13416
|
function isNonNegativeInteger2(value) {
|
|
13305
13417
|
return Number.isInteger(value) && value >= 0;
|
|
@@ -13814,7 +13926,7 @@ function cloneSourceRange2(sourceRange) {
|
|
|
13814
13926
|
import { createHash } from "node:crypto";
|
|
13815
13927
|
|
|
13816
13928
|
// src/internal/package-version.ts
|
|
13817
|
-
var MARKDOWN_ENGINE_PACKAGE_VERSION = "3.
|
|
13929
|
+
var MARKDOWN_ENGINE_PACKAGE_VERSION = "3.6.0";
|
|
13818
13930
|
|
|
13819
13931
|
// src/internal/stable-json.ts
|
|
13820
13932
|
function normalizeStableJsonValue(value) {
|
|
@@ -26608,7 +26720,7 @@ function toEngineNode(node2, options) {
|
|
|
26608
26720
|
const type = typeof node2.type === "string" ? node2.type : "unknown";
|
|
26609
26721
|
const attributes = nodeAttributes(type, node2, options);
|
|
26610
26722
|
const sourceRange = toSourceRange(node2.position, options);
|
|
26611
|
-
const text5 =
|
|
26723
|
+
const text5 = nodeText3(type, node2);
|
|
26612
26724
|
const children = childNodes(node2).map((child) => toEngineNode(child, options));
|
|
26613
26725
|
return {
|
|
26614
26726
|
type,
|
|
@@ -26715,7 +26827,7 @@ function assignStringAttribute(attributes, name, value) {
|
|
|
26715
26827
|
function isTableAlignment(alignment) {
|
|
26716
26828
|
return alignment === "left" || alignment === "right" || alignment === "center" || alignment === null;
|
|
26717
26829
|
}
|
|
26718
|
-
function
|
|
26830
|
+
function nodeText3(type, node2) {
|
|
26719
26831
|
if (typeof node2.value === "string") {
|
|
26720
26832
|
return node2.value;
|
|
26721
26833
|
}
|
|
@@ -323,7 +323,9 @@ interface DeclarativeAssertion {
|
|
|
323
323
|
mustAppearIn: readonly string[];
|
|
324
324
|
};
|
|
325
325
|
tableColumnCoverage?: {
|
|
326
|
+
allowEmptySource?: boolean;
|
|
326
327
|
source: {
|
|
328
|
+
rowWhere?: { column: string; equals?: string; includes?: string };
|
|
327
329
|
section: string;
|
|
328
330
|
column: string;
|
|
329
331
|
prefix?: string;
|
|
@@ -349,6 +351,7 @@ interface DeclarativeAssertion {
|
|
|
349
351
|
}[];
|
|
350
352
|
};
|
|
351
353
|
text?: {
|
|
354
|
+
nonBlank?: true; // v2 only
|
|
352
355
|
contains?: string;
|
|
353
356
|
excludes?: readonly string[];
|
|
354
357
|
};
|
|
@@ -461,6 +464,22 @@ target section do not satisfy coverage. Missing target sections, missing target
|
|
|
461
464
|
columns, and missing target-column IDs emit deterministic validation diagnostics
|
|
462
465
|
source-grounded to the source ID when source evidence is available.
|
|
463
466
|
|
|
467
|
+
The opt-in `source.rowWhere` filter uses the same closed sibling-cell predicate
|
|
468
|
+
as the `tableCell` selector: a non-empty `column` plus `equals`, `includes`, or
|
|
469
|
+
both. Both predicates must match when both are present. Matching uses normalized,
|
|
470
|
+
case-sensitive cell text before ID extraction; `source.caseSensitive` affects ID
|
|
471
|
+
comparison only. Rows marked optional can therefore be excluded without encoding
|
|
472
|
+
consumer vocabulary in the engine. A missing predicate column is unresolved,
|
|
473
|
+
not a resolved empty source.
|
|
474
|
+
|
|
475
|
+
`allowEmptySource` is an optional boolean. Absent or `false` preserves the existing
|
|
476
|
+
`profile.validation.emptySelection` failure when no source IDs are extracted.
|
|
477
|
+
`true` permits zero extracted IDs only when the source section and column resolve
|
|
478
|
+
(including the predicate column when configured). With no source IDs there are no
|
|
479
|
+
target obligations; target resolution is not evaluated. A missing source structure
|
|
480
|
+
still fails. Pair this policy with consumer shape rules when tables or rows are
|
|
481
|
+
mandatory. This option also permits prose sentinels in reverse declared-ID checks.
|
|
482
|
+
|
|
464
483
|
For v2 profiles, `frontmatterShape` is admitted as a flat-rule schema and
|
|
465
484
|
internal compiled-plan assertion. It is compatible only with a `document`
|
|
466
485
|
selector because frontmatter is document metadata. `presence` is optional and
|
|
@@ -505,7 +524,18 @@ is combined with `equals` or `nonBlank`, a non-string value emits only
|
|
|
505
524
|
string-predicate diagnostic. Diagnostic messages do not include frontmatter
|
|
506
525
|
values.
|
|
507
526
|
|
|
508
|
-
`text` must include `contains
|
|
527
|
+
`text` must include `contains`, a non-empty `excludes` array, or (v2 only)
|
|
528
|
+
`nonBlank: true`. `nonBlank` cannot be `false`. It requires at least one character
|
|
529
|
+
after JavaScript `trim()` on normalized node text excluding raw `html`, `definition`,
|
|
530
|
+
and `yaml` nodes. Comments, empty HTML tags, and decoded whitespace entities cannot
|
|
531
|
+
satisfy it. Markdown emphasis, link labels, literal code, and escaped angle brackets
|
|
532
|
+
retain their text. Each selected target is checked independently; an empty selector
|
|
533
|
+
still fails. Section selection includes its heading and direct body, as with ordinary
|
|
534
|
+
section text. Original source ranges are optional; the normalized nodes provide the
|
|
535
|
+
content. This is a structural textual-content check, not CSS visibility evaluation
|
|
536
|
+
or browser rendering. It does not evaluate external evidence, freshness, truth,
|
|
537
|
+
or implementation acceptance. Existing `contains`, `excludes`, and `textLength`
|
|
538
|
+
semantics are unchanged, even when combined with `nonBlank`.
|
|
509
539
|
`textOccurrenceCount.count` is a finite number and counts non-overlapping
|
|
510
540
|
literal occurrences per selected target.
|
|
511
541
|
`textLength` must include `min`, `max`, or both. Bounds are non-negative
|
|
@@ -592,6 +622,7 @@ an unproven pass.
|
|
|
592
622
|
| `profile.validation.tableColumnCoverageIdMissing` | Rule severity | A source ID is absent from the configured target table column. |
|
|
593
623
|
| `profile.validation.tableColumnCoverageTargetColumnMissing` | Rule severity | The configured target table column cannot be resolved. |
|
|
594
624
|
| `profile.validation.tableColumnCoverageTargetSectionMissing` | Rule severity | The configured target section cannot be resolved. |
|
|
625
|
+
| `profile.validation.textBlank` | Rule severity | A target configured with `text.nonBlank` lacks non-whitespace text outside raw HTML. |
|
|
595
626
|
| `profile.validation.textExcluded` | Rule severity | A selected target contains forbidden literal text. |
|
|
596
627
|
| `profile.validation.textMissing` | Rule severity | A selected target lacks required literal text from a `text.contains` assertion. |
|
|
597
628
|
| `profile.validation.assertionUnsupported` | `error` | A compiled assertion or compiled assertion feature has no evaluator implementation; this is an internal safety diagnostic. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jasonbelmonti/markdown-engine",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "Deterministic Markdown parsing and validation engine package.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"test:validation:profile": "npm run build && vitest run tests/declarative-validation-profile.test.ts \"--exclude=.worktrees/**\"",
|
|
72
72
|
"test:validation:compiler": "npm run build && vitest run tests/declarative-validation-compiler.test.ts \"--exclude=.worktrees/**\"",
|
|
73
73
|
"test:validation:selectors": "npm run build && vitest run tests/declarative-validation-selectors.test.ts \"--exclude=.worktrees/**\"",
|
|
74
|
-
"test:validation:assertions": "npm run build && vitest run tests/declarative-validation-assertions.test.ts tests/declarative-validation-frontmatter-shape-assertions.test.ts tests/declarative-validation-table-column-coverage-fixtures.test.ts tests/declarative-validation-table-columns-exact.test.ts tests/declarative-validation-source-assertion-coverage.test.ts tests/declarative-validation-source-length.test.ts tests/declarative-validation-selection-count.test.ts tests/declarative-validation-normalized-source-ranges.test.ts tests/declarative-validation-grouped-rules-fixtures.test.ts tests/declarative-validation-when-skipped-rules-fixtures.test.ts \"--exclude=.worktrees/**\"",
|
|
74
|
+
"test:validation:assertions": "npm run build && vitest run tests/declarative-validation-assertions.test.ts tests/declarative-validation-filtered-coverage.test.ts tests/declarative-validation-visible-text.test.ts tests/declarative-validation-frontmatter-shape-assertions.test.ts tests/declarative-validation-table-column-coverage-fixtures.test.ts tests/declarative-validation-table-columns-exact.test.ts tests/declarative-validation-source-assertion-coverage.test.ts tests/declarative-validation-source-length.test.ts tests/declarative-validation-selection-count.test.ts tests/declarative-validation-normalized-source-ranges.test.ts tests/declarative-validation-grouped-rules-fixtures.test.ts tests/declarative-validation-when-skipped-rules-fixtures.test.ts \"--exclude=.worktrees/**\"",
|
|
75
75
|
"test:validation:diagnostics": "npm run build && vitest run tests/declarative-validation-diagnostics.test.ts \"--exclude=.worktrees/**\"",
|
|
76
76
|
"test:validation:cli": "npm run build && vitest run tests/declarative-validation-cli.test.ts \"--exclude=.worktrees/**\"",
|
|
77
77
|
"test:validation:examples": "npm run build && vitest run tests/declarative-validation-examples.test.ts \"--exclude=.worktrees/**\"",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env sh
|
|
2
2
|
set -eu
|
|
3
3
|
|
|
4
|
-
VERSION="3.
|
|
4
|
+
VERSION="3.6.0"
|
|
5
5
|
PACKAGE="@jasonbelmonti/markdown-engine"
|
|
6
|
-
EXPECTED_SHA256="
|
|
6
|
+
EXPECTED_SHA256="69b855d4d3623d7cdb91a3ed09631715dde8b96fc57f49864ae2c7c7c071ad83"
|
|
7
7
|
DEFAULT_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
|
|
8
8
|
MARKDOWN_ENGINE_HOME="${MARKDOWN_ENGINE_HOME:-$DEFAULT_DATA_HOME/markdown-engine}"
|
|
9
9
|
MARKDOWN_ENGINE_BIN_DIR="${MARKDOWN_ENGINE_BIN_DIR:-$HOME/.local/bin}"
|