@atlaskit/eslint-plugin-platform 2.8.0 → 2.9.1

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 (47) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/index.js +8 -1
  3. package/dist/cjs/rules/ensure-critical-dependency-resolutions/index.js +0 -1
  4. package/dist/cjs/rules/ensure-use-sync-external-store-server-snapshot/index.js +41 -0
  5. package/dist/cjs/rules/import/no-barrel-entry-imports/index.js +534 -74
  6. package/dist/cjs/rules/import/no-barrel-entry-jest-mock/index.js +428 -119
  7. package/dist/cjs/rules/import/no-jest-mock-barrel-files/index.js +3 -2
  8. package/dist/cjs/rules/import/no-relative-barrel-file-imports/index.js +7 -3
  9. package/dist/cjs/rules/import/shared/jest-utils.js +62 -9
  10. package/dist/cjs/rules/import/shared/package-resolution.js +300 -22
  11. package/dist/cjs/rules/no-restricted-fedramp-imports/index.js +65 -0
  12. package/dist/cjs/rules/visit-example-type-import-required/index.js +409 -0
  13. package/dist/es2019/index.js +8 -1
  14. package/dist/es2019/rules/ensure-critical-dependency-resolutions/index.js +0 -1
  15. package/dist/es2019/rules/ensure-use-sync-external-store-server-snapshot/index.js +43 -0
  16. package/dist/es2019/rules/import/no-barrel-entry-imports/index.js +431 -25
  17. package/dist/es2019/rules/import/no-barrel-entry-jest-mock/index.js +287 -25
  18. package/dist/es2019/rules/import/no-jest-mock-barrel-files/index.js +3 -2
  19. package/dist/es2019/rules/import/no-relative-barrel-file-imports/index.js +7 -3
  20. package/dist/es2019/rules/import/shared/jest-utils.js +44 -0
  21. package/dist/es2019/rules/import/shared/package-resolution.js +211 -4
  22. package/dist/es2019/rules/no-restricted-fedramp-imports/index.js +47 -0
  23. package/dist/es2019/rules/visit-example-type-import-required/index.js +375 -0
  24. package/dist/esm/index.js +8 -1
  25. package/dist/esm/rules/ensure-critical-dependency-resolutions/index.js +0 -1
  26. package/dist/esm/rules/ensure-use-sync-external-store-server-snapshot/index.js +35 -0
  27. package/dist/esm/rules/import/no-barrel-entry-imports/index.js +535 -75
  28. package/dist/esm/rules/import/no-barrel-entry-jest-mock/index.js +430 -121
  29. package/dist/esm/rules/import/no-jest-mock-barrel-files/index.js +3 -2
  30. package/dist/esm/rules/import/no-relative-barrel-file-imports/index.js +7 -3
  31. package/dist/esm/rules/import/shared/jest-utils.js +61 -9
  32. package/dist/esm/rules/import/shared/package-resolution.js +298 -24
  33. package/dist/esm/rules/no-restricted-fedramp-imports/index.js +59 -0
  34. package/dist/esm/rules/visit-example-type-import-required/index.js +402 -0
  35. package/dist/types/index.d.ts +14 -0
  36. package/dist/types/rules/ensure-use-sync-external-store-server-snapshot/index.d.ts +3 -0
  37. package/dist/types/rules/import/shared/jest-utils.d.ts +8 -0
  38. package/dist/types/rules/import/shared/package-resolution.d.ts +47 -2
  39. package/dist/types/rules/no-restricted-fedramp-imports/index.d.ts +3 -0
  40. package/dist/types/rules/visit-example-type-import-required/index.d.ts +4 -0
  41. package/dist/types-ts4.5/index.d.ts +14 -0
  42. package/dist/types-ts4.5/rules/ensure-use-sync-external-store-server-snapshot/index.d.ts +3 -0
  43. package/dist/types-ts4.5/rules/import/shared/jest-utils.d.ts +8 -0
  44. package/dist/types-ts4.5/rules/import/shared/package-resolution.d.ts +47 -2
  45. package/dist/types-ts4.5/rules/no-restricted-fedramp-imports/index.d.ts +3 -0
  46. package/dist/types-ts4.5/rules/visit-example-type-import-required/index.d.ts +4 -0
  47. package/package.json +3 -1
@@ -0,0 +1,409 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = exports.RULE_NAME = void 0;
8
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
9
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
10
+ var _fs = _interopRequireDefault(require("fs"));
11
+ var _path = _interopRequireDefault(require("path"));
12
+ var _utils = require("@typescript-eslint/utils");
13
+ var _typescriptEstree = require("@typescript-eslint/typescript-estree");
14
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
15
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
16
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
17
+ var RULE_NAME = exports.RULE_NAME = 'visit-example-type-import-required';
18
+ var messages = {
19
+ missingTypeofImport: 'visitExample must use typeof import(...) generic type parameter. ' + 'Use visitExample<typeof import("path/to/example.tsx")>(groupId, packageId, exampleId).',
20
+ invalidTypeParameter: 'visitExample generic type parameter must be a typeof import(...) expression.',
21
+ pathMismatch: 'The import path "{{ importPath }}" does not match the expected example file for ' + 'visitExample({{ groupId }}, {{ packageId }}, {{ exampleId }}). ' + 'Expected import to resolve to: {{ expectedPath }}',
22
+ noPackageImports: 'Package imports (e.g., @atlaskit/...) are not allowed in visitExample type parameters. Use a relative import path.',
23
+ typeAliasNotInlined: 'Type aliases for typeof import(...) must be inlined directly into the visitExample call. ' + 'Use visitExample<typeof import("...")>(...) instead of defining a type alias.',
24
+ suggestFixPath: 'Update import path to match visitExample arguments'
25
+ };
26
+ function isTargetFile(filename) {
27
+ return filename.endsWith('.spec.tsx') || filename.endsWith('.spec.ts');
28
+ }
29
+
30
+ /**
31
+ * Extracts the import path string from a TSTypeQuery node of the form `typeof import('...')`.
32
+ * Returns null if the node doesn't match that shape.
33
+ */
34
+ function extractImportPathFromTypeQuery(typeQuery) {
35
+ // TSTypeQuery { exprName: TSImportType { argument: TSLiteralType { literal: Literal } } }
36
+ var exprName = typeQuery.exprName;
37
+ if (exprName.type !== _utils.AST_NODE_TYPES.TSImportType) {
38
+ return null;
39
+ }
40
+ var argument = exprName.argument;
41
+ if (argument.type === _utils.AST_NODE_TYPES.TSLiteralType && argument.literal.type === _utils.AST_NODE_TYPES.Literal && typeof argument.literal.value === 'string') {
42
+ return argument.literal.value;
43
+ }
44
+ return null;
45
+ }
46
+ /**
47
+ * Builds a map of all TSTypeAliasDeclaration nodes in the file, keyed by name.
48
+ * Each entry records whether the alias is at the top level of the program (file-level).
49
+ */
50
+ function collectTypeAliases(ast) {
51
+ var result = new Map();
52
+
53
+ // Cast through unknown to work around a version mismatch: @typescript-eslint/utils
54
+ // vendors its own copy of @typescript-eslint/types (v7) while the root node_modules
55
+ // has a different version (v5). The TSESTree types are structurally identical at
56
+ // runtime — the cast is safe.
57
+ (0, _typescriptEstree.simpleTraverse)(ast, {
58
+ enter: function enter(node, parent) {
59
+ // A type alias is "file-level" if its immediate parent is the Program,
60
+ // or if it's the declaration of a top-level ExportNamedDeclaration.
61
+ if (node.type === _utils.AST_NODE_TYPES.TSTypeAliasDeclaration) {
62
+ var isFileLevel = (parent === null || parent === void 0 ? void 0 : parent.type) === _utils.AST_NODE_TYPES.Program || (parent === null || parent === void 0 ? void 0 : parent.type) === _utils.AST_NODE_TYPES.ExportNamedDeclaration;
63
+ result.set(node.id.name, {
64
+ node: node,
65
+ isFileLevel: isFileLevel
66
+ });
67
+ }
68
+ }
69
+ });
70
+ return result;
71
+ }
72
+
73
+ /**
74
+ * Resolves a top-level `const foo = 'literal'` declaration to its string value.
75
+ * Returns null for non-const, non-string, or not-found variables.
76
+ */
77
+ function resolveVariableToConstant(programBody, variableName, cache) {
78
+ if (cache.has(variableName)) {
79
+ var _cache$get;
80
+ return (_cache$get = cache.get(variableName)) !== null && _cache$get !== void 0 ? _cache$get : null;
81
+ }
82
+ var _iterator = _createForOfIteratorHelper(programBody),
83
+ _step;
84
+ try {
85
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
86
+ var node = _step.value;
87
+ if (node.type !== _utils.AST_NODE_TYPES.VariableDeclaration || node.kind !== 'const') {
88
+ continue;
89
+ }
90
+ var _iterator2 = _createForOfIteratorHelper(node.declarations),
91
+ _step2;
92
+ try {
93
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
94
+ var _declarator$init;
95
+ var declarator = _step2.value;
96
+ if (declarator.id.type === _utils.AST_NODE_TYPES.Identifier && declarator.id.name === variableName && ((_declarator$init = declarator.init) === null || _declarator$init === void 0 ? void 0 : _declarator$init.type) === _utils.AST_NODE_TYPES.Literal && typeof declarator.init.value === 'string') {
97
+ cache.set(variableName, declarator.init.value);
98
+ return declarator.init.value;
99
+ }
100
+ }
101
+ } catch (err) {
102
+ _iterator2.e(err);
103
+ } finally {
104
+ _iterator2.f();
105
+ }
106
+ }
107
+ } catch (err) {
108
+ _iterator.e(err);
109
+ } finally {
110
+ _iterator.f();
111
+ }
112
+ cache.set(variableName, null);
113
+ return null;
114
+ }
115
+ /**
116
+ * Extracts the (groupId, packageId, exampleId) string arguments from a visitExample call.
117
+ * Each argument may be a string literal or a reference to a top-level const string variable.
118
+ * Returns null for any argument that can't be statically resolved.
119
+ */
120
+ function extractCallArgs(node, programBody, variableCache) {
121
+ if (node.arguments.length < 3) {
122
+ return null;
123
+ }
124
+ function resolveArg(arg) {
125
+ if (arg.type === _utils.AST_NODE_TYPES.Literal && typeof arg.value === 'string') {
126
+ return arg.value;
127
+ }
128
+ if (arg.type === _utils.AST_NODE_TYPES.Identifier) {
129
+ return resolveVariableToConstant(programBody, arg.name, variableCache);
130
+ }
131
+ return null;
132
+ }
133
+ var groupId = resolveArg(node.arguments[0]);
134
+ var packageId = resolveArg(node.arguments[1]);
135
+ var exampleId = resolveArg(node.arguments[2]);
136
+ if (!groupId || !packageId || !exampleId) {
137
+ return null;
138
+ }
139
+ return {
140
+ groupId: groupId,
141
+ packageId: packageId,
142
+ exampleId: exampleId
143
+ };
144
+ }
145
+ function getPackagesBasePath(testFilePath) {
146
+ var testFileDir = _path.default.dirname(testFilePath);
147
+ var testFileSegments = testFileDir.split(_path.default.sep);
148
+ var packagesIndex = testFileSegments.findIndex(function (seg) {
149
+ return seg === 'packages';
150
+ });
151
+ if (packagesIndex === -1) {
152
+ return null;
153
+ }
154
+ var baseSegments = testFileSegments.slice(0, packagesIndex + 1);
155
+ var basePath = _path.default.isAbsolute(testFilePath) ? _path.default.resolve.apply(_path.default, ['/'].concat((0, _toConsumableArray2.default)(baseSegments))) : _path.default.resolve.apply(_path.default, [process.cwd()].concat((0, _toConsumableArray2.default)(baseSegments)));
156
+ return {
157
+ basePath: basePath,
158
+ packagesIndex: packagesIndex
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Resolves the expected example file path from visitExample arguments.
164
+ *
165
+ * visitExample('groupId', 'packageId', 'exampleId') maps to:
166
+ * packages/{groupId}/{packageId}/examples/{exampleId}.tsx
167
+ *
168
+ * Example files may also have a numeric sort prefix, e.g.:
169
+ * packages/{groupId}/{packageId}/examples/00-{exampleId}.tsx
170
+ *
171
+ * We scan the examples directory once and match against all candidates.
172
+ * Falls back to the bare `{exampleId}.tsx` name when the directory can't
173
+ * be read (e.g. in unit-test environments where the files don't exist).
174
+ */
175
+ function resolveExamplePathFromArgs(groupId, packageId, exampleId, testFilePath) {
176
+ var packagesBase = getPackagesBasePath(testFilePath);
177
+ if (!packagesBase) {
178
+ return null;
179
+ }
180
+ var examplesDir = _path.default.resolve(packagesBase.basePath, groupId, packageId, 'examples');
181
+ var fallback = _path.default.resolve(examplesDir, "".concat(exampleId, ".tsx"));
182
+
183
+ // Match: exact name OR numeric-prefixed variant, with optional `.examples` infix
184
+ var candidateRe = new RegExp("^(?:\\d+-)?".concat(exampleId, "(?:\\.examples?)?\\.tsx$"));
185
+ try {
186
+ var match = _fs.default.readdirSync(examplesDir).find(function (f) {
187
+ return candidateRe.test(f);
188
+ });
189
+ if (match) {
190
+ return _path.default.resolve(examplesDir, match);
191
+ }
192
+ } catch (_unused) {
193
+ // Directory doesn't exist or can't be read (e.g. in test environments)
194
+ }
195
+ return fallback;
196
+ }
197
+
198
+ /**
199
+ * Computes a relative import path from one file to another
200
+ */
201
+ function computeRelativeImportPath(fromFile, toFile) {
202
+ var fromDir = _path.default.dirname(fromFile);
203
+ var relativePath = _path.default.relative(fromDir, toFile);
204
+ // Normalize to forward slashes for import statements (standard in JavaScript/TypeScript)
205
+ relativePath = relativePath.replace(/\\/g, '/');
206
+ // Ensure relative imports start with ./ or ../
207
+ if (!relativePath.startsWith('.') && !relativePath.startsWith('/')) {
208
+ relativePath = "./".concat(relativePath);
209
+ }
210
+ return relativePath;
211
+ }
212
+ /**
213
+ * Extracts the generic type argument from a `visitExample<...>(...)` call expression
214
+ * using the AST directly (no regex on source text).
215
+ *
216
+ * Returns:
217
+ * { type: 'inline', importPath } — for `visitExample<typeof import('...')>(...)`
218
+ * { type: 'alias', name } — for `visitExample<SomeTypeAlias>(...)`
219
+ * null — if no generic type parameter is present
220
+ */
221
+ function extractGenericType(node) {
222
+ var _params, _ref, _node$typeArguments;
223
+ // `typeArguments` is the current property name; `typeParameters` is the deprecated alias.
224
+ // We fall back to `typeParameters` for compatibility with older parser versions.
225
+ var params = (_params = (_ref = (_node$typeArguments = node.typeArguments) !== null && _node$typeArguments !== void 0 ? _node$typeArguments : node.typeParameters) === null || _ref === void 0 ? void 0 : _ref.params) !== null && _params !== void 0 ? _params : [];
226
+ if (params.length === 0) {
227
+ return null;
228
+ }
229
+ var _params2 = (0, _slicedToArray2.default)(params, 1),
230
+ typeParam = _params2[0];
231
+
232
+ // `typeof import('...')` → TSTypeQuery { exprName: TSImportType { ... } }
233
+ if (typeParam.type === _utils.AST_NODE_TYPES.TSTypeQuery) {
234
+ var importPath = extractImportPathFromTypeQuery(typeParam);
235
+ if (importPath !== null) {
236
+ return {
237
+ type: 'inline',
238
+ importPath: importPath
239
+ };
240
+ }
241
+ }
242
+
243
+ // `SomeTypeAlias` → TSTypeReference { typeName: Identifier { name } }
244
+ if (typeParam.type === _utils.AST_NODE_TYPES.TSTypeReference && typeParam.typeName.type === _utils.AST_NODE_TYPES.Identifier) {
245
+ return {
246
+ type: 'alias',
247
+ name: typeParam.typeName.name
248
+ };
249
+ }
250
+ return null;
251
+ }
252
+ var rule = {
253
+ meta: {
254
+ type: 'problem',
255
+ docs: {
256
+ description: 'Ensures that visitExample uses a typeof import(...) generic and that the import path matches the example file resolved from the call arguments.'
257
+ },
258
+ fixable: 'code',
259
+ messages: messages,
260
+ schema: []
261
+ },
262
+ create: function create(context) {
263
+ var filename = context.filename;
264
+ var ast = context.sourceCode.ast;
265
+ var programBody = ast.body;
266
+
267
+ // Build the type alias map once per file (lazily on first visitExample call)
268
+ var typeAliases = null;
269
+ function getTypeAliases() {
270
+ if (!typeAliases) {
271
+ typeAliases = collectTypeAliases(ast);
272
+ }
273
+ return typeAliases;
274
+ }
275
+ var variableCache = new Map();
276
+ return {
277
+ CallExpression: function CallExpression(estreeNode) {
278
+ if (!isTargetFile(filename)) {
279
+ return;
280
+ }
281
+ var node = estreeNode;
282
+ // Only handle `<anything>.visitExample(...)` calls
283
+ if (node.callee.type !== _utils.AST_NODE_TYPES.MemberExpression || node.callee.property.type !== _utils.AST_NODE_TYPES.Identifier || node.callee.property.name !== 'visitExample') {
284
+ return;
285
+ }
286
+
287
+ // Narrow callee — we've confirmed property is an Identifier above
288
+ var callee = node.callee;
289
+ // reportCallee is typed as estree.Node for context.report compatibility
290
+ var reportCallee = estreeNode.callee;
291
+ var genericType = extractGenericType(node);
292
+
293
+ // ── Case 1: No generic type parameter ────────────────────────────────
294
+ if (genericType === null) {
295
+ var _args = extractCallArgs(node, programBody, variableCache);
296
+ context.report({
297
+ node: reportCallee,
298
+ messageId: 'missingTypeofImport',
299
+ fix: function fix(fixer) {
300
+ if (!_args) {
301
+ return null;
302
+ }
303
+ var examplePath = resolveExamplePathFromArgs(_args.groupId, _args.packageId, _args.exampleId, filename);
304
+ if (!examplePath) {
305
+ return null;
306
+ }
307
+ var importPath = computeRelativeImportPath(filename, examplePath);
308
+ var _ref2 = callee.property.range,
309
+ _ref3 = (0, _slicedToArray2.default)(_ref2, 2),
310
+ start = _ref3[0],
311
+ end = _ref3[1];
312
+ return fixer.insertTextAfterRange([start, end], "<typeof import('".concat(importPath, "')>"));
313
+ }
314
+ });
315
+ return;
316
+ }
317
+
318
+ // ── Case 2: Generic is a type alias reference (`visitExample<Foo>`) ──
319
+ var importPath;
320
+ if (genericType.type === 'alias') {
321
+ var found = getTypeAliases().get(genericType.name);
322
+ if (!found) {
323
+ // Unknown type alias — not a typeof import
324
+ context.report({
325
+ node: reportCallee,
326
+ messageId: 'missingTypeofImport'
327
+ });
328
+ return;
329
+ }
330
+ if (found.isFileLevel) {
331
+ // Top-level `type Foo = typeof import(...)` is disallowed
332
+ context.report({
333
+ node: reportCallee,
334
+ messageId: 'typeAliasNotInlined'
335
+ });
336
+ return;
337
+ }
338
+ var typeAnnotation = found.node.typeAnnotation;
339
+ if (typeAnnotation.type !== _utils.AST_NODE_TYPES.TSTypeQuery) {
340
+ context.report({
341
+ node: reportCallee,
342
+ messageId: 'missingTypeofImport'
343
+ });
344
+ return;
345
+ }
346
+ var resolved = extractImportPathFromTypeQuery(typeAnnotation);
347
+ if (!resolved) {
348
+ context.report({
349
+ node: reportCallee,
350
+ messageId: 'missingTypeofImport'
351
+ });
352
+ return;
353
+ }
354
+ importPath = resolved;
355
+ } else {
356
+ // ── Case 3: Inline `typeof import('...')` ────────────────────────
357
+ importPath = genericType.importPath;
358
+ }
359
+
360
+ // Package-scoped imports (e.g. @atlaskit/foo/examples/...) are not allowed
361
+ if (importPath.startsWith('@')) {
362
+ context.report({
363
+ node: reportCallee,
364
+ messageId: 'noPackageImports'
365
+ });
366
+ return;
367
+ }
368
+
369
+ // Validate that the import path matches the arguments
370
+ var args = extractCallArgs(node, programBody, variableCache);
371
+ if (!args) {
372
+ // Dynamic arguments — can't validate statically
373
+ return;
374
+ }
375
+ var expectedPath = resolveExamplePathFromArgs(args.groupId, args.packageId, args.exampleId, filename);
376
+ if (!expectedPath) {
377
+ return;
378
+ }
379
+ var resolvedImport = _path.default.normalize(_path.default.resolve(_path.default.dirname(filename), importPath));
380
+ var resolvedExpected = _path.default.normalize(expectedPath);
381
+
382
+ // Compare without extensions so `.tsx` vs no extension doesn't matter
383
+ if (resolvedImport.replace(/\.(tsx?|jsx?)$/, '') !== resolvedExpected.replace(/\.(tsx?|jsx?)$/, '')) {
384
+ context.report({
385
+ node: estreeNode.arguments[0],
386
+ messageId: 'pathMismatch',
387
+ data: {
388
+ importPath: importPath,
389
+ groupId: args.groupId,
390
+ packageId: args.packageId,
391
+ exampleId: args.exampleId,
392
+ expectedPath: resolvedExpected
393
+ },
394
+ fix: function fix(fixer) {
395
+ var _node$typeArguments2;
396
+ var correctedPath = computeRelativeImportPath(filename, resolvedExpected);
397
+ var typeParams = (_node$typeArguments2 = node.typeArguments) !== null && _node$typeArguments2 !== void 0 ? _node$typeArguments2 : node.typeParameters;
398
+ if (!(typeParams !== null && typeParams !== void 0 && typeParams.range)) {
399
+ return null;
400
+ }
401
+ return fixer.replaceTextRange(typeParams.range, "<typeof import('".concat(correctedPath, "')>"));
402
+ }
403
+ });
404
+ }
405
+ }
406
+ };
407
+ }
408
+ };
409
+ var _default = exports.default = rule;
@@ -29,11 +29,14 @@ import noSparseCheckout from './rules/no-sparse-checkout';
29
29
  import noDirectDocumentUsage from './rules/no-direct-document-usage';
30
30
  import noSetImmediate from './rules/no-set-immediate';
31
31
  import preferCryptoRandomUuid from './rules/prefer-crypto-random-uuid';
32
+ import noRestrictedFedrampImports from './rules/no-restricted-fedramp-imports';
32
33
  import noBarrelEntryImports from './rules/import/no-barrel-entry-imports';
33
34
  import noBarrelEntryJestMock from './rules/import/no-barrel-entry-jest-mock';
34
35
  import noJestMockBarrelFiles from './rules/import/no-jest-mock-barrel-files';
35
36
  import noRelativeBarrelFileImports from './rules/import/no-relative-barrel-file-imports';
36
37
  import noConversationAssistantBarrelImports from './rules/import/no-conversation-assistant-barrel-imports';
38
+ import visitExampleTypeImportRequired from './rules/visit-example-type-import-required';
39
+ import ensureUseSyncExternalStoreServerSnapshot from './rules/ensure-use-sync-external-store-server-snapshot';
37
40
  import { join, normalize } from 'node:path';
38
41
  import { readFileSync } from 'node:fs';
39
42
  let jiraRoot;
@@ -83,15 +86,19 @@ const rules = {
83
86
  'no-direct-document-usage': noDirectDocumentUsage,
84
87
  'no-set-immediate': noSetImmediate,
85
88
  'prefer-crypto-random-uuid': preferCryptoRandomUuid,
89
+ 'no-restricted-fedramp-imports': noRestrictedFedrampImports,
86
90
  'no-barrel-entry-imports': noBarrelEntryImports,
87
91
  'no-barrel-entry-jest-mock': noBarrelEntryJestMock,
88
92
  'no-jest-mock-barrel-files': noJestMockBarrelFiles,
89
93
  'no-relative-barrel-file-imports': noRelativeBarrelFileImports,
90
- 'no-conversation-assistant-barrel-imports': noConversationAssistantBarrelImports
94
+ 'no-conversation-assistant-barrel-imports': noConversationAssistantBarrelImports,
95
+ 'visit-example-type-import-required': visitExampleTypeImportRequired,
96
+ 'ensure-use-sync-external-store-server-snapshot': ensureUseSyncExternalStoreServerSnapshot
91
97
  };
92
98
  const commonConfig = {
93
99
  '@atlaskit/platform/ensure-test-runner-arguments': 'error',
94
100
  '@atlaskit/platform/ensure-test-runner-nested-count': 'warn',
101
+ '@atlaskit/platform/ensure-use-sync-external-store-server-snapshot': 'error',
95
102
  '@atlaskit/platform/no-invalid-feature-flag-usage': 'error',
96
103
  '@atlaskit/platform/no-invalid-storybook-decorator-usage': 'error',
97
104
  '@atlaskit/platform/ensure-atlassian-team': 'error',
@@ -11,7 +11,6 @@ const DESIRED_PKG_VERSIONS = {
11
11
  tslib: ['2.6', '2.8'],
12
12
  '@types/react': ['16.14', '18.2', '18.3'],
13
13
  'react-relay': ['npm:atl-react-relay@0.0.0-main-39e79f66'],
14
- 'relay-compiler': ['npm:atl-relay-compiler@0.0.0-main-39e79f66'],
15
14
  'relay-runtime': ['npm:atl-relay-runtime@0.0.0-main-39e79f66'],
16
15
  'relay-test-utils': ['npm:atl-relay-test-utils@0.0.0-main-39e79f66']
17
16
  };
@@ -0,0 +1,43 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
+
3
+ const FUNCTION_NAME = 'useSyncExternalStore';
4
+ const rule = {
5
+ meta: {
6
+ type: 'problem',
7
+ docs: {
8
+ description: `Enforce that ${FUNCTION_NAME} is called with a third argument (getServerSnapshot) for SSR compatibility`,
9
+ recommended: true
10
+ },
11
+ messages: {
12
+ missingServerSnapshot: `'${FUNCTION_NAME}' must be called with a third argument (getServerSnapshot). Without it, React will throw during server-side rendering.
13
+
14
+ If your component relies on browser-only APIs (e.g. localStorage, WebRTC, WebGL) and must not render on the server, pass \`() => null\` (or another stable fallback) as the third argument — this is the correct way to opt out of SSR, not an omission.
15
+
16
+ Prefer higher-level APIs that wrap ${FUNCTION_NAME} where available, as they handle SSR concerns for you.
17
+
18
+ See the React docs for usage guidance: https://react.dev/reference/react/useSyncExternalStore`
19
+ }
20
+ },
21
+ create(context) {
22
+ return {
23
+ CallExpression(node) {
24
+ const {
25
+ callee,
26
+ arguments: args
27
+ } = node;
28
+ const isDirectCall = callee.type === 'Identifier' && callee.name === FUNCTION_NAME;
29
+ const isMemberCall = callee.type === 'MemberExpression' && callee.property.type === 'Identifier' && callee.property.name === FUNCTION_NAME;
30
+ if (!isDirectCall && !isMemberCall) {
31
+ return;
32
+ }
33
+ if (args.length < 3) {
34
+ context.report({
35
+ node,
36
+ messageId: 'missingServerSnapshot'
37
+ });
38
+ }
39
+ }
40
+ };
41
+ }
42
+ };
43
+ export default rule;