perttool 0.1.0-alpha.2

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.
Files changed (176) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/LICENSE +21 -0
  3. package/README.md +134 -0
  4. package/dist/analysis/graph.d.ts +41 -0
  5. package/dist/analysis/graph.d.ts.map +1 -0
  6. package/dist/analysis/graph.js +199 -0
  7. package/dist/analysis/graph.js.map +1 -0
  8. package/dist/analysis/precedence.d.ts +52 -0
  9. package/dist/analysis/precedence.d.ts.map +1 -0
  10. package/dist/analysis/precedence.js +187 -0
  11. package/dist/analysis/precedence.js.map +1 -0
  12. package/dist/analysis/resource.d.ts +96 -0
  13. package/dist/analysis/resource.d.ts.map +1 -0
  14. package/dist/analysis/resource.js +522 -0
  15. package/dist/analysis/resource.js.map +1 -0
  16. package/dist/application/analyze.d.ts +32 -0
  17. package/dist/application/analyze.d.ts.map +1 -0
  18. package/dist/application/analyze.js +144 -0
  19. package/dist/application/analyze.js.map +1 -0
  20. package/dist/application/check.d.ts +24 -0
  21. package/dist/application/check.d.ts.map +1 -0
  22. package/dist/application/check.js +43 -0
  23. package/dist/application/check.js.map +1 -0
  24. package/dist/application/format.d.ts +21 -0
  25. package/dist/application/format.d.ts.map +1 -0
  26. package/dist/application/format.js +52 -0
  27. package/dist/application/format.js.map +1 -0
  28. package/dist/application/mutate.d.ts +4 -0
  29. package/dist/application/mutate.d.ts.map +1 -0
  30. package/dist/application/mutate.js +175 -0
  31. package/dist/application/mutate.js.map +1 -0
  32. package/dist/application/next.d.ts +86 -0
  33. package/dist/application/next.d.ts.map +1 -0
  34. package/dist/application/next.js +310 -0
  35. package/dist/application/next.js.map +1 -0
  36. package/dist/cli.d.ts +3 -0
  37. package/dist/cli.d.ts.map +1 -0
  38. package/dist/cli.js +2103 -0
  39. package/dist/cli.js.map +1 -0
  40. package/dist/conversion/mermaid-import.d.ts +26 -0
  41. package/dist/conversion/mermaid-import.d.ts.map +1 -0
  42. package/dist/conversion/mermaid-import.js +655 -0
  43. package/dist/conversion/mermaid-import.js.map +1 -0
  44. package/dist/conversion/mermaid.d.ts +38 -0
  45. package/dist/conversion/mermaid.d.ts.map +1 -0
  46. package/dist/conversion/mermaid.js +362 -0
  47. package/dist/conversion/mermaid.js.map +1 -0
  48. package/dist/editing/unified-diff.d.ts +7 -0
  49. package/dist/editing/unified-diff.d.ts.map +1 -0
  50. package/dist/editing/unified-diff.js +81 -0
  51. package/dist/editing/unified-diff.js.map +1 -0
  52. package/dist/formatter/source-formatter.d.ts +17 -0
  53. package/dist/formatter/source-formatter.d.ts.map +1 -0
  54. package/dist/formatter/source-formatter.js +210 -0
  55. package/dist/formatter/source-formatter.js.map +1 -0
  56. package/dist/help/registry.d.ts +37 -0
  57. package/dist/help/registry.d.ts.map +1 -0
  58. package/dist/help/registry.js +657 -0
  59. package/dist/help/registry.js.map +1 -0
  60. package/dist/index.d.ts +44 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +23 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/io/document-file.d.ts +9 -0
  65. package/dist/io/document-file.d.ts.map +1 -0
  66. package/dist/io/document-file.js +19 -0
  67. package/dist/io/document-file.js.map +1 -0
  68. package/dist/io/safe-write.d.ts +33 -0
  69. package/dist/io/safe-write.d.ts.map +1 -0
  70. package/dist/io/safe-write.js +280 -0
  71. package/dist/io/safe-write.js.map +1 -0
  72. package/dist/model/diagnostics.d.ts +43 -0
  73. package/dist/model/diagnostics.d.ts.map +1 -0
  74. package/dist/model/diagnostics.js +47 -0
  75. package/dist/model/diagnostics.js.map +1 -0
  76. package/dist/model/rational.d.ts +21 -0
  77. package/dist/model/rational.d.ts.map +1 -0
  78. package/dist/model/rational.js +84 -0
  79. package/dist/model/rational.js.map +1 -0
  80. package/dist/model/syntax.d.ts +67 -0
  81. package/dist/model/syntax.d.ts.map +1 -0
  82. package/dist/model/syntax.js +15 -0
  83. package/dist/model/syntax.js.map +1 -0
  84. package/dist/model/units.d.ts +18 -0
  85. package/dist/model/units.d.ts.map +1 -0
  86. package/dist/model/units.js +29 -0
  87. package/dist/model/units.js.map +1 -0
  88. package/dist/mutation/advance.d.ts +26 -0
  89. package/dist/mutation/advance.d.ts.map +1 -0
  90. package/dist/mutation/advance.js +261 -0
  91. package/dist/mutation/advance.js.map +1 -0
  92. package/dist/mutation/diagnostics.d.ts +10 -0
  93. package/dist/mutation/diagnostics.d.ts.map +1 -0
  94. package/dist/mutation/diagnostics.js +10 -0
  95. package/dist/mutation/diagnostics.js.map +1 -0
  96. package/dist/mutation/entity-editor.d.ts +16 -0
  97. package/dist/mutation/entity-editor.d.ts.map +1 -0
  98. package/dist/mutation/entity-editor.js +103 -0
  99. package/dist/mutation/entity-editor.js.map +1 -0
  100. package/dist/mutation/milestone.d.ts +5 -0
  101. package/dist/mutation/milestone.d.ts.map +1 -0
  102. package/dist/mutation/milestone.js +194 -0
  103. package/dist/mutation/milestone.js.map +1 -0
  104. package/dist/mutation/resource.d.ts +5 -0
  105. package/dist/mutation/resource.d.ts.map +1 -0
  106. package/dist/mutation/resource.js +145 -0
  107. package/dist/mutation/resource.js.map +1 -0
  108. package/dist/mutation/source.d.ts +22 -0
  109. package/dist/mutation/source.d.ts.map +1 -0
  110. package/dist/mutation/source.js +155 -0
  111. package/dist/mutation/source.js.map +1 -0
  112. package/dist/mutation/task.d.ts +10 -0
  113. package/dist/mutation/task.d.ts.map +1 -0
  114. package/dist/mutation/task.js +644 -0
  115. package/dist/mutation/task.js.map +1 -0
  116. package/dist/mutation/text-edits.d.ts +8 -0
  117. package/dist/mutation/text-edits.d.ts.map +1 -0
  118. package/dist/mutation/text-edits.js +33 -0
  119. package/dist/mutation/text-edits.js.map +1 -0
  120. package/dist/mutation/types.d.ts +153 -0
  121. package/dist/mutation/types.d.ts.map +1 -0
  122. package/dist/mutation/types.js +2 -0
  123. package/dist/mutation/types.js.map +1 -0
  124. package/dist/parser/document-parser.d.ts +6 -0
  125. package/dist/parser/document-parser.d.ts.map +1 -0
  126. package/dist/parser/document-parser.js +735 -0
  127. package/dist/parser/document-parser.js.map +1 -0
  128. package/dist/recommendation/explanation-types.d.ts +226 -0
  129. package/dist/recommendation/explanation-types.d.ts.map +1 -0
  130. package/dist/recommendation/explanation-types.js +2 -0
  131. package/dist/recommendation/explanation-types.js.map +1 -0
  132. package/dist/recommendation/explanation-validation.d.ts +4 -0
  133. package/dist/recommendation/explanation-validation.d.ts.map +1 -0
  134. package/dist/recommendation/explanation-validation.js +745 -0
  135. package/dist/recommendation/explanation-validation.js.map +1 -0
  136. package/dist/recommendation/explanation-values.d.ts +7 -0
  137. package/dist/recommendation/explanation-values.d.ts.map +1 -0
  138. package/dist/recommendation/explanation-values.js +283 -0
  139. package/dist/recommendation/explanation-values.js.map +1 -0
  140. package/dist/recommendation/explanation.d.ts +8 -0
  141. package/dist/recommendation/explanation.d.ts.map +1 -0
  142. package/dist/recommendation/explanation.js +1022 -0
  143. package/dist/recommendation/explanation.js.map +1 -0
  144. package/dist/recommendation/failure.d.ts +3 -0
  145. package/dist/recommendation/failure.d.ts.map +1 -0
  146. package/dist/recommendation/failure.js +4 -0
  147. package/dist/recommendation/failure.js.map +1 -0
  148. package/dist/recommendation/json.d.ts +3 -0
  149. package/dist/recommendation/json.d.ts.map +1 -0
  150. package/dist/recommendation/json.js +252 -0
  151. package/dist/recommendation/json.js.map +1 -0
  152. package/dist/recommendation/override-types.d.ts +117 -0
  153. package/dist/recommendation/override-types.d.ts.map +1 -0
  154. package/dist/recommendation/override-types.js +2 -0
  155. package/dist/recommendation/override-types.js.map +1 -0
  156. package/dist/recommendation/override.d.ts +7 -0
  157. package/dist/recommendation/override.d.ts.map +1 -0
  158. package/dist/recommendation/override.js +717 -0
  159. package/dist/recommendation/override.js.map +1 -0
  160. package/dist/recommendation/ranking.d.ts +7 -0
  161. package/dist/recommendation/ranking.d.ts.map +1 -0
  162. package/dist/recommendation/ranking.js +391 -0
  163. package/dist/recommendation/ranking.js.map +1 -0
  164. package/dist/recommendation/types.d.ts +83 -0
  165. package/dist/recommendation/types.d.ts.map +1 -0
  166. package/dist/recommendation/types.js +2 -0
  167. package/dist/recommendation/types.js.map +1 -0
  168. package/dist/semantic/validator.d.ts +4 -0
  169. package/dist/semantic/validator.d.ts.map +1 -0
  170. package/dist/semantic/validator.js +490 -0
  171. package/dist/semantic/validator.js.map +1 -0
  172. package/dist/version.d.ts +2 -0
  173. package/dist/version.d.ts.map +1 -0
  174. package/dist/version.js +2 -0
  175. package/dist/version.js.map +1 -0
  176. package/package.json +61 -0
@@ -0,0 +1,644 @@
1
+ import { fieldNamed } from "../model/syntax.js";
2
+ import { mutationDiagnostic } from "./diagnostics.js";
3
+ import { appendDeclarationEdit, contentEndOffset, contentTextEndOffset, deleteDeclarationEdit, deleteFieldEdit, fieldInsertionOffset, insertionText, leadingCommentStart, lineIndexAt, majorLineEnding, serializeTags, serializeTextField, splitPhysicalLines, } from "./source.js";
4
+ const fieldOrder = [
5
+ "title",
6
+ "description",
7
+ "duration",
8
+ "estimate",
9
+ "status",
10
+ "priority",
11
+ "requires",
12
+ "owner",
13
+ "tags",
14
+ "blocked_reason",
15
+ "source",
16
+ ];
17
+ const fieldRank = new Map(fieldOrder.map((name, index) => [name, name === "estimate" ? 2 : index]));
18
+ fieldRank.set("duration", 2);
19
+ const taskStatuses = new Set(["planned", "active", "blocked", "done"]);
20
+ const clearableFields = new Set([
21
+ "description",
22
+ "status",
23
+ "priority",
24
+ "owner",
25
+ "blocked_reason",
26
+ "source",
27
+ "tags",
28
+ "requires",
29
+ ]);
30
+ function validEstimate(value) {
31
+ if (value === null || typeof value !== "object")
32
+ return false;
33
+ const estimate = value;
34
+ return (typeof estimate.optimistic === "string" &&
35
+ typeof estimate.mostLikely === "string" &&
36
+ typeof estimate.pessimistic === "string");
37
+ }
38
+ function validRequirement(value) {
39
+ if (value === null || typeof value !== "object")
40
+ return false;
41
+ const requirement = value;
42
+ return (typeof requirement.resourceId === "string" &&
43
+ Number.isSafeInteger(requirement.units));
44
+ }
45
+ function validStringArray(value) {
46
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
47
+ }
48
+ function taskDefinitionError(value) {
49
+ if (value === null || typeof value !== "object")
50
+ return "task definitionがobjectではありません";
51
+ const task = value;
52
+ const knownFields = new Set([
53
+ "title",
54
+ "description",
55
+ "duration",
56
+ "estimate",
57
+ "status",
58
+ "priority",
59
+ "requirements",
60
+ "owner",
61
+ "tags",
62
+ "blockedReason",
63
+ "source",
64
+ ]);
65
+ if (Object.keys(task).some((name) => !knownFields.has(name))) {
66
+ return "task definitionに未対応fieldが含まれています";
67
+ }
68
+ if (typeof task["title"] !== "string")
69
+ return "task titleがstringではありません";
70
+ if ((task["duration"] === undefined) === (task["estimate"] === undefined)) {
71
+ return "durationまたはestimateのexactly oneを必要とします";
72
+ }
73
+ if (task["duration"] !== undefined && typeof task["duration"] !== "string") {
74
+ return "durationがstringではありません";
75
+ }
76
+ if (task["estimate"] !== undefined && !validEstimate(task["estimate"])) {
77
+ return "estimateはoptimistic、mostLikely、pessimisticのstringを必要とします";
78
+ }
79
+ for (const name of ["description", "owner", "blockedReason", "source"]) {
80
+ if (task[name] !== undefined && typeof task[name] !== "string") {
81
+ return `${name}がstringではありません`;
82
+ }
83
+ }
84
+ if (task["status"] !== undefined && !taskStatuses.has(task["status"])) {
85
+ return "statusがtask statusではありません";
86
+ }
87
+ if (task["priority"] !== undefined && !Number.isSafeInteger(task["priority"])) {
88
+ return "priorityがsafe integerではありません";
89
+ }
90
+ if (task["tags"] !== undefined && !validStringArray(task["tags"])) {
91
+ return "tagsがstring arrayではありません";
92
+ }
93
+ if (task["requirements"] !== undefined &&
94
+ (!Array.isArray(task["requirements"]) || !task["requirements"].every(validRequirement))) {
95
+ return "requirementsがresourceIdとsafe integer unitsのarrayではありません";
96
+ }
97
+ return undefined;
98
+ }
99
+ function taskMutationRequestError(value) {
100
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
101
+ return "mutation requestがobjectではありません";
102
+ }
103
+ const request = value;
104
+ const kind = request["kind"];
105
+ if (kind !== "task.add" &&
106
+ kind !== "task.set" &&
107
+ kind !== "task.remove" &&
108
+ kind !== "task.finish") {
109
+ return "mutation kindが未対応です";
110
+ }
111
+ if (typeof request["id"] !== "string")
112
+ return "mutation idがstringではありません";
113
+ const fieldsByKind = {
114
+ "task.add": new Set(["kind", "id", "from", "to", "task"]),
115
+ "task.set": new Set([
116
+ "kind",
117
+ "id",
118
+ "from",
119
+ "to",
120
+ "set",
121
+ "clear",
122
+ "addTags",
123
+ "removeTags",
124
+ "upsertRequirements",
125
+ "removeRequirements",
126
+ ]),
127
+ "task.remove": new Set(["kind", "id"]),
128
+ "task.finish": new Set(["kind", "id"]),
129
+ };
130
+ if (Object.keys(request).some((name) => !fieldsByKind[kind].has(name))) {
131
+ return `${kind} requestに未対応fieldが含まれています`;
132
+ }
133
+ return undefined;
134
+ }
135
+ function canonicalDuration(value) {
136
+ const match = /^(\d+)(?:\.(\d+))?([dhp])$/.exec(value);
137
+ if (match === null)
138
+ return value;
139
+ const whole = match[1].replace(/^0+(?=\d)/, "");
140
+ const fraction = (match[2] ?? "").replace(/0+$/, "");
141
+ return `${whole}${fraction === "" ? "" : `.${fraction}`}${match[3]}`;
142
+ }
143
+ function serializeEstimate(estimate, lineEnding) {
144
+ return [
145
+ " estimate:",
146
+ ` optimistic ${canonicalDuration(estimate.optimistic)}`,
147
+ ` most_likely ${canonicalDuration(estimate.mostLikely)}`,
148
+ ` pessimistic ${canonicalDuration(estimate.pessimistic)}`,
149
+ ].join(lineEnding);
150
+ }
151
+ function serializeRequirements(requirements, lineEnding) {
152
+ return [
153
+ " requires:",
154
+ ...requirements.map(({ resourceId, units }) => ` ${resourceId} ${units}`),
155
+ ].join(lineEnding);
156
+ }
157
+ function serializeField(name, value, lineEnding) {
158
+ switch (name) {
159
+ case "title":
160
+ case "owner":
161
+ case "source":
162
+ return ` ${name} ${JSON.stringify(value)}`;
163
+ case "description":
164
+ case "blocked_reason":
165
+ return serializeTextField(name, value, lineEnding);
166
+ case "duration":
167
+ return ` duration ${canonicalDuration(value)}`;
168
+ case "estimate":
169
+ return serializeEstimate(value, lineEnding);
170
+ case "status":
171
+ case "priority":
172
+ return ` ${name} ${value}`;
173
+ case "tags":
174
+ return ` tags ${serializeTags(value)}`;
175
+ case "requires":
176
+ return serializeRequirements(value, lineEnding);
177
+ default:
178
+ throw new Error(`unsupported task field ${name}`);
179
+ }
180
+ }
181
+ function taskFields(task) {
182
+ const fields = new Map([["title", task.title]]);
183
+ if (task.description !== undefined)
184
+ fields.set("description", task.description);
185
+ if (task.duration !== undefined)
186
+ fields.set("duration", task.duration);
187
+ if (task.estimate !== undefined)
188
+ fields.set("estimate", task.estimate);
189
+ if (task.status !== undefined)
190
+ fields.set("status", task.status);
191
+ if (task.priority !== undefined)
192
+ fields.set("priority", task.priority);
193
+ if (task.requirements !== undefined)
194
+ fields.set("requires", task.requirements);
195
+ if (task.owner !== undefined)
196
+ fields.set("owner", task.owner);
197
+ if (task.tags !== undefined)
198
+ fields.set("tags", task.tags);
199
+ if (task.blockedReason !== undefined)
200
+ fields.set("blocked_reason", task.blockedReason);
201
+ if (task.source !== undefined)
202
+ fields.set("source", task.source);
203
+ return fields;
204
+ }
205
+ function serializeTask(mutation, lineEnding) {
206
+ const fields = taskFields(mutation.task);
207
+ return [
208
+ `task ${mutation.id} ${mutation.from} -> ${mutation.to}:`,
209
+ ...fieldOrder.flatMap((name) => {
210
+ if (name === "duration" && fields.has("estimate"))
211
+ return [];
212
+ if (name === "estimate" && fields.has("duration"))
213
+ return [];
214
+ return fields.has(name) ? [serializeField(name, fields.get(name), lineEnding)] : [];
215
+ }),
216
+ ].join(lineEnding);
217
+ }
218
+ function addTaskPlan(text, mutation) {
219
+ const task = mutation.task;
220
+ const taskError = taskDefinitionError(task);
221
+ if (typeof mutation.id !== "string" ||
222
+ typeof mutation.from !== "string" ||
223
+ typeof mutation.to !== "string" ||
224
+ taskError !== undefined) {
225
+ return {
226
+ edits: [],
227
+ diagnostic: mutationDiagnostic("PTMUT-301", `task.add requestが不正です${taskError === undefined ? "" : `: ${taskError}`}`),
228
+ };
229
+ }
230
+ const lineEnding = majorLineEnding(text);
231
+ return {
232
+ edits: [appendDeclarationEdit(text, serializeTask(mutation, lineEnding), lineEnding)],
233
+ };
234
+ }
235
+ function setRequestError(mutation) {
236
+ const rawSet = mutation.set;
237
+ const rawClear = mutation.clear;
238
+ const rawAddTags = mutation.addTags;
239
+ const rawRemoveTags = mutation.removeTags;
240
+ const rawUpsertRequirements = mutation.upsertRequirements;
241
+ const rawRemoveRequirements = mutation.removeRequirements;
242
+ if (rawSet !== undefined &&
243
+ (rawSet === null || typeof rawSet !== "object" || Array.isArray(rawSet))) {
244
+ return "setがobjectではありません";
245
+ }
246
+ for (const [name, value] of [
247
+ ["clear", rawClear],
248
+ ["addTags", rawAddTags],
249
+ ["removeTags", rawRemoveTags],
250
+ ["upsertRequirements", rawUpsertRequirements],
251
+ ["removeRequirements", rawRemoveRequirements],
252
+ ]) {
253
+ if (value !== undefined && !Array.isArray(value))
254
+ return `${name}がarrayではありません`;
255
+ }
256
+ const set = (rawSet ?? {});
257
+ const setEntries = Object.entries(set).filter(([, value]) => value !== undefined);
258
+ const clear = (rawClear ?? []);
259
+ const addTags = (rawAddTags ?? []);
260
+ const removeTags = (rawRemoveTags ?? []);
261
+ const upsertRequirements = (rawUpsertRequirements ?? []);
262
+ const removeRequirements = (rawRemoveRequirements ?? []);
263
+ if (mutation.from === undefined &&
264
+ mutation.to === undefined &&
265
+ setEntries.length === 0 &&
266
+ clear.length === 0 &&
267
+ addTags.length === 0 &&
268
+ removeTags.length === 0 &&
269
+ upsertRequirements.length === 0 &&
270
+ removeRequirements.length === 0) {
271
+ return "task.setは少なくとも1つの変更指定を必要とします";
272
+ }
273
+ if (set.duration !== undefined && set.estimate !== undefined) {
274
+ return "durationとestimateは同時にsetできません";
275
+ }
276
+ if (mutation.from !== undefined && typeof mutation.from !== "string") {
277
+ return "fromがstringではありません";
278
+ }
279
+ if (mutation.to !== undefined && typeof mutation.to !== "string") {
280
+ return "toがstringではありません";
281
+ }
282
+ const knownSetFields = new Set([
283
+ "title",
284
+ "description",
285
+ "duration",
286
+ "estimate",
287
+ "status",
288
+ "priority",
289
+ "owner",
290
+ "blockedReason",
291
+ "source",
292
+ ]);
293
+ if (Object.keys(set).some((name) => !knownSetFields.has(name))) {
294
+ return "setに未対応fieldが含まれています";
295
+ }
296
+ for (const name of ["title", "description", "duration", "owner", "blockedReason", "source"]) {
297
+ if (set[name] !== undefined && typeof set[name] !== "string") {
298
+ return `${name}がstringではありません`;
299
+ }
300
+ }
301
+ if (set.estimate !== undefined && !validEstimate(set.estimate)) {
302
+ return "estimateはoptimistic、mostLikely、pessimisticのstringを必要とします";
303
+ }
304
+ if (set.status !== undefined && !taskStatuses.has(set.status)) {
305
+ return "statusがtask statusではありません";
306
+ }
307
+ if (set.priority !== undefined && !Number.isSafeInteger(set.priority)) {
308
+ return "priorityがsafe integerではありません";
309
+ }
310
+ if (!Array.isArray(clear) || clear.some((name) => !clearableFields.has(name))) {
311
+ return "clearに未対応fieldが含まれています";
312
+ }
313
+ if (!validStringArray(addTags) || !validStringArray(removeTags)) {
314
+ return "addTagsとremoveTagsはstring arrayである必要があります";
315
+ }
316
+ if (!Array.isArray(upsertRequirements) ||
317
+ !upsertRequirements.every(validRequirement) ||
318
+ !validStringArray(removeRequirements)) {
319
+ return "requirement変更指定が不正です";
320
+ }
321
+ if (new Set(clear).size !== clear.length)
322
+ return "clear fieldが重複しています";
323
+ const clearNames = new Set(clear);
324
+ const setToClear = {
325
+ title: "title",
326
+ description: "description",
327
+ duration: "duration",
328
+ estimate: "estimate",
329
+ status: "status",
330
+ priority: "priority",
331
+ owner: "owner",
332
+ blockedReason: "blocked_reason",
333
+ source: "source",
334
+ };
335
+ for (const [name] of setEntries) {
336
+ if (clearNames.has(setToClear[name])) {
337
+ return `${setToClear[name]}をsetとclearへ同時指定できません`;
338
+ }
339
+ }
340
+ if (clearNames.has("tags") && (addTags.length > 0 || removeTags.length > 0)) {
341
+ return "clear tagsとadd/remove tagは併用できません";
342
+ }
343
+ if (clearNames.has("requires") &&
344
+ (upsertRequirements.length > 0 || removeRequirements.length > 0)) {
345
+ return "clear requiresとupsert/remove requirementは併用できません";
346
+ }
347
+ const removedTags = new Set(removeTags);
348
+ if (addTags.some((tag) => removedTags.has(tag))) {
349
+ return "同じtagをaddとremoveへ同時指定できません";
350
+ }
351
+ const removedResources = new Set(removeRequirements);
352
+ if (upsertRequirements.some(({ resourceId }) => removedResources.has(resourceId))) {
353
+ return "同じresourceをupsertとremoveへ同時指定できません";
354
+ }
355
+ const upsertIds = upsertRequirements.map(({ resourceId }) => resourceId);
356
+ if (new Set(upsertIds).size !== upsertIds.length) {
357
+ return "upsert requirementのresourceが重複しています";
358
+ }
359
+ return undefined;
360
+ }
361
+ function currentTags(task) {
362
+ const value = fieldNamed(task, "tags")?.value;
363
+ return Array.isArray(value)
364
+ ? value.filter((tag) => typeof tag === "string")
365
+ : [];
366
+ }
367
+ function currentRequirements(task) {
368
+ const value = fieldNamed(task, "requires")?.value;
369
+ return Array.isArray(value)
370
+ ? value.map(({ resourceId, units }) => ({ resourceId, units }))
371
+ : [];
372
+ }
373
+ function planSetTask(text, task, mutation) {
374
+ const error = setRequestError(mutation);
375
+ if (error !== undefined) {
376
+ return { edits: [], diagnostic: mutationDiagnostic("PTMUT-301", error, task) };
377
+ }
378
+ const lines = splitPhysicalLines(text);
379
+ const lineEnding = majorLineEnding(text);
380
+ const edits = [];
381
+ const deleted = new Set(mutation.clear ?? []);
382
+ const additions = new Map();
383
+ const queueAddition = (name, value) => {
384
+ const offset = fieldInsertionOffset(task, name, deleted, lines, fieldRank);
385
+ const entries = additions.get(offset) ?? [];
386
+ entries.push({ name, serialized: serializeField(name, value, lineEnding) });
387
+ additions.set(offset, entries);
388
+ };
389
+ const setScalar = (name, value, valueText) => {
390
+ const field = fieldNamed(task, name);
391
+ if (field === undefined)
392
+ queueAddition(name, value);
393
+ else
394
+ edits.push({
395
+ startOffset: field.valueSpan.start.offset,
396
+ endOffset: field.valueSpan.end.offset,
397
+ replacement: valueText,
398
+ });
399
+ };
400
+ const setText = (name, value) => {
401
+ const field = fieldNamed(task, name);
402
+ if (field === undefined) {
403
+ queueAddition(name, value);
404
+ }
405
+ else if (field.contentSpan === undefined && !value.includes("\n")) {
406
+ edits.push({
407
+ startOffset: field.valueSpan.start.offset,
408
+ endOffset: field.valueSpan.end.offset,
409
+ replacement: JSON.stringify(value),
410
+ });
411
+ }
412
+ else {
413
+ edits.push({
414
+ startOffset: field.span.start.offset,
415
+ endOffset: contentTextEndOffset(field, lines),
416
+ replacement: serializeTextField(name, value, lineEnding),
417
+ });
418
+ }
419
+ };
420
+ if (mutation.from !== undefined) {
421
+ edits.push({
422
+ startOffset: task.fromSpan.start.offset,
423
+ endOffset: task.fromSpan.end.offset,
424
+ replacement: mutation.from,
425
+ });
426
+ }
427
+ if (mutation.to !== undefined) {
428
+ edits.push({
429
+ startOffset: task.toSpan.start.offset,
430
+ endOffset: task.toSpan.end.offset,
431
+ replacement: mutation.to,
432
+ });
433
+ }
434
+ const set = mutation.set ?? {};
435
+ if (set.title !== undefined)
436
+ setScalar("title", set.title, JSON.stringify(set.title));
437
+ if (set.description !== undefined)
438
+ setText("description", set.description);
439
+ if (set.duration !== undefined) {
440
+ const duration = fieldNamed(task, "duration");
441
+ const estimate = fieldNamed(task, "estimate");
442
+ if (duration !== undefined) {
443
+ setScalar("duration", set.duration, canonicalDuration(set.duration));
444
+ }
445
+ else if (estimate !== undefined) {
446
+ edits.push({
447
+ startOffset: estimate.span.start.offset,
448
+ endOffset: contentTextEndOffset(estimate, lines),
449
+ replacement: serializeField("duration", set.duration, lineEnding),
450
+ });
451
+ }
452
+ }
453
+ if (set.estimate !== undefined) {
454
+ const estimate = fieldNamed(task, "estimate");
455
+ const duration = fieldNamed(task, "duration");
456
+ if (estimate !== undefined) {
457
+ for (const [name, value] of [
458
+ ["optimistic", set.estimate.optimistic],
459
+ ["most_likely", set.estimate.mostLikely],
460
+ ["pessimistic", set.estimate.pessimistic],
461
+ ]) {
462
+ const child = estimate.children.find((candidate) => candidate.name === name);
463
+ edits.push({
464
+ startOffset: child.valueSpan.start.offset,
465
+ endOffset: child.valueSpan.end.offset,
466
+ replacement: canonicalDuration(value),
467
+ });
468
+ }
469
+ }
470
+ else if (duration !== undefined) {
471
+ edits.push({
472
+ startOffset: duration.span.start.offset,
473
+ endOffset: contentTextEndOffset(duration, lines),
474
+ replacement: serializeEstimate(set.estimate, lineEnding),
475
+ });
476
+ }
477
+ }
478
+ if (set.status !== undefined)
479
+ setScalar("status", set.status, set.status);
480
+ if (set.priority !== undefined)
481
+ setScalar("priority", set.priority, String(set.priority));
482
+ if (set.owner !== undefined)
483
+ setScalar("owner", set.owner, JSON.stringify(set.owner));
484
+ if (set.blockedReason !== undefined)
485
+ setText("blocked_reason", set.blockedReason);
486
+ if (set.source !== undefined)
487
+ setScalar("source", set.source, JSON.stringify(set.source));
488
+ if ((mutation.clear ?? []).includes("tags")) {
489
+ deleted.add("tags");
490
+ }
491
+ else if ((mutation.addTags?.length ?? 0) > 0 || (mutation.removeTags?.length ?? 0) > 0) {
492
+ const removed = new Set(mutation.removeTags ?? []);
493
+ const final = currentTags(task).filter((tag) => !removed.has(tag));
494
+ for (const tag of mutation.addTags ?? []) {
495
+ if (!final.includes(tag))
496
+ final.push(tag);
497
+ }
498
+ const tags = fieldNamed(task, "tags");
499
+ if (final.length === 0) {
500
+ if (tags !== undefined)
501
+ deleted.add("tags");
502
+ }
503
+ else if (tags === undefined) {
504
+ queueAddition("tags", final);
505
+ }
506
+ else {
507
+ edits.push({
508
+ startOffset: tags.valueSpan.start.offset,
509
+ endOffset: tags.valueSpan.end.offset,
510
+ replacement: serializeTags(final),
511
+ });
512
+ }
513
+ }
514
+ if ((mutation.clear ?? []).includes("requires")) {
515
+ deleted.add("requires");
516
+ }
517
+ else if ((mutation.upsertRequirements?.length ?? 0) > 0 ||
518
+ (mutation.removeRequirements?.length ?? 0) > 0) {
519
+ const removed = new Set(mutation.removeRequirements ?? []);
520
+ const current = currentRequirements(task);
521
+ const upserts = new Map((mutation.upsertRequirements ?? []).map((requirement) => [
522
+ requirement.resourceId,
523
+ requirement,
524
+ ]));
525
+ const final = current
526
+ .filter(({ resourceId }) => !removed.has(resourceId))
527
+ .map((requirement) => upserts.get(requirement.resourceId) ?? requirement);
528
+ for (const requirement of mutation.upsertRequirements ?? []) {
529
+ if (!current.some(({ resourceId }) => resourceId === requirement.resourceId)) {
530
+ final.push(requirement);
531
+ }
532
+ }
533
+ const requires = fieldNamed(task, "requires");
534
+ if (final.length === 0) {
535
+ if (requires !== undefined)
536
+ deleted.add("requires");
537
+ }
538
+ else if (requires === undefined) {
539
+ queueAddition("requires", final);
540
+ }
541
+ else {
542
+ const original = requires.value;
543
+ const surviving = original.filter(({ resourceId }) => !removed.has(resourceId));
544
+ const appended = final.filter(({ resourceId }) => !original.some((candidate) => candidate.resourceId === resourceId));
545
+ if (surviving.length === 0) {
546
+ edits.push({
547
+ startOffset: requires.span.start.offset,
548
+ endOffset: contentTextEndOffset(requires, lines),
549
+ replacement: serializeRequirements(final, lineEnding),
550
+ });
551
+ }
552
+ else {
553
+ for (const requirement of original) {
554
+ if (removed.has(requirement.resourceId)) {
555
+ edits.push({
556
+ startOffset: leadingCommentStart(lines, requirement.span.start.offset, 4),
557
+ endOffset: lines[lineIndexAt(lines, requirement.span.end.offset)].endWithEnding,
558
+ replacement: "",
559
+ });
560
+ continue;
561
+ }
562
+ const updated = upserts.get(requirement.resourceId);
563
+ if (updated !== undefined) {
564
+ edits.push({
565
+ startOffset: requirement.unitsSpan.start.offset,
566
+ endOffset: requirement.unitsSpan.end.offset,
567
+ replacement: String(updated.units),
568
+ });
569
+ }
570
+ }
571
+ if (appended.length > 0) {
572
+ const last = surviving[surviving.length - 1];
573
+ const offset = lines[lineIndexAt(lines, last.span.end.offset)].endWithEnding;
574
+ edits.push({
575
+ startOffset: offset,
576
+ endOffset: offset,
577
+ replacement: insertionText(text, offset, appended.map(({ resourceId, units }) => ` ${resourceId} ${units}`), lineEnding),
578
+ });
579
+ }
580
+ }
581
+ }
582
+ }
583
+ for (const name of deleted) {
584
+ const field = fieldNamed(task, name);
585
+ if (field !== undefined)
586
+ edits.push(deleteFieldEdit(field, lines));
587
+ }
588
+ for (const [offset, entries] of additions) {
589
+ entries.sort((left, right) => (fieldRank.get(left.name) ?? 99) - (fieldRank.get(right.name) ?? 99));
590
+ edits.push({
591
+ startOffset: offset,
592
+ endOffset: offset,
593
+ replacement: insertionText(text, offset, entries.map(({ serialized }) => serialized), lineEnding),
594
+ });
595
+ }
596
+ return { edits };
597
+ }
598
+ export function planTaskMutationEdits(text, document, mutation) {
599
+ const requestError = taskMutationRequestError(mutation);
600
+ if (requestError !== undefined) {
601
+ return {
602
+ edits: [],
603
+ diagnostic: mutationDiagnostic("PTMUT-301", requestError),
604
+ };
605
+ }
606
+ const entity = document.declarations.find(({ id }) => id === mutation.id);
607
+ if (mutation.kind === "task.add") {
608
+ if (entity !== undefined) {
609
+ return {
610
+ edits: [],
611
+ diagnostic: mutationDiagnostic("PTMUT-304", `entity ID ${mutation.id}はすでに使用されています`, entity),
612
+ };
613
+ }
614
+ return addTaskPlan(text, mutation);
615
+ }
616
+ if (entity === undefined) {
617
+ return {
618
+ edits: [],
619
+ diagnostic: mutationDiagnostic("PTMUT-302", `task ${mutation.id}が存在しません`),
620
+ };
621
+ }
622
+ if (entity.kind !== "task") {
623
+ return {
624
+ edits: [],
625
+ diagnostic: mutationDiagnostic("PTMUT-303", `entity ${mutation.id}はtaskではありません`, entity),
626
+ };
627
+ }
628
+ const lines = splitPhysicalLines(text);
629
+ if (mutation.kind === "task.remove") {
630
+ return { edits: [deleteDeclarationEdit(entity, lines)] };
631
+ }
632
+ if (mutation.kind === "task.finish") {
633
+ return planSetTask(text, entity, {
634
+ kind: "task.set",
635
+ id: mutation.id,
636
+ set: { status: "done" },
637
+ ...(fieldNamed(entity, "blocked_reason") === undefined
638
+ ? {}
639
+ : { clear: ["blocked_reason"] }),
640
+ });
641
+ }
642
+ return planSetTask(text, entity, mutation);
643
+ }
644
+ //# sourceMappingURL=task.js.map