@sshkeda/task 0.0.0-stage → 1.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 +131 -2
- package/package.json +41 -4
- package/src/index.ts +2 -0
- package/src/lib/errors/index.ts +15 -0
- package/src/lib/errors/internal/errors.ts +147 -0
- package/src/lib/kill-leftover-processes/index.ts +17 -0
- package/src/lib/kill-leftover-processes/internal/command-words.ts +16 -0
- package/src/lib/kill-leftover-processes/internal/find-process-ids.ts +36 -0
- package/src/lib/kill-leftover-processes/internal/kill-process-groups.ts +270 -0
- package/src/lib/kill-leftover-processes/internal/list-linux-processes-from-proc.ts +191 -0
- package/src/lib/kill-leftover-processes/internal/list-macos-processes-with-sysctl.ts +335 -0
- package/src/lib/kill-leftover-processes/internal/list-processes.ts +58 -0
- package/src/lib/kill-leftover-processes/internal/mark-run-processes.ts +367 -0
- package/src/lib/kill-leftover-processes/internal/process-cleanup-errors.ts +17 -0
- package/src/lib/kill-leftover-processes/internal/send-signal.ts +32 -0
- package/src/lib/kill-leftover-processes/internal/signal-run-processes.ts +48 -0
- package/src/lib/kill-leftover-processes/internal/started-process-groups.ts +107 -0
- package/src/lib/main-thread/index.ts +3 -0
- package/src/lib/main-thread/internal/listen-for-stop-requests.ts +102 -0
- package/src/lib/main-thread/internal/run-main-thread.ts +81 -0
- package/src/lib/main-thread/internal/run-root-task-fiber.ts +191 -0
- package/src/lib/main-thread/internal/stops-at-first-failure.ts +20 -0
- package/src/lib/main-thread/internal/trace-the-run.ts +107 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/index.ts +7 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/follow-imports-from-entry-files.ts +306 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/load-all-reporting-first-failure.ts +11 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/make-module-graph.ts +48 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/read-configured-loaders.ts +64 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/resolve-module-specifiers.ts +60 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/rewrite-worker-imports.ts +118 -0
- package/src/lib/main-thread/lib/find-code-each-task-depends-on/internal/worker-import-errors.ts +19 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/index.ts +7 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/internal/handle-worker-events.ts +348 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/internal/make-task-runs.ts +207 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/index.ts +1 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/await-within-counted-limit.ts +29 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/execute-task.ts +272 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/make-task-setup-clock.ts +129 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/lib/run-requested-task-once/internal/run-requested-tasks.ts +135 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/define-task-run-state.ts +288 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/make-stopwatch.ts +39 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/share-standard-input-with-root-task.ts +36 -0
- package/src/lib/main-thread/lib/run-tasks-on-workers/shared/stop-task.ts +166 -0
- package/src/lib/main-thread/lib/scan-module-source/index.ts +1 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/find-lexical-bindings.ts +154 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/find-task-declarations.ts +205 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/parse-module-text.ts +34 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/read-literal-string.ts +15 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/read-module-loads.ts +316 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/scan-module-text.ts +70 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/unwrap-type-assertions.ts +10 -0
- package/src/lib/main-thread/lib/scan-module-source/internal/visit-non-reference-identifiers.ts +21 -0
- package/src/lib/main-thread/shared/fail-run.ts +36 -0
- package/src/lib/main-thread/shared/find-failure-reason.ts +43 -0
- package/src/lib/main-thread/shared/find-package-directories.ts +34 -0
- package/src/lib/main-thread/shared/find-tasks-reachable-from-root-task-file.ts +246 -0
- package/src/lib/main-thread/shared/list-directory-paths-up-to-root.ts +13 -0
- package/src/lib/main-thread/shared/main-thread-errors.ts +373 -0
- package/src/lib/main-thread/shared/package-json-errors.ts +93 -0
- package/src/lib/main-thread/shared/provide-discovered-tasks.ts +26 -0
- package/src/lib/main-thread/shared/provide-process-groups.ts +63 -0
- package/src/lib/main-thread/shared/provide-run-root-task-config.ts +66 -0
- package/src/lib/main-thread/shared/provide-stop-state.ts +150 -0
- package/src/lib/main-thread/shared/provide-worker-pool.ts +607 -0
- package/src/lib/main-thread/shared/read-bare-package-name.ts +3 -0
- package/src/lib/main-thread/shared/read-package-json.ts +149 -0
- package/src/lib/main-thread/shared/report-task-failure.ts +31 -0
- package/src/lib/preload-rewritten-imports-into-each-worker/index.ts +5 -0
- package/src/lib/preload-rewritten-imports-into-each-worker/internal/load-rewritten-imports.ts +34 -0
- package/src/lib/supervise-the-run/index.ts +15 -0
- package/src/lib/supervise-the-run/internal/adopt-orphans-on-linux.ts +73 -0
- package/src/lib/supervise-the-run/internal/check-root-task-file.ts +8 -0
- package/src/lib/supervise-the-run/internal/report-ready.ts +12 -0
- package/src/lib/supervise-the-run/internal/share-process-groups.ts +25 -0
- package/src/lib/supervise-the-run/internal/start-watching-the-supervisor.ts +6 -0
- package/src/lib/supervise-the-run/internal/supervise-run.ts +155 -0
- package/src/lib/supervise-the-run/internal/talk-to-the-supervisor.ts +53 -0
- package/src/lib/supervise-the-run/internal/watch-the-supervisor.ts +61 -0
- package/src/lib/task/index.ts +5 -0
- package/src/lib/task/internal/make-tasks.ts +82 -0
- package/src/lib/task/internal/run-command.ts +194 -0
- package/src/lib/task/internal/run-root-task.ts +263 -0
- package/src/lib/task/internal/run-task-lists.ts +51 -0
- package/src/lib/task/internal/trust-output-mode-names.ts +18 -0
- package/src/lib/task/internal/trust-task-results.ts +35 -0
- package/src/lib/task/internal/use-task-services.ts +4 -0
- package/src/lib/worker-thread/index.ts +3 -0
- package/src/lib/worker-thread/internal/answer-main-thread-messages.ts +141 -0
- package/src/lib/worker-thread/internal/describe-task-body-result.ts +91 -0
- package/src/lib/worker-thread/internal/name-config-key-not-set.ts +57 -0
- package/src/lib/worker-thread/internal/run-task-body.ts +117 -0
- package/src/lib/worker-thread/internal/serve-on-the-parent-port.ts +63 -0
- package/src/lib/worker-thread/internal/start-worker-thread.ts +41 -0
- package/src/lib/worker-thread/lib/provide-task-services/index.ts +1 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/make-task-logger.ts +34 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/provide-task-services.ts +86 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/read-standard-input-from-main-thread.ts +58 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/resolve-paths-from-package-directory.ts +61 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/run-commands-in-package-directory.ts +24 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/spawn-commands-with-bun.ts +545 -0
- package/src/lib/worker-thread/lib/provide-task-services/internal/spawn-reported-process.ts +151 -0
- package/src/lib/worker-thread/shared/await-main-thread-answers.ts +76 -0
- package/src/lib/worker-thread/shared/decode-task-result-json.ts +114 -0
- package/src/lib/worker-thread/shared/define-task-body-record.ts +11 -0
- package/src/lib/worker-thread/shared/describe-issue-input.ts +52 -0
- package/src/lib/worker-thread/shared/plain-object.ts +8 -0
- package/src/lib/worker-thread/shared/provide-worker-state.ts +160 -0
- package/src/lib/worker-thread/shared/worker-thread-errors.ts +41 -0
- package/src/shared/attribute-reported-failures.ts +25 -0
- package/src/shared/claim-processes-to-report.ts +22 -0
- package/src/shared/command-errors.ts +44 -0
- package/src/shared/complete-discarded-calls-once-sent.ts +12 -0
- package/src/shared/cut-to-graphemes.ts +12 -0
- package/src/shared/environment-config-errors.ts +74 -0
- package/src/shared/error-fields.ts +13 -0
- package/src/shared/find-bun-loader-for-script.ts +32 -0
- package/src/shared/find-node-modules-package-directory.ts +35 -0
- package/src/shared/find-user-locations-in-stack-traces.ts +64 -0
- package/src/shared/format-error-lines.ts +53 -0
- package/src/shared/format-task-lines.ts +21 -0
- package/src/shared/hold-task-logs.ts +24 -0
- package/src/shared/invariant-errors.ts +28 -0
- package/src/shared/mark-task-worker-thread-contexts.ts +29 -0
- package/src/shared/module-graph-errors.ts +49 -0
- package/src/shared/name-config-key.ts +42 -0
- package/src/shared/names-from-package-json.ts +7 -0
- package/src/shared/print-to-standard-error.ts +48 -0
- package/src/shared/process-errors.ts +191 -0
- package/src/shared/read-environment-config.ts +239 -0
- package/src/shared/record-tasks-made.ts +10 -0
- package/src/shared/report-cause.ts +140 -0
- package/src/shared/report-errors.ts +121 -0
- package/src/shared/resolve-task-options.ts +20 -0
- package/src/shared/rpc.ts +161 -0
- package/src/shared/run-in-child-fiber-and-join.ts +16 -0
- package/src/shared/run-marker.ts +7 -0
- package/src/shared/save-standard-stream-flags.ts +57 -0
- package/src/shared/select-cause-reasons.ts +14 -0
- package/src/shared/signal-run-called-twice.ts +11 -0
- package/src/shared/standard-input-streams.ts +41 -0
- package/src/shared/stop-signals.ts +41 -0
- package/src/shared/task-errors.ts +224 -0
- package/src/shared/task-package.ts +5 -0
- package/src/shared/task-results.ts +55 -0
- package/src/shared/task-services.ts +30 -0
- package/src/shared/write-trace-file.ts +112 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { Cause, Deferred, Effect, Exit, FiberSet, Option, Schema } from "effect";
|
|
2
|
+
import type { Fiber } from "effect";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
dieOnInvariantFailure,
|
|
6
|
+
InternalInvariantBroken,
|
|
7
|
+
} from "../../../../../shared/invariant-errors.ts";
|
|
8
|
+
import type { WorkerExited } from "../../../../../shared/process-errors.ts";
|
|
9
|
+
import type { TaskEvent, TaskFiberResult } from "../../../../../shared/rpc.ts";
|
|
10
|
+
import { isAwaitedTasksFailure } from "../../../../../shared/task-errors.ts";
|
|
11
|
+
import type {
|
|
12
|
+
AwaitedTasksFailure,
|
|
13
|
+
TaskBodyFailed,
|
|
14
|
+
TaskCleanupFailed,
|
|
15
|
+
} from "../../../../../shared/task-errors.ts";
|
|
16
|
+
import type { findTasksReachableFromRootTaskFile } from "../../../shared/find-tasks-reachable-from-root-task-file.ts";
|
|
17
|
+
import { makeStopwatch } from "./make-stopwatch.ts";
|
|
18
|
+
import { makeRootTaskStandardInput } from "./share-standard-input-with-root-task.ts";
|
|
19
|
+
|
|
20
|
+
export const TaskRunStatus = Schema.TaggedUnion({
|
|
21
|
+
Preparing: {},
|
|
22
|
+
Running: { workerId: Schema.Int },
|
|
23
|
+
Serving: { workerId: Schema.Int },
|
|
24
|
+
Closed: { workerId: Schema.Int },
|
|
25
|
+
Passed: { workerId: Schema.Int },
|
|
26
|
+
Failed: {
|
|
27
|
+
workerId: Schema.NullOr(Schema.Int),
|
|
28
|
+
causedByAwaitedTasks: Schema.Boolean,
|
|
29
|
+
},
|
|
30
|
+
Cancelled: { workerId: Schema.NullOr(Schema.Int) },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const makeTaskRun = (initial: {
|
|
34
|
+
readonly status: typeof TaskRunStatus.Type;
|
|
35
|
+
readonly stepBeforeBody: string | null;
|
|
36
|
+
}) => ({
|
|
37
|
+
...initial,
|
|
38
|
+
logMessages: new Array<string>(),
|
|
39
|
+
awaitedTaskNames: new Array<string>(),
|
|
40
|
+
requestFibersByDependencyRequestId: new Map<number, Fiber.Fiber<unknown, unknown>>(),
|
|
41
|
+
bodyRequestedTaskNames: new Set<string>(),
|
|
42
|
+
bodyWaitingOnRequestedTasks: makeStopwatch(),
|
|
43
|
+
bodyOutcomeDeferred: Deferred.makeUnsafe<
|
|
44
|
+
typeof TaskEvent.cases.TaskFiberServed.Type | typeof TaskFiberResult.cases.Returned.Type,
|
|
45
|
+
TaskBodyFailed | TaskCleanupFailed | WorkerExited | typeof AwaitedTasksFailure.Type
|
|
46
|
+
>(),
|
|
47
|
+
bodyLoadingDeferred: Deferred.makeUnsafe<void>(),
|
|
48
|
+
bodyStartedAtMillisecondsDeferred: Deferred.makeUnsafe<number>(),
|
|
49
|
+
bodyCleanupStartedDeferred: Deferred.makeUnsafe<void>(),
|
|
50
|
+
bodyFailedBeforeCleanupDeferred: Deferred.makeUnsafe<void>(),
|
|
51
|
+
bodyFinishedDeferred: Deferred.makeUnsafe<void>(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export const findAssignedWorkerId = (status: typeof TaskRunStatus.Type) =>
|
|
55
|
+
"workerId" in status ? status.workerId : null;
|
|
56
|
+
|
|
57
|
+
export const isUnfinishedOnWorker = (taskRun: ReturnType<typeof makeTaskRun>, workerId: number) =>
|
|
58
|
+
findAssignedWorkerId(taskRun.status) === workerId &&
|
|
59
|
+
!Deferred.isDoneUnsafe(taskRun.bodyFinishedDeferred);
|
|
60
|
+
|
|
61
|
+
const isServingStatus = TaskRunStatus.isAnyOf(["Serving"]);
|
|
62
|
+
|
|
63
|
+
const isServing = (taskRun: ReturnType<typeof makeTaskRun>) => isServingStatus(taskRun.status);
|
|
64
|
+
|
|
65
|
+
const isOccupyingWorker = (taskRun: ReturnType<typeof makeTaskRun>) =>
|
|
66
|
+
!isServing(taskRun) && taskRun.requestFibersByDependencyRequestId.size === 0;
|
|
67
|
+
|
|
68
|
+
export const findTaskNamesOccupyingWorker = (
|
|
69
|
+
runsByTaskName: ReadonlyMap<string, ReturnType<typeof makeTaskRun>>,
|
|
70
|
+
workerId: number,
|
|
71
|
+
) =>
|
|
72
|
+
[...runsByTaskName]
|
|
73
|
+
.filter(([, taskRun]) => isUnfinishedOnWorker(taskRun, workerId) && isOccupyingWorker(taskRun))
|
|
74
|
+
.map(([taskName]) => taskName);
|
|
75
|
+
|
|
76
|
+
export const formatWaitingForWorker = (
|
|
77
|
+
workerId: number,
|
|
78
|
+
longestOccupyingTaskName: string,
|
|
79
|
+
otherTaskNames: readonly string[],
|
|
80
|
+
) => {
|
|
81
|
+
const others =
|
|
82
|
+
otherTaskNames.length === 0
|
|
83
|
+
? ""
|
|
84
|
+
: ` and ${otherTaskNames.length} other task${otherTaskNames.length === 1 ? "" : "s"}`;
|
|
85
|
+
return `waiting for worker ${workerId}, which is running "${longestOccupyingTaskName}"${others}`;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const countWorkerLoad = (
|
|
89
|
+
unfinishedTaskRunsOnWorker: readonly ReturnType<typeof makeTaskRun>[],
|
|
90
|
+
) => ({
|
|
91
|
+
occupyingTaskCount: unfinishedTaskRunsOnWorker.filter((taskRun) => isOccupyingWorker(taskRun))
|
|
92
|
+
.length,
|
|
93
|
+
servingTaskCount: unfinishedTaskRunsOnWorker.filter((taskRun) => isServing(taskRun)).length,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export const formatTaskRunStatusForTrace = (status: typeof TaskRunStatus.Type) =>
|
|
97
|
+
TaskRunStatus.guards.Preparing(status) ? "running" : status._tag.toLowerCase();
|
|
98
|
+
|
|
99
|
+
const withReadOnlyStatus = (
|
|
100
|
+
taskRun: ReturnType<typeof makeTaskRun>,
|
|
101
|
+
): Omit<ReturnType<typeof makeTaskRun>, "status"> & {
|
|
102
|
+
readonly status: typeof TaskRunStatus.Type;
|
|
103
|
+
} => taskRun;
|
|
104
|
+
|
|
105
|
+
const requireAssignedWorkerId = (taskName: string, status: typeof TaskRunStatus.Type) => {
|
|
106
|
+
const workerId = findAssignedWorkerId(status);
|
|
107
|
+
return workerId === null
|
|
108
|
+
? Effect.die(
|
|
109
|
+
new InternalInvariantBroken({
|
|
110
|
+
area: "task lifecycle",
|
|
111
|
+
invariant: `"${taskName}" has an assigned worker before its body completes`,
|
|
112
|
+
}),
|
|
113
|
+
)
|
|
114
|
+
: Effect.succeed(workerId);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const makeTaskRunsState = Effect.gen(function* () {
|
|
118
|
+
const discoveredTasksByName: Effect.Success<
|
|
119
|
+
ReturnType<typeof findTasksReachableFromRootTaskFile>
|
|
120
|
+
> = new Map();
|
|
121
|
+
const taskNamesWhoseFailureReachedAnAwaitingTask = new Set<string>();
|
|
122
|
+
const taskRunsByName = new Map<string, ReturnType<typeof makeTaskRun>>();
|
|
123
|
+
const taskRunsSeenByCallers: ReadonlyMap<
|
|
124
|
+
string,
|
|
125
|
+
ReturnType<typeof withReadOnlyStatus>
|
|
126
|
+
> = taskRunsByName;
|
|
127
|
+
const taskNamesAssignedToWorker = new Map<number, Set<string>>();
|
|
128
|
+
const bodyBusyClocksByWorkerId = new Map<
|
|
129
|
+
number,
|
|
130
|
+
{
|
|
131
|
+
readonly busy: ReturnType<typeof makeStopwatch>;
|
|
132
|
+
readonly taskNamesRunningOccupyingBodies: Set<string>;
|
|
133
|
+
}
|
|
134
|
+
>();
|
|
135
|
+
return {
|
|
136
|
+
discoveredTasksByName,
|
|
137
|
+
taskNamesWhoseFailureReachedAnAwaitingTask,
|
|
138
|
+
runsByTaskName: taskRunsSeenByCallers,
|
|
139
|
+
taskFibers: yield* FiberSet.make(),
|
|
140
|
+
rootTaskStandardInput: yield* makeRootTaskStandardInput,
|
|
141
|
+
runSpan: yield* Effect.currentSpan.pipe(
|
|
142
|
+
dieOnInvariantFailure("task runs", "task execution has a run span"),
|
|
143
|
+
),
|
|
144
|
+
createRecord: (taskName: string) => {
|
|
145
|
+
const taskRun = makeTaskRun({
|
|
146
|
+
status: TaskRunStatus.cases.Preparing.make({}),
|
|
147
|
+
stepBeforeBody: null,
|
|
148
|
+
});
|
|
149
|
+
taskRunsByName.set(taskName, taskRun);
|
|
150
|
+
return withReadOnlyStatus(taskRun);
|
|
151
|
+
},
|
|
152
|
+
recordWorkerAssignment: (taskName: string, workerId: number) =>
|
|
153
|
+
Effect.gen(function* () {
|
|
154
|
+
const taskRun = taskRunsByName.get(taskName);
|
|
155
|
+
const isNotAwaitingWorker =
|
|
156
|
+
taskRun === undefined || !TaskRunStatus.guards.Preparing(taskRun.status);
|
|
157
|
+
if (isNotAwaitingWorker) {
|
|
158
|
+
return yield* Effect.die(
|
|
159
|
+
new InternalInvariantBroken({
|
|
160
|
+
area: "task lifecycle",
|
|
161
|
+
invariant: `"${taskName}" is waiting before worker assignment`,
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
taskRun.status = TaskRunStatus.cases.Running.make({ workerId });
|
|
166
|
+
const assignedTaskNames = taskNamesAssignedToWorker.get(workerId) ?? new Set();
|
|
167
|
+
taskNamesAssignedToWorker.set(workerId, assignedTaskNames.add(taskName));
|
|
168
|
+
}),
|
|
169
|
+
readWorkerLoad: (workerId: number) => {
|
|
170
|
+
const assignedTaskNames = taskNamesAssignedToWorker.get(workerId) ?? new Set();
|
|
171
|
+
const unfinishedTaskRuns: ReturnType<typeof makeTaskRun>[] = [];
|
|
172
|
+
for (const taskName of assignedTaskNames) {
|
|
173
|
+
const taskRun = taskRunsByName.get(taskName);
|
|
174
|
+
const isNoLongerOnWorker =
|
|
175
|
+
taskRun === undefined || !isUnfinishedOnWorker(taskRun, workerId);
|
|
176
|
+
if (isNoLongerOnWorker) {
|
|
177
|
+
assignedTaskNames.delete(taskName);
|
|
178
|
+
} else {
|
|
179
|
+
unfinishedTaskRuns.push(taskRun);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return countWorkerLoad(unfinishedTaskRuns);
|
|
183
|
+
},
|
|
184
|
+
refreshBodyClocks: (taskName: string, nowMilliseconds: number) => {
|
|
185
|
+
const taskRun = taskRunsByName.get(taskName);
|
|
186
|
+
if (taskRun === undefined) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
taskRun.bodyWaitingOnRequestedTasks.update(
|
|
190
|
+
taskRun.requestFibersByDependencyRequestId.size > 0,
|
|
191
|
+
nowMilliseconds,
|
|
192
|
+
);
|
|
193
|
+
const workerId = findAssignedWorkerId(taskRun.status);
|
|
194
|
+
if (workerId === null) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const busyClock = bodyBusyClocksByWorkerId.get(workerId) ?? {
|
|
198
|
+
busy: makeStopwatch(),
|
|
199
|
+
taskNamesRunningOccupyingBodies: new Set<string>(),
|
|
200
|
+
};
|
|
201
|
+
bodyBusyClocksByWorkerId.set(workerId, busyClock);
|
|
202
|
+
const isRunningOccupyingBody =
|
|
203
|
+
Deferred.isDoneUnsafe(taskRun.bodyStartedAtMillisecondsDeferred) &&
|
|
204
|
+
isUnfinishedOnWorker(taskRun, workerId) &&
|
|
205
|
+
isOccupyingWorker(taskRun);
|
|
206
|
+
if (isRunningOccupyingBody) {
|
|
207
|
+
busyClock.taskNamesRunningOccupyingBodies.add(taskName);
|
|
208
|
+
} else {
|
|
209
|
+
busyClock.taskNamesRunningOccupyingBodies.delete(taskName);
|
|
210
|
+
}
|
|
211
|
+
busyClock.busy.update(busyClock.taskNamesRunningOccupyingBodies.size > 0, nowMilliseconds);
|
|
212
|
+
},
|
|
213
|
+
readWorkerBodyBusyMilliseconds: (workerId: number, nowMilliseconds: number) => {
|
|
214
|
+
const busyClock = bodyBusyClocksByWorkerId.get(workerId);
|
|
215
|
+
return busyClock === undefined ? 0 : busyClock.busy.readMilliseconds(nowMilliseconds);
|
|
216
|
+
},
|
|
217
|
+
markServing: (taskName: string) => {
|
|
218
|
+
const taskRun = taskRunsByName.get(taskName);
|
|
219
|
+
if (taskRun === undefined) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (TaskRunStatus.guards.Running(taskRun.status)) {
|
|
223
|
+
taskRun.status = TaskRunStatus.cases.Serving.make({
|
|
224
|
+
workerId: taskRun.status.workerId,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
markClosed: (taskName: string) => {
|
|
229
|
+
const taskRun = taskRunsByName.get(taskName);
|
|
230
|
+
if (taskRun === undefined) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
if (!TaskRunStatus.guards.Serving(taskRun.status)) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
taskRun.status = TaskRunStatus.cases.Closed.make({
|
|
237
|
+
workerId: taskRun.status.workerId,
|
|
238
|
+
});
|
|
239
|
+
return true;
|
|
240
|
+
},
|
|
241
|
+
markPassed: (taskName: string) =>
|
|
242
|
+
Effect.gen(function* () {
|
|
243
|
+
const taskRun = taskRunsByName.get(taskName);
|
|
244
|
+
if (taskRun !== undefined) {
|
|
245
|
+
taskRun.status = TaskRunStatus.cases.Passed.make({
|
|
246
|
+
workerId: yield* requireAssignedWorkerId(taskName, taskRun.status),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}),
|
|
250
|
+
markEndedFromExit: (
|
|
251
|
+
taskName: string,
|
|
252
|
+
taskExit: Exit.Exit<Schema.Json | undefined, unknown>,
|
|
253
|
+
) => {
|
|
254
|
+
const taskRun = taskRunsByName.get(taskName);
|
|
255
|
+
const isNotEndingFromFailure =
|
|
256
|
+
taskRun === undefined ||
|
|
257
|
+
(!TaskRunStatus.guards.Preparing(taskRun.status) &&
|
|
258
|
+
!TaskRunStatus.guards.Running(taskRun.status)) ||
|
|
259
|
+
!Exit.isFailure(taskExit);
|
|
260
|
+
if (isNotEndingFromFailure) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const workerId = findAssignedWorkerId(taskRun.status);
|
|
264
|
+
if (Cause.hasInterruptsOnly(taskExit.cause)) {
|
|
265
|
+
taskRun.status = TaskRunStatus.cases.Cancelled.make({ workerId });
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const failure = Cause.findErrorOption(taskExit.cause);
|
|
269
|
+
taskRun.status = TaskRunStatus.cases.Failed.make({
|
|
270
|
+
workerId,
|
|
271
|
+
causedByAwaitedTasks: Option.exists(failure, isAwaitedTasksFailure),
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
recordFailureReachedAwaitingTask: (taskName: string) => {
|
|
275
|
+
taskNamesWhoseFailureReachedAnAwaitingTask.add(taskName);
|
|
276
|
+
},
|
|
277
|
+
markFailed: (taskName: string) =>
|
|
278
|
+
Effect.sync(() => {
|
|
279
|
+
const taskRun = taskRunsByName.get(taskName);
|
|
280
|
+
if (taskRun !== undefined) {
|
|
281
|
+
taskRun.status = TaskRunStatus.cases.Failed.make({
|
|
282
|
+
workerId: findAssignedWorkerId(taskRun.status),
|
|
283
|
+
causedByAwaitedTasks: false,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}),
|
|
287
|
+
};
|
|
288
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Option } from "effect";
|
|
2
|
+
|
|
3
|
+
const STOPPED_STOPWATCH = {
|
|
4
|
+
millisecondsBeforeRunningSince: 0,
|
|
5
|
+
runningSince: Option.none<number>(),
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const readStopwatchMilliseconds = (stopwatch: typeof STOPPED_STOPWATCH, nowMilliseconds: number) =>
|
|
9
|
+
stopwatch.millisecondsBeforeRunningSince +
|
|
10
|
+
Option.match(stopwatch.runningSince, {
|
|
11
|
+
onNone: () => 0,
|
|
12
|
+
onSome: (runningSince) => nowMilliseconds - runningSince,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const switchStopwatch = (
|
|
16
|
+
stopwatch: typeof STOPPED_STOPWATCH,
|
|
17
|
+
isRunning: boolean,
|
|
18
|
+
nowMilliseconds: number,
|
|
19
|
+
) => {
|
|
20
|
+
const isAlreadyInState = isRunning === Option.isSome(stopwatch.runningSince);
|
|
21
|
+
if (isAlreadyInState) {
|
|
22
|
+
return stopwatch;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
millisecondsBeforeRunningSince: readStopwatchMilliseconds(stopwatch, nowMilliseconds),
|
|
26
|
+
runningSince: isRunning ? Option.some(nowMilliseconds) : Option.none(),
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const makeStopwatch = () => {
|
|
31
|
+
const stopwatch = { ...STOPPED_STOPWATCH };
|
|
32
|
+
return {
|
|
33
|
+
readMilliseconds: (nowMilliseconds: number) =>
|
|
34
|
+
readStopwatchMilliseconds(stopwatch, nowMilliseconds),
|
|
35
|
+
update: (isRunning: boolean, nowMilliseconds: number) => {
|
|
36
|
+
Object.assign(stopwatch, switchStopwatch(stopwatch, isRunning, nowMilliseconds));
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
};
|
package/src/lib/main-thread/lib/run-tasks-on-workers/shared/share-standard-input-with-root-task.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Cause, Effect, FiberSet, Pull, Scope, Stdio, Stream } from "effect";
|
|
2
|
+
|
|
3
|
+
import { describeCauseMessage } from "../../../../../shared/report-errors.ts";
|
|
4
|
+
import { StandardInputRead } from "../../../../../shared/rpc.ts";
|
|
5
|
+
import { RunRootTaskConfig } from "../../../shared/provide-run-root-task-config.ts";
|
|
6
|
+
|
|
7
|
+
export const makeRootTaskStandardInput = Effect.gen(function* () {
|
|
8
|
+
const stdio = yield* Stdio.Stdio;
|
|
9
|
+
const rootTaskName = (yield* RunRootTaskConfig).rootTask.name;
|
|
10
|
+
const isTerminal = yield* stdio.stdinIsTerminal;
|
|
11
|
+
const runScope = yield* Effect.scope;
|
|
12
|
+
const pullStandardInputChunks = yield* Effect.cached(
|
|
13
|
+
Stream.toPull(stdio.stdin).pipe(Scope.provide(runScope)),
|
|
14
|
+
);
|
|
15
|
+
const readFibers = yield* FiberSet.make();
|
|
16
|
+
const readNextChunks = pullStandardInputChunks.pipe(
|
|
17
|
+
Effect.flatMap((pull) =>
|
|
18
|
+
Pull.matchEffect(pull, {
|
|
19
|
+
onSuccess: (chunks) => Effect.succeed(StandardInputRead.cases.Chunks.make({ chunks })),
|
|
20
|
+
onDone: () => Effect.succeed(StandardInputRead.cases.Ended.make({})),
|
|
21
|
+
onFailure: (cause) =>
|
|
22
|
+
Effect.succeed(
|
|
23
|
+
StandardInputRead.cases.Failed.make({
|
|
24
|
+
reason: describeCauseMessage(Cause.squash(cause)),
|
|
25
|
+
}),
|
|
26
|
+
),
|
|
27
|
+
}),
|
|
28
|
+
),
|
|
29
|
+
);
|
|
30
|
+
return {
|
|
31
|
+
standardInput: { isTerminal, rootTaskName },
|
|
32
|
+
answerNextRead: <Failure, Services>(
|
|
33
|
+
answer: (read: typeof StandardInputRead.Type) => Effect.Effect<void, Failure, Services>,
|
|
34
|
+
) => FiberSet.run(readFibers, Effect.flatMap(readNextChunks, answer)),
|
|
35
|
+
};
|
|
36
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { Array as Arrays, Deferred, Effect, Option, Duration } from "effect";
|
|
2
|
+
|
|
3
|
+
import { dieOnInvariantFailure } from "../../../../../shared/invariant-errors.ts";
|
|
4
|
+
import { TASK_TRACE_LABEL_PREFIX } from "../../../../../shared/names-from-package-json.ts";
|
|
5
|
+
import { printTaskLines } from "../../../../../shared/print-to-standard-error.ts";
|
|
6
|
+
import { TASK_PACKAGE } from "../../../../../shared/task-package.ts";
|
|
7
|
+
import { failTask } from "../../../shared/fail-run.ts";
|
|
8
|
+
import {
|
|
9
|
+
TaskCleanupFrozeWorker,
|
|
10
|
+
TaskCleanupNotStarted,
|
|
11
|
+
TaskCleanupTimedOut,
|
|
12
|
+
} from "../../../shared/main-thread-errors.ts";
|
|
13
|
+
import { ProcessGroups } from "../../../shared/provide-process-groups.ts";
|
|
14
|
+
import { StopState } from "../../../shared/provide-stop-state.ts";
|
|
15
|
+
import { WorkerPool } from "../../../shared/provide-worker-pool.ts";
|
|
16
|
+
import { findAssignedWorkerId, isUnfinishedOnWorker } from "./define-task-run-state.ts";
|
|
17
|
+
import type { makeTaskRunsState } from "./define-task-run-state.ts";
|
|
18
|
+
|
|
19
|
+
const STILL_STOPPING_NOTICE_SECONDS = 2;
|
|
20
|
+
|
|
21
|
+
const STILL_STOPPING_NOTICE_AFTER = Duration.seconds(STILL_STOPPING_NOTICE_SECONDS);
|
|
22
|
+
|
|
23
|
+
const failCleanup = Effect.fn("failCleanup")(function* (
|
|
24
|
+
taskRunsState: Effect.Success<typeof makeTaskRunsState>,
|
|
25
|
+
failure: TaskCleanupFrozeWorker | TaskCleanupNotStarted | TaskCleanupTimedOut,
|
|
26
|
+
) {
|
|
27
|
+
yield* (yield* StopState).recordFirstFailure(failure);
|
|
28
|
+
yield* failTask(failure, taskRunsState.markFailed);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const findCleanupDuration = (
|
|
32
|
+
taskRunsState: Effect.Success<typeof makeTaskRunsState>,
|
|
33
|
+
taskName: string,
|
|
34
|
+
) =>
|
|
35
|
+
Effect.fromNullishOr(taskRunsState.discoveredTasksByName.get(taskName)).pipe(
|
|
36
|
+
dieOnInvariantFailure("task lifecycle", "a task cleaning up on a worker was discovered"),
|
|
37
|
+
Effect.map((discoveredTask) => discoveredTask.cleanupDuration),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const watchWorkerOfCleanupPastItsLimit = Effect.fn("watchWorkerOfCleanupPastItsLimit")(function* (
|
|
41
|
+
taskRunsState: Effect.Success<typeof makeTaskRunsState>,
|
|
42
|
+
taskName: string,
|
|
43
|
+
workerId: number,
|
|
44
|
+
cleanupDuration: Duration.Duration,
|
|
45
|
+
) {
|
|
46
|
+
const workerPool = yield* WorkerPool;
|
|
47
|
+
const taskRun = yield* Effect.fromNullishOr(taskRunsState.runsByTaskName.get(taskName)).pipe(
|
|
48
|
+
dieOnInvariantFailure("task lifecycle", "a task whose cleanup is watched was started"),
|
|
49
|
+
);
|
|
50
|
+
const cleanupEnded = Deferred.await(taskRun.bodyFinishedDeferred).pipe(
|
|
51
|
+
Effect.as("cleanup ended" as const),
|
|
52
|
+
);
|
|
53
|
+
const checkWorkerAnswers = Effect.gen(function* () {
|
|
54
|
+
const cleanupDurationsOnWorker = yield* Effect.forEach(
|
|
55
|
+
[...taskRunsState.runsByTaskName].filter(
|
|
56
|
+
([, otherTaskRun]) =>
|
|
57
|
+
isUnfinishedOnWorker(otherTaskRun, workerId) &&
|
|
58
|
+
(Deferred.isDoneUnsafe(otherTaskRun.bodyCleanupStartedDeferred) ||
|
|
59
|
+
Deferred.isDoneUnsafe(otherTaskRun.bodyFailedBeforeCleanupDeferred)),
|
|
60
|
+
),
|
|
61
|
+
([otherTaskName]) => findCleanupDuration(taskRunsState, otherTaskName),
|
|
62
|
+
);
|
|
63
|
+
const answerLimit = Arrays.max([cleanupDuration, ...cleanupDurationsOnWorker], Duration.Order);
|
|
64
|
+
const answer = yield* workerPool
|
|
65
|
+
.awaitAnswer(workerId)
|
|
66
|
+
.pipe(
|
|
67
|
+
Effect.raceFirst(cleanupEnded),
|
|
68
|
+
Effect.timeoutOption(answerLimit),
|
|
69
|
+
Effect.map(
|
|
70
|
+
Option.match({ onNone: () => "unanswered" as const, onSome: (outcome) => outcome }),
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
if (answer === "answered") {
|
|
74
|
+
yield* cleanupEnded.pipe(Effect.timeoutOption(cleanupDuration));
|
|
75
|
+
}
|
|
76
|
+
return answer;
|
|
77
|
+
});
|
|
78
|
+
const lastAnswer = yield* checkWorkerAnswers.pipe(
|
|
79
|
+
Effect.repeat({ while: (answer) => answer === "answered" }),
|
|
80
|
+
);
|
|
81
|
+
if (lastAnswer !== "unanswered") {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
yield* workerPool.requestWorkerTermination(
|
|
85
|
+
workerId,
|
|
86
|
+
`because its cleanup of "${taskName}" ran past its ${Duration.format(cleanupDuration)} limit and then it stopped answering`,
|
|
87
|
+
);
|
|
88
|
+
yield* failCleanup(
|
|
89
|
+
taskRunsState,
|
|
90
|
+
new TaskCleanupFrozeWorker({
|
|
91
|
+
taskName,
|
|
92
|
+
cleanupMilliseconds: Duration.toMillis(cleanupDuration),
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export const stopTask = Effect.fn("stopTask")(function* (
|
|
98
|
+
taskRunsState: Effect.Success<typeof makeTaskRunsState>,
|
|
99
|
+
taskName: string,
|
|
100
|
+
watchUntilStopsEnd: <Services>(
|
|
101
|
+
watch: Effect.Effect<void, never, Services>,
|
|
102
|
+
) => Effect.Effect<void, never, Services>,
|
|
103
|
+
) {
|
|
104
|
+
yield* Effect.annotateCurrentSpan(`${TASK_TRACE_LABEL_PREFIX}.task`, taskName);
|
|
105
|
+
const processGroups = yield* ProcessGroups;
|
|
106
|
+
const discoveredTask = taskRunsState.discoveredTasksByName.get(taskName);
|
|
107
|
+
const taskRun = taskRunsState.runsByTaskName.get(taskName);
|
|
108
|
+
const isTaskNotStarted = discoveredTask === undefined || taskRun === undefined;
|
|
109
|
+
if (isTaskNotStarted) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const workerId = findAssignedWorkerId(taskRun.status);
|
|
113
|
+
const hasNoBodyToStop = workerId === null || Deferred.isDoneUnsafe(taskRun.bodyFinishedDeferred);
|
|
114
|
+
if (hasNoBodyToStop) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const workerPool = yield* WorkerPool;
|
|
119
|
+
yield* workerPool.stopTaskFiber(workerId, taskName);
|
|
120
|
+
const cleanupDuration = discoveredTask.cleanupDuration;
|
|
121
|
+
const awaitBodyFinished = Effect.gen(function* () {
|
|
122
|
+
const finishedBeforeNotice = yield* Deferred.await(taskRun.bodyFinishedDeferred).pipe(
|
|
123
|
+
Effect.timeoutOption(STILL_STOPPING_NOTICE_AFTER),
|
|
124
|
+
);
|
|
125
|
+
if (Option.isSome(finishedBeforeNotice)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
yield* printTaskLines(
|
|
129
|
+
"Warn",
|
|
130
|
+
taskName,
|
|
131
|
+
`still stopping after ${Duration.format(STILL_STOPPING_NOTICE_AFTER)}; ${TASK_PACKAGE.name} ends it when its ${Duration.format(cleanupDuration)} cleanup limit passes`,
|
|
132
|
+
);
|
|
133
|
+
yield* Deferred.await(taskRun.bodyFinishedDeferred);
|
|
134
|
+
});
|
|
135
|
+
const ended = yield* awaitBodyFinished.pipe(Effect.timeoutOption(cleanupDuration));
|
|
136
|
+
if (Option.isSome(ended)) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
yield* Effect.uninterruptible(
|
|
141
|
+
Effect.gen(function* () {
|
|
142
|
+
const workerAnsweredStop = Deferred.isDoneUnsafe(taskRun.bodyCleanupStartedDeferred);
|
|
143
|
+
if (!workerAnsweredStop) {
|
|
144
|
+
yield* workerPool.requestWorkerTermination(
|
|
145
|
+
workerId,
|
|
146
|
+
`because it did not answer the stop of "${taskName}" within ${Duration.format(cleanupDuration)}`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
yield* processGroups.killForTask(taskName);
|
|
150
|
+
const cleanupMilliseconds = Duration.toMillis(cleanupDuration);
|
|
151
|
+
const cleanupStarted =
|
|
152
|
+
workerAnsweredStop || Deferred.isDoneUnsafe(taskRun.bodyFailedBeforeCleanupDeferred);
|
|
153
|
+
yield* failCleanup(
|
|
154
|
+
taskRunsState,
|
|
155
|
+
cleanupStarted
|
|
156
|
+
? new TaskCleanupTimedOut({ taskName, cleanupMilliseconds })
|
|
157
|
+
: new TaskCleanupNotStarted({ taskName, cleanupMilliseconds }),
|
|
158
|
+
);
|
|
159
|
+
if (workerAnsweredStop) {
|
|
160
|
+
yield* watchUntilStopsEnd(
|
|
161
|
+
watchWorkerOfCleanupPastItsLimit(taskRunsState, taskName, workerId, cleanupDuration),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { scanProjectModule } from "./internal/scan-module-text.ts";
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Visitor } from "oxc-parser";
|
|
2
|
+
import type { BindingPattern, ParamPattern, Program } from "oxc-parser";
|
|
3
|
+
|
|
4
|
+
class LexicalScope {
|
|
5
|
+
public readonly bindings = new Map<string, number>();
|
|
6
|
+
|
|
7
|
+
public readonly parent: LexicalScope | null;
|
|
8
|
+
|
|
9
|
+
public readonly holdsVarDeclarations: boolean;
|
|
10
|
+
|
|
11
|
+
public constructor(parent: LexicalScope | null, holdsVarDeclarations: boolean) {
|
|
12
|
+
this.parent = parent;
|
|
13
|
+
this.holdsVarDeclarations = holdsVarDeclarations;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public bindingFor(name: string): number | null {
|
|
17
|
+
return this.bindings.get(name) ?? this.parent?.bindingFor(name) ?? null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public scopeForVar(): LexicalScope {
|
|
21
|
+
return this.holdsVarDeclarations ? this : (this.parent?.scopeForVar() ?? this);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const declarePattern = (scope: LexicalScope, pattern: BindingPattern | ParamPattern): void => {
|
|
26
|
+
if (pattern.type === "Identifier") {
|
|
27
|
+
if (!scope.bindings.has(pattern.name)) {
|
|
28
|
+
scope.bindings.set(pattern.name, pattern.start);
|
|
29
|
+
}
|
|
30
|
+
} else if (pattern.type === "AssignmentPattern") {
|
|
31
|
+
declarePattern(scope, pattern.left);
|
|
32
|
+
} else if (pattern.type === "RestElement") {
|
|
33
|
+
declarePattern(scope, pattern.argument);
|
|
34
|
+
} else if (pattern.type === "TSParameterProperty") {
|
|
35
|
+
declarePattern(scope, pattern.parameter);
|
|
36
|
+
} else if (pattern.type === "ObjectPattern") {
|
|
37
|
+
for (const property of pattern.properties) {
|
|
38
|
+
declarePattern(scope, property.type === "Property" ? property.value : property.argument);
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
for (const element of pattern.elements) {
|
|
42
|
+
if (element !== null) {
|
|
43
|
+
declarePattern(scope, element);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const findLexicalBindings = (program: Program) => {
|
|
50
|
+
const current = { scope: new LexicalScope(null, true) };
|
|
51
|
+
const scopesAtIdentifierStarts = new Map<number, LexicalScope>();
|
|
52
|
+
const enterScope = () => {
|
|
53
|
+
current.scope = new LexicalScope(current.scope, false);
|
|
54
|
+
};
|
|
55
|
+
const leaveScope = () => {
|
|
56
|
+
current.scope = current.scope.parent ?? current.scope;
|
|
57
|
+
};
|
|
58
|
+
const enterFunction = (parameters: readonly ParamPattern[]) => {
|
|
59
|
+
current.scope = new LexicalScope(current.scope, true);
|
|
60
|
+
for (const parameter of parameters) {
|
|
61
|
+
declarePattern(current.scope, parameter);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
new Visitor({
|
|
65
|
+
Identifier: (identifier) => {
|
|
66
|
+
scopesAtIdentifierStarts.set(identifier.start, current.scope);
|
|
67
|
+
},
|
|
68
|
+
VariableDeclaration: (declaration) => {
|
|
69
|
+
if (declaration.declare === true) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const declarationScope =
|
|
73
|
+
declaration.kind === "var" ? current.scope.scopeForVar() : current.scope;
|
|
74
|
+
for (const declarator of declaration.declarations) {
|
|
75
|
+
declarePattern(declarationScope, declarator.id);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
ImportDeclaration: (declaration) => {
|
|
79
|
+
if (declaration.importKind === "type") {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
for (const specifier of declaration.specifiers) {
|
|
83
|
+
const importsValue =
|
|
84
|
+
specifier.type !== "ImportSpecifier" || specifier.importKind !== "type";
|
|
85
|
+
if (importsValue) {
|
|
86
|
+
declarePattern(current.scope, specifier.local);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
TSImportEqualsDeclaration: (declaration) => {
|
|
91
|
+
if (declaration.importKind !== "type") {
|
|
92
|
+
declarePattern(current.scope, declaration.id);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
FunctionDeclaration: (declaration) => {
|
|
96
|
+
if (declaration.id !== null) {
|
|
97
|
+
declarePattern(current.scope, declaration.id);
|
|
98
|
+
}
|
|
99
|
+
enterFunction(declaration.params);
|
|
100
|
+
},
|
|
101
|
+
"FunctionDeclaration:exit": leaveScope,
|
|
102
|
+
FunctionExpression: (expression) => {
|
|
103
|
+
enterFunction(expression.params);
|
|
104
|
+
if (expression.id !== null) {
|
|
105
|
+
declarePattern(current.scope, expression.id);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"FunctionExpression:exit": leaveScope,
|
|
109
|
+
ArrowFunctionExpression: (expression) => {
|
|
110
|
+
enterFunction(expression.params);
|
|
111
|
+
},
|
|
112
|
+
"ArrowFunctionExpression:exit": leaveScope,
|
|
113
|
+
BlockStatement: enterScope,
|
|
114
|
+
"BlockStatement:exit": leaveScope,
|
|
115
|
+
ForStatement: enterScope,
|
|
116
|
+
"ForStatement:exit": leaveScope,
|
|
117
|
+
ForInStatement: enterScope,
|
|
118
|
+
"ForInStatement:exit": leaveScope,
|
|
119
|
+
ForOfStatement: enterScope,
|
|
120
|
+
"ForOfStatement:exit": leaveScope,
|
|
121
|
+
SwitchStatement: enterScope,
|
|
122
|
+
"SwitchStatement:exit": leaveScope,
|
|
123
|
+
CatchClause: (clause) => {
|
|
124
|
+
enterScope();
|
|
125
|
+
if (clause.param !== null) {
|
|
126
|
+
declarePattern(current.scope, clause.param);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"CatchClause:exit": leaveScope,
|
|
130
|
+
ClassDeclaration: (declaration) => {
|
|
131
|
+
const { id, declare: declaresOnlyType } = declaration;
|
|
132
|
+
const declaresValue = id !== null && declaresOnlyType !== true;
|
|
133
|
+
if (declaresValue) {
|
|
134
|
+
declarePattern(current.scope, id);
|
|
135
|
+
}
|
|
136
|
+
enterScope();
|
|
137
|
+
},
|
|
138
|
+
"ClassDeclaration:exit": leaveScope,
|
|
139
|
+
ClassExpression: (expression) => {
|
|
140
|
+
enterScope();
|
|
141
|
+
if (expression.id !== null) {
|
|
142
|
+
declarePattern(current.scope, expression.id);
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"ClassExpression:exit": leaveScope,
|
|
146
|
+
StaticBlock: () => {
|
|
147
|
+
current.scope = new LexicalScope(current.scope, true);
|
|
148
|
+
},
|
|
149
|
+
"StaticBlock:exit": leaveScope,
|
|
150
|
+
}).visit(program);
|
|
151
|
+
|
|
152
|
+
return (identifier: { readonly name: string; readonly start: number }) =>
|
|
153
|
+
scopesAtIdentifierStarts.get(identifier.start)?.bindingFor(identifier.name) ?? null;
|
|
154
|
+
};
|