@wdprlib/parser 4.4.0 → 5.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.
@@ -1,11 +1,13 @@
1
- import { DEFAULT_SETTINGS, type PageRef, type WikitextPageContext } from "@wdprlib/ast";
2
- import { parse } from "../parser";
3
- import { extractDataRequirements } from "../parser/rules/block/module/listpages/extract";
4
1
  import {
5
- resolveIncludesAsyncWithTrace,
6
- type AsyncIncludeFetcher,
7
- type IncludeDependency,
8
- } from "../parser/rules/block/module/include";
2
+ DEFAULT_SETTINGS,
3
+ type Diagnostic,
4
+ type PageRef,
5
+ type WikitextPageContext,
6
+ } from "@wdprlib/ast";
7
+ import { parseWithIncludeDeferral } from "../parser/parse";
8
+ import { extractDataRequirements } from "../parser/rules/block/module/listpages/extract";
9
+ import type { AsyncIncludeFetcher, IncludeDependency } from "../parser/rules/block/module/include";
10
+ import { resolveIncludesAsyncWithTraceSelective } from "../parser/rules/block/module/include/resolve";
9
11
  import type { DataProvider } from "../parser/rules/block/module/types-common";
10
12
  import { resolveModulesWithAsyncParse } from "../parser/rules/block/module/resolution/resolve-async";
11
13
  import type {
@@ -14,6 +16,12 @@ import type {
14
16
  ProcessWikitextOptions,
15
17
  } from "./types";
16
18
 
19
+ const DEFAULT_MODULE_MAX_PASSES = 5;
20
+ const PIPELINE_DIAGNOSTIC_POSITION = {
21
+ start: { line: 1, column: 1, offset: 0 },
22
+ end: { line: 1, column: 1, offset: 0 },
23
+ };
24
+
17
25
  export async function processWikitext<TPage extends WikitextPageContext>(
18
26
  source: string,
19
27
  options: ProcessWikitextOptions<TPage>,
@@ -24,52 +32,128 @@ export async function processWikitext<TPage extends WikitextPageContext>(
24
32
  settings,
25
33
  };
26
34
  const dependencies: IncludeDependency[] = [];
35
+ const diagnostics: Diagnostic[] = [];
27
36
  const fetchInclude = createRequestIncludeFetcher(
28
37
  options.dataProvider?.fetchInclude
29
38
  ? (pageRef) => options.dataProvider!.fetchInclude!(pageRef, callbackContext)
30
39
  : undefined,
31
40
  );
32
41
  const resolveSource = async (input: string): Promise<string> => {
33
- if (!fetchInclude) return input;
34
- const resolution = await resolveIncludesAsyncWithTrace(input, fetchInclude, {
35
- maxIterations: options.includeMaxIterations,
36
- settings,
37
- });
38
- dependencies.push(...resolution.dependencies);
39
- return resolution.source;
42
+ let expanded = input;
43
+ if (fetchInclude) {
44
+ const resolution = await resolveIncludesAsyncWithTraceSelective(
45
+ input,
46
+ fetchInclude,
47
+ (reference) => isCurrentPageInclude(reference.location, options.page),
48
+ {
49
+ maxIterations: options.includeMaxIterations,
50
+ settings,
51
+ },
52
+ );
53
+ dependencies.push(...resolution.dependencies);
54
+ if (resolution.reachedMaxIterations) {
55
+ diagnostics.push(
56
+ createLimitDiagnostic(
57
+ "include-resolution-limit",
58
+ `Include expansion stopped after ${options.includeMaxIterations ?? 10} iterations.`,
59
+ ),
60
+ );
61
+ }
62
+ expanded = resolution.source;
63
+ }
64
+ return expanded;
40
65
  };
41
66
 
42
- const expandedSource = await resolveSource(source);
43
- const initial = parse(expandedSource, {
44
- settings,
45
- pageTags: options.page.tags,
46
- appendImplicitFootnoteBlock: false,
47
- });
48
- const extraction = extractDataRequirements(initial.ast);
49
- const dataProvider = createModuleDataProvider(options, callbackContext);
50
- const resolved = await resolveModulesWithAsyncParse(initial.ast, dataProvider, {
51
- parse: async (fragmentSource) =>
52
- parse(await resolveSource(fragmentSource), {
67
+ const parseSource = async (input: string) => {
68
+ const expanded = await resolveSource(input);
69
+ return parseWithIncludeDeferral(
70
+ expanded,
71
+ {
53
72
  settings,
54
73
  pageTags: options.page.tags,
55
74
  appendImplicitFootnoteBlock: false,
56
- }),
57
- compiledListPagesTemplates: extraction.compiledListPagesTemplates,
58
- compiledListUsersTemplates: extraction.compiledListUsersTemplates,
59
- requirements: extraction.requirements,
60
- urlPath: options.page.urlPath,
61
- pageTags: options.page.tags,
62
- });
75
+ },
76
+ (location) => isCurrentPageInclude(location, options.page),
77
+ );
78
+ };
79
+
80
+ const initial = await parseSource(source);
81
+ diagnostics.push(...initial.diagnostics);
82
+ const dataProvider = createModuleDataProvider(options, callbackContext);
83
+ const parseFragment = parseSource;
84
+ let ast = initial.ast;
85
+
86
+ for (let pass = 0; pass < DEFAULT_MODULE_MAX_PASSES; pass++) {
87
+ const extraction = extractDataRequirements(ast);
88
+ if (pass > 0 && !hasResolvableRequirements(extraction.requirements, options.dataProvider)) {
89
+ break;
90
+ }
91
+
92
+ const resolved = await resolveModulesWithAsyncParse(ast, dataProvider, {
93
+ parse: parseFragment,
94
+ compiledListPagesTemplates: extraction.compiledListPagesTemplates,
95
+ compiledListUsersTemplates: extraction.compiledListUsersTemplates,
96
+ requirements: extraction.requirements,
97
+ urlPath: options.page.urlPath,
98
+ pageTags: options.page.tags,
99
+ });
100
+ ast = resolved.ast;
101
+ diagnostics.push(...resolved.diagnostics);
102
+ }
103
+
104
+ if (hasResolvableRequirements(extractDataRequirements(ast).requirements, options.dataProvider)) {
105
+ diagnostics.push(
106
+ createLimitDiagnostic(
107
+ "module-resolution-limit",
108
+ `Module resolution stopped after ${DEFAULT_MODULE_MAX_PASSES} passes.`,
109
+ ),
110
+ );
111
+ }
63
112
 
64
113
  return {
65
- ast: resolved.ast,
114
+ ast,
66
115
  page: options.page,
67
116
  settings,
68
- diagnostics: [...initial.diagnostics, ...resolved.diagnostics],
117
+ diagnostics,
69
118
  dependencies,
70
119
  };
71
120
  }
72
121
 
122
+ function isCurrentPageInclude(location: PageRef, page: WikitextPageContext): boolean {
123
+ if (location.site !== null) {
124
+ if (!page.site || location.site.toLowerCase() !== page.site.toLowerCase()) return false;
125
+ }
126
+
127
+ const target = location.page.toLowerCase();
128
+ return target === page.fullName.toLowerCase() || target === page.unixName?.toLowerCase();
129
+ }
130
+
131
+ function hasResolvableRequirements(
132
+ requirements: ReturnType<typeof extractDataRequirements>["requirements"],
133
+ provider:
134
+ | {
135
+ fetchListPages?: unknown;
136
+ fetchListUsers?: unknown;
137
+ fetchTagCloud?: unknown;
138
+ }
139
+ | undefined,
140
+ ): boolean {
141
+ return Boolean(
142
+ (provider?.fetchListPages && requirements.listPages.length > 0) ||
143
+ (provider?.fetchListUsers && requirements.listUsers.length > 0) ||
144
+ (provider?.fetchTagCloud && requirements.tagCloud.length > 0),
145
+ );
146
+ }
147
+
148
+ function createLimitDiagnostic(code: string, message: string): Diagnostic {
149
+ return {
150
+ severity: "warning",
151
+ code,
152
+ message,
153
+ position: PIPELINE_DIAGNOSTIC_POSITION,
154
+ };
155
+ }
156
+
73
157
  function createModuleDataProvider<TPage extends WikitextPageContext>(
74
158
  options: ProcessWikitextOptions<TPage>,
75
159
  context: ProcessWikitextCallbackContext<TPage>,