@ontrails/adapter-kit 1.0.0-beta.32 → 1.0.0-beta.41
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/package.json +6 -2
- package/src/catalog.ts +14 -520
- package/src/check.ts +658 -237
- package/src/index.ts +12 -0
- package/src/overlay.ts +158 -0
- package/src/source.ts +684 -0
package/src/check.ts
CHANGED
|
@@ -19,6 +19,13 @@ import type {
|
|
|
19
19
|
DiagnosticBase,
|
|
20
20
|
WorkspacePackage as CoreWorkspacePackage,
|
|
21
21
|
} from '@ontrails/core';
|
|
22
|
+
import {
|
|
23
|
+
identifierName,
|
|
24
|
+
isShadowed,
|
|
25
|
+
parse,
|
|
26
|
+
walkWithScopes,
|
|
27
|
+
} from '@ontrails/source';
|
|
28
|
+
import type { AstNode } from '@ontrails/source';
|
|
22
29
|
|
|
23
30
|
import { deriveAdapterTargetCatalog } from './catalog.js';
|
|
24
31
|
import type {
|
|
@@ -114,9 +121,23 @@ interface AdapterMetadata {
|
|
|
114
121
|
readonly target: string;
|
|
115
122
|
}
|
|
116
123
|
|
|
124
|
+
interface SubpathAdapterMetadata extends AdapterMetadata {
|
|
125
|
+
readonly exportKey: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
interface AdapterCheckCandidate {
|
|
129
|
+
readonly exportKey?: string | undefined;
|
|
130
|
+
readonly key: string;
|
|
131
|
+
readonly metadata: AdapterMetadata;
|
|
132
|
+
readonly packageName: string;
|
|
133
|
+
readonly placement: AdapterTargetPlacement;
|
|
134
|
+
}
|
|
135
|
+
|
|
117
136
|
const adapterKitPackageName = '@ontrails/adapter-kit';
|
|
118
137
|
|
|
119
138
|
const targetIdPattern = /^[a-z][a-z0-9-]*$/u;
|
|
139
|
+
const subpathAdapterExportKeyPattern =
|
|
140
|
+
/^\.\/[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-]*)*$/u;
|
|
120
141
|
|
|
121
142
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
122
143
|
Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
@@ -169,39 +190,46 @@ const exportTargetIsFile = (packageRoot: string, target: string): boolean => {
|
|
|
169
190
|
}
|
|
170
191
|
};
|
|
171
192
|
|
|
172
|
-
const
|
|
193
|
+
const resolvableExportTarget = (
|
|
173
194
|
workspace: WorkspacePackage,
|
|
174
195
|
key: string
|
|
175
|
-
):
|
|
196
|
+
): string | undefined => {
|
|
176
197
|
const { exports: exportsValue } = workspace.manifest;
|
|
177
198
|
if (typeof exportsValue === 'string') {
|
|
178
|
-
return
|
|
179
|
-
|
|
180
|
-
|
|
199
|
+
return key === '.' &&
|
|
200
|
+
exportTargetIsFile(workspace.packageRoot, exportsValue)
|
|
201
|
+
? normalizeRealPath(resolve(workspace.packageRoot, exportsValue))
|
|
202
|
+
: undefined;
|
|
181
203
|
}
|
|
182
204
|
|
|
183
205
|
if (!isRecord(exportsValue)) {
|
|
184
|
-
return
|
|
206
|
+
return undefined;
|
|
185
207
|
}
|
|
186
208
|
|
|
187
209
|
if (!Object.hasOwn(exportsValue, key)) {
|
|
188
210
|
if (key !== '.') {
|
|
189
|
-
return
|
|
211
|
+
return undefined;
|
|
190
212
|
}
|
|
191
213
|
|
|
192
214
|
const rootTarget = resolveExportTarget(exportsValue);
|
|
193
|
-
return
|
|
194
|
-
rootTarget !== undefined &&
|
|
215
|
+
return rootTarget !== undefined &&
|
|
195
216
|
exportTargetIsFile(workspace.packageRoot, rootTarget)
|
|
196
|
-
|
|
217
|
+
? normalizeRealPath(resolve(workspace.packageRoot, rootTarget))
|
|
218
|
+
: undefined;
|
|
197
219
|
}
|
|
198
220
|
|
|
199
221
|
const target = resolveExportTarget(exportsValue[key]);
|
|
200
|
-
return
|
|
201
|
-
|
|
202
|
-
|
|
222
|
+
return target !== undefined &&
|
|
223
|
+
exportTargetIsFile(workspace.packageRoot, target)
|
|
224
|
+
? normalizeRealPath(resolve(workspace.packageRoot, target))
|
|
225
|
+
: undefined;
|
|
203
226
|
};
|
|
204
227
|
|
|
228
|
+
const hasResolvableExport = (
|
|
229
|
+
workspace: WorkspacePackage,
|
|
230
|
+
key: string
|
|
231
|
+
): boolean => resolvableExportTarget(workspace, key) !== undefined;
|
|
232
|
+
|
|
205
233
|
const dependencyMap = (value: unknown): Readonly<Record<string, unknown>> =>
|
|
206
234
|
isRecord(value) ? value : {};
|
|
207
235
|
|
|
@@ -232,6 +260,37 @@ const trailAdapterMetadata = (
|
|
|
232
260
|
: null;
|
|
233
261
|
};
|
|
234
262
|
|
|
263
|
+
const trailSubpathAdapterMetadata = (
|
|
264
|
+
manifest: AdapterCheckPackageManifest
|
|
265
|
+
): readonly SubpathAdapterMetadata[] | undefined | null => {
|
|
266
|
+
const trails = isRecord(manifest.trails) ? manifest.trails : undefined;
|
|
267
|
+
const adapters = trails?.['adapters'];
|
|
268
|
+
if (adapters === undefined) {
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
if (!isRecord(adapters)) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const metadata: SubpathAdapterMetadata[] = [];
|
|
276
|
+
for (const [exportKey, adapter] of Object.entries(adapters).toSorted()) {
|
|
277
|
+
if (!subpathAdapterExportKeyPattern.test(exportKey)) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
if (!isRecord(adapter)) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const { target } = adapter;
|
|
285
|
+
if (typeof target !== 'string' || !targetIdPattern.test(target)) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
metadata.push({ exportKey, target });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return metadata;
|
|
292
|
+
};
|
|
293
|
+
|
|
235
294
|
const placementForWorkspace = (
|
|
236
295
|
workspacePath: string
|
|
237
296
|
): AdapterTargetPlacement | undefined =>
|
|
@@ -302,7 +361,10 @@ const adapterFacts = (
|
|
|
302
361
|
placement: subject.placement,
|
|
303
362
|
provenance: {
|
|
304
363
|
packageJsonPath: subject.packageJsonPath,
|
|
305
|
-
source:
|
|
364
|
+
source:
|
|
365
|
+
subject.placement === 'subpath'
|
|
366
|
+
? 'owner-package-manifest'
|
|
367
|
+
: 'adapter-package-manifest',
|
|
306
368
|
},
|
|
307
369
|
target: subject.target,
|
|
308
370
|
targetKey: subject.targetKey,
|
|
@@ -1105,48 +1167,147 @@ const dynamicImportNamespaceBindings = (
|
|
|
1105
1167
|
const stringsMaskedCode = maskSource(source, { strings: true });
|
|
1106
1168
|
const escapedSpecifier = escapeRegExp(specifier);
|
|
1107
1169
|
const pattern = new RegExp(
|
|
1108
|
-
`\\
|
|
1170
|
+
`\\bconst\\s+(?<local>[A-Za-z_$][\\w$]*)(?:\\s*:\\s*[^=;]+)?\\s*=\\s*await\\s+import\\s*\\(\\s*['"]${escapedSpecifier}['"]\\s*\\)`,
|
|
1109
1171
|
'gu'
|
|
1110
1172
|
);
|
|
1111
1173
|
|
|
1112
1174
|
return [...code.matchAll(pattern)]
|
|
1113
1175
|
.filter((match) =>
|
|
1114
|
-
matchStartsWithAnyKeyword(stringsMaskedCode, match, [
|
|
1115
|
-
'const',
|
|
1116
|
-
'let',
|
|
1117
|
-
'var',
|
|
1118
|
-
])
|
|
1176
|
+
matchStartsWithAnyKeyword(stringsMaskedCode, match, ['const'])
|
|
1119
1177
|
)
|
|
1120
1178
|
.map((match) => match.groups?.['local'])
|
|
1121
1179
|
.filter((local): local is string => local !== undefined);
|
|
1122
1180
|
};
|
|
1123
1181
|
|
|
1124
|
-
const
|
|
1182
|
+
const topLevelNamespaceAliases = (
|
|
1183
|
+
ast: AstNode,
|
|
1184
|
+
namespaceBindings: ReadonlySet<string>
|
|
1185
|
+
): readonly string[] => {
|
|
1186
|
+
if (ast.type !== 'Program') {
|
|
1187
|
+
return [];
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
const aliases = new Set(namespaceBindings);
|
|
1191
|
+
const body = (ast as unknown as { body?: readonly AstNode[] }).body ?? [];
|
|
1192
|
+
let changed = true;
|
|
1193
|
+
while (changed) {
|
|
1194
|
+
changed = false;
|
|
1195
|
+
for (const statement of body) {
|
|
1196
|
+
if (statement.type !== 'VariableDeclaration') {
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
if (statement['kind'] !== 'const') {
|
|
1200
|
+
continue;
|
|
1201
|
+
}
|
|
1202
|
+
const declarations =
|
|
1203
|
+
(statement as unknown as { declarations?: readonly AstNode[] })
|
|
1204
|
+
.declarations ?? [];
|
|
1205
|
+
for (const declaration of declarations) {
|
|
1206
|
+
const declarator = declaration as unknown as {
|
|
1207
|
+
id?: AstNode;
|
|
1208
|
+
init?: AstNode;
|
|
1209
|
+
};
|
|
1210
|
+
const local = identifierName(declarator.id);
|
|
1211
|
+
const sourceBinding = identifierName(declarator.init);
|
|
1212
|
+
if (
|
|
1213
|
+
local &&
|
|
1214
|
+
sourceBinding &&
|
|
1215
|
+
aliases.has(sourceBinding) &&
|
|
1216
|
+
!aliases.has(local)
|
|
1217
|
+
) {
|
|
1218
|
+
aliases.add(local);
|
|
1219
|
+
changed = true;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
return [...aliases].filter((binding) => !namespaceBindings.has(binding));
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
interface DynamicNamedImportBinding {
|
|
1229
|
+
readonly imported: string;
|
|
1230
|
+
readonly local: string;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
const closingDestructuringBrace = (
|
|
1125
1234
|
source: string,
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1235
|
+
openingBrace: number
|
|
1236
|
+
): number | undefined => {
|
|
1237
|
+
let depth = 0;
|
|
1238
|
+
for (let index = openingBrace; index < source.length; index += 1) {
|
|
1239
|
+
const skippedIndex = skipImportScanIgnoredToken(source, index);
|
|
1240
|
+
if (skippedIndex !== undefined) {
|
|
1241
|
+
index = skippedIndex - 1;
|
|
1242
|
+
continue;
|
|
1243
|
+
}
|
|
1244
|
+
const char = source[index];
|
|
1245
|
+
if (char === '{') {
|
|
1246
|
+
depth += 1;
|
|
1247
|
+
} else if (char === '}') {
|
|
1248
|
+
depth -= 1;
|
|
1249
|
+
if (depth === 0) {
|
|
1250
|
+
return index;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
return undefined;
|
|
1255
|
+
};
|
|
1256
|
+
|
|
1257
|
+
const splitDestructuringBindings = (source: string): readonly string[] => {
|
|
1258
|
+
const bindings: string[] = [];
|
|
1259
|
+
let start = 0;
|
|
1260
|
+
let depth = 0;
|
|
1261
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
1262
|
+
const skippedIndex = skipImportScanIgnoredToken(source, index);
|
|
1263
|
+
if (skippedIndex !== undefined) {
|
|
1264
|
+
index = skippedIndex - 1;
|
|
1265
|
+
continue;
|
|
1266
|
+
}
|
|
1267
|
+
const char = source[index];
|
|
1268
|
+
if (char === '{' || char === '[' || char === '(') {
|
|
1269
|
+
depth += 1;
|
|
1270
|
+
} else if (char === '}' || char === ']' || char === ')') {
|
|
1271
|
+
depth = Math.max(0, depth - 1);
|
|
1272
|
+
} else if (char === ',' && depth === 0) {
|
|
1273
|
+
bindings.push(source.slice(start, index));
|
|
1274
|
+
start = index + 1;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
bindings.push(source.slice(start));
|
|
1278
|
+
return bindings;
|
|
1279
|
+
};
|
|
1280
|
+
|
|
1281
|
+
const dynamicImportNamedBindings = (
|
|
1282
|
+
source: string,
|
|
1283
|
+
specifier: string
|
|
1284
|
+
): readonly DynamicNamedImportBinding[] => {
|
|
1129
1285
|
const code = maskSource(source, { strings: false });
|
|
1130
1286
|
const stringsMaskedCode = maskSource(source, { strings: true });
|
|
1131
1287
|
const escapedSpecifier = escapeRegExp(specifier);
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
|
|
1288
|
+
const bindings: DynamicNamedImportBinding[] = [];
|
|
1289
|
+
const declarationPattern = /\bconst\s*\{/gu;
|
|
1290
|
+
const assignmentPattern = new RegExp(
|
|
1291
|
+
`^\\s*(?::\\s*typeof\\s+import\\s*\\(\\s*['"]${escapedSpecifier}['"]\\s*\\))?\\s*=\\s*(?:await\\s+)?import\\s*\\(\\s*['"]${escapedSpecifier}['"]\\s*\\)`,
|
|
1292
|
+
'u'
|
|
1135
1293
|
);
|
|
1136
1294
|
|
|
1137
|
-
for (const match of code.matchAll(
|
|
1295
|
+
for (const match of code.matchAll(declarationPattern)) {
|
|
1296
|
+
if (!matchStartsWithAnyKeyword(stringsMaskedCode, match, ['const'])) {
|
|
1297
|
+
continue;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
const openingBrace = (match.index ?? 0) + match[0].lastIndexOf('{');
|
|
1301
|
+
const closingBrace = closingDestructuringBrace(code, openingBrace);
|
|
1138
1302
|
if (
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
'let',
|
|
1142
|
-
'var',
|
|
1143
|
-
])
|
|
1303
|
+
closingBrace === undefined ||
|
|
1304
|
+
!assignmentPattern.test(code.slice(closingBrace + 1))
|
|
1144
1305
|
) {
|
|
1145
1306
|
continue;
|
|
1146
1307
|
}
|
|
1147
1308
|
|
|
1148
|
-
const namedImports =
|
|
1149
|
-
for (const item of namedImports
|
|
1309
|
+
const namedImports = code.slice(openingBrace + 1, closingBrace);
|
|
1310
|
+
for (const item of splitDestructuringBindings(namedImports)) {
|
|
1150
1311
|
const specifierText = item.trim();
|
|
1151
1312
|
if (specifierText.length === 0 || specifierText.startsWith('...')) {
|
|
1152
1313
|
continue;
|
|
@@ -1156,15 +1317,27 @@ const dynamicImportNamedBinding = (
|
|
|
1156
1317
|
/^(?<imported>[A-Za-z_$][\w$]*)(?:\s*:\s*(?<local>[A-Za-z_$][\w$]*))?(?:\s*=.*)?$/u.exec(
|
|
1157
1318
|
specifierText
|
|
1158
1319
|
)?.groups;
|
|
1159
|
-
if (imported?.['imported']
|
|
1160
|
-
|
|
1320
|
+
if (imported?.['imported']) {
|
|
1321
|
+
bindings.push({
|
|
1322
|
+
imported: imported['imported'],
|
|
1323
|
+
local: imported['local'] ?? imported['imported'],
|
|
1324
|
+
});
|
|
1161
1325
|
}
|
|
1162
1326
|
}
|
|
1163
1327
|
}
|
|
1164
1328
|
|
|
1165
|
-
return
|
|
1329
|
+
return bindings;
|
|
1166
1330
|
};
|
|
1167
1331
|
|
|
1332
|
+
const dynamicImportNamedLocals = (
|
|
1333
|
+
source: string,
|
|
1334
|
+
specifier: string,
|
|
1335
|
+
exportedName: string
|
|
1336
|
+
): readonly string[] =>
|
|
1337
|
+
dynamicImportNamedBindings(source, specifier)
|
|
1338
|
+
.filter((binding) => binding.imported === exportedName)
|
|
1339
|
+
.map((binding) => binding.local);
|
|
1340
|
+
|
|
1168
1341
|
interface LocalValueExport {
|
|
1169
1342
|
readonly identifier: string;
|
|
1170
1343
|
readonly sourcePath: string;
|
|
@@ -1495,90 +1668,218 @@ const previousNonWhitespace = (source: string, index: number): string => {
|
|
|
1495
1668
|
|
|
1496
1669
|
const containsCall = (source: string, identifier: string): boolean => {
|
|
1497
1670
|
const escapedIdentifier = escapeRegExp(identifier);
|
|
1498
|
-
const callPattern = new RegExp(
|
|
1671
|
+
const callPattern = new RegExp(`${escapedIdentifier}\\s*\\(`, 'gu');
|
|
1499
1672
|
for (const match of source.matchAll(callPattern)) {
|
|
1500
|
-
|
|
1673
|
+
const index = match.index ?? 0;
|
|
1674
|
+
if (
|
|
1675
|
+
!isIdentifierChar(source[index - 1]) &&
|
|
1676
|
+
previousNonWhitespace(source, index) !== '.'
|
|
1677
|
+
) {
|
|
1501
1678
|
return true;
|
|
1502
1679
|
}
|
|
1503
1680
|
}
|
|
1504
1681
|
return false;
|
|
1505
1682
|
};
|
|
1506
1683
|
|
|
1507
|
-
const
|
|
1508
|
-
const args: string[] = [];
|
|
1509
|
-
let start = 0;
|
|
1510
|
-
let parenDepth = 0;
|
|
1511
|
-
let braceDepth = 0;
|
|
1512
|
-
let bracketDepth = 0;
|
|
1684
|
+
const promiseMethodNames = new Set(['then', 'catch', 'finally']);
|
|
1513
1685
|
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1686
|
+
const inlineDynamicImportMemberIsCalled = (
|
|
1687
|
+
source: string,
|
|
1688
|
+
specifier: string
|
|
1689
|
+
): boolean => {
|
|
1690
|
+
const searchableCode = maskSource(source, { strings: false });
|
|
1691
|
+
const codePositions = maskSource(source, { strings: true });
|
|
1692
|
+
const escapedSpecifier = escapeRegExp(specifier);
|
|
1693
|
+
const pattern = new RegExp(
|
|
1694
|
+
`\\(\\s*await\\s+import\\s*\\(\\s*['"]${escapedSpecifier}['"]\\s*\\)\\s*\\)\\s*\\.\\s*(?<member>[A-Za-z_$][\\w$]*)\\s*\\(`,
|
|
1695
|
+
'gu'
|
|
1696
|
+
);
|
|
1697
|
+
return [...searchableCode.matchAll(pattern)].some(
|
|
1698
|
+
(match) =>
|
|
1699
|
+
codePositions[match.index ?? 0] !== ' ' &&
|
|
1700
|
+
!promiseMethodNames.has(match.groups?.['member'] ?? '')
|
|
1701
|
+
);
|
|
1702
|
+
};
|
|
1703
|
+
|
|
1704
|
+
interface RuntimeImportBindings {
|
|
1705
|
+
readonly direct: readonly string[];
|
|
1706
|
+
readonly namespaces: readonly string[];
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
const staticRuntimeImportBindings = (
|
|
1710
|
+
source: string,
|
|
1711
|
+
specifier: string
|
|
1712
|
+
): RuntimeImportBindings => {
|
|
1713
|
+
const direct = new Set<string>();
|
|
1714
|
+
const namespaces = new Set<string>();
|
|
1715
|
+
for (const clause of staticImportClausesForSpecifier(source, specifier)) {
|
|
1716
|
+
const importCode = maskSource(clause, { strings: false });
|
|
1717
|
+
const namespaceBinding =
|
|
1718
|
+
/(?:^|,)\s*\*\s+as\s+(?<local>[A-Za-z_$][\w$]*)(?:\s*$|,)/u.exec(
|
|
1719
|
+
importCode
|
|
1720
|
+
)?.groups?.['local'];
|
|
1721
|
+
if (namespaceBinding) {
|
|
1722
|
+
namespaces.add(namespaceBinding);
|
|
1527
1723
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1724
|
+
|
|
1725
|
+
const namedImports = /\{(?<imports>[\s\S]*?)\}/u.exec(importCode)?.groups?.[
|
|
1726
|
+
'imports'
|
|
1727
|
+
];
|
|
1728
|
+
for (const item of namedImports?.split(',') ?? []) {
|
|
1729
|
+
const trimmedItem = item.trim();
|
|
1730
|
+
const imported =
|
|
1731
|
+
/^(?<imported>[A-Za-z_$][\w$]*)(?:\s+as\s+(?<local>[A-Za-z_$][\w$]*))?$/u.exec(
|
|
1732
|
+
trimmedItem.replace(/^type\s+/u, '')
|
|
1733
|
+
)?.groups;
|
|
1734
|
+
if (!trimmedItem.startsWith('type ') && imported?.['imported']) {
|
|
1735
|
+
direct.add(imported['local'] ?? imported['imported']);
|
|
1736
|
+
}
|
|
1531
1737
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1738
|
+
|
|
1739
|
+
const defaultBinding = /^(?<local>[A-Za-z_$][\w$]*)(?:\s*,|\s*$)/u.exec(
|
|
1740
|
+
importCode.trim()
|
|
1741
|
+
)?.groups?.['local'];
|
|
1742
|
+
if (defaultBinding) {
|
|
1743
|
+
direct.add(defaultBinding);
|
|
1535
1744
|
}
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
return { direct: [...direct], namespaces: [...namespaces] };
|
|
1748
|
+
};
|
|
1749
|
+
|
|
1750
|
+
interface RunnerCall {
|
|
1751
|
+
readonly arguments: readonly string[];
|
|
1752
|
+
readonly argumentNodes: readonly AstNode[];
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
const namespacedCalleeName = (
|
|
1756
|
+
callee: AstNode
|
|
1757
|
+
): { readonly property: string; readonly receiver: string } | undefined => {
|
|
1758
|
+
if (
|
|
1759
|
+
(callee.type !== 'MemberExpression' &&
|
|
1760
|
+
callee.type !== 'StaticMemberExpression') ||
|
|
1761
|
+
callee['computed'] === true
|
|
1762
|
+
) {
|
|
1763
|
+
return undefined;
|
|
1764
|
+
}
|
|
1765
|
+
const receiver = identifierName(callee['object'] as AstNode | undefined);
|
|
1766
|
+
const property = identifierName(callee['property'] as AstNode | undefined);
|
|
1767
|
+
return receiver && property ? { property, receiver } : undefined;
|
|
1768
|
+
};
|
|
1769
|
+
|
|
1770
|
+
interface ProvenBindingCall {
|
|
1771
|
+
readonly arguments: readonly AstNode[];
|
|
1772
|
+
readonly end: number;
|
|
1773
|
+
readonly start: number;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
const provenBindingCalls = (
|
|
1777
|
+
ast: AstNode,
|
|
1778
|
+
binding: string
|
|
1779
|
+
): readonly ProvenBindingCall[] => {
|
|
1780
|
+
const calls: ProvenBindingCall[] = [];
|
|
1781
|
+
walkWithScopes(ast, (node, scopes) => {
|
|
1782
|
+
if (node.type !== 'CallExpression') {
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
const callee = node['callee'] as AstNode | undefined;
|
|
1786
|
+
if (!callee) {
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
const member = namespacedCalleeName(callee);
|
|
1791
|
+
const bare = identifierName(callee);
|
|
1792
|
+
const matches = member
|
|
1793
|
+
? `${member.receiver}.${member.property}` === binding &&
|
|
1794
|
+
!isShadowed(member.receiver, scopes)
|
|
1795
|
+
: bare === binding && !isShadowed(binding, scopes);
|
|
1796
|
+
if (!matches) {
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
calls.push({
|
|
1801
|
+
arguments:
|
|
1802
|
+
(node as unknown as { arguments?: readonly AstNode[] }).arguments ?? [],
|
|
1803
|
+
end: node.end,
|
|
1804
|
+
start: node.start,
|
|
1805
|
+
});
|
|
1806
|
+
});
|
|
1807
|
+
return calls;
|
|
1808
|
+
};
|
|
1809
|
+
|
|
1810
|
+
const provenNamespaceIsCalled = (ast: AstNode, binding: string): boolean => {
|
|
1811
|
+
let called = false;
|
|
1812
|
+
walkWithScopes(ast, (node, scopes) => {
|
|
1813
|
+
if (called || node.type !== 'CallExpression') {
|
|
1814
|
+
return;
|
|
1539
1815
|
}
|
|
1816
|
+
const callee = node['callee'] as AstNode | undefined;
|
|
1817
|
+
const member = callee && namespacedCalleeName(callee);
|
|
1540
1818
|
if (
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
bracketDepth === 0
|
|
1819
|
+
member?.receiver === binding &&
|
|
1820
|
+
!promiseMethodNames.has(member.property) &&
|
|
1821
|
+
!isShadowed(binding, scopes)
|
|
1545
1822
|
) {
|
|
1546
|
-
|
|
1547
|
-
start = index + 1;
|
|
1823
|
+
called = true;
|
|
1548
1824
|
}
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
const trailing = source.slice(start).trim();
|
|
1552
|
-
if (trailing || args.length > 0) {
|
|
1553
|
-
args.push(trailing);
|
|
1554
|
-
}
|
|
1555
|
-
return args;
|
|
1825
|
+
});
|
|
1826
|
+
return called;
|
|
1556
1827
|
};
|
|
1557
1828
|
|
|
1558
1829
|
const runnerCallArguments = (
|
|
1559
1830
|
source: string,
|
|
1560
|
-
runner: string
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1831
|
+
runner: string,
|
|
1832
|
+
ast: AstNode
|
|
1833
|
+
): readonly RunnerCall[] =>
|
|
1834
|
+
provenBindingCalls(ast, runner).map((call) => ({
|
|
1835
|
+
argumentNodes: call.arguments,
|
|
1836
|
+
arguments: call.arguments.map((argument) =>
|
|
1837
|
+
source.slice(argument.start, argument.end)
|
|
1838
|
+
),
|
|
1839
|
+
}));
|
|
1566
1840
|
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1841
|
+
const argumentContainsProvenCall = (
|
|
1842
|
+
argument: AstNode | undefined,
|
|
1843
|
+
ast: AstNode,
|
|
1844
|
+
binding: string
|
|
1845
|
+
): boolean =>
|
|
1846
|
+
argument !== undefined &&
|
|
1847
|
+
provenBindingCalls(ast, binding).some(
|
|
1848
|
+
(call) => call.start >= argument.start && call.end <= argument.end
|
|
1849
|
+
);
|
|
1572
1850
|
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1851
|
+
const importedRuntimeBindingIsCalled = (
|
|
1852
|
+
source: string,
|
|
1853
|
+
specifier: string,
|
|
1854
|
+
ast: AstNode
|
|
1855
|
+
): boolean => {
|
|
1856
|
+
const staticBindings = staticRuntimeImportBindings(source, specifier);
|
|
1857
|
+
const directBindings = new Set([
|
|
1858
|
+
...staticBindings.direct,
|
|
1859
|
+
...dynamicImportNamedBindings(source, specifier).map(
|
|
1860
|
+
(binding) => binding.local
|
|
1861
|
+
),
|
|
1862
|
+
]);
|
|
1863
|
+
if (
|
|
1864
|
+
[...directBindings].some(
|
|
1865
|
+
(binding) => provenBindingCalls(ast, binding).length > 0
|
|
1866
|
+
)
|
|
1867
|
+
) {
|
|
1868
|
+
return true;
|
|
1579
1869
|
}
|
|
1580
1870
|
|
|
1581
|
-
|
|
1871
|
+
const namespaceBindings = new Set([
|
|
1872
|
+
...staticBindings.namespaces,
|
|
1873
|
+
...dynamicImportNamespaceBindings(source, specifier),
|
|
1874
|
+
]);
|
|
1875
|
+
for (const alias of topLevelNamespaceAliases(ast, namespaceBindings)) {
|
|
1876
|
+
namespaceBindings.add(alias);
|
|
1877
|
+
}
|
|
1878
|
+
return (
|
|
1879
|
+
[...namespaceBindings].some((binding) =>
|
|
1880
|
+
provenNamespaceIsCalled(ast, binding)
|
|
1881
|
+
) || inlineDynamicImportMemberIsCalled(source, specifier)
|
|
1882
|
+
);
|
|
1582
1883
|
};
|
|
1583
1884
|
|
|
1584
1885
|
const isSentinelAdapterArgument = (argument: string): boolean =>
|
|
@@ -1586,33 +1887,42 @@ const isSentinelAdapterArgument = (argument: string): boolean =>
|
|
|
1586
1887
|
|
|
1587
1888
|
const runnerInvokesCasesFactory = (
|
|
1588
1889
|
source: string,
|
|
1890
|
+
ast: AstNode,
|
|
1589
1891
|
runner: string,
|
|
1590
1892
|
casesFactory: string
|
|
1591
1893
|
): boolean =>
|
|
1592
|
-
runnerCallArguments(source, runner).some((
|
|
1894
|
+
runnerCallArguments(source, runner, ast).some((call) => {
|
|
1895
|
+
const args = call.arguments;
|
|
1593
1896
|
const adapterArgument = args[0]?.trim();
|
|
1594
1897
|
return (
|
|
1595
1898
|
adapterArgument !== undefined &&
|
|
1596
1899
|
adapterArgument.length > 0 &&
|
|
1597
1900
|
!isSentinelAdapterArgument(adapterArgument) &&
|
|
1598
|
-
!
|
|
1599
|
-
|
|
1901
|
+
!argumentContainsProvenCall(call.argumentNodes[0], ast, casesFactory) &&
|
|
1902
|
+
call.argumentNodes
|
|
1903
|
+
.slice(1)
|
|
1904
|
+
.some((argument) =>
|
|
1905
|
+
argumentContainsProvenCall(argument, ast, casesFactory)
|
|
1906
|
+
)
|
|
1600
1907
|
);
|
|
1601
1908
|
});
|
|
1602
1909
|
|
|
1603
1910
|
const runnerInvokedWithAdapterArgument = (
|
|
1604
1911
|
source: string,
|
|
1912
|
+
ast: AstNode,
|
|
1605
1913
|
runner: string,
|
|
1606
1914
|
casesFactories: readonly string[] = []
|
|
1607
1915
|
): boolean =>
|
|
1608
|
-
runnerCallArguments(source, runner).some((
|
|
1916
|
+
runnerCallArguments(source, runner, ast).some((call) => {
|
|
1917
|
+
const args = call.arguments;
|
|
1609
1918
|
const adapterArgument = args[0]?.trim();
|
|
1610
1919
|
return (
|
|
1611
1920
|
adapterArgument !== undefined &&
|
|
1612
1921
|
adapterArgument.length > 0 &&
|
|
1613
1922
|
!isSentinelAdapterArgument(adapterArgument) &&
|
|
1614
1923
|
casesFactories.every(
|
|
1615
|
-
(casesFactory) =>
|
|
1924
|
+
(casesFactory) =>
|
|
1925
|
+
!argumentContainsProvenCall(call.argumentNodes[0], ast, casesFactory)
|
|
1616
1926
|
)
|
|
1617
1927
|
);
|
|
1618
1928
|
});
|
|
@@ -1689,12 +1999,15 @@ const ownerRunnerDefaultsCasesFactory = (
|
|
|
1689
1999
|
|
|
1690
2000
|
const runnerBindingProvesConformance = (
|
|
1691
2001
|
source: string,
|
|
2002
|
+
ast: AstNode,
|
|
1692
2003
|
targetEntry: AdapterTargetCatalogEntry,
|
|
1693
2004
|
runnerBinding: string,
|
|
1694
2005
|
casesFactoryBindings: readonly string[]
|
|
1695
2006
|
): boolean => {
|
|
1696
2007
|
for (const casesFactoryBinding of casesFactoryBindings) {
|
|
1697
|
-
if (
|
|
2008
|
+
if (
|
|
2009
|
+
runnerInvokesCasesFactory(source, ast, runnerBinding, casesFactoryBinding)
|
|
2010
|
+
) {
|
|
1698
2011
|
return true;
|
|
1699
2012
|
}
|
|
1700
2013
|
}
|
|
@@ -1703,6 +2016,7 @@ const runnerBindingProvesConformance = (
|
|
|
1703
2016
|
ownerRunnerDefaultsCasesFactory(targetEntry) &&
|
|
1704
2017
|
runnerInvokedWithAdapterArgument(
|
|
1705
2018
|
source,
|
|
2019
|
+
ast,
|
|
1706
2020
|
runnerBinding,
|
|
1707
2021
|
casesFactoryBindings
|
|
1708
2022
|
)
|
|
@@ -1724,72 +2038,63 @@ const provesConformance = (
|
|
|
1724
2038
|
return false;
|
|
1725
2039
|
}
|
|
1726
2040
|
|
|
2041
|
+
const ast = parse('adapter-conformance.ts', source);
|
|
2042
|
+
if (!ast) {
|
|
2043
|
+
return false;
|
|
2044
|
+
}
|
|
2045
|
+
|
|
1727
2046
|
if (!conformance) {
|
|
1728
|
-
return
|
|
2047
|
+
return importedRuntimeBindingIsCalled(source, testingImport, ast);
|
|
1729
2048
|
}
|
|
1730
2049
|
|
|
1731
|
-
const runnerBindings =
|
|
2050
|
+
const runnerBindings = new Set(
|
|
2051
|
+
namedImportBindings(source, testingImport, conformance.runner)
|
|
2052
|
+
);
|
|
2053
|
+
const casesFactoryBindings = new Set(
|
|
2054
|
+
namedImportBindings(source, testingImport, conformance.casesFactory)
|
|
2055
|
+
);
|
|
2056
|
+
const dynamicRunnerBindings = dynamicImportNamedLocals(
|
|
1732
2057
|
source,
|
|
1733
2058
|
testingImport,
|
|
1734
2059
|
conformance.runner
|
|
1735
2060
|
);
|
|
1736
|
-
const
|
|
2061
|
+
for (const dynamicRunnerBinding of dynamicRunnerBindings) {
|
|
2062
|
+
runnerBindings.add(dynamicRunnerBinding);
|
|
2063
|
+
}
|
|
2064
|
+
const dynamicCasesFactoryBindings = dynamicImportNamedLocals(
|
|
1737
2065
|
source,
|
|
1738
2066
|
testingImport,
|
|
1739
2067
|
conformance.casesFactory
|
|
1740
2068
|
);
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
);
|
|
1752
|
-
const dynamicCasesFactoryBindings = dynamicCasesFactoryBinding
|
|
1753
|
-
? [dynamicCasesFactoryBinding]
|
|
1754
|
-
: [];
|
|
1755
|
-
if (
|
|
1756
|
-
dynamicRunnerBinding &&
|
|
1757
|
-
runnerBindingProvesConformance(
|
|
1758
|
-
source,
|
|
1759
|
-
targetEntry,
|
|
1760
|
-
dynamicRunnerBinding,
|
|
1761
|
-
dynamicCasesFactoryBindings
|
|
1762
|
-
)
|
|
1763
|
-
) {
|
|
1764
|
-
return true;
|
|
1765
|
-
}
|
|
1766
|
-
|
|
1767
|
-
for (const namespaceBinding of dynamicImportNamespaceBindings(
|
|
1768
|
-
source,
|
|
1769
|
-
testingImport
|
|
1770
|
-
)) {
|
|
1771
|
-
const namespaceRunnerBinding = `${namespaceBinding}.${conformance.runner}`;
|
|
1772
|
-
const namespaceCasesFactoryBinding = `${namespaceBinding}.${conformance.casesFactory}`;
|
|
1773
|
-
if (
|
|
1774
|
-
runnerBindingProvesConformance(
|
|
1775
|
-
source,
|
|
1776
|
-
targetEntry,
|
|
1777
|
-
namespaceRunnerBinding,
|
|
1778
|
-
[namespaceCasesFactoryBinding]
|
|
1779
|
-
)
|
|
1780
|
-
) {
|
|
1781
|
-
return true;
|
|
1782
|
-
}
|
|
1783
|
-
}
|
|
2069
|
+
for (const dynamicCasesFactoryBinding of dynamicCasesFactoryBindings) {
|
|
2070
|
+
casesFactoryBindings.add(dynamicCasesFactoryBinding);
|
|
2071
|
+
}
|
|
2072
|
+
for (const namespaceBinding of dynamicImportNamespaceBindings(
|
|
2073
|
+
source,
|
|
2074
|
+
testingImport
|
|
2075
|
+
)) {
|
|
2076
|
+
runnerBindings.add(`${namespaceBinding}.${conformance.runner}`);
|
|
2077
|
+
casesFactoryBindings.add(`${namespaceBinding}.${conformance.casesFactory}`);
|
|
2078
|
+
}
|
|
1784
2079
|
|
|
1785
|
-
|
|
2080
|
+
const namespaceBindings = new Set(
|
|
2081
|
+
[...runnerBindings]
|
|
2082
|
+
.filter((binding) => binding.endsWith(`.${conformance.runner}`))
|
|
2083
|
+
.map((binding) => binding.slice(0, -conformance.runner.length - 1))
|
|
2084
|
+
);
|
|
2085
|
+
for (const alias of topLevelNamespaceAliases(ast, namespaceBindings)) {
|
|
2086
|
+
runnerBindings.add(`${alias}.${conformance.runner}`);
|
|
2087
|
+
casesFactoryBindings.add(`${alias}.${conformance.casesFactory}`);
|
|
1786
2088
|
}
|
|
1787
|
-
|
|
2089
|
+
|
|
2090
|
+
const allCasesFactoryBindings = [...casesFactoryBindings];
|
|
2091
|
+
return [...runnerBindings].some((runnerBinding) =>
|
|
1788
2092
|
runnerBindingProvesConformance(
|
|
1789
2093
|
source,
|
|
2094
|
+
ast,
|
|
1790
2095
|
targetEntry,
|
|
1791
2096
|
runnerBinding,
|
|
1792
|
-
|
|
2097
|
+
allCasesFactoryBindings
|
|
1793
2098
|
)
|
|
1794
2099
|
);
|
|
1795
2100
|
};
|
|
@@ -1923,126 +2228,244 @@ const assertToolingBoundary = (
|
|
|
1923
2228
|
}
|
|
1924
2229
|
};
|
|
1925
2230
|
|
|
2231
|
+
const sourceFilesForCandidate = (
|
|
2232
|
+
workspace: WorkspacePackage,
|
|
2233
|
+
candidate: AdapterCheckCandidate,
|
|
2234
|
+
sourceFiles: readonly string[]
|
|
2235
|
+
): readonly string[] => {
|
|
2236
|
+
if (!candidate.exportKey) {
|
|
2237
|
+
return sourceFiles;
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
const exportTarget = resolvableExportTarget(workspace, candidate.exportKey);
|
|
2241
|
+
if (!exportTarget) {
|
|
2242
|
+
return sourceFiles;
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
const normalizedTarget = normalizePath(exportTarget);
|
|
2246
|
+
const targetDir = normalizePath(dirname(normalizedTarget));
|
|
2247
|
+
if (normalizedTarget.endsWith('/index.ts')) {
|
|
2248
|
+
return sourceFiles.filter((sourceFile) =>
|
|
2249
|
+
normalizePath(sourceFile).startsWith(`${targetDir}/`)
|
|
2250
|
+
);
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
const targetStem = normalizedTarget.replace(/\.ts$/u, '');
|
|
2254
|
+
return sourceFiles.filter((sourceFile) => {
|
|
2255
|
+
const normalizedSourceFile = normalizePath(sourceFile);
|
|
2256
|
+
return (
|
|
2257
|
+
normalizedSourceFile === normalizedTarget ||
|
|
2258
|
+
normalizedSourceFile.startsWith(`${targetStem}.`) ||
|
|
2259
|
+
normalizedSourceFile.startsWith(`${targetStem}/`)
|
|
2260
|
+
);
|
|
2261
|
+
});
|
|
2262
|
+
};
|
|
2263
|
+
|
|
1926
2264
|
const checkAdapterPackage = (
|
|
1927
2265
|
workspace: WorkspacePackage,
|
|
1928
2266
|
targetById: ReadonlyMap<string, AdapterTargetCatalogEntry>
|
|
1929
2267
|
): {
|
|
1930
2268
|
readonly diagnostics: readonly AdapterCheckDiagnostic[];
|
|
1931
|
-
readonly
|
|
2269
|
+
readonly subjects: readonly AdapterCheckSubject[];
|
|
1932
2270
|
} => {
|
|
1933
2271
|
const packageName = workspace.manifest.name as string;
|
|
1934
2272
|
const diagnostics: AdapterCheckDiagnostic[] = [];
|
|
1935
2273
|
const placement = placementForWorkspace(workspace.workspacePath);
|
|
1936
2274
|
const metadata = trailAdapterMetadata(workspace.manifest);
|
|
2275
|
+
const subpathMetadata = trailSubpathAdapterMetadata(workspace.manifest);
|
|
1937
2276
|
|
|
1938
|
-
if (!placement || metadata === undefined) {
|
|
1939
|
-
return { diagnostics: [] };
|
|
2277
|
+
if ((!placement || metadata === undefined) && subpathMetadata === undefined) {
|
|
2278
|
+
return { diagnostics: [], subjects: [] };
|
|
1940
2279
|
}
|
|
1941
2280
|
|
|
1942
2281
|
assertPackageExports(workspace, diagnostics);
|
|
1943
2282
|
const sourceFiles = collectSourceFiles(join(workspace.packageRoot, 'src'));
|
|
1944
2283
|
assertToolingBoundary(workspace, sourceFiles, diagnostics);
|
|
1945
2284
|
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
2285
|
+
const subjects: AdapterCheckSubject[] = [];
|
|
2286
|
+
const checkCandidate = (
|
|
2287
|
+
candidate: AdapterCheckCandidate
|
|
2288
|
+
): AdapterCheckSubject | undefined => {
|
|
2289
|
+
if (
|
|
2290
|
+
candidate.exportKey &&
|
|
2291
|
+
!hasResolvableExport(workspace, candidate.exportKey)
|
|
2292
|
+
) {
|
|
2293
|
+
diagnostics.push(
|
|
1950
2294
|
diagnostic(
|
|
1951
2295
|
workspace.packageJsonPath,
|
|
1952
|
-
packageName,
|
|
1953
|
-
'
|
|
1954
|
-
`${packageName} must
|
|
1955
|
-
|
|
1956
|
-
placement
|
|
1957
|
-
)
|
|
1958
|
-
|
|
1959
|
-
}
|
|
1960
|
-
}
|
|
2296
|
+
candidate.packageName,
|
|
2297
|
+
'missing-package-export',
|
|
2298
|
+
`${packageName} must export "${candidate.exportKey}" so ${candidate.packageName} can be resolved as a subpath adapter.`,
|
|
2299
|
+
candidate.metadata.target,
|
|
2300
|
+
candidate.placement
|
|
2301
|
+
)
|
|
2302
|
+
);
|
|
2303
|
+
}
|
|
1961
2304
|
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
diagnostics: [
|
|
1966
|
-
...diagnostics,
|
|
2305
|
+
const targetEntry = targetById.get(candidate.metadata.target);
|
|
2306
|
+
if (!targetEntry) {
|
|
2307
|
+
diagnostics.push(
|
|
1967
2308
|
diagnostic(
|
|
1968
2309
|
workspace.packageJsonPath,
|
|
1969
|
-
packageName,
|
|
2310
|
+
candidate.packageName,
|
|
1970
2311
|
'unknown-adapter-target',
|
|
1971
|
-
`${packageName} declares unknown adapter target "${metadata.target}".`,
|
|
1972
|
-
metadata.target,
|
|
1973
|
-
placement
|
|
1974
|
-
)
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
2312
|
+
`${candidate.packageName} declares unknown adapter target "${candidate.metadata.target}".`,
|
|
2313
|
+
candidate.metadata.target,
|
|
2314
|
+
candidate.placement
|
|
2315
|
+
)
|
|
2316
|
+
);
|
|
2317
|
+
return undefined;
|
|
2318
|
+
}
|
|
1978
2319
|
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
2320
|
+
if (
|
|
2321
|
+
candidate.placement === 'subpath' &&
|
|
2322
|
+
targetEntry.ownerPackage !== packageName
|
|
2323
|
+
) {
|
|
2324
|
+
diagnostics.push(
|
|
2325
|
+
diagnostic(
|
|
2326
|
+
workspace.packageJsonPath,
|
|
2327
|
+
candidate.packageName,
|
|
2328
|
+
'invalid-adapter-metadata',
|
|
2329
|
+
`${candidate.packageName} declares target "${targetEntry.target}", but subpath adapters must live in the owner package ${targetEntry.ownerPackage}.`,
|
|
2330
|
+
targetEntry.target,
|
|
2331
|
+
candidate.placement
|
|
2332
|
+
)
|
|
2333
|
+
);
|
|
2334
|
+
return undefined;
|
|
2335
|
+
}
|
|
1991
2336
|
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
2337
|
+
if (!targetEntry.placements.includes(candidate.placement)) {
|
|
2338
|
+
diagnostics.push(
|
|
2339
|
+
diagnostic(
|
|
2340
|
+
workspace.packageJsonPath,
|
|
2341
|
+
candidate.packageName,
|
|
2342
|
+
'unsupported-placement',
|
|
2343
|
+
`${targetEntry.ownerPackage}:${targetEntry.target} does not support ${candidate.placement} adapter placement.`,
|
|
2344
|
+
targetEntry.target,
|
|
2345
|
+
candidate.placement
|
|
2346
|
+
)
|
|
2347
|
+
);
|
|
2348
|
+
}
|
|
1995
2349
|
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
: [];
|
|
2350
|
+
if (candidate.placement === 'extracted') {
|
|
2351
|
+
assertDependencyDirection(workspace, targetEntry, diagnostics);
|
|
2352
|
+
}
|
|
2000
2353
|
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
'missing-owner-conformance',
|
|
2007
|
-
`${targetEntry.ownerPackage}:${targetEntry.target} must declare testingImport before adapters can prove conformance.`,
|
|
2008
|
-
targetEntry.target,
|
|
2009
|
-
placement
|
|
2010
|
-
)
|
|
2354
|
+
const { conformance, testingImport } = targetEntry;
|
|
2355
|
+
const candidateSourceFiles = sourceFilesForCandidate(
|
|
2356
|
+
workspace,
|
|
2357
|
+
candidate,
|
|
2358
|
+
sourceFiles
|
|
2011
2359
|
);
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
workspace.packageJsonPath,
|
|
2019
|
-
packageName,
|
|
2020
|
-
'missing-conformance',
|
|
2021
|
-
`${packageName} must import ${testingImport} from a conformance test${conformanceHint}.`,
|
|
2022
|
-
targetEntry.target,
|
|
2023
|
-
placement
|
|
2024
|
-
)
|
|
2025
|
-
);
|
|
2026
|
-
}
|
|
2360
|
+
const conformanceTestPaths = testingImport
|
|
2361
|
+
? pathsProvingConformance(
|
|
2362
|
+
candidateSourceFiles.filter(isTestFile),
|
|
2363
|
+
targetEntry
|
|
2364
|
+
)
|
|
2365
|
+
: [];
|
|
2027
2366
|
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2367
|
+
if (!testingImport) {
|
|
2368
|
+
diagnostics.push(
|
|
2369
|
+
diagnostic(
|
|
2370
|
+
workspace.packageJsonPath,
|
|
2371
|
+
candidate.packageName,
|
|
2372
|
+
'missing-owner-conformance',
|
|
2373
|
+
`${targetEntry.ownerPackage}:${targetEntry.target} must declare testingImport before adapters can prove conformance.`,
|
|
2374
|
+
targetEntry.target,
|
|
2375
|
+
candidate.placement
|
|
2376
|
+
)
|
|
2377
|
+
);
|
|
2378
|
+
} else if (conformanceTestPaths.length === 0) {
|
|
2379
|
+
const conformanceHint = conformance
|
|
2380
|
+
? ` and call ${conformance.runner}(adapter, ${conformance.casesFactory}(...))`
|
|
2381
|
+
: '';
|
|
2382
|
+
diagnostics.push(
|
|
2383
|
+
diagnostic(
|
|
2384
|
+
workspace.packageJsonPath,
|
|
2385
|
+
candidate.packageName,
|
|
2386
|
+
'missing-conformance',
|
|
2387
|
+
`${candidate.packageName} must import ${testingImport} from a conformance test${conformanceHint}.`,
|
|
2388
|
+
targetEntry.target,
|
|
2389
|
+
candidate.placement
|
|
2390
|
+
)
|
|
2391
|
+
);
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
return {
|
|
2031
2395
|
conformanceTestPaths,
|
|
2032
|
-
key:
|
|
2396
|
+
key: candidate.key,
|
|
2033
2397
|
ownerPackage: targetEntry.ownerPackage,
|
|
2034
2398
|
packageJsonPath: workspace.packageJsonPath,
|
|
2035
|
-
packageName,
|
|
2399
|
+
packageName: candidate.packageName,
|
|
2036
2400
|
packageRoot: workspace.packageRoot,
|
|
2037
|
-
placement,
|
|
2401
|
+
placement: candidate.placement,
|
|
2038
2402
|
target: targetEntry.target,
|
|
2039
2403
|
targetKey: targetEntry.key,
|
|
2040
2404
|
...(conformance?.adapterType
|
|
2041
2405
|
? { adapterType: conformance.adapterType }
|
|
2042
2406
|
: {}),
|
|
2043
2407
|
...(testingImport ? { testingImport } : {}),
|
|
2044
|
-
}
|
|
2408
|
+
};
|
|
2045
2409
|
};
|
|
2410
|
+
|
|
2411
|
+
if (placement && metadata !== undefined) {
|
|
2412
|
+
if (metadata === null) {
|
|
2413
|
+
diagnostics.push(
|
|
2414
|
+
diagnostic(
|
|
2415
|
+
workspace.packageJsonPath,
|
|
2416
|
+
packageName,
|
|
2417
|
+
'invalid-adapter-metadata',
|
|
2418
|
+
`${packageName} must declare trails.adapter as an object with a kebab-case target string.`,
|
|
2419
|
+
undefined,
|
|
2420
|
+
placement
|
|
2421
|
+
)
|
|
2422
|
+
);
|
|
2423
|
+
} else {
|
|
2424
|
+
const subject = checkCandidate({
|
|
2425
|
+
key: packageName,
|
|
2426
|
+
metadata,
|
|
2427
|
+
packageName,
|
|
2428
|
+
placement,
|
|
2429
|
+
});
|
|
2430
|
+
if (subject) {
|
|
2431
|
+
subjects.push(subject);
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
if (subpathMetadata === null) {
|
|
2437
|
+
diagnostics.push(
|
|
2438
|
+
diagnostic(
|
|
2439
|
+
workspace.packageJsonPath,
|
|
2440
|
+
packageName,
|
|
2441
|
+
'invalid-adapter-metadata',
|
|
2442
|
+
`${packageName} must declare trails.adapters as an object keyed by exported subpath, with each value declaring a kebab-case target string.`,
|
|
2443
|
+
undefined,
|
|
2444
|
+
'subpath'
|
|
2445
|
+
)
|
|
2446
|
+
);
|
|
2447
|
+
} else if (subpathMetadata !== undefined) {
|
|
2448
|
+
for (const subpathAdapter of subpathMetadata) {
|
|
2449
|
+
const subpathPackageName = `${packageName}/${subpathAdapter.exportKey.slice(2)}`;
|
|
2450
|
+
const targetEntry = targetById.get(subpathAdapter.target);
|
|
2451
|
+
const subpathPlacement =
|
|
2452
|
+
targetEntry?.ownerPackage === packageName
|
|
2453
|
+
? 'subpath'
|
|
2454
|
+
: (placement ?? 'subpath');
|
|
2455
|
+
const subject = checkCandidate({
|
|
2456
|
+
exportKey: subpathAdapter.exportKey,
|
|
2457
|
+
key: subpathPackageName,
|
|
2458
|
+
metadata: subpathAdapter,
|
|
2459
|
+
packageName: subpathPackageName,
|
|
2460
|
+
placement: subpathPlacement,
|
|
2461
|
+
});
|
|
2462
|
+
if (subject) {
|
|
2463
|
+
subjects.push(subject);
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
return { diagnostics, subjects };
|
|
2046
2469
|
};
|
|
2047
2470
|
|
|
2048
2471
|
export const checkAdapters = (rootDir: string): AdapterCheckReport => {
|
|
@@ -2056,9 +2479,7 @@ export const checkAdapters = (rootDir: string): AdapterCheckReport => {
|
|
|
2056
2479
|
for (const workspace of workspacePackages(rootDir)) {
|
|
2057
2480
|
const result = checkAdapterPackage(workspace, targetById);
|
|
2058
2481
|
diagnostics.push(...result.diagnostics);
|
|
2059
|
-
|
|
2060
|
-
subjects.push(result.subject);
|
|
2061
|
-
}
|
|
2482
|
+
subjects.push(...result.subjects);
|
|
2062
2483
|
}
|
|
2063
2484
|
|
|
2064
2485
|
const sortedSubjects = subjects.toSorted((left, right) =>
|