@rejot-dev/thalo 0.0.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/LICENSE +21 -0
- package/README.md +396 -0
- package/dist/ast/ast-types.d.ts +469 -0
- package/dist/ast/ast-types.d.ts.map +1 -0
- package/dist/ast/ast-types.js +11 -0
- package/dist/ast/ast-types.js.map +1 -0
- package/dist/ast/builder.js +158 -0
- package/dist/ast/builder.js.map +1 -0
- package/dist/ast/extract.js +748 -0
- package/dist/ast/extract.js.map +1 -0
- package/dist/ast/node-at-position.d.ts +147 -0
- package/dist/ast/node-at-position.d.ts.map +1 -0
- package/dist/ast/node-at-position.js +382 -0
- package/dist/ast/node-at-position.js.map +1 -0
- package/dist/ast/visitor.js +232 -0
- package/dist/ast/visitor.js.map +1 -0
- package/dist/checker/check.d.ts +53 -0
- package/dist/checker/check.d.ts.map +1 -0
- package/dist/checker/check.js +105 -0
- package/dist/checker/check.js.map +1 -0
- package/dist/checker/rules/actualize-missing-updated.js +34 -0
- package/dist/checker/rules/actualize-missing-updated.js.map +1 -0
- package/dist/checker/rules/actualize-unresolved-target.js +42 -0
- package/dist/checker/rules/actualize-unresolved-target.js.map +1 -0
- package/dist/checker/rules/alter-before-define.js +53 -0
- package/dist/checker/rules/alter-before-define.js.map +1 -0
- package/dist/checker/rules/alter-undefined-entity.js +32 -0
- package/dist/checker/rules/alter-undefined-entity.js.map +1 -0
- package/dist/checker/rules/create-requires-section.js +34 -0
- package/dist/checker/rules/create-requires-section.js.map +1 -0
- package/dist/checker/rules/define-entity-requires-section.js +31 -0
- package/dist/checker/rules/define-entity-requires-section.js.map +1 -0
- package/dist/checker/rules/duplicate-entity-definition.js +37 -0
- package/dist/checker/rules/duplicate-entity-definition.js.map +1 -0
- package/dist/checker/rules/duplicate-field-in-schema.js +38 -0
- package/dist/checker/rules/duplicate-field-in-schema.js.map +1 -0
- package/dist/checker/rules/duplicate-link-id.js +52 -0
- package/dist/checker/rules/duplicate-link-id.js.map +1 -0
- package/dist/checker/rules/duplicate-metadata-key.js +21 -0
- package/dist/checker/rules/duplicate-metadata-key.js.map +1 -0
- package/dist/checker/rules/duplicate-section-heading.js +41 -0
- package/dist/checker/rules/duplicate-section-heading.js.map +1 -0
- package/dist/checker/rules/duplicate-section-in-schema.js +38 -0
- package/dist/checker/rules/duplicate-section-in-schema.js.map +1 -0
- package/dist/checker/rules/duplicate-timestamp.js +104 -0
- package/dist/checker/rules/duplicate-timestamp.js.map +1 -0
- package/dist/checker/rules/empty-required-value.js +45 -0
- package/dist/checker/rules/empty-required-value.js.map +1 -0
- package/dist/checker/rules/empty-section.js +21 -0
- package/dist/checker/rules/empty-section.js.map +1 -0
- package/dist/checker/rules/invalid-date-range-value.js +56 -0
- package/dist/checker/rules/invalid-date-range-value.js.map +1 -0
- package/dist/checker/rules/invalid-default-value.js +86 -0
- package/dist/checker/rules/invalid-default-value.js.map +1 -0
- package/dist/checker/rules/invalid-field-type.js +45 -0
- package/dist/checker/rules/invalid-field-type.js.map +1 -0
- package/dist/checker/rules/missing-required-field.js +48 -0
- package/dist/checker/rules/missing-required-field.js.map +1 -0
- package/dist/checker/rules/missing-required-section.js +51 -0
- package/dist/checker/rules/missing-required-section.js.map +1 -0
- package/dist/checker/rules/missing-title.js +56 -0
- package/dist/checker/rules/missing-title.js.map +1 -0
- package/dist/checker/rules/remove-undefined-field.js +42 -0
- package/dist/checker/rules/remove-undefined-field.js.map +1 -0
- package/dist/checker/rules/remove-undefined-section.js +42 -0
- package/dist/checker/rules/remove-undefined-section.js.map +1 -0
- package/dist/checker/rules/rules.d.ts +71 -0
- package/dist/checker/rules/rules.d.ts.map +1 -0
- package/dist/checker/rules/rules.js +102 -0
- package/dist/checker/rules/rules.js.map +1 -0
- package/dist/checker/rules/synthesis-empty-query.js +35 -0
- package/dist/checker/rules/synthesis-empty-query.js.map +1 -0
- package/dist/checker/rules/synthesis-missing-prompt.js +42 -0
- package/dist/checker/rules/synthesis-missing-prompt.js.map +1 -0
- package/dist/checker/rules/synthesis-missing-sources.js +32 -0
- package/dist/checker/rules/synthesis-missing-sources.js.map +1 -0
- package/dist/checker/rules/synthesis-unknown-query-entity.js +39 -0
- package/dist/checker/rules/synthesis-unknown-query-entity.js.map +1 -0
- package/dist/checker/rules/timestamp-out-of-order.js +55 -0
- package/dist/checker/rules/timestamp-out-of-order.js.map +1 -0
- package/dist/checker/rules/unknown-entity.js +32 -0
- package/dist/checker/rules/unknown-entity.js.map +1 -0
- package/dist/checker/rules/unknown-field.js +40 -0
- package/dist/checker/rules/unknown-field.js.map +1 -0
- package/dist/checker/rules/unknown-section.js +47 -0
- package/dist/checker/rules/unknown-section.js.map +1 -0
- package/dist/checker/rules/unresolved-link.js +34 -0
- package/dist/checker/rules/unresolved-link.js.map +1 -0
- package/dist/checker/rules/update-without-create.js +65 -0
- package/dist/checker/rules/update-without-create.js.map +1 -0
- package/dist/checker/visitor.d.ts +69 -0
- package/dist/checker/visitor.d.ts.map +1 -0
- package/dist/checker/visitor.js +67 -0
- package/dist/checker/visitor.js.map +1 -0
- package/dist/checker/workspace-index.d.ts +50 -0
- package/dist/checker/workspace-index.d.ts.map +1 -0
- package/dist/checker/workspace-index.js +108 -0
- package/dist/checker/workspace-index.js.map +1 -0
- package/dist/commands/actualize.d.ts +113 -0
- package/dist/commands/actualize.d.ts.map +1 -0
- package/dist/commands/actualize.js +111 -0
- package/dist/commands/actualize.js.map +1 -0
- package/dist/commands/check.d.ts +65 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +61 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/format.d.ts +90 -0
- package/dist/commands/format.d.ts.map +1 -0
- package/dist/commands/format.js +80 -0
- package/dist/commands/format.js.map +1 -0
- package/dist/commands/query.d.ts +152 -0
- package/dist/commands/query.d.ts.map +1 -0
- package/dist/commands/query.js +151 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/constants.d.ts +31 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +51 -0
- package/dist/constants.js.map +1 -0
- package/dist/files.d.ts +58 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +103 -0
- package/dist/files.js.map +1 -0
- package/dist/formatters.d.ts +39 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +200 -0
- package/dist/formatters.js.map +1 -0
- package/dist/fragment.d.ts +22 -0
- package/dist/fragment.d.ts.map +1 -0
- package/dist/git/git.js +240 -0
- package/dist/git/git.js.map +1 -0
- package/dist/merge/conflict-detector.d.ts +89 -0
- package/dist/merge/conflict-detector.d.ts.map +1 -0
- package/dist/merge/conflict-detector.js +352 -0
- package/dist/merge/conflict-detector.js.map +1 -0
- package/dist/merge/conflict-formatter.js +143 -0
- package/dist/merge/conflict-formatter.js.map +1 -0
- package/dist/merge/driver.d.ts +54 -0
- package/dist/merge/driver.d.ts.map +1 -0
- package/dist/merge/driver.js +112 -0
- package/dist/merge/driver.js.map +1 -0
- package/dist/merge/entry-matcher.d.ts +50 -0
- package/dist/merge/entry-matcher.d.ts.map +1 -0
- package/dist/merge/entry-matcher.js +141 -0
- package/dist/merge/entry-matcher.js.map +1 -0
- package/dist/merge/entry-merger.js +194 -0
- package/dist/merge/entry-merger.js.map +1 -0
- package/dist/merge/merge-result-builder.d.ts +62 -0
- package/dist/merge/merge-result-builder.d.ts.map +1 -0
- package/dist/merge/merge-result-builder.js +89 -0
- package/dist/merge/merge-result-builder.js.map +1 -0
- package/dist/mod.d.ts +31 -0
- package/dist/mod.js +23 -0
- package/dist/model/document.d.ts +134 -0
- package/dist/model/document.d.ts.map +1 -0
- package/dist/model/document.js +275 -0
- package/dist/model/document.js.map +1 -0
- package/dist/model/line-index.d.ts +85 -0
- package/dist/model/line-index.d.ts.map +1 -0
- package/dist/model/line-index.js +159 -0
- package/dist/model/line-index.js.map +1 -0
- package/dist/model/workspace.d.ts +296 -0
- package/dist/model/workspace.d.ts.map +1 -0
- package/dist/model/workspace.js +562 -0
- package/dist/model/workspace.js.map +1 -0
- package/dist/parser.js +27 -0
- package/dist/parser.js.map +1 -0
- package/dist/parser.native.d.ts +51 -0
- package/dist/parser.native.d.ts.map +1 -0
- package/dist/parser.native.js +62 -0
- package/dist/parser.native.js.map +1 -0
- package/dist/parser.shared.d.ts +99 -0
- package/dist/parser.shared.d.ts.map +1 -0
- package/dist/parser.shared.js +124 -0
- package/dist/parser.shared.js.map +1 -0
- package/dist/parser.web.d.ts +67 -0
- package/dist/parser.web.d.ts.map +1 -0
- package/dist/parser.web.js +49 -0
- package/dist/parser.web.js.map +1 -0
- package/dist/schema/registry.d.ts +108 -0
- package/dist/schema/registry.d.ts.map +1 -0
- package/dist/schema/registry.js +281 -0
- package/dist/schema/registry.js.map +1 -0
- package/dist/semantic/analyzer.d.ts +107 -0
- package/dist/semantic/analyzer.d.ts.map +1 -0
- package/dist/semantic/analyzer.js +261 -0
- package/dist/semantic/analyzer.js.map +1 -0
- package/dist/services/change-tracker/change-tracker.d.ts +111 -0
- package/dist/services/change-tracker/change-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/change-tracker.js +62 -0
- package/dist/services/change-tracker/change-tracker.js.map +1 -0
- package/dist/services/change-tracker/create-tracker.d.ts +42 -0
- package/dist/services/change-tracker/create-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/create-tracker.js +53 -0
- package/dist/services/change-tracker/create-tracker.js.map +1 -0
- package/dist/services/change-tracker/git-tracker.d.ts +59 -0
- package/dist/services/change-tracker/git-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/git-tracker.js +218 -0
- package/dist/services/change-tracker/git-tracker.js.map +1 -0
- package/dist/services/change-tracker/timestamp-tracker.d.ts +22 -0
- package/dist/services/change-tracker/timestamp-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/timestamp-tracker.js +74 -0
- package/dist/services/change-tracker/timestamp-tracker.js.map +1 -0
- package/dist/services/definition.d.ts +37 -0
- package/dist/services/definition.d.ts.map +1 -0
- package/dist/services/definition.js +43 -0
- package/dist/services/definition.js.map +1 -0
- package/dist/services/entity-navigation.d.ts +200 -0
- package/dist/services/entity-navigation.d.ts.map +1 -0
- package/dist/services/entity-navigation.js +211 -0
- package/dist/services/entity-navigation.js.map +1 -0
- package/dist/services/hover.d.ts +81 -0
- package/dist/services/hover.d.ts.map +1 -0
- package/dist/services/hover.js +669 -0
- package/dist/services/hover.js.map +1 -0
- package/dist/services/query.d.ts +116 -0
- package/dist/services/query.d.ts.map +1 -0
- package/dist/services/query.js +225 -0
- package/dist/services/query.js.map +1 -0
- package/dist/services/references.d.ts +52 -0
- package/dist/services/references.d.ts.map +1 -0
- package/dist/services/references.js +66 -0
- package/dist/services/references.js.map +1 -0
- package/dist/services/semantic-tokens.d.ts +54 -0
- package/dist/services/semantic-tokens.d.ts.map +1 -0
- package/dist/services/semantic-tokens.js +213 -0
- package/dist/services/semantic-tokens.js.map +1 -0
- package/dist/services/synthesis.d.ts +90 -0
- package/dist/services/synthesis.d.ts.map +1 -0
- package/dist/services/synthesis.js +113 -0
- package/dist/services/synthesis.js.map +1 -0
- package/dist/source-map.d.ts +42 -0
- package/dist/source-map.d.ts.map +1 -0
- package/dist/source-map.js +170 -0
- package/dist/source-map.js.map +1 -0
- package/package.json +128 -0
- package/tree-sitter-thalo.wasm +0 -0
- package/web-tree-sitter.wasm +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { formatTimestamp } from "../../formatters.js";
|
|
2
|
+
|
|
3
|
+
//#region src/checker/rules/alter-before-define.ts
|
|
4
|
+
const category = "schema";
|
|
5
|
+
const visitor = { afterCheck(ctx) {
|
|
6
|
+
const { index } = ctx;
|
|
7
|
+
for (const [entityName, alterEntries] of index.alterEntitiesByName) {
|
|
8
|
+
const defineEntries = index.defineEntitiesByName.get(entityName);
|
|
9
|
+
if (!defineEntries || defineEntries.length === 0) continue;
|
|
10
|
+
let earliestDefine = null;
|
|
11
|
+
let earliestDefineTs = "";
|
|
12
|
+
for (const entry of defineEntries) {
|
|
13
|
+
const ts = formatTimestamp(entry.entry.header.timestamp);
|
|
14
|
+
if (!earliestDefine || ts < earliestDefineTs) {
|
|
15
|
+
earliestDefine = entry;
|
|
16
|
+
earliestDefineTs = ts;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
for (const { entry, file, sourceMap } of alterEntries) {
|
|
20
|
+
const alterTs = formatTimestamp(entry.header.timestamp);
|
|
21
|
+
if (alterTs < earliestDefineTs) ctx.report({
|
|
22
|
+
message: `alter-entity for '${entityName}' has timestamp ${alterTs} which is before the define-entity at ${earliestDefineTs}.`,
|
|
23
|
+
file,
|
|
24
|
+
location: entry.location,
|
|
25
|
+
sourceMap,
|
|
26
|
+
data: {
|
|
27
|
+
entityName,
|
|
28
|
+
alterTimestamp: alterTs,
|
|
29
|
+
defineTimestamp: earliestDefineTs
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} };
|
|
35
|
+
/**
|
|
36
|
+
* Check for alter-entity entries with timestamps earlier than the define-entity
|
|
37
|
+
*/
|
|
38
|
+
const alterBeforeDefineRule = {
|
|
39
|
+
code: "alter-before-define",
|
|
40
|
+
name: "Alter Before Define",
|
|
41
|
+
description: "alter-entity timestamp before define-entity",
|
|
42
|
+
category,
|
|
43
|
+
defaultSeverity: "error",
|
|
44
|
+
dependencies: {
|
|
45
|
+
scope: "workspace",
|
|
46
|
+
schemas: true
|
|
47
|
+
},
|
|
48
|
+
visitor
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
export { alterBeforeDefineRule };
|
|
53
|
+
//# sourceMappingURL=alter-before-define.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alter-before-define.js","names":["category: RuleCategory","visitor: RuleVisitor","earliestDefine: IndexedEntry<SchemaEntry> | null","alterBeforeDefineRule: Rule"],"sources":["../../../src/checker/rules/alter-before-define.ts"],"sourcesContent":["import type { Rule, RuleCategory } from \"../rules/rules.js\";\nimport type { RuleVisitor, VisitorContext } from \"../visitor.js\";\nimport type { SchemaEntry } from \"../../ast/ast-types.js\";\nimport type { IndexedEntry } from \"../workspace-index.js\";\nimport { formatTimestamp } from \"../../formatters.js\";\n\nconst category: RuleCategory = \"schema\";\n\nconst visitor: RuleVisitor = {\n afterCheck(ctx: VisitorContext) {\n const { index } = ctx;\n\n // For each entity with alter entries, check against define entries\n for (const [entityName, alterEntries] of index.alterEntitiesByName) {\n const defineEntries = index.defineEntitiesByName.get(entityName);\n if (!defineEntries || defineEntries.length === 0) {\n continue; // No define - handled by alter-undefined-entity rule\n }\n\n // Find the earliest define timestamp\n let earliestDefine: IndexedEntry<SchemaEntry> | null = null;\n let earliestDefineTs = \"\";\n\n for (const entry of defineEntries) {\n const ts = formatTimestamp(entry.entry.header.timestamp);\n if (!earliestDefine || ts < earliestDefineTs) {\n earliestDefine = entry;\n earliestDefineTs = ts;\n }\n }\n\n // Check each alter entry\n for (const { entry, file, sourceMap } of alterEntries) {\n const alterTs = formatTimestamp(entry.header.timestamp);\n\n if (alterTs < earliestDefineTs) {\n ctx.report({\n message: `alter-entity for '${entityName}' has timestamp ${alterTs} which is before the define-entity at ${earliestDefineTs}.`,\n file,\n location: entry.location,\n sourceMap,\n data: {\n entityName,\n alterTimestamp: alterTs,\n defineTimestamp: earliestDefineTs,\n },\n });\n }\n }\n }\n },\n};\n\n/**\n * Check for alter-entity entries with timestamps earlier than the define-entity\n */\nexport const alterBeforeDefineRule: Rule = {\n code: \"alter-before-define\",\n name: \"Alter Before Define\",\n description: \"alter-entity timestamp before define-entity\",\n category,\n defaultSeverity: \"error\",\n dependencies: { scope: \"workspace\", schemas: true },\n visitor,\n};\n"],"mappings":";;;AAMA,MAAMA,WAAyB;AAE/B,MAAMC,UAAuB,EAC3B,WAAW,KAAqB;CAC9B,MAAM,EAAE,UAAU;AAGlB,MAAK,MAAM,CAAC,YAAY,iBAAiB,MAAM,qBAAqB;EAClE,MAAM,gBAAgB,MAAM,qBAAqB,IAAI,WAAW;AAChE,MAAI,CAAC,iBAAiB,cAAc,WAAW,EAC7C;EAIF,IAAIC,iBAAmD;EACvD,IAAI,mBAAmB;AAEvB,OAAK,MAAM,SAAS,eAAe;GACjC,MAAM,KAAK,gBAAgB,MAAM,MAAM,OAAO,UAAU;AACxD,OAAI,CAAC,kBAAkB,KAAK,kBAAkB;AAC5C,qBAAiB;AACjB,uBAAmB;;;AAKvB,OAAK,MAAM,EAAE,OAAO,MAAM,eAAe,cAAc;GACrD,MAAM,UAAU,gBAAgB,MAAM,OAAO,UAAU;AAEvD,OAAI,UAAU,iBACZ,KAAI,OAAO;IACT,SAAS,qBAAqB,WAAW,kBAAkB,QAAQ,wCAAwC,iBAAiB;IAC5H;IACA,UAAU,MAAM;IAChB;IACA,MAAM;KACJ;KACA,gBAAgB;KAChB,iBAAiB;KAClB;IACF,CAAC;;;GAKX;;;;AAKD,MAAaC,wBAA8B;CACzC,MAAM;CACN,MAAM;CACN,aAAa;CACb;CACA,iBAAiB;CACjB,cAAc;EAAE,OAAO;EAAa,SAAS;EAAM;CACnD;CACD"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/checker/rules/alter-undefined-entity.ts
|
|
2
|
+
const category = "schema";
|
|
3
|
+
const visitor = { afterCheck(ctx) {
|
|
4
|
+
const { index } = ctx;
|
|
5
|
+
const definedEntities = new Set(index.defineEntitiesByName.keys());
|
|
6
|
+
for (const [entityName, alters] of index.alterEntitiesByName) if (!definedEntities.has(entityName)) for (const { entry, file, sourceMap } of alters) ctx.report({
|
|
7
|
+
message: `Cannot alter undefined entity '${entityName}'. Define it first using 'define-entity ${entityName}'.`,
|
|
8
|
+
file,
|
|
9
|
+
location: entry.location,
|
|
10
|
+
sourceMap,
|
|
11
|
+
data: { entityName }
|
|
12
|
+
});
|
|
13
|
+
} };
|
|
14
|
+
/**
|
|
15
|
+
* Check for alter-entity entries targeting entities that were never defined
|
|
16
|
+
*/
|
|
17
|
+
const alterUndefinedEntityRule = {
|
|
18
|
+
code: "alter-undefined-entity",
|
|
19
|
+
name: "Alter Undefined Entity",
|
|
20
|
+
description: "alter-entity targets an undefined entity",
|
|
21
|
+
category,
|
|
22
|
+
defaultSeverity: "error",
|
|
23
|
+
dependencies: {
|
|
24
|
+
scope: "workspace",
|
|
25
|
+
schemas: true
|
|
26
|
+
},
|
|
27
|
+
visitor
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
export { alterUndefinedEntityRule };
|
|
32
|
+
//# sourceMappingURL=alter-undefined-entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alter-undefined-entity.js","names":["category: RuleCategory","visitor: RuleVisitor","alterUndefinedEntityRule: Rule"],"sources":["../../../src/checker/rules/alter-undefined-entity.ts"],"sourcesContent":["import type { Rule, RuleCategory } from \"../rules/rules.js\";\nimport type { RuleVisitor, VisitorContext } from \"../visitor.js\";\n\nconst category: RuleCategory = \"schema\";\n\nconst visitor: RuleVisitor = {\n afterCheck(ctx: VisitorContext) {\n const { index } = ctx;\n\n // Get all defined entity names\n const definedEntities = new Set(index.defineEntitiesByName.keys());\n\n // Check alter-entity entries\n for (const [entityName, alters] of index.alterEntitiesByName) {\n if (!definedEntities.has(entityName)) {\n for (const { entry, file, sourceMap } of alters) {\n ctx.report({\n message: `Cannot alter undefined entity '${entityName}'. Define it first using 'define-entity ${entityName}'.`,\n file,\n location: entry.location,\n sourceMap,\n data: { entityName },\n });\n }\n }\n }\n },\n};\n\n/**\n * Check for alter-entity entries targeting entities that were never defined\n */\nexport const alterUndefinedEntityRule: Rule = {\n code: \"alter-undefined-entity\",\n name: \"Alter Undefined Entity\",\n description: \"alter-entity targets an undefined entity\",\n category,\n defaultSeverity: \"error\",\n dependencies: { scope: \"workspace\", schemas: true },\n visitor,\n};\n"],"mappings":";AAGA,MAAMA,WAAyB;AAE/B,MAAMC,UAAuB,EAC3B,WAAW,KAAqB;CAC9B,MAAM,EAAE,UAAU;CAGlB,MAAM,kBAAkB,IAAI,IAAI,MAAM,qBAAqB,MAAM,CAAC;AAGlE,MAAK,MAAM,CAAC,YAAY,WAAW,MAAM,oBACvC,KAAI,CAAC,gBAAgB,IAAI,WAAW,CAClC,MAAK,MAAM,EAAE,OAAO,MAAM,eAAe,OACvC,KAAI,OAAO;EACT,SAAS,kCAAkC,WAAW,0CAA0C,WAAW;EAC3G;EACA,UAAU,MAAM;EAChB;EACA,MAAM,EAAE,YAAY;EACrB,CAAC;GAKX;;;;AAKD,MAAaC,2BAAiC;CAC5C,MAAM;CACN,MAAM;CACN,aAAa;CACb;CACA,iBAAiB;CACjB,cAAc;EAAE,OAAO;EAAa,SAAS;EAAM;CACnD;CACD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/checker/rules/create-requires-section.ts
|
|
2
|
+
const category = "instance";
|
|
3
|
+
const visitor = { visitInstanceEntry(entry, ctx) {
|
|
4
|
+
if (entry.header.directive !== "create") return;
|
|
5
|
+
if ((entry.content?.children.filter((c) => c.type === "markdown_header").length ?? 0) === 0) {
|
|
6
|
+
const title = entry.header.title?.value ?? "(no title)";
|
|
7
|
+
ctx.report({
|
|
8
|
+
message: `Create entry '${title}' must use at least one section.`,
|
|
9
|
+
file: ctx.file,
|
|
10
|
+
location: entry.location,
|
|
11
|
+
sourceMap: ctx.sourceMap,
|
|
12
|
+
data: {
|
|
13
|
+
title,
|
|
14
|
+
entity: entry.header.entity
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
} };
|
|
19
|
+
/**
|
|
20
|
+
* Check that create entries have at least one section in their content
|
|
21
|
+
*/
|
|
22
|
+
const createRequiresSectionRule = {
|
|
23
|
+
code: "create-requires-section",
|
|
24
|
+
name: "Create Requires Section",
|
|
25
|
+
description: "Create entry must use at least one section",
|
|
26
|
+
category,
|
|
27
|
+
defaultSeverity: "error",
|
|
28
|
+
dependencies: { scope: "entry" },
|
|
29
|
+
visitor
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
export { createRequiresSectionRule };
|
|
34
|
+
//# sourceMappingURL=create-requires-section.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-requires-section.js","names":["category: RuleCategory","visitor: RuleVisitor","createRequiresSectionRule: Rule"],"sources":["../../../src/checker/rules/create-requires-section.ts"],"sourcesContent":["import type { Rule, RuleCategory } from \"../rules/rules.js\";\nimport type { RuleVisitor } from \"../visitor.js\";\n\nconst category: RuleCategory = \"instance\";\n\nconst visitor: RuleVisitor = {\n visitInstanceEntry(entry, ctx) {\n if (entry.header.directive !== \"create\") {\n return;\n }\n\n // Count markdown headers (sections) in content\n const content = entry.content;\n const sectionCount = content?.children.filter((c) => c.type === \"markdown_header\").length ?? 0;\n\n if (sectionCount === 0) {\n const title = entry.header.title?.value ?? \"(no title)\";\n ctx.report({\n message: `Create entry '${title}' must use at least one section.`,\n file: ctx.file,\n location: entry.location,\n sourceMap: ctx.sourceMap,\n data: { title, entity: entry.header.entity },\n });\n }\n },\n};\n\n/**\n * Check that create entries have at least one section in their content\n */\nexport const createRequiresSectionRule: Rule = {\n code: \"create-requires-section\",\n name: \"Create Requires Section\",\n description: \"Create entry must use at least one section\",\n category,\n defaultSeverity: \"error\",\n dependencies: { scope: \"entry\" },\n visitor,\n};\n"],"mappings":";AAGA,MAAMA,WAAyB;AAE/B,MAAMC,UAAuB,EAC3B,mBAAmB,OAAO,KAAK;AAC7B,KAAI,MAAM,OAAO,cAAc,SAC7B;AAOF,MAHgB,MAAM,SACQ,SAAS,QAAQ,MAAM,EAAE,SAAS,kBAAkB,CAAC,UAAU,OAExE,GAAG;EACtB,MAAM,QAAQ,MAAM,OAAO,OAAO,SAAS;AAC3C,MAAI,OAAO;GACT,SAAS,iBAAiB,MAAM;GAChC,MAAM,IAAI;GACV,UAAU,MAAM;GAChB,WAAW,IAAI;GACf,MAAM;IAAE;IAAO,QAAQ,MAAM,OAAO;IAAQ;GAC7C,CAAC;;GAGP;;;;AAKD,MAAaC,4BAAkC;CAC7C,MAAM;CACN,MAAM;CACN,aAAa;CACb;CACA,iBAAiB;CACjB,cAAc,EAAE,OAAO,SAAS;CAChC;CACD"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/checker/rules/define-entity-requires-section.ts
|
|
2
|
+
const category = "schema";
|
|
3
|
+
const visitor = { visitSchemaEntry(entry, ctx) {
|
|
4
|
+
if (entry.header.directive !== "define-entity") return;
|
|
5
|
+
if ((entry.sectionsBlock?.sections.length ?? 0) === 0) {
|
|
6
|
+
const entityName = entry.header.entityName.value;
|
|
7
|
+
ctx.report({
|
|
8
|
+
message: `Entity definition '${entityName}' must have at least one section.`,
|
|
9
|
+
file: ctx.file,
|
|
10
|
+
location: entry.location,
|
|
11
|
+
sourceMap: ctx.sourceMap,
|
|
12
|
+
data: { entityName }
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
} };
|
|
16
|
+
/**
|
|
17
|
+
* Check that define-entity entries have at least one section defined
|
|
18
|
+
*/
|
|
19
|
+
const defineEntityRequiresSectionRule = {
|
|
20
|
+
code: "define-entity-requires-section",
|
|
21
|
+
name: "Define Entity Requires Section",
|
|
22
|
+
description: "Entity definition must have at least one section",
|
|
23
|
+
category,
|
|
24
|
+
defaultSeverity: "error",
|
|
25
|
+
dependencies: { scope: "entry" },
|
|
26
|
+
visitor
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
export { defineEntityRequiresSectionRule };
|
|
31
|
+
//# sourceMappingURL=define-entity-requires-section.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-entity-requires-section.js","names":["category: RuleCategory","visitor: RuleVisitor","defineEntityRequiresSectionRule: Rule"],"sources":["../../../src/checker/rules/define-entity-requires-section.ts"],"sourcesContent":["import type { Rule, RuleCategory } from \"../rules/rules.js\";\nimport type { RuleVisitor } from \"../visitor.js\";\n\nconst category: RuleCategory = \"schema\";\n\nconst visitor: RuleVisitor = {\n visitSchemaEntry(entry, ctx) {\n if (entry.header.directive !== \"define-entity\") {\n return;\n }\n\n const sectionCount = entry.sectionsBlock?.sections.length ?? 0;\n if (sectionCount === 0) {\n const entityName = entry.header.entityName.value;\n ctx.report({\n message: `Entity definition '${entityName}' must have at least one section.`,\n file: ctx.file,\n location: entry.location,\n sourceMap: ctx.sourceMap,\n data: { entityName },\n });\n }\n },\n};\n\n/**\n * Check that define-entity entries have at least one section defined\n */\nexport const defineEntityRequiresSectionRule: Rule = {\n code: \"define-entity-requires-section\",\n name: \"Define Entity Requires Section\",\n description: \"Entity definition must have at least one section\",\n category,\n defaultSeverity: \"error\",\n dependencies: { scope: \"entry\" },\n visitor,\n};\n"],"mappings":";AAGA,MAAMA,WAAyB;AAE/B,MAAMC,UAAuB,EAC3B,iBAAiB,OAAO,KAAK;AAC3B,KAAI,MAAM,OAAO,cAAc,gBAC7B;AAIF,MADqB,MAAM,eAAe,SAAS,UAAU,OACxC,GAAG;EACtB,MAAM,aAAa,MAAM,OAAO,WAAW;AAC3C,MAAI,OAAO;GACT,SAAS,sBAAsB,WAAW;GAC1C,MAAM,IAAI;GACV,UAAU,MAAM;GAChB,WAAW,IAAI;GACf,MAAM,EAAE,YAAY;GACrB,CAAC;;GAGP;;;;AAKD,MAAaC,kCAAwC;CACnD,MAAM;CACN,MAAM;CACN,aAAa;CACb;CACA,iBAAiB;CACjB,cAAc,EAAE,OAAO,SAAS;CAChC;CACD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//#region src/checker/rules/duplicate-entity-definition.ts
|
|
2
|
+
const category = "schema";
|
|
3
|
+
const visitor = { afterCheck(ctx) {
|
|
4
|
+
const { index } = ctx;
|
|
5
|
+
for (const [entityName, defs] of index.defineEntitiesByName) if (defs.length > 1) for (const { entry, file, sourceMap } of defs) {
|
|
6
|
+
const otherLocations = defs.filter((d) => d.entry !== entry).map((d) => `${d.file}:${d.entry.location.startPosition.row + 1}`).join(", ");
|
|
7
|
+
ctx.report({
|
|
8
|
+
message: `Duplicate definition for entity '${entityName}'. Also defined at: ${otherLocations}`,
|
|
9
|
+
file,
|
|
10
|
+
location: entry.location,
|
|
11
|
+
sourceMap,
|
|
12
|
+
data: {
|
|
13
|
+
entityName,
|
|
14
|
+
otherLocations
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
} };
|
|
19
|
+
/**
|
|
20
|
+
* Check for multiple define-entity entries for the same entity name
|
|
21
|
+
*/
|
|
22
|
+
const duplicateEntityDefinitionRule = {
|
|
23
|
+
code: "duplicate-entity-definition",
|
|
24
|
+
name: "Duplicate Entity Definition",
|
|
25
|
+
description: "Multiple define-entity for the same entity name",
|
|
26
|
+
category,
|
|
27
|
+
defaultSeverity: "error",
|
|
28
|
+
dependencies: {
|
|
29
|
+
scope: "workspace",
|
|
30
|
+
schemas: true
|
|
31
|
+
},
|
|
32
|
+
visitor
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
export { duplicateEntityDefinitionRule };
|
|
37
|
+
//# sourceMappingURL=duplicate-entity-definition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-entity-definition.js","names":["category: RuleCategory","visitor: RuleVisitor","duplicateEntityDefinitionRule: Rule"],"sources":["../../../src/checker/rules/duplicate-entity-definition.ts"],"sourcesContent":["import type { Rule, RuleCategory } from \"../rules/rules.js\";\nimport type { RuleVisitor, VisitorContext } from \"../visitor.js\";\n\nconst category: RuleCategory = \"schema\";\n\nconst visitor: RuleVisitor = {\n afterCheck(ctx: VisitorContext) {\n const { index } = ctx;\n\n // Report duplicates\n for (const [entityName, defs] of index.defineEntitiesByName) {\n if (defs.length > 1) {\n for (const { entry, file, sourceMap } of defs) {\n const otherLocations = defs\n .filter((d) => d.entry !== entry)\n .map((d) => `${d.file}:${d.entry.location.startPosition.row + 1}`)\n .join(\", \");\n\n ctx.report({\n message: `Duplicate definition for entity '${entityName}'. Also defined at: ${otherLocations}`,\n file,\n location: entry.location,\n sourceMap,\n data: { entityName, otherLocations },\n });\n }\n }\n }\n },\n};\n\n/**\n * Check for multiple define-entity entries for the same entity name\n */\nexport const duplicateEntityDefinitionRule: Rule = {\n code: \"duplicate-entity-definition\",\n name: \"Duplicate Entity Definition\",\n description: \"Multiple define-entity for the same entity name\",\n category,\n defaultSeverity: \"error\",\n dependencies: { scope: \"workspace\", schemas: true },\n visitor,\n};\n"],"mappings":";AAGA,MAAMA,WAAyB;AAE/B,MAAMC,UAAuB,EAC3B,WAAW,KAAqB;CAC9B,MAAM,EAAE,UAAU;AAGlB,MAAK,MAAM,CAAC,YAAY,SAAS,MAAM,qBACrC,KAAI,KAAK,SAAS,EAChB,MAAK,MAAM,EAAE,OAAO,MAAM,eAAe,MAAM;EAC7C,MAAM,iBAAiB,KACpB,QAAQ,MAAM,EAAE,UAAU,MAAM,CAChC,KAAK,MAAM,GAAG,EAAE,KAAK,GAAG,EAAE,MAAM,SAAS,cAAc,MAAM,IAAI,CACjE,KAAK,KAAK;AAEb,MAAI,OAAO;GACT,SAAS,oCAAoC,WAAW,sBAAsB;GAC9E;GACA,UAAU,MAAM;GAChB;GACA,MAAM;IAAE;IAAY;IAAgB;GACrC,CAAC;;GAKX;;;;AAKD,MAAaC,gCAAsC;CACjD,MAAM;CACN,MAAM;CACN,aAAa;CACb;CACA,iBAAiB;CACjB,cAAc;EAAE,OAAO;EAAa,SAAS;EAAM;CACnD;CACD"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/checker/rules/duplicate-field-in-schema.ts
|
|
2
|
+
const category = "schema";
|
|
3
|
+
const visitor = { visitSchemaEntry(entry, ctx) {
|
|
4
|
+
const fields = entry.metadataBlock?.fields ?? [];
|
|
5
|
+
const seenFields = /* @__PURE__ */ new Map();
|
|
6
|
+
for (const field of fields) {
|
|
7
|
+
const fieldName = field.name.value;
|
|
8
|
+
const fieldLine = field.location?.startPosition.row ?? 0;
|
|
9
|
+
const existingLine = seenFields.get(fieldName);
|
|
10
|
+
if (existingLine !== void 0) ctx.report({
|
|
11
|
+
message: `Duplicate field '${fieldName}' in schema entry. First defined at line ${existingLine}.`,
|
|
12
|
+
file: ctx.file,
|
|
13
|
+
location: field.location,
|
|
14
|
+
sourceMap: ctx.sourceMap,
|
|
15
|
+
data: {
|
|
16
|
+
fieldName,
|
|
17
|
+
firstLine: existingLine
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
else seenFields.set(fieldName, fieldLine + 1);
|
|
21
|
+
}
|
|
22
|
+
} };
|
|
23
|
+
/**
|
|
24
|
+
* Check for duplicate field names within a single schema entry
|
|
25
|
+
*/
|
|
26
|
+
const duplicateFieldInSchemaRule = {
|
|
27
|
+
code: "duplicate-field-in-schema",
|
|
28
|
+
name: "Duplicate Field in Schema",
|
|
29
|
+
description: "Same field defined twice in a schema entry",
|
|
30
|
+
category,
|
|
31
|
+
defaultSeverity: "error",
|
|
32
|
+
dependencies: { scope: "entry" },
|
|
33
|
+
visitor
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
export { duplicateFieldInSchemaRule };
|
|
38
|
+
//# sourceMappingURL=duplicate-field-in-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-field-in-schema.js","names":["category: RuleCategory","visitor: RuleVisitor","duplicateFieldInSchemaRule: Rule"],"sources":["../../../src/checker/rules/duplicate-field-in-schema.ts"],"sourcesContent":["import type { Rule, RuleCategory } from \"../rules/rules.js\";\nimport type { RuleVisitor } from \"../visitor.js\";\n\nconst category: RuleCategory = \"schema\";\n\nconst visitor: RuleVisitor = {\n visitSchemaEntry(entry, ctx) {\n const fields = entry.metadataBlock?.fields ?? [];\n const seenFields = new Map<string, number>();\n\n for (const field of fields) {\n const fieldName = field.name.value;\n const fieldLine = field.location?.startPosition.row ?? 0;\n const existingLine = seenFields.get(fieldName);\n\n if (existingLine !== undefined) {\n ctx.report({\n message: `Duplicate field '${fieldName}' in schema entry. First defined at line ${existingLine}.`,\n file: ctx.file,\n location: field.location,\n sourceMap: ctx.sourceMap,\n data: { fieldName, firstLine: existingLine },\n });\n } else {\n seenFields.set(fieldName, fieldLine + 1);\n }\n }\n },\n};\n\n/**\n * Check for duplicate field names within a single schema entry\n */\nexport const duplicateFieldInSchemaRule: Rule = {\n code: \"duplicate-field-in-schema\",\n name: \"Duplicate Field in Schema\",\n description: \"Same field defined twice in a schema entry\",\n category,\n defaultSeverity: \"error\",\n dependencies: { scope: \"entry\" },\n visitor,\n};\n"],"mappings":";AAGA,MAAMA,WAAyB;AAE/B,MAAMC,UAAuB,EAC3B,iBAAiB,OAAO,KAAK;CAC3B,MAAM,SAAS,MAAM,eAAe,UAAU,EAAE;CAChD,MAAM,6BAAa,IAAI,KAAqB;AAE5C,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,YAAY,MAAM,KAAK;EAC7B,MAAM,YAAY,MAAM,UAAU,cAAc,OAAO;EACvD,MAAM,eAAe,WAAW,IAAI,UAAU;AAE9C,MAAI,iBAAiB,OACnB,KAAI,OAAO;GACT,SAAS,oBAAoB,UAAU,2CAA2C,aAAa;GAC/F,MAAM,IAAI;GACV,UAAU,MAAM;GAChB,WAAW,IAAI;GACf,MAAM;IAAE;IAAW,WAAW;IAAc;GAC7C,CAAC;MAEF,YAAW,IAAI,WAAW,YAAY,EAAE;;GAI/C;;;;AAKD,MAAaC,6BAAmC;CAC9C,MAAM;CACN,MAAM;CACN,aAAa;CACb;CACA,iBAAiB;CACjB,cAAc,EAAE,OAAO,SAAS;CAChC;CACD"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/checker/rules/duplicate-link-id.ts
|
|
2
|
+
const category = "link";
|
|
3
|
+
const visitor = { afterCheck(ctx) {
|
|
4
|
+
const { workspace } = ctx;
|
|
5
|
+
const definitionsByLinkId = /* @__PURE__ */ new Map();
|
|
6
|
+
for (const model of workspace.allModels()) for (const [linkId, def] of model.linkIndex.definitions) {
|
|
7
|
+
const defs = definitionsByLinkId.get(linkId) ?? [];
|
|
8
|
+
defs.push(def);
|
|
9
|
+
definitionsByLinkId.set(linkId, defs);
|
|
10
|
+
}
|
|
11
|
+
for (const [linkId, defs] of definitionsByLinkId) if (defs.length > 1) {
|
|
12
|
+
const explicitDefs = defs.filter((d) => {
|
|
13
|
+
const entry = d.entry;
|
|
14
|
+
if (entry.type === "instance_entry" || entry.type === "schema_entry") return entry.header.link?.id === linkId;
|
|
15
|
+
if (entry.type === "synthesis_entry") return entry.header.linkId.id === linkId;
|
|
16
|
+
return false;
|
|
17
|
+
});
|
|
18
|
+
if (explicitDefs.length > 1) for (const def of explicitDefs) {
|
|
19
|
+
const model = workspace.getModel(def.file);
|
|
20
|
+
const otherFiles = explicitDefs.filter((d) => d !== def).map((d) => d.file).join(", ");
|
|
21
|
+
ctx.report({
|
|
22
|
+
message: `Duplicate link ID '^${linkId}'. Also defined in: ${otherFiles}`,
|
|
23
|
+
file: def.file,
|
|
24
|
+
location: def.location,
|
|
25
|
+
sourceMap: model?.sourceMap,
|
|
26
|
+
data: {
|
|
27
|
+
linkId,
|
|
28
|
+
otherFiles
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} };
|
|
34
|
+
/**
|
|
35
|
+
* Check for duplicate link ID definitions
|
|
36
|
+
*/
|
|
37
|
+
const duplicateLinkIdRule = {
|
|
38
|
+
code: "duplicate-link-id",
|
|
39
|
+
name: "Duplicate Link ID",
|
|
40
|
+
description: "Same explicit ^link-id defined multiple times",
|
|
41
|
+
category,
|
|
42
|
+
defaultSeverity: "error",
|
|
43
|
+
dependencies: {
|
|
44
|
+
scope: "workspace",
|
|
45
|
+
links: true
|
|
46
|
+
},
|
|
47
|
+
visitor
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
export { duplicateLinkIdRule };
|
|
52
|
+
//# sourceMappingURL=duplicate-link-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-link-id.js","names":["category: RuleCategory","visitor: RuleVisitor","duplicateLinkIdRule: Rule"],"sources":["../../../src/checker/rules/duplicate-link-id.ts"],"sourcesContent":["import type { Rule, RuleCategory } from \"../rules/rules.js\";\nimport type { RuleVisitor, VisitorContext } from \"../visitor.js\";\nimport type { LinkDefinition } from \"../../semantic/analyzer.js\";\n\nconst category: RuleCategory = \"link\";\n\nconst visitor: RuleVisitor = {\n afterCheck(ctx: VisitorContext) {\n const { workspace } = ctx;\n\n // Track all definitions we've seen for each link ID\n const definitionsByLinkId = new Map<string, LinkDefinition[]>();\n\n // Collect definitions from all models\n for (const model of workspace.allModels()) {\n for (const [linkId, def] of model.linkIndex.definitions) {\n const defs = definitionsByLinkId.get(linkId) ?? [];\n defs.push(def);\n definitionsByLinkId.set(linkId, defs);\n }\n }\n\n // Report duplicates\n for (const [linkId, defs] of definitionsByLinkId) {\n if (defs.length > 1) {\n // Skip timestamps - they're implicitly unique per entry\n // Only report explicit ^link-id duplicates\n const explicitDefs = defs.filter((d) => {\n const entry = d.entry;\n if (entry.type === \"instance_entry\" || entry.type === \"schema_entry\") {\n return entry.header.link?.id === linkId;\n }\n if (entry.type === \"synthesis_entry\") {\n return entry.header.linkId.id === linkId;\n }\n return false;\n });\n\n if (explicitDefs.length > 1) {\n for (const def of explicitDefs) {\n const model = workspace.getModel(def.file);\n const otherFiles = explicitDefs\n .filter((d) => d !== def)\n .map((d) => d.file)\n .join(\", \");\n\n ctx.report({\n message: `Duplicate link ID '^${linkId}'. Also defined in: ${otherFiles}`,\n file: def.file,\n location: def.location,\n sourceMap: model?.sourceMap,\n data: { linkId, otherFiles },\n });\n }\n }\n }\n }\n },\n};\n\n/**\n * Check for duplicate link ID definitions\n */\nexport const duplicateLinkIdRule: Rule = {\n code: \"duplicate-link-id\",\n name: \"Duplicate Link ID\",\n description: \"Same explicit ^link-id defined multiple times\",\n category,\n defaultSeverity: \"error\",\n dependencies: { scope: \"workspace\", links: true },\n visitor,\n};\n"],"mappings":";AAIA,MAAMA,WAAyB;AAE/B,MAAMC,UAAuB,EAC3B,WAAW,KAAqB;CAC9B,MAAM,EAAE,cAAc;CAGtB,MAAM,sCAAsB,IAAI,KAA+B;AAG/D,MAAK,MAAM,SAAS,UAAU,WAAW,CACvC,MAAK,MAAM,CAAC,QAAQ,QAAQ,MAAM,UAAU,aAAa;EACvD,MAAM,OAAO,oBAAoB,IAAI,OAAO,IAAI,EAAE;AAClD,OAAK,KAAK,IAAI;AACd,sBAAoB,IAAI,QAAQ,KAAK;;AAKzC,MAAK,MAAM,CAAC,QAAQ,SAAS,oBAC3B,KAAI,KAAK,SAAS,GAAG;EAGnB,MAAM,eAAe,KAAK,QAAQ,MAAM;GACtC,MAAM,QAAQ,EAAE;AAChB,OAAI,MAAM,SAAS,oBAAoB,MAAM,SAAS,eACpD,QAAO,MAAM,OAAO,MAAM,OAAO;AAEnC,OAAI,MAAM,SAAS,kBACjB,QAAO,MAAM,OAAO,OAAO,OAAO;AAEpC,UAAO;IACP;AAEF,MAAI,aAAa,SAAS,EACxB,MAAK,MAAM,OAAO,cAAc;GAC9B,MAAM,QAAQ,UAAU,SAAS,IAAI,KAAK;GAC1C,MAAM,aAAa,aAChB,QAAQ,MAAM,MAAM,IAAI,CACxB,KAAK,MAAM,EAAE,KAAK,CAClB,KAAK,KAAK;AAEb,OAAI,OAAO;IACT,SAAS,uBAAuB,OAAO,sBAAsB;IAC7D,MAAM,IAAI;IACV,UAAU,IAAI;IACd,WAAW,OAAO;IAClB,MAAM;KAAE;KAAQ;KAAY;IAC7B,CAAC;;;GAMb;;;;AAKD,MAAaC,sBAA4B;CACvC,MAAM;CACN,MAAM;CACN,aAAa;CACb;CACA,iBAAiB;CACjB,cAAc;EAAE,OAAO;EAAa,OAAO;EAAM;CACjD;CACD"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/checker/rules/duplicate-metadata-key.ts
|
|
2
|
+
const category = "metadata";
|
|
3
|
+
/**
|
|
4
|
+
* Check for duplicate metadata keys within a single instance entry
|
|
5
|
+
*
|
|
6
|
+
* Note: This rule is a placeholder. The parser collapses duplicate keys into a Map,
|
|
7
|
+
* so detection would require AST-level checking before model extraction.
|
|
8
|
+
*/
|
|
9
|
+
const duplicateMetadataKeyRule = {
|
|
10
|
+
code: "duplicate-metadata-key",
|
|
11
|
+
name: "Duplicate Metadata Key",
|
|
12
|
+
description: "Same metadata key appears twice in an entry",
|
|
13
|
+
category,
|
|
14
|
+
defaultSeverity: "error",
|
|
15
|
+
dependencies: { scope: "entry" },
|
|
16
|
+
visitor: {}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
export { duplicateMetadataKeyRule };
|
|
21
|
+
//# sourceMappingURL=duplicate-metadata-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-metadata-key.js","names":["category: RuleCategory","duplicateMetadataKeyRule: Rule"],"sources":["../../../src/checker/rules/duplicate-metadata-key.ts"],"sourcesContent":["import type { Rule, RuleCategory } from \"../rules/rules.js\";\n\nconst category: RuleCategory = \"metadata\";\n\n/**\n * Check for duplicate metadata keys within a single instance entry\n *\n * Note: This rule is a placeholder. The parser collapses duplicate keys into a Map,\n * so detection would require AST-level checking before model extraction.\n */\nexport const duplicateMetadataKeyRule: Rule = {\n code: \"duplicate-metadata-key\",\n name: \"Duplicate Metadata Key\",\n description: \"Same metadata key appears twice in an entry\",\n category,\n defaultSeverity: \"error\",\n dependencies: { scope: \"entry\" },\n visitor: {},\n};\n"],"mappings":";AAEA,MAAMA,WAAyB;;;;;;;AAQ/B,MAAaC,2BAAiC;CAC5C,MAAM;CACN,MAAM;CACN,aAAa;CACb;CACA,iBAAiB;CACjB,cAAc,EAAE,OAAO,SAAS;CAChC,SAAS,EAAE;CACZ"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region src/checker/rules/duplicate-section-heading.ts
|
|
2
|
+
const category = "content";
|
|
3
|
+
/**
|
|
4
|
+
* Extract markdown headers from entry content
|
|
5
|
+
*/
|
|
6
|
+
function getMarkdownHeaders(entry) {
|
|
7
|
+
if (!entry.content) return [];
|
|
8
|
+
return entry.content.children.filter((c) => c.type === "markdown_header");
|
|
9
|
+
}
|
|
10
|
+
const visitor = { visitInstanceEntry(entry, ctx) {
|
|
11
|
+
const headers = getMarkdownHeaders(entry);
|
|
12
|
+
const seenSections = /* @__PURE__ */ new Map();
|
|
13
|
+
for (const header of headers) {
|
|
14
|
+
const match = header.text.match(/^#+\s*(.+)$/);
|
|
15
|
+
const sectionName = match ? match[1].trim() : header.text;
|
|
16
|
+
if (seenSections.get(sectionName)) ctx.report({
|
|
17
|
+
message: `Duplicate section heading '# ${sectionName}' in entry content.`,
|
|
18
|
+
file: ctx.file,
|
|
19
|
+
location: header.location,
|
|
20
|
+
sourceMap: ctx.sourceMap,
|
|
21
|
+
data: { sectionName }
|
|
22
|
+
});
|
|
23
|
+
else seenSections.set(sectionName, header);
|
|
24
|
+
}
|
|
25
|
+
} };
|
|
26
|
+
/**
|
|
27
|
+
* Check for duplicate section headings within a single entry's content
|
|
28
|
+
*/
|
|
29
|
+
const duplicateSectionHeadingRule = {
|
|
30
|
+
code: "duplicate-section-heading",
|
|
31
|
+
name: "Duplicate Section Heading",
|
|
32
|
+
description: "Same # Section appears twice in entry content",
|
|
33
|
+
category,
|
|
34
|
+
defaultSeverity: "error",
|
|
35
|
+
dependencies: { scope: "entry" },
|
|
36
|
+
visitor
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
export { duplicateSectionHeadingRule };
|
|
41
|
+
//# sourceMappingURL=duplicate-section-heading.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-section-heading.js","names":["category: RuleCategory","visitor: RuleVisitor","duplicateSectionHeadingRule: Rule"],"sources":["../../../src/checker/rules/duplicate-section-heading.ts"],"sourcesContent":["import type { Rule, RuleCategory } from \"../rules/rules.js\";\nimport type { RuleVisitor } from \"../visitor.js\";\nimport type { InstanceEntry, MarkdownHeader } from \"../../ast/ast-types.js\";\n\nconst category: RuleCategory = \"content\";\n\n/**\n * Extract markdown headers from entry content\n */\nfunction getMarkdownHeaders(entry: InstanceEntry): MarkdownHeader[] {\n if (!entry.content) {\n return [];\n }\n return entry.content.children.filter((c): c is MarkdownHeader => c.type === \"markdown_header\");\n}\n\nconst visitor: RuleVisitor = {\n visitInstanceEntry(entry, ctx) {\n const headers = getMarkdownHeaders(entry);\n const seenSections = new Map<string, MarkdownHeader>();\n\n for (const header of headers) {\n // Extract section name from \"# SectionName\" format\n const match = header.text.match(/^#+\\s*(.+)$/);\n const sectionName = match ? match[1].trim() : header.text;\n const existing = seenSections.get(sectionName);\n\n if (existing) {\n ctx.report({\n message: `Duplicate section heading '# ${sectionName}' in entry content.`,\n file: ctx.file,\n location: header.location,\n sourceMap: ctx.sourceMap,\n data: { sectionName },\n });\n } else {\n seenSections.set(sectionName, header);\n }\n }\n },\n};\n\n/**\n * Check for duplicate section headings within a single entry's content\n */\nexport const duplicateSectionHeadingRule: Rule = {\n code: \"duplicate-section-heading\",\n name: \"Duplicate Section Heading\",\n description: \"Same # Section appears twice in entry content\",\n category,\n defaultSeverity: \"error\",\n dependencies: { scope: \"entry\" },\n visitor,\n};\n"],"mappings":";AAIA,MAAMA,WAAyB;;;;AAK/B,SAAS,mBAAmB,OAAwC;AAClE,KAAI,CAAC,MAAM,QACT,QAAO,EAAE;AAEX,QAAO,MAAM,QAAQ,SAAS,QAAQ,MAA2B,EAAE,SAAS,kBAAkB;;AAGhG,MAAMC,UAAuB,EAC3B,mBAAmB,OAAO,KAAK;CAC7B,MAAM,UAAU,mBAAmB,MAAM;CACzC,MAAM,+BAAe,IAAI,KAA6B;AAEtD,MAAK,MAAM,UAAU,SAAS;EAE5B,MAAM,QAAQ,OAAO,KAAK,MAAM,cAAc;EAC9C,MAAM,cAAc,QAAQ,MAAM,GAAG,MAAM,GAAG,OAAO;AAGrD,MAFiB,aAAa,IAAI,YAAY,CAG5C,KAAI,OAAO;GACT,SAAS,gCAAgC,YAAY;GACrD,MAAM,IAAI;GACV,UAAU,OAAO;GACjB,WAAW,IAAI;GACf,MAAM,EAAE,aAAa;GACtB,CAAC;MAEF,cAAa,IAAI,aAAa,OAAO;;GAI5C;;;;AAKD,MAAaC,8BAAoC;CAC/C,MAAM;CACN,MAAM;CACN,aAAa;CACb;CACA,iBAAiB;CACjB,cAAc,EAAE,OAAO,SAAS;CAChC;CACD"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/checker/rules/duplicate-section-in-schema.ts
|
|
2
|
+
const category = "schema";
|
|
3
|
+
const visitor = { visitSchemaEntry(entry, ctx) {
|
|
4
|
+
const sections = entry.sectionsBlock?.sections ?? [];
|
|
5
|
+
const seenSections = /* @__PURE__ */ new Map();
|
|
6
|
+
for (const section of sections) {
|
|
7
|
+
const sectionName = section.name.value;
|
|
8
|
+
const sectionLine = section.location?.startPosition.row ?? 0;
|
|
9
|
+
const existingLine = seenSections.get(sectionName);
|
|
10
|
+
if (existingLine !== void 0) ctx.report({
|
|
11
|
+
message: `Duplicate section '${sectionName}' in schema entry. First defined at line ${existingLine}.`,
|
|
12
|
+
file: ctx.file,
|
|
13
|
+
location: section.location,
|
|
14
|
+
sourceMap: ctx.sourceMap,
|
|
15
|
+
data: {
|
|
16
|
+
sectionName,
|
|
17
|
+
firstLine: existingLine
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
else seenSections.set(sectionName, sectionLine + 1);
|
|
21
|
+
}
|
|
22
|
+
} };
|
|
23
|
+
/**
|
|
24
|
+
* Check for duplicate section names within a single schema entry
|
|
25
|
+
*/
|
|
26
|
+
const duplicateSectionInSchemaRule = {
|
|
27
|
+
code: "duplicate-section-in-schema",
|
|
28
|
+
name: "Duplicate Section in Schema",
|
|
29
|
+
description: "Same section defined twice in a schema entry",
|
|
30
|
+
category,
|
|
31
|
+
defaultSeverity: "error",
|
|
32
|
+
dependencies: { scope: "entry" },
|
|
33
|
+
visitor
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
export { duplicateSectionInSchemaRule };
|
|
38
|
+
//# sourceMappingURL=duplicate-section-in-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-section-in-schema.js","names":["category: RuleCategory","visitor: RuleVisitor","duplicateSectionInSchemaRule: Rule"],"sources":["../../../src/checker/rules/duplicate-section-in-schema.ts"],"sourcesContent":["import type { Rule, RuleCategory } from \"../rules/rules.js\";\nimport type { RuleVisitor } from \"../visitor.js\";\n\nconst category: RuleCategory = \"schema\";\n\nconst visitor: RuleVisitor = {\n visitSchemaEntry(entry, ctx) {\n const sections = entry.sectionsBlock?.sections ?? [];\n const seenSections = new Map<string, number>();\n\n for (const section of sections) {\n const sectionName = section.name.value;\n const sectionLine = section.location?.startPosition.row ?? 0;\n const existingLine = seenSections.get(sectionName);\n\n if (existingLine !== undefined) {\n ctx.report({\n message: `Duplicate section '${sectionName}' in schema entry. First defined at line ${existingLine}.`,\n file: ctx.file,\n location: section.location,\n sourceMap: ctx.sourceMap,\n data: { sectionName, firstLine: existingLine },\n });\n } else {\n seenSections.set(sectionName, sectionLine + 1);\n }\n }\n },\n};\n\n/**\n * Check for duplicate section names within a single schema entry\n */\nexport const duplicateSectionInSchemaRule: Rule = {\n code: \"duplicate-section-in-schema\",\n name: \"Duplicate Section in Schema\",\n description: \"Same section defined twice in a schema entry\",\n category,\n defaultSeverity: \"error\",\n dependencies: { scope: \"entry\" },\n visitor,\n};\n"],"mappings":";AAGA,MAAMA,WAAyB;AAE/B,MAAMC,UAAuB,EAC3B,iBAAiB,OAAO,KAAK;CAC3B,MAAM,WAAW,MAAM,eAAe,YAAY,EAAE;CACpD,MAAM,+BAAe,IAAI,KAAqB;AAE9C,MAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,cAAc,QAAQ,KAAK;EACjC,MAAM,cAAc,QAAQ,UAAU,cAAc,OAAO;EAC3D,MAAM,eAAe,aAAa,IAAI,YAAY;AAElD,MAAI,iBAAiB,OACnB,KAAI,OAAO;GACT,SAAS,sBAAsB,YAAY,2CAA2C,aAAa;GACnG,MAAM,IAAI;GACV,UAAU,QAAQ;GAClB,WAAW,IAAI;GACf,MAAM;IAAE;IAAa,WAAW;IAAc;GAC/C,CAAC;MAEF,cAAa,IAAI,aAAa,cAAc,EAAE;;GAIrD;;;;AAKD,MAAaC,+BAAqC;CAChD,MAAM;CACN,MAAM;CACN,aAAa;CACb;CACA,iBAAiB;CACjB,cAAc,EAAE,OAAO,SAAS;CAChC;CACD"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
//#region src/checker/rules/duplicate-timestamp.ts
|
|
2
|
+
const category = "instance";
|
|
3
|
+
const visitor = { afterCheck(ctx) {
|
|
4
|
+
const { workspace } = ctx;
|
|
5
|
+
for (const model of workspace.allModels()) {
|
|
6
|
+
const entriesByIdentity = /* @__PURE__ */ new Map();
|
|
7
|
+
for (const entry of model.ast.entries) {
|
|
8
|
+
const timestamp = getEntryTimestamp(entry);
|
|
9
|
+
if (!timestamp) continue;
|
|
10
|
+
if (hasExplicitLinkId(entry)) continue;
|
|
11
|
+
const identityKey = `${timestamp}|${entry.type}`;
|
|
12
|
+
const entries = entriesByIdentity.get(identityKey) ?? [];
|
|
13
|
+
entries.push(entry);
|
|
14
|
+
entriesByIdentity.set(identityKey, entries);
|
|
15
|
+
}
|
|
16
|
+
for (const [identityKey, entries] of entriesByIdentity) if (entries.length > 1) {
|
|
17
|
+
const [timestamp, entryType] = identityKey.split("|");
|
|
18
|
+
const entryTypeName = formatEntryType(entryType);
|
|
19
|
+
for (const entry of entries) ctx.report({
|
|
20
|
+
message: `Duplicate timestamp '${timestamp}' for ${entryTypeName} without explicit ^link-id. Add a unique ^link-id to disambiguate entries with the same timestamp.`,
|
|
21
|
+
file: model.file,
|
|
22
|
+
location: getTimestampLocation(entry),
|
|
23
|
+
sourceMap: model.sourceMap,
|
|
24
|
+
data: {
|
|
25
|
+
timestamp,
|
|
26
|
+
entryType,
|
|
27
|
+
duplicateCount: entries.length
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} };
|
|
33
|
+
/**
|
|
34
|
+
* Get timestamp string from an entry
|
|
35
|
+
*/
|
|
36
|
+
function getEntryTimestamp(entry) {
|
|
37
|
+
switch (entry.type) {
|
|
38
|
+
case "instance_entry":
|
|
39
|
+
case "schema_entry":
|
|
40
|
+
case "synthesis_entry":
|
|
41
|
+
case "actualize_entry": return entry.header.timestamp.value;
|
|
42
|
+
default: return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Check if entry has an explicit link ID
|
|
47
|
+
*/
|
|
48
|
+
function hasExplicitLinkId(entry) {
|
|
49
|
+
switch (entry.type) {
|
|
50
|
+
case "instance_entry":
|
|
51
|
+
case "schema_entry": return entry.header.link !== null;
|
|
52
|
+
case "synthesis_entry": return true;
|
|
53
|
+
case "actualize_entry": return true;
|
|
54
|
+
default: return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get the location of the timestamp in an entry (for error reporting)
|
|
59
|
+
*/
|
|
60
|
+
function getTimestampLocation(entry) {
|
|
61
|
+
switch (entry.type) {
|
|
62
|
+
case "instance_entry":
|
|
63
|
+
case "schema_entry":
|
|
64
|
+
case "synthesis_entry":
|
|
65
|
+
case "actualize_entry": return entry.header.timestamp.location;
|
|
66
|
+
default: return entry.location;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Format entry type for human-readable error messages
|
|
71
|
+
*/
|
|
72
|
+
function formatEntryType(entryType) {
|
|
73
|
+
switch (entryType) {
|
|
74
|
+
case "instance_entry": return "instance entry";
|
|
75
|
+
case "schema_entry": return "schema entry";
|
|
76
|
+
case "synthesis_entry": return "synthesis entry";
|
|
77
|
+
case "actualize_entry": return "actualize entry";
|
|
78
|
+
default: return entryType;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Check for duplicate timestamps without link IDs
|
|
83
|
+
*
|
|
84
|
+
* When multiple entries in the same file have identical timestamps and the same
|
|
85
|
+
* entry type, they must have explicit ^link-id values to be distinguished during
|
|
86
|
+
* merge operations. Without link IDs, only the first entry will be used during
|
|
87
|
+
* three-way merges, potentially causing data loss.
|
|
88
|
+
*/
|
|
89
|
+
const duplicateTimestampRule = {
|
|
90
|
+
code: "duplicate-timestamp",
|
|
91
|
+
name: "Duplicate Timestamp Without Link ID",
|
|
92
|
+
description: "Multiple entries with the same timestamp and type must have explicit ^link-id for merge disambiguation",
|
|
93
|
+
category,
|
|
94
|
+
defaultSeverity: "error",
|
|
95
|
+
dependencies: {
|
|
96
|
+
scope: "document",
|
|
97
|
+
links: false
|
|
98
|
+
},
|
|
99
|
+
visitor
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
export { duplicateTimestampRule };
|
|
104
|
+
//# sourceMappingURL=duplicate-timestamp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-timestamp.js","names":["category: RuleCategory","visitor: RuleVisitor","duplicateTimestampRule: Rule"],"sources":["../../../src/checker/rules/duplicate-timestamp.ts"],"sourcesContent":["import type { Rule, RuleCategory } from \"../rules/rules.js\";\nimport type { RuleVisitor, VisitorContext } from \"../visitor.js\";\nimport type { Entry } from \"../../ast/ast-types.js\";\n\nconst category: RuleCategory = \"instance\";\n\nconst visitor: RuleVisitor = {\n afterCheck(ctx: VisitorContext) {\n const { workspace } = ctx;\n\n // Check each file independently since timestamps are per-file\n for (const model of workspace.allModels()) {\n // Track entries by timestamp+type (the merge identity key)\n const entriesByIdentity = new Map<string, Entry[]>();\n\n for (const entry of model.ast.entries) {\n // Get timestamp\n const timestamp = getEntryTimestamp(entry);\n if (!timestamp) {\n continue;\n }\n\n // Check if entry has an explicit link ID\n const hasLinkId = hasExplicitLinkId(entry);\n if (hasLinkId) {\n // Entries with link IDs are always uniquely identified\n continue;\n }\n\n // Build identity key: timestamp|type (using | since timestamps contain :)\n const identityKey = `${timestamp}|${entry.type}`;\n\n const entries = entriesByIdentity.get(identityKey) ?? [];\n entries.push(entry);\n entriesByIdentity.set(identityKey, entries);\n }\n\n // Report duplicates\n for (const [identityKey, entries] of entriesByIdentity) {\n if (entries.length > 1) {\n const [timestamp, entryType] = identityKey.split(\"|\");\n const entryTypeName = formatEntryType(entryType);\n\n for (const entry of entries) {\n ctx.report({\n message: `Duplicate timestamp '${timestamp}' for ${entryTypeName} without explicit ^link-id. Add a unique ^link-id to disambiguate entries with the same timestamp.`,\n file: model.file,\n location: getTimestampLocation(entry),\n sourceMap: model.sourceMap,\n data: { timestamp, entryType, duplicateCount: entries.length },\n });\n }\n }\n }\n }\n },\n};\n\n/**\n * Get timestamp string from an entry\n */\nfunction getEntryTimestamp(entry: Entry): string | null {\n switch (entry.type) {\n case \"instance_entry\":\n case \"schema_entry\":\n case \"synthesis_entry\":\n case \"actualize_entry\":\n return entry.header.timestamp.value;\n default:\n return null;\n }\n}\n\n/**\n * Check if entry has an explicit link ID\n */\nfunction hasExplicitLinkId(entry: Entry): boolean {\n switch (entry.type) {\n case \"instance_entry\":\n case \"schema_entry\":\n return entry.header.link !== null;\n case \"synthesis_entry\":\n // Synthesis entries always have a required linkId\n return true;\n case \"actualize_entry\":\n // Actualize entries use target, which is always present\n return true;\n default:\n return false;\n }\n}\n\n/**\n * Get the location of the timestamp in an entry (for error reporting)\n */\nfunction getTimestampLocation(entry: Entry): import(\"../../ast/ast-types.js\").Location {\n switch (entry.type) {\n case \"instance_entry\":\n case \"schema_entry\":\n case \"synthesis_entry\":\n case \"actualize_entry\":\n return entry.header.timestamp.location;\n default: {\n // This should never happen since all entry types have timestamps\n const _exhaustive: never = entry;\n return (_exhaustive as Entry).location;\n }\n }\n}\n\n/**\n * Format entry type for human-readable error messages\n */\nfunction formatEntryType(entryType: string): string {\n switch (entryType) {\n case \"instance_entry\":\n return \"instance entry\";\n case \"schema_entry\":\n return \"schema entry\";\n case \"synthesis_entry\":\n return \"synthesis entry\";\n case \"actualize_entry\":\n return \"actualize entry\";\n default:\n return entryType;\n }\n}\n\n/**\n * Check for duplicate timestamps without link IDs\n *\n * When multiple entries in the same file have identical timestamps and the same\n * entry type, they must have explicit ^link-id values to be distinguished during\n * merge operations. Without link IDs, only the first entry will be used during\n * three-way merges, potentially causing data loss.\n */\nexport const duplicateTimestampRule: Rule = {\n code: \"duplicate-timestamp\",\n name: \"Duplicate Timestamp Without Link ID\",\n description:\n \"Multiple entries with the same timestamp and type must have explicit ^link-id for merge disambiguation\",\n category,\n defaultSeverity: \"error\",\n dependencies: { scope: \"document\", links: false },\n visitor,\n};\n"],"mappings":";AAIA,MAAMA,WAAyB;AAE/B,MAAMC,UAAuB,EAC3B,WAAW,KAAqB;CAC9B,MAAM,EAAE,cAAc;AAGtB,MAAK,MAAM,SAAS,UAAU,WAAW,EAAE;EAEzC,MAAM,oCAAoB,IAAI,KAAsB;AAEpD,OAAK,MAAM,SAAS,MAAM,IAAI,SAAS;GAErC,MAAM,YAAY,kBAAkB,MAAM;AAC1C,OAAI,CAAC,UACH;AAKF,OADkB,kBAAkB,MAAM,CAGxC;GAIF,MAAM,cAAc,GAAG,UAAU,GAAG,MAAM;GAE1C,MAAM,UAAU,kBAAkB,IAAI,YAAY,IAAI,EAAE;AACxD,WAAQ,KAAK,MAAM;AACnB,qBAAkB,IAAI,aAAa,QAAQ;;AAI7C,OAAK,MAAM,CAAC,aAAa,YAAY,kBACnC,KAAI,QAAQ,SAAS,GAAG;GACtB,MAAM,CAAC,WAAW,aAAa,YAAY,MAAM,IAAI;GACrD,MAAM,gBAAgB,gBAAgB,UAAU;AAEhD,QAAK,MAAM,SAAS,QAClB,KAAI,OAAO;IACT,SAAS,wBAAwB,UAAU,QAAQ,cAAc;IACjE,MAAM,MAAM;IACZ,UAAU,qBAAqB,MAAM;IACrC,WAAW,MAAM;IACjB,MAAM;KAAE;KAAW;KAAW,gBAAgB,QAAQ;KAAQ;IAC/D,CAAC;;;GAMb;;;;AAKD,SAAS,kBAAkB,OAA6B;AACtD,SAAQ,MAAM,MAAd;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,kBACH,QAAO,MAAM,OAAO,UAAU;EAChC,QACE,QAAO;;;;;;AAOb,SAAS,kBAAkB,OAAuB;AAChD,SAAQ,MAAM,MAAd;EACE,KAAK;EACL,KAAK,eACH,QAAO,MAAM,OAAO,SAAS;EAC/B,KAAK,kBAEH,QAAO;EACT,KAAK,kBAEH,QAAO;EACT,QACE,QAAO;;;;;;AAOb,SAAS,qBAAqB,OAAyD;AACrF,SAAQ,MAAM,MAAd;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,kBACH,QAAO,MAAM,OAAO,UAAU;EAChC,QAGE,QAD2B,MACG;;;;;;AAQpC,SAAS,gBAAgB,WAA2B;AAClD,SAAQ,WAAR;EACE,KAAK,iBACH,QAAO;EACT,KAAK,eACH,QAAO;EACT,KAAK,kBACH,QAAO;EACT,KAAK,kBACH,QAAO;EACT,QACE,QAAO;;;;;;;;;;;AAYb,MAAaC,yBAA+B;CAC1C,MAAM;CACN,MAAM;CACN,aACE;CACF;CACA,iBAAiB;CACjB,cAAc;EAAE,OAAO;EAAY,OAAO;EAAO;CACjD;CACD"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/checker/rules/empty-required-value.ts
|
|
2
|
+
const category = "metadata";
|
|
3
|
+
const visitor = { visitInstanceEntry(entry, ctx) {
|
|
4
|
+
const registry = ctx.workspace.schemaRegistry;
|
|
5
|
+
const entity = entry.header.entity;
|
|
6
|
+
const schema = registry.get(entity);
|
|
7
|
+
if (!schema) return;
|
|
8
|
+
for (const meta of entry.metadata) {
|
|
9
|
+
const fieldName = meta.key.value;
|
|
10
|
+
const fieldSchema = schema.fields.get(fieldName);
|
|
11
|
+
if (!fieldSchema) continue;
|
|
12
|
+
if (!fieldSchema.optional && fieldSchema.defaultValue === null) {
|
|
13
|
+
const rawValue = meta.value.raw.trim();
|
|
14
|
+
if (rawValue === "" || rawValue === "\"\"") ctx.report({
|
|
15
|
+
message: `Required field '${fieldName}' has an empty value.`,
|
|
16
|
+
file: ctx.file,
|
|
17
|
+
location: meta.value.location,
|
|
18
|
+
sourceMap: ctx.sourceMap,
|
|
19
|
+
data: {
|
|
20
|
+
fieldName,
|
|
21
|
+
entity
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
} };
|
|
27
|
+
/**
|
|
28
|
+
* Check for required fields that are present but have empty values
|
|
29
|
+
*/
|
|
30
|
+
const emptyRequiredValueRule = {
|
|
31
|
+
code: "empty-required-value",
|
|
32
|
+
name: "Empty Required Value",
|
|
33
|
+
description: "Required field has empty value",
|
|
34
|
+
category,
|
|
35
|
+
defaultSeverity: "error",
|
|
36
|
+
dependencies: {
|
|
37
|
+
scope: "entry",
|
|
38
|
+
schemas: true
|
|
39
|
+
},
|
|
40
|
+
visitor
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
export { emptyRequiredValueRule };
|
|
45
|
+
//# sourceMappingURL=empty-required-value.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty-required-value.js","names":["category: RuleCategory","visitor: RuleVisitor","emptyRequiredValueRule: Rule"],"sources":["../../../src/checker/rules/empty-required-value.ts"],"sourcesContent":["import type { Rule, RuleCategory } from \"../rules/rules.js\";\nimport type { RuleVisitor } from \"../visitor.js\";\n\nconst category: RuleCategory = \"metadata\";\n\nconst visitor: RuleVisitor = {\n visitInstanceEntry(entry, ctx) {\n const registry = ctx.workspace.schemaRegistry;\n const entity = entry.header.entity;\n const schema = registry.get(entity);\n\n if (!schema) {\n return; // Will be caught by unknown-entity rule\n }\n\n for (const meta of entry.metadata) {\n const fieldName = meta.key.value;\n const fieldSchema = schema.fields.get(fieldName);\n\n if (!fieldSchema) {\n continue; // Will be caught by unknown-field rule\n }\n\n // Check if the field is required and has an empty value\n if (!fieldSchema.optional && fieldSchema.defaultValue === null) {\n const rawValue = meta.value.raw.trim();\n // Check for empty or effectively empty values\n if (rawValue === \"\" || rawValue === '\"\"') {\n ctx.report({\n message: `Required field '${fieldName}' has an empty value.`,\n file: ctx.file,\n location: meta.value.location,\n sourceMap: ctx.sourceMap,\n data: { fieldName, entity },\n });\n }\n }\n }\n },\n};\n\n/**\n * Check for required fields that are present but have empty values\n */\nexport const emptyRequiredValueRule: Rule = {\n code: \"empty-required-value\",\n name: \"Empty Required Value\",\n description: \"Required field has empty value\",\n category,\n defaultSeverity: \"error\",\n dependencies: { scope: \"entry\", schemas: true },\n visitor,\n};\n"],"mappings":";AAGA,MAAMA,WAAyB;AAE/B,MAAMC,UAAuB,EAC3B,mBAAmB,OAAO,KAAK;CAC7B,MAAM,WAAW,IAAI,UAAU;CAC/B,MAAM,SAAS,MAAM,OAAO;CAC5B,MAAM,SAAS,SAAS,IAAI,OAAO;AAEnC,KAAI,CAAC,OACH;AAGF,MAAK,MAAM,QAAQ,MAAM,UAAU;EACjC,MAAM,YAAY,KAAK,IAAI;EAC3B,MAAM,cAAc,OAAO,OAAO,IAAI,UAAU;AAEhD,MAAI,CAAC,YACH;AAIF,MAAI,CAAC,YAAY,YAAY,YAAY,iBAAiB,MAAM;GAC9D,MAAM,WAAW,KAAK,MAAM,IAAI,MAAM;AAEtC,OAAI,aAAa,MAAM,aAAa,OAClC,KAAI,OAAO;IACT,SAAS,mBAAmB,UAAU;IACtC,MAAM,IAAI;IACV,UAAU,KAAK,MAAM;IACrB,WAAW,IAAI;IACf,MAAM;KAAE;KAAW;KAAQ;IAC5B,CAAC;;;GAKX;;;;AAKD,MAAaC,yBAA+B;CAC1C,MAAM;CACN,MAAM;CACN,aAAa;CACb;CACA,iBAAiB;CACjB,cAAc;EAAE,OAAO;EAAS,SAAS;EAAM;CAC/C;CACD"}
|