@mandolin/jsdoc-plugin-hia-sys 0.1.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.
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ module.exports = {
4
+ name: "doclet-normalizer",
5
+ order: 50,
6
+
7
+ newDoclet(event, context) {
8
+ if (!event || !event.doclet) {
9
+ return;
10
+ }
11
+
12
+ const doclet = event.doclet;
13
+ const metadata = context.markMicroPlugin(doclet, this.name);
14
+
15
+ metadata.doclet = {
16
+ name: doclet.name || "",
17
+ longname: doclet.longname || doclet.name || "",
18
+ kind: doclet.kind || "",
19
+ memberof: doclet.memberof || "",
20
+ scope: doclet.scope || ""
21
+ };
22
+ }
23
+ };
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+
3
+ const {
4
+ buildOutputContract,
5
+ writeIntegrationOutput
6
+ } = require("../runtime/output-contract.cjs");
7
+ const { VERSION } = require("../version.cjs");
8
+
9
+ function toNode(doclet, context) {
10
+ const metadata = context.ensureDocletHia(doclet);
11
+
12
+ return {
13
+ id: metadata.doclet.longname || metadata.doclet.name,
14
+ name: metadata.doclet.name,
15
+ longname: metadata.doclet.longname,
16
+ kind: metadata.doclet.kind,
17
+ source: metadata.source,
18
+ i18n: metadata.i18n
19
+ };
20
+ }
21
+
22
+ module.exports = {
23
+ name: "hia-ir-exporter",
24
+ order: 110,
25
+
26
+ parseComplete(event, context) {
27
+ const doclets = event && Array.isArray(event.doclets) ? event.doclets : [];
28
+
29
+ context.state.doclets = doclets;
30
+ context.state.integration.ir = {
31
+ version: VERSION,
32
+ mode: context.config.mode,
33
+ nodes: doclets.map((doclet) => toNode(doclet, context))
34
+ };
35
+ },
36
+
37
+ processingComplete(event, context) {
38
+ const doclets = event && Array.isArray(event.doclets)
39
+ ? event.doclets
40
+ : context.state.doclets;
41
+ const output = buildOutputContract(context.state, doclets);
42
+
43
+ context.state.output.standalone = output.standalone;
44
+ context.state.output.integration = output.integration;
45
+ context.state.output.themeContract = output.standalone.theme;
46
+ context.state.integration.ir = output.integration.ir;
47
+
48
+ try {
49
+ const writtenFile = writeIntegrationOutput(context.state, output.integration);
50
+
51
+ if (writtenFile) {
52
+ context.state.output.integrationOutputFile = writtenFile;
53
+ }
54
+ } catch (error) {
55
+ context.addDiagnostic({
56
+ code: "HIA_INTEGRATION_OUTPUT_WRITE_FAILED",
57
+ severity: "error",
58
+ message: `Cannot write HIA integration output: ${error.message}`,
59
+ plugin: this.name,
60
+ data: {
61
+ outputFile: context.config.integration.outputFile || ""
62
+ }
63
+ });
64
+ context.state.output.diagnostics = context.diagnostics.list();
65
+ context.state.output.diagnosticCounts = context.diagnostics.countBySeverity();
66
+ }
67
+ }
68
+ };
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ const codeFragment = require("./code-fragment.cjs");
4
+ const diagnostics = require("./diagnostics.cjs");
5
+ const docI18n = require("./doc-i18n.cjs");
6
+ const docletNormalizer = require("./doclet-normalizer.cjs");
7
+ const hiaIrExporter = require("./hia-ir-exporter.cjs");
8
+ const sourceLink = require("./source-link.cjs");
9
+ const sourcePreview = require("./source-preview.cjs");
10
+
11
+ const builtInMicroPlugins = [
12
+ codeFragment,
13
+ sourceLink,
14
+ sourcePreview,
15
+ docI18n,
16
+ docletNormalizer,
17
+ hiaIrExporter,
18
+ diagnostics
19
+ ];
20
+
21
+ module.exports = {
22
+ builtInMicroPlugins
23
+ };
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+
3
+ module.exports = {
4
+ name: "source-link",
5
+ order: 20,
6
+
7
+ newDoclet(event, context) {
8
+ if (!event || !event.doclet) {
9
+ return;
10
+ }
11
+
12
+ const metadata = context.markMicroPlugin(event.doclet, this.name);
13
+ metadata.source.link = {
14
+ enabled: Boolean(context.config.source.link.enabled),
15
+ rootUrl: context.config.source.link.rootUrl,
16
+ openMode: context.config.source.link.openMode
17
+ };
18
+ }
19
+ };
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ module.exports = {
4
+ name: "source-preview",
5
+ order: 30,
6
+
7
+ newDoclet(event, context) {
8
+ if (!event || !event.doclet) {
9
+ return;
10
+ }
11
+
12
+ const metadata = context.markMicroPlugin(event.doclet, this.name);
13
+ metadata.source.preview = {
14
+ enabled: Boolean(context.config.source.preview.enabled),
15
+ defaultExpanded: Boolean(context.config.source.preview.defaultExpanded)
16
+ };
17
+ }
18
+ };
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+
3
+ const { loadConfig } = require("../config/defaults.cjs");
4
+ const { DiagnosticsCollector } = require("./diagnostics.cjs");
5
+ const {
6
+ attachDiagnostics,
7
+ ensureDocletHia,
8
+ markMicroPlugin
9
+ } = require("./metadata.cjs");
10
+
11
+ function createInitialState(config) {
12
+ return {
13
+ config,
14
+ diagnostics: new DiagnosticsCollector(),
15
+ sourceFiles: new Map(),
16
+ registries: {
17
+ sourceFragments: new Map()
18
+ },
19
+ doclets: [],
20
+ integration: {
21
+ ir: null
22
+ },
23
+ output: {}
24
+ };
25
+ }
26
+
27
+ function createPluginContext(state) {
28
+ return {
29
+ state,
30
+ get config() {
31
+ return state.config;
32
+ },
33
+ diagnostics: state.diagnostics,
34
+ addDiagnostic(diagnostic) {
35
+ return state.diagnostics.add(diagnostic);
36
+ },
37
+ ensureDocletHia(doclet) {
38
+ return ensureDocletHia(doclet, state);
39
+ },
40
+ markMicroPlugin(doclet, pluginName) {
41
+ return markMicroPlugin(doclet, state, pluginName);
42
+ },
43
+ attachDiagnostics(doclet) {
44
+ return attachDiagnostics(doclet, state);
45
+ }
46
+ };
47
+ }
48
+
49
+ function sortMicroPlugins(microPlugins) {
50
+ return microPlugins.slice().sort((left, right) => {
51
+ return (left.order || 100) - (right.order || 100);
52
+ });
53
+ }
54
+
55
+ function filterEnabledMicroPlugins(microPlugins, config) {
56
+ const enabledNames = new Set(config.microPlugins || []);
57
+ return sortMicroPlugins(
58
+ microPlugins.filter((plugin) => enabledNames.has(plugin.name))
59
+ );
60
+ }
61
+
62
+ function createPluginSystem(options) {
63
+ const config = loadConfig(options && options.configOverrides);
64
+ const state = createInitialState(config);
65
+ const context = createPluginContext(state);
66
+ const microPlugins = filterEnabledMicroPlugins(
67
+ (options && options.microPlugins) || [],
68
+ config
69
+ );
70
+
71
+ for (const plugin of microPlugins) {
72
+ if (typeof plugin.setup === "function") {
73
+ plugin.setup(context);
74
+ }
75
+ }
76
+
77
+ function runHook(hookName, event) {
78
+ for (const plugin of microPlugins) {
79
+ const hook = plugin[hookName];
80
+
81
+ if (typeof hook !== "function") {
82
+ continue;
83
+ }
84
+
85
+ try {
86
+ hook.call(plugin, event, context);
87
+ } catch (error) {
88
+ context.addDiagnostic({
89
+ code: "HIA_PLUGIN_HOOK_FAILED",
90
+ severity: "error",
91
+ message: `${plugin.name}.${hookName} failed: ${error.message}`,
92
+ plugin: plugin.name
93
+ });
94
+
95
+ if (config.diagnostics.throwOnError) {
96
+ throw error;
97
+ }
98
+ }
99
+ }
100
+ }
101
+
102
+ return {
103
+ defineTags(dictionary) {
104
+ runHook("defineTags", { dictionary });
105
+ },
106
+ handle(hookName, event) {
107
+ runHook(hookName, event);
108
+ },
109
+ getState() {
110
+ return state;
111
+ },
112
+ getMicroPlugins() {
113
+ return microPlugins.slice();
114
+ }
115
+ };
116
+ }
117
+
118
+ module.exports = {
119
+ createPluginSystem
120
+ };
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+
3
+ const VALID_SEVERITIES = new Set(["error", "warning", "info"]);
4
+
5
+ function normalizeSeverity(severity) {
6
+ return severity === "hint" ? "info" : severity;
7
+ }
8
+
9
+ class DiagnosticsCollector {
10
+ constructor() {
11
+ this.items = [];
12
+ }
13
+
14
+ add(diagnostic) {
15
+ const item = {
16
+ code: diagnostic.code || "HIA0000",
17
+ severity: VALID_SEVERITIES.has(normalizeSeverity(diagnostic.severity))
18
+ ? normalizeSeverity(diagnostic.severity)
19
+ : "warning",
20
+ message: diagnostic.message || "Unknown HIA diagnostic.",
21
+ plugin: diagnostic.plugin || "core",
22
+ filePath: diagnostic.filePath || "",
23
+ range: diagnostic.range || null,
24
+ data: diagnostic.data || null
25
+ };
26
+
27
+ this.items.push(item);
28
+ return item;
29
+ }
30
+
31
+ list() {
32
+ return this.items.slice();
33
+ }
34
+
35
+ countBySeverity() {
36
+ return this.items.reduce((counts, item) => {
37
+ counts[item.severity] = (counts[item.severity] || 0) + 1;
38
+ return counts;
39
+ }, {});
40
+ }
41
+ }
42
+
43
+ module.exports = {
44
+ DiagnosticsCollector
45
+ };
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+
6
+ function parseLocaleValue(value) {
7
+ const text = String(value || "").trim();
8
+ const match = /^(\S+)\s+([\s\S]+)$/.exec(text);
9
+
10
+ if (!match) {
11
+ return {
12
+ locale: "",
13
+ value: text
14
+ };
15
+ }
16
+
17
+ return {
18
+ locale: match[1],
19
+ value: match[2].trim()
20
+ };
21
+ }
22
+
23
+ function ensureLocaleBucket(target, locale) {
24
+ if (!target[locale]) {
25
+ target[locale] = {};
26
+ }
27
+
28
+ return target[locale];
29
+ }
30
+
31
+ function resolveResourcePath(resourcePath, config) {
32
+ if (path.isAbsolute(resourcePath)) {
33
+ return resourcePath;
34
+ }
35
+
36
+ const basePath =
37
+ config.i18n.resourceBasePath || config.source.basePath || process.cwd();
38
+
39
+ return path.resolve(basePath, resourcePath);
40
+ }
41
+
42
+ function toPosixPath(filePath) {
43
+ return String(filePath || "").split(path.sep).join("/");
44
+ }
45
+
46
+ function isSafeRelativePath(filePath) {
47
+ const text = toPosixPath(filePath);
48
+
49
+ return Boolean(text) &&
50
+ !path.isAbsolute(text) &&
51
+ !/^[A-Za-z]:\//.test(text) &&
52
+ !text.startsWith("/") &&
53
+ !text.startsWith("//") &&
54
+ !text.split("/").includes("..");
55
+ }
56
+
57
+ function getResourceRelativePath(absolutePath, resourcePath, config) {
58
+ if (isSafeRelativePath(resourcePath)) {
59
+ return toPosixPath(resourcePath);
60
+ }
61
+
62
+ const basePath =
63
+ config.i18n.resourceBasePath || config.source.basePath || process.cwd();
64
+ const relativePath = toPosixPath(path.relative(basePath, absolutePath));
65
+
66
+ if (isSafeRelativePath(relativePath)) {
67
+ return relativePath;
68
+ }
69
+
70
+ return path.basename(absolutePath);
71
+ }
72
+
73
+ function collectResourceFields(entries) {
74
+ const fields = new Set();
75
+
76
+ for (const localeEntries of Object.values(entries || {})) {
77
+ for (const [key, value] of Object.entries(localeEntries || {})) {
78
+ fields.add(key);
79
+
80
+ if (value && typeof value === "object" && !Array.isArray(value)) {
81
+ const nestedFields = value.fields && typeof value.fields === "object"
82
+ ? Object.keys(value.fields)
83
+ : [];
84
+
85
+ for (const fieldPath of nestedFields) {
86
+ fields.add(fieldPath);
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ return Array.from(fields).sort();
93
+ }
94
+
95
+ function normalizeResourceData(data, absolutePath) {
96
+ if (data && typeof data.locale === "string" && data.entries) {
97
+ return {
98
+ filePath: absolutePath,
99
+ entries: {
100
+ [data.locale]: data.entries
101
+ }
102
+ };
103
+ }
104
+
105
+ return {
106
+ filePath: absolutePath,
107
+ entries: data && typeof data === "object" ? data : {}
108
+ };
109
+ }
110
+
111
+ function loadLocalizationResources(config, diagnostics) {
112
+ const resources = [];
113
+ const entries = {};
114
+
115
+ for (const resourcePath of config.i18n.resources || []) {
116
+ const absolutePath = resolveResourcePath(resourcePath, config);
117
+
118
+ if (!fs.existsSync(absolutePath)) {
119
+ diagnostics.add({
120
+ code: "HIA_I18N_RESOURCE_MISSING",
121
+ severity: "error",
122
+ message: `Localization resource does not exist: ${resourcePath}`,
123
+ plugin: "doc-i18n",
124
+ filePath: absolutePath,
125
+ data: {
126
+ resourcePath
127
+ }
128
+ });
129
+ continue;
130
+ }
131
+
132
+ try {
133
+ const resource = normalizeResourceData(
134
+ JSON.parse(fs.readFileSync(absolutePath, "utf8")),
135
+ absolutePath
136
+ );
137
+
138
+ resources.push({
139
+ kind: "external-resource",
140
+ path: getResourceRelativePath(absolutePath, resourcePath, config),
141
+ format: "hia-i18n-json",
142
+ fields: collectResourceFields(resource.entries),
143
+ locales: Object.keys(resource.entries).sort(),
144
+ localeCount: Object.keys(resource.entries).length
145
+ });
146
+
147
+ for (const [locale, localeEntries] of Object.entries(resource.entries)) {
148
+ const bucket = ensureLocaleBucket(entries, locale);
149
+
150
+ for (const [key, value] of Object.entries(localeEntries || {})) {
151
+ if (Object.prototype.hasOwnProperty.call(bucket, key)) {
152
+ diagnostics.add({
153
+ code: "HIA_I18N_RESOURCE_KEY_DUPLICATE",
154
+ severity: "error",
155
+ message: `Duplicate localization resource key: ${locale}:${key}`,
156
+ plugin: "doc-i18n",
157
+ filePath: absolutePath,
158
+ data: {
159
+ locale,
160
+ key
161
+ }
162
+ });
163
+ }
164
+
165
+ bucket[key] = value;
166
+ }
167
+ }
168
+ } catch (error) {
169
+ diagnostics.add({
170
+ code: "HIA_I18N_RESOURCE_INVALID",
171
+ severity: "error",
172
+ message: `Cannot read localization resource ${resourcePath}: ${error.message}`,
173
+ plugin: "doc-i18n",
174
+ filePath: absolutePath,
175
+ data: {
176
+ resourcePath
177
+ }
178
+ });
179
+ }
180
+ }
181
+
182
+ return {
183
+ resources,
184
+ entries
185
+ };
186
+ }
187
+
188
+ function getResourceEntry(entries, locale, key) {
189
+ const bucket = entries[locale];
190
+
191
+ if (!bucket) {
192
+ return null;
193
+ }
194
+
195
+ return bucket[key] || null;
196
+ }
197
+
198
+ function normalizeLocalizedValue(value) {
199
+ if (value && typeof value === "object" && !Array.isArray(value)) {
200
+ return {
201
+ text: value.text || value.description || "",
202
+ block: value.block || value.markdown || "",
203
+ source: "resource",
204
+ data: value
205
+ };
206
+ }
207
+
208
+ return {
209
+ text: String(value || ""),
210
+ block: "",
211
+ source: "resource",
212
+ data: value
213
+ };
214
+ }
215
+
216
+ module.exports = {
217
+ getResourceEntry,
218
+ loadLocalizationResources,
219
+ normalizeLocalizedValue,
220
+ parseLocaleValue
221
+ };
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+
3
+ const { VERSION } = require("../version.cjs");
4
+
5
+ function ensureDocletHia(doclet, state) {
6
+ const key = state.config.metadataKey || "hia";
7
+
8
+ if (!doclet[key]) {
9
+ doclet[key] = {
10
+ version: VERSION,
11
+ mode: state.config.mode,
12
+ microPlugins: [],
13
+ doclet: {},
14
+ source: {
15
+ model: "hia-jsdoc-source",
16
+ modelVersion: "0.2.0",
17
+ mode: state.config.source.mode || "all",
18
+ definedIn: null,
19
+ primaryBlock: null,
20
+ fragments: [],
21
+ references: [],
22
+ diagnostics: []
23
+ },
24
+ i18n: {},
25
+ diagnostics: []
26
+ };
27
+ }
28
+
29
+ return doclet[key];
30
+ }
31
+
32
+ function markMicroPlugin(doclet, state, pluginName) {
33
+ const metadata = ensureDocletHia(doclet, state);
34
+
35
+ if (!metadata.microPlugins.includes(pluginName)) {
36
+ metadata.microPlugins.push(pluginName);
37
+ }
38
+
39
+ return metadata;
40
+ }
41
+
42
+ function attachDiagnostics(doclet, state) {
43
+ const metadata = ensureDocletHia(doclet, state);
44
+ metadata.diagnostics = state.diagnostics.list();
45
+ return metadata;
46
+ }
47
+
48
+ module.exports = {
49
+ attachDiagnostics,
50
+ ensureDocletHia,
51
+ markMicroPlugin
52
+ };