@atlaskit/eslint-plugin-platform 2.7.1 → 2.8.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.
Files changed (125) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/index.js +17 -9
  3. package/dist/cjs/rules/constants.js +1 -1
  4. package/dist/cjs/rules/ensure-critical-dependency-resolutions/index.js +5 -5
  5. package/dist/cjs/rules/ensure-no-private-dependencies/index.js +48 -66
  6. package/dist/cjs/rules/feature-gating/inline-usage/index.js +14 -3
  7. package/dist/cjs/rules/feature-gating/no-alias/index.js +1 -1
  8. package/dist/cjs/rules/feature-gating/no-module-level-eval/index.js +1 -1
  9. package/dist/cjs/rules/feature-gating/no-module-level-eval-nav4/index.js +1 -1
  10. package/dist/cjs/rules/feature-gating/no-preconditioning/index.js +4 -1
  11. package/dist/cjs/rules/feature-gating/prefer-fg/index.js +1 -1
  12. package/dist/cjs/rules/feature-gating/static-feature-flags/index.js +2 -2
  13. package/dist/cjs/rules/feature-gating/use-recommended-utils/index.js +1 -1
  14. package/dist/cjs/rules/feature-gating/valid-gate-name/index.js +60 -0
  15. package/dist/cjs/rules/import/no-barrel-entry-imports/index.js +871 -0
  16. package/dist/cjs/rules/import/no-barrel-entry-jest-mock/index.js +1384 -0
  17. package/dist/cjs/rules/import/no-conversation-assistant-barrel-imports/index.js +43 -0
  18. package/dist/cjs/rules/import/no-jest-mock-barrel-files/index.js +1401 -0
  19. package/dist/cjs/rules/import/no-relative-barrel-file-imports/index.js +777 -0
  20. package/dist/cjs/rules/import/shared/barrel-parsing.js +511 -0
  21. package/dist/cjs/rules/import/shared/file-system.js +186 -0
  22. package/dist/cjs/rules/import/shared/jest-utils.js +191 -0
  23. package/dist/cjs/rules/import/shared/package-registry.js +263 -0
  24. package/dist/cjs/rules/import/shared/package-resolution.js +185 -0
  25. package/dist/cjs/rules/import/shared/perf.js +89 -0
  26. package/dist/cjs/rules/import/shared/types.js +67 -0
  27. package/dist/cjs/rules/no-invalid-storybook-decorator-usage/index.js +1 -1
  28. package/dist/cjs/rules/no-sparse-checkout/index.js +1 -1
  29. package/dist/cjs/rules/prefer-crypto-random-uuid/index.js +87 -0
  30. package/dist/cjs/rules/use-entrypoints-in-examples/index.js +1 -1
  31. package/dist/es2019/index.js +17 -9
  32. package/dist/es2019/rules/constants.js +1 -1
  33. package/dist/es2019/rules/ensure-critical-dependency-resolutions/index.js +5 -5
  34. package/dist/es2019/rules/ensure-no-private-dependencies/index.js +10 -9
  35. package/dist/es2019/rules/feature-gating/inline-usage/index.js +14 -3
  36. package/dist/es2019/rules/feature-gating/no-alias/index.js +1 -1
  37. package/dist/es2019/rules/feature-gating/no-module-level-eval/index.js +1 -1
  38. package/dist/es2019/rules/feature-gating/no-module-level-eval-nav4/index.js +1 -1
  39. package/dist/es2019/rules/feature-gating/no-preconditioning/index.js +4 -1
  40. package/dist/es2019/rules/feature-gating/prefer-fg/index.js +1 -1
  41. package/dist/es2019/rules/feature-gating/static-feature-flags/index.js +2 -2
  42. package/dist/es2019/rules/feature-gating/use-recommended-utils/index.js +1 -1
  43. package/dist/es2019/rules/feature-gating/valid-gate-name/index.js +52 -0
  44. package/dist/es2019/rules/import/no-barrel-entry-imports/index.js +801 -0
  45. package/dist/es2019/rules/import/no-barrel-entry-jest-mock/index.js +1113 -0
  46. package/dist/es2019/rules/import/no-conversation-assistant-barrel-imports/index.js +37 -0
  47. package/dist/es2019/rules/import/no-jest-mock-barrel-files/index.js +1179 -0
  48. package/dist/es2019/rules/import/no-relative-barrel-file-imports/index.js +738 -0
  49. package/dist/es2019/rules/import/shared/barrel-parsing.js +433 -0
  50. package/dist/es2019/rules/import/shared/file-system.js +174 -0
  51. package/dist/es2019/rules/import/shared/jest-utils.js +159 -0
  52. package/dist/es2019/rules/import/shared/package-registry.js +240 -0
  53. package/dist/es2019/rules/import/shared/package-resolution.js +161 -0
  54. package/dist/es2019/rules/import/shared/perf.js +83 -0
  55. package/dist/es2019/rules/import/shared/types.js +57 -0
  56. package/dist/es2019/rules/no-invalid-storybook-decorator-usage/index.js +1 -1
  57. package/dist/es2019/rules/no-sparse-checkout/index.js +1 -1
  58. package/dist/es2019/rules/prefer-crypto-random-uuid/index.js +81 -0
  59. package/dist/es2019/rules/use-entrypoints-in-examples/index.js +1 -1
  60. package/dist/esm/index.js +17 -9
  61. package/dist/esm/rules/constants.js +1 -1
  62. package/dist/esm/rules/ensure-critical-dependency-resolutions/index.js +5 -5
  63. package/dist/esm/rules/ensure-no-private-dependencies/index.js +48 -65
  64. package/dist/esm/rules/feature-gating/inline-usage/index.js +14 -3
  65. package/dist/esm/rules/feature-gating/no-alias/index.js +1 -1
  66. package/dist/esm/rules/feature-gating/no-module-level-eval/index.js +1 -1
  67. package/dist/esm/rules/feature-gating/no-module-level-eval-nav4/index.js +1 -1
  68. package/dist/esm/rules/feature-gating/no-preconditioning/index.js +4 -1
  69. package/dist/esm/rules/feature-gating/prefer-fg/index.js +1 -1
  70. package/dist/esm/rules/feature-gating/static-feature-flags/index.js +2 -2
  71. package/dist/esm/rules/feature-gating/use-recommended-utils/index.js +1 -1
  72. package/dist/esm/rules/feature-gating/valid-gate-name/index.js +53 -0
  73. package/dist/esm/rules/import/no-barrel-entry-imports/index.js +864 -0
  74. package/dist/esm/rules/import/no-barrel-entry-jest-mock/index.js +1375 -0
  75. package/dist/esm/rules/import/no-conversation-assistant-barrel-imports/index.js +37 -0
  76. package/dist/esm/rules/import/no-jest-mock-barrel-files/index.js +1391 -0
  77. package/dist/esm/rules/import/no-relative-barrel-file-imports/index.js +770 -0
  78. package/dist/esm/rules/import/shared/barrel-parsing.js +500 -0
  79. package/dist/esm/rules/import/shared/file-system.js +176 -0
  80. package/dist/esm/rules/import/shared/jest-utils.js +179 -0
  81. package/dist/esm/rules/import/shared/package-registry.js +256 -0
  82. package/dist/esm/rules/import/shared/package-resolution.js +175 -0
  83. package/dist/esm/rules/import/shared/perf.js +80 -0
  84. package/dist/esm/rules/import/shared/types.js +61 -0
  85. package/dist/esm/rules/no-invalid-storybook-decorator-usage/index.js +1 -1
  86. package/dist/esm/rules/no-sparse-checkout/index.js +1 -1
  87. package/dist/esm/rules/prefer-crypto-random-uuid/index.js +81 -0
  88. package/dist/esm/rules/use-entrypoints-in-examples/index.js +1 -1
  89. package/dist/types/index.d.ts +18 -16
  90. package/dist/types/rules/import/no-barrel-entry-imports/index.d.ts +9 -0
  91. package/dist/types/rules/import/no-barrel-entry-jest-mock/index.d.ts +9 -0
  92. package/dist/types/rules/import/no-conversation-assistant-barrel-imports/index.d.ts +3 -0
  93. package/dist/types/rules/import/no-jest-mock-barrel-files/index.d.ts +22 -0
  94. package/dist/types/rules/import/no-relative-barrel-file-imports/index.d.ts +5 -0
  95. package/dist/types/rules/import/shared/barrel-parsing.d.ts +30 -0
  96. package/dist/types/rules/import/shared/file-system.d.ts +38 -0
  97. package/dist/types/rules/import/shared/jest-utils.d.ts +47 -0
  98. package/dist/types/rules/import/shared/package-registry.d.ts +26 -0
  99. package/dist/types/rules/import/shared/package-resolution.d.ts +38 -0
  100. package/dist/types/rules/import/shared/perf.d.ts +13 -0
  101. package/dist/types/rules/import/shared/types.d.ts +131 -0
  102. package/dist/types/rules/prefer-crypto-random-uuid/index.d.ts +3 -0
  103. package/dist/types-ts4.5/index.d.ts +18 -28
  104. package/dist/types-ts4.5/rules/import/no-barrel-entry-imports/index.d.ts +9 -0
  105. package/dist/types-ts4.5/rules/import/no-barrel-entry-jest-mock/index.d.ts +9 -0
  106. package/dist/types-ts4.5/rules/import/no-jest-mock-barrel-files/index.d.ts +22 -0
  107. package/dist/types-ts4.5/rules/import/no-relative-barrel-file-imports/index.d.ts +5 -0
  108. package/dist/types-ts4.5/rules/import/shared/barrel-parsing.d.ts +30 -0
  109. package/dist/types-ts4.5/rules/import/shared/file-system.d.ts +38 -0
  110. package/dist/types-ts4.5/rules/import/shared/jest-utils.d.ts +47 -0
  111. package/dist/types-ts4.5/rules/import/shared/package-registry.d.ts +26 -0
  112. package/dist/types-ts4.5/rules/import/shared/package-resolution.d.ts +38 -0
  113. package/dist/types-ts4.5/rules/import/shared/perf.d.ts +13 -0
  114. package/dist/types-ts4.5/rules/import/shared/types.d.ts +131 -0
  115. package/package.json +4 -5
  116. package/dist/cjs/rules/ensure-feature-flag-prefix/index.js +0 -75
  117. package/dist/cjs/rules/ensure-native-and-af-exports-synced/index.js +0 -158
  118. package/dist/es2019/rules/ensure-feature-flag-prefix/index.js +0 -65
  119. package/dist/es2019/rules/ensure-native-and-af-exports-synced/index.js +0 -146
  120. package/dist/esm/rules/ensure-feature-flag-prefix/index.js +0 -69
  121. package/dist/esm/rules/ensure-native-and-af-exports-synced/index.js +0 -151
  122. /package/dist/types/rules/{ensure-native-and-af-exports-synced → feature-gating/valid-gate-name}/index.d.ts +0 -0
  123. /package/dist/types-ts4.5/rules/{ensure-feature-flag-prefix → feature-gating/valid-gate-name}/index.d.ts +0 -0
  124. /package/dist/types-ts4.5/rules/{ensure-native-and-af-exports-synced → import/no-conversation-assistant-barrel-imports}/index.d.ts +0 -0
  125. /package/dist/{types/rules/ensure-feature-flag-prefix → types-ts4.5/rules/prefer-crypto-random-uuid}/index.d.ts +0 -0
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Shared utilities for jest.mock-related lint rules.
3
+ *
4
+ * These helpers are used by both `no-barrel-entry-jest-mock` (cross-package)
5
+ * and `no-jest-mock-barrel-files` (relative imports).
6
+ */
7
+
8
+ /**
9
+ * Check if a CallExpression node is a jest.mock() call
10
+ */
11
+ export function isJestMockCall(node) {
12
+ const callee = node.callee;
13
+ if (callee.type === 'MemberExpression') {
14
+ return callee.object.type === 'Identifier' && callee.object.name === 'jest' && callee.property.type === 'Identifier' && callee.property.name === 'mock';
15
+ }
16
+ if (callee.type === 'Identifier') {
17
+ return callee.name === 'jest.mock';
18
+ }
19
+ return false;
20
+ }
21
+
22
+ /**
23
+ * Check if a node is a jest.requireActual() call
24
+ */
25
+ export function isJestRequireActual(node) {
26
+ if (node.type !== 'CallExpression') {
27
+ return false;
28
+ }
29
+ const callee = node.callee;
30
+ if (callee.type === 'MemberExpression') {
31
+ return callee.object.type === 'Identifier' && callee.object.name === 'jest' && callee.property.type === 'Identifier' && callee.property.name === 'requireActual';
32
+ }
33
+ return false;
34
+ }
35
+
36
+ /**
37
+ * Check if a node is a jest.requireMock() call
38
+ */
39
+ export function isJestRequireMock(node) {
40
+ if (node.type !== 'CallExpression') {
41
+ return false;
42
+ }
43
+ const callee = node.callee;
44
+ if (callee.type === 'MemberExpression') {
45
+ return callee.object.type === 'Identifier' && callee.object.name === 'jest' && callee.property.type === 'Identifier' && callee.property.name === 'requireMock';
46
+ }
47
+ return false;
48
+ }
49
+
50
+ /**
51
+ * Extract the import path string from a jest.mock/jest.requireMock/jest.requireActual call's arguments.
52
+ * Returns null if the path cannot be statically determined.
53
+ */
54
+ export function extractImportPath(node) {
55
+ if (node.arguments.length === 0) {
56
+ return null;
57
+ }
58
+ const firstArg = node.arguments[0];
59
+ if (firstArg.type === 'Literal') {
60
+ return String(firstArg.value);
61
+ }
62
+ if (firstArg.type === 'TemplateLiteral' && firstArg.expressions.length === 0) {
63
+ return firstArg.quasis[0].value.raw;
64
+ }
65
+ return null;
66
+ }
67
+
68
+ /**
69
+ * Find all jest.requireMock() calls in the AST whose import path matches a given target.
70
+ *
71
+ * The `matchPath` callback allows callers to provide their own path-matching strategy:
72
+ * - Cross-package rules can use simple string equality
73
+ * - Relative import rules can use normalized/resolved path comparison
74
+ */
75
+ export function findJestRequireMockCalls({
76
+ ast,
77
+ matchPath
78
+ }) {
79
+ const results = [];
80
+ const visited = new WeakSet();
81
+ const skipKeys = new Set(['parent', 'loc', 'range', 'tokens', 'comments']);
82
+ function visit(node) {
83
+ if (visited.has(node)) {
84
+ return;
85
+ }
86
+ visited.add(node);
87
+ if (node.type === 'CallExpression' && isJestRequireMock(node)) {
88
+ const path = extractImportPath(node);
89
+ if (path && matchPath(path)) {
90
+ results.push(node);
91
+ }
92
+ }
93
+ for (const key in node) {
94
+ if (skipKeys.has(key)) {
95
+ continue;
96
+ }
97
+ const value = node[key];
98
+ if (value && typeof value === 'object') {
99
+ if (Array.isArray(value)) {
100
+ for (const child of value) {
101
+ if (child && typeof child === 'object' && 'type' in child) {
102
+ visit(child);
103
+ }
104
+ }
105
+ } else if ('type' in value) {
106
+ visit(value);
107
+ }
108
+ }
109
+ }
110
+ }
111
+ visit(ast);
112
+ return results;
113
+ }
114
+
115
+ /**
116
+ * Determine the best new import path for a jest.requireMock() call by inspecting
117
+ * the destructured symbols or property access at the call site.
118
+ *
119
+ * @param requireMockNode - The jest.requireMock() CallExpression node
120
+ * @param symbolToNewPath - Map from symbol name to the new mock path that provides it
121
+ * @returns The resolved new path, or null if it cannot be determined
122
+ */
123
+ export function resolveNewPathForRequireMock({
124
+ requireMockNode,
125
+ symbolToNewPath
126
+ }) {
127
+ const parent = requireMockNode.parent;
128
+ if (parent) {
129
+ var _parent;
130
+ // Check for destructuring pattern: const { foo } = jest.requireMock('...')
131
+ const declarator = parent.type === 'VariableDeclarator' ? parent : ((_parent = parent.parent) === null || _parent === void 0 ? void 0 : _parent.type) === 'VariableDeclarator' ? parent.parent : null;
132
+ if (declarator && declarator.type === 'VariableDeclarator' && declarator.id.type === 'ObjectPattern') {
133
+ for (const prop of declarator.id.properties) {
134
+ if (prop.type === 'Property' && prop.key.type === 'Identifier') {
135
+ const matchedPath = symbolToNewPath.get(prop.key.name);
136
+ if (matchedPath) {
137
+ return matchedPath;
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ // Check for property access pattern: jest.requireMock('...').foo
144
+ if (parent.type === 'MemberExpression' && parent.property.type === 'Identifier') {
145
+ const matchedPath = symbolToNewPath.get(parent.property.name);
146
+ if (matchedPath) {
147
+ return matchedPath;
148
+ }
149
+ }
150
+ }
151
+
152
+ // Fallback: if only one new path exists, use it
153
+ const uniquePaths = new Set(symbolToNewPath.values());
154
+ if (uniquePaths.size === 1) {
155
+ var _uniquePaths$values$n;
156
+ return (_uniquePaths$values$n = uniquePaths.values().next().value) !== null && _uniquePaths$values$n !== void 0 ? _uniquePaths$values$n : null;
157
+ }
158
+ return null;
159
+ }
@@ -0,0 +1,240 @@
1
+ import { join } from 'path';
2
+ import { DEFAULT_TARGET_FOLDERS } from './file-system';
3
+ import { perfInc, perfTime } from './perf';
4
+ /**
5
+ * The folder paths used for package resolution.
6
+ * All packages under these folders can be resolved regardless of applyToImportsFrom.
7
+ * applyToImportsFrom is only used to filter which packages the lint rules apply to.
8
+ */
9
+ const PACKAGE_RESOLUTION_ROOTS = ['platform/packages'];
10
+
11
+ /**
12
+ * Get yarn.lock modification time for cache invalidation.
13
+ * Returns 0 if yarn.lock doesn't exist.
14
+ */
15
+ function getYarnLockMtime({
16
+ workspaceRoot,
17
+ fs
18
+ }) {
19
+ const yarnLockPath = join(workspaceRoot, 'yarn.lock');
20
+ try {
21
+ if (fs.existsSync(yarnLockPath)) {
22
+ var _stats$mtimeMs;
23
+ const stats = fs.statSync(yarnLockPath);
24
+ return (_stats$mtimeMs = stats.mtimeMs) !== null && _stats$mtimeMs !== void 0 ? _stats$mtimeMs : 0;
25
+ }
26
+ } catch {
27
+ // Ignore errors
28
+ }
29
+ return 0;
30
+ }
31
+
32
+ /**
33
+ * Check if the cache is valid for the given workspace root.
34
+ * Cache is invalid if:
35
+ * - The cache is not fully initialized
36
+ * - The workspace root has changed
37
+ * - The yarn.lock file has been modified
38
+ */
39
+ function isCacheValid({
40
+ cache,
41
+ workspaceRoot,
42
+ fs
43
+ }) {
44
+ // Cache is invalid if not initialized
45
+ if (!cache.packageNameToDir || !cache.scannedDirectories) {
46
+ return false;
47
+ }
48
+
49
+ // Cache is invalid if workspace root changed
50
+ if (cache.workspaceRoot !== workspaceRoot) {
51
+ return false;
52
+ }
53
+
54
+ // Cache is invalid if yarn.lock mtime changed
55
+ const currentMtime = getYarnLockMtime({
56
+ workspaceRoot,
57
+ fs
58
+ });
59
+ return currentMtime === cache.yarnLockMtime;
60
+ }
61
+
62
+ /**
63
+ * Read package name from a package.json file.
64
+ * Returns null if the file doesn't exist or doesn't have a valid name.
65
+ */
66
+ function readPackageName({
67
+ packageJsonPath,
68
+ fs
69
+ }) {
70
+ try {
71
+ if (!fs.existsSync(packageJsonPath)) {
72
+ return null;
73
+ }
74
+ const content = fs.readFileSync(packageJsonPath, 'utf-8');
75
+ const pkg = JSON.parse(content);
76
+ if (pkg.name && typeof pkg.name === 'string') {
77
+ return pkg.name;
78
+ }
79
+ } catch {
80
+ // Ignore errors (invalid JSON, etc.)
81
+ }
82
+ return null;
83
+ }
84
+
85
+ /**
86
+ * Recursively scan a directory for packages and update the cache.
87
+ * Directories are cached (including those without packages) to avoid re-scanning.
88
+ *
89
+ * Once a package.json is found, subdirectories are not scanned since packages
90
+ * don't contain nested packages. The exception is target folder roots (e.g., 'platform')
91
+ * which may have a package.json but still contain packages in subdirectories.
92
+ */
93
+ function scanDirectoryForPackages({
94
+ dir,
95
+ cache,
96
+ fs,
97
+ isTargetRoot = false,
98
+ nestedTargetRoots
99
+ }) {
100
+ // Skip if already scanned
101
+ if (cache.scannedDirectories.has(dir)) {
102
+ return;
103
+ }
104
+
105
+ // Mark as scanned (even if it doesn't contain a package)
106
+ cache.scannedDirectories.add(dir);
107
+ try {
108
+ // Check for package.json in current directory
109
+ const packageJsonPath = join(dir, 'package.json');
110
+ const packageName = readPackageName({
111
+ packageJsonPath,
112
+ fs
113
+ });
114
+ if (packageName) {
115
+ cache.packageNameToDir.set(packageName, dir);
116
+ // Don't scan subdirectories - packages don't contain nested packages
117
+ // Exception: target folder roots (e.g., 'platform') may have packages in subdirectories
118
+ if (!isTargetRoot) {
119
+ return;
120
+ }
121
+ }
122
+
123
+ // Recursively scan subdirectories
124
+ const entries = fs.readdirSync(dir, {
125
+ withFileTypes: true
126
+ });
127
+ for (const entry of entries) {
128
+ // Skip node_modules, hidden directories, and non-directories
129
+ if (!entry.isDirectory() || entry.name === 'node_modules' || entry.name.startsWith('.')) {
130
+ continue;
131
+ }
132
+ scanDirectoryForPackages({
133
+ dir: join(dir, entry.name),
134
+ cache,
135
+ fs,
136
+ // Only certain directory levels are treated as "target roots"
137
+ isTargetRoot: nestedTargetRoots.has(dir),
138
+ nestedTargetRoots
139
+ });
140
+ }
141
+ } catch {
142
+ // Directory doesn't exist or not readable, skip
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Ensure all packages under platform/packages have been scanned.
148
+ * Initializes or updates the cache on fs.cache as needed.
149
+ * Package resolution is not constrained by applyToImportsFrom - any package can be resolved.
150
+ */
151
+ function ensureCachePopulated({
152
+ workspaceRoot,
153
+ fs
154
+ }) {
155
+ // Check if cache is still valid
156
+ if (isCacheValid({
157
+ cache: fs.cache,
158
+ workspaceRoot,
159
+ fs
160
+ })) {
161
+ return;
162
+ }
163
+ perfInc({
164
+ fs,
165
+ key: 'packageRegistry.rebuild'
166
+ });
167
+ return perfTime({
168
+ fs,
169
+ key: 'packageRegistry.rebuildMs',
170
+ fn: () => {
171
+ // Initialize fresh cache
172
+ fs.cache.packageNameToDir = new Map();
173
+ fs.cache.scannedDirectories = new Set();
174
+ fs.cache.yarnLockMtime = getYarnLockMtime({
175
+ workspaceRoot,
176
+ fs
177
+ });
178
+ fs.cache.workspaceRoot = workspaceRoot;
179
+ // When the workspace graph changes, clear derived caches as well
180
+ fs.cache.packageExportsByDir = new Map();
181
+
182
+ // Scan all packages under the resolution roots
183
+ // This is not constrained by applyToImportsFrom - any package can be resolved
184
+ // The immediate children of each root (e.g., ai-mate, search) are treated as
185
+ // "nested target roots" - they may have a package.json but still contain nested packages
186
+ for (const resolutionRoot of PACKAGE_RESOLUTION_ROOTS) {
187
+ const targetPath = join(workspaceRoot, resolutionRoot);
188
+ scanDirectoryForPackages({
189
+ dir: targetPath,
190
+ cache: fs.cache,
191
+ fs,
192
+ isTargetRoot: true,
193
+ nestedTargetRoots: new Set([targetPath])
194
+ });
195
+ }
196
+ }
197
+ });
198
+ }
199
+
200
+ /**
201
+ * Find the package directory for a given package name.
202
+ * Returns the absolute path to the package directory or null if not found.
203
+ *
204
+ * This function uses lazy scanning - it will scan platform/packages on first lookup
205
+ * and cache results in fs.cache for subsequent lookups.
206
+ *
207
+ * Note: Package resolution is NOT constrained by applyToImportsFrom. Any package under
208
+ * platform/packages can be resolved. Use isPackageInApplyToImportsFrom to check if a
209
+ * package should be processed by the lint rule.
210
+ */
211
+ export function findPackageInRegistry({
212
+ packageName,
213
+ workspaceRoot,
214
+ fs
215
+ }) {
216
+ var _fs$cache$packageName, _fs$cache$packageName2;
217
+ // Ensure cache is populated
218
+ ensureCachePopulated({
219
+ workspaceRoot,
220
+ fs
221
+ });
222
+
223
+ // Look up the package
224
+ return (_fs$cache$packageName = (_fs$cache$packageName2 = fs.cache.packageNameToDir) === null || _fs$cache$packageName2 === void 0 ? void 0 : _fs$cache$packageName2.get(packageName)) !== null && _fs$cache$packageName !== void 0 ? _fs$cache$packageName : null;
225
+ }
226
+
227
+ /**
228
+ * Check if a package is within one of the applyToImportsFrom folders.
229
+ * This can be used to quickly filter out packages that shouldn't be checked.
230
+ */
231
+ export function isPackageInApplyToImportsFrom({
232
+ packageDir,
233
+ workspaceRoot,
234
+ applyToImportsFrom = DEFAULT_TARGET_FOLDERS
235
+ }) {
236
+ return applyToImportsFrom.some(folder => {
237
+ const targetPath = join(workspaceRoot, folder);
238
+ return packageDir.startsWith(targetPath);
239
+ });
240
+ }
@@ -0,0 +1,161 @@
1
+ import { join } from 'path';
2
+ import { readFileContent, resolveImportPath } from './file-system';
3
+ import { findPackageInRegistry } from './package-registry';
4
+ /**
5
+ * Parse the package.json exports field and return a map of export paths to resolved file paths.
6
+ */
7
+ export function parsePackageExports({
8
+ packageDir,
9
+ fs
10
+ }) {
11
+ // Memoize per-package to avoid repeated reads/parses during IDE lint runs.
12
+ // Additionally, invalidate per-package if the package.json mtime changes
13
+ // (covers unstaged local edits in IDE).
14
+ if (!fs.cache.packageExportsByDir) {
15
+ fs.cache.packageExportsByDir = new Map();
16
+ }
17
+ const packageJsonPath = join(packageDir, 'package.json');
18
+ let currentMtimeMs = null;
19
+ try {
20
+ var _fs$statSync$mtimeMs;
21
+ currentMtimeMs = (_fs$statSync$mtimeMs = fs.statSync(packageJsonPath).mtimeMs) !== null && _fs$statSync$mtimeMs !== void 0 ? _fs$statSync$mtimeMs : null;
22
+ } catch {
23
+ // If package.json can't be stat'ed (missing/inaccessible), use null to force re-read
24
+ currentMtimeMs = null;
25
+ }
26
+ const cached = fs.cache.packageExportsByDir.get(packageDir);
27
+ // Only use cache if we have a valid mtime and it matches
28
+ if (cached && currentMtimeMs !== null && cached.packageJsonMtimeMs === currentMtimeMs) {
29
+ return cached.exportsMap;
30
+ }
31
+ const exportsMap = new Map();
32
+ try {
33
+ const content = readFileContent({
34
+ filePath: packageJsonPath,
35
+ fs
36
+ });
37
+ if (!content) {
38
+ return exportsMap;
39
+ }
40
+ const packageJson = JSON.parse(content);
41
+ const exports = packageJson.exports;
42
+ if (!exports || typeof exports !== 'object') {
43
+ return exportsMap;
44
+ }
45
+ for (const [exportPath, exportValue] of Object.entries(exports)) {
46
+ // Handle both simple string values and conditional exports objects
47
+ let resolvedPath = null;
48
+ if (typeof exportValue === 'string') {
49
+ resolvedPath = exportValue;
50
+ } else if (typeof exportValue === 'object' && exportValue !== null) {
51
+ // Handle conditional exports like { "import": "./...", "require": "./..." }
52
+ // Prefer "import" or "default" or first available
53
+ const condExports = exportValue;
54
+ resolvedPath = condExports['import'] || condExports['default'] || Object.values(condExports)[0];
55
+ }
56
+ if (resolvedPath && typeof resolvedPath === 'string') {
57
+ // Resolve the path relative to the package directory
58
+ const absolutePath = resolveImportPath({
59
+ basedir: packageDir,
60
+ importPath: resolvedPath,
61
+ fs
62
+ });
63
+ if (absolutePath) {
64
+ exportsMap.set(exportPath, absolutePath);
65
+ }
66
+ }
67
+ }
68
+ } catch {
69
+ // Ignore parsing errors
70
+ }
71
+
72
+ // Cache even empty maps to avoid re-reading invalid/missing exports repeatedly.
73
+ fs.cache.packageExportsByDir.set(packageDir, {
74
+ packageJsonMtimeMs: currentMtimeMs,
75
+ exportsMap
76
+ });
77
+ return exportsMap;
78
+ }
79
+
80
+ /**
81
+ * Find a matching export entry for a given source file path.
82
+ * Returns the export path (e.g., "./controllers/analytics") or null if not found.
83
+ */
84
+ export function findExportForSourceFile({
85
+ sourceFilePath,
86
+ exportsMap
87
+ }) {
88
+ for (const [exportPath, resolvedPath] of exportsMap) {
89
+ if (resolvedPath === sourceFilePath) {
90
+ return exportPath;
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+
96
+ /**
97
+ * Extract the package name and subpath from an import specifier.
98
+ * Returns null if the import is not a scoped package import.
99
+ */
100
+ export function extractPackageNameFromImport(moduleSpecifier) {
101
+ const match = moduleSpecifier.match(/^(@[^/]+\/[^/]+)(\/.*)?$/);
102
+ if (!match) {
103
+ return null;
104
+ }
105
+ return {
106
+ packageName: match[1],
107
+ subPath: match[2] || ''
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Resolve a cross-package import to its package directory and export info.
113
+ * Returns null if the package is not in the target folder or cannot be resolved.
114
+ */
115
+ export function resolveCrossPackageImport({
116
+ moduleSpecifier,
117
+ workspaceRoot,
118
+ fs
119
+ }) {
120
+ // Only handle @atlassian/* scoped packages
121
+ const parsed = extractPackageNameFromImport(moduleSpecifier);
122
+ if (!parsed) {
123
+ return null;
124
+ }
125
+ const {
126
+ packageName,
127
+ subPath
128
+ } = parsed;
129
+
130
+ // Check if package is in target folder
131
+ const packageDir = findPackageInRegistry({
132
+ packageName,
133
+ workspaceRoot,
134
+ fs
135
+ });
136
+ if (!packageDir) {
137
+ return null;
138
+ }
139
+
140
+ // Parse package.json exports
141
+ const exportsMap = parsePackageExports({
142
+ packageDir,
143
+ fs
144
+ });
145
+ if (exportsMap.size === 0) {
146
+ return null;
147
+ }
148
+
149
+ // Determine the export path (e.g., '.' or './utils')
150
+ const exportPath = subPath ? '.' + subPath : '.';
151
+ const entryFilePath = exportsMap.get(exportPath);
152
+ if (!entryFilePath) {
153
+ return null;
154
+ }
155
+ return {
156
+ packageName,
157
+ packageDir,
158
+ exportPath,
159
+ entryFilePath
160
+ };
161
+ }
@@ -0,0 +1,83 @@
1
+ export const PERF_ENV_VAR = 'INTERNAL_ESLINT_BARREL_PERF';
2
+ function nowMs() {
3
+ // eslint-disable-next-line no-restricted-globals
4
+ return typeof performance !== 'undefined' && performance.now ? performance.now() : Date.now();
5
+ }
6
+ export function isPerfEnabled() {
7
+ return process.env[PERF_ENV_VAR] === '1' || process.env[PERF_ENV_VAR] === 'true';
8
+ }
9
+ function ensurePerfInitialized({
10
+ fs
11
+ }) {
12
+ if (!fs.cache.perf) {
13
+ fs.cache.perf = {
14
+ installedExitHook: false,
15
+ counters: {},
16
+ timers: {}
17
+ };
18
+ }
19
+ }
20
+ function ensureExitHookInstalled({
21
+ fs
22
+ }) {
23
+ if (!isPerfEnabled()) {
24
+ return;
25
+ }
26
+ ensurePerfInitialized({
27
+ fs
28
+ });
29
+ if (fs.cache.perf.installedExitHook) {
30
+ return;
31
+ }
32
+ fs.cache.perf.installedExitHook = true;
33
+ // eslint-disable-next-line no-console
34
+ console.error(`[eslint-plugin-internal] perf enabled via ${PERF_ENV_VAR}`);
35
+ process.once('exit', () => {
36
+ var _fs$cache$perf$counte, _fs$cache$perf, _fs$cache$perf$timers, _fs$cache$perf2;
37
+ // eslint-disable-next-line no-console
38
+ console.error(`[eslint-plugin-internal] perf exit hook fired (${PERF_ENV_VAR})`);
39
+ // eslint-disable-next-line no-console
40
+ console.log(`[eslint-plugin-internal] perf summary (${PERF_ENV_VAR})`);
41
+ // eslint-disable-next-line no-console
42
+ console.log(JSON.stringify({
43
+ counters: (_fs$cache$perf$counte = (_fs$cache$perf = fs.cache.perf) === null || _fs$cache$perf === void 0 ? void 0 : _fs$cache$perf.counters) !== null && _fs$cache$perf$counte !== void 0 ? _fs$cache$perf$counte : {},
44
+ timers: (_fs$cache$perf$timers = (_fs$cache$perf2 = fs.cache.perf) === null || _fs$cache$perf2 === void 0 ? void 0 : _fs$cache$perf2.timers) !== null && _fs$cache$perf$timers !== void 0 ? _fs$cache$perf$timers : {}
45
+ }, null, 2));
46
+ });
47
+ }
48
+ export function perfInc({
49
+ fs,
50
+ key,
51
+ by = 1
52
+ }) {
53
+ var _perf$counters$key;
54
+ if (!isPerfEnabled()) {
55
+ return;
56
+ }
57
+ ensureExitHookInstalled({
58
+ fs
59
+ });
60
+ const perf = fs.cache.perf;
61
+ perf.counters[key] = ((_perf$counters$key = perf.counters[key]) !== null && _perf$counters$key !== void 0 ? _perf$counters$key : 0) + by;
62
+ }
63
+ export function perfTime({
64
+ fs,
65
+ key,
66
+ fn
67
+ }) {
68
+ if (!isPerfEnabled()) {
69
+ return fn();
70
+ }
71
+ ensureExitHookInstalled({
72
+ fs
73
+ });
74
+ const perf = fs.cache.perf;
75
+ const start = nowMs();
76
+ try {
77
+ return fn();
78
+ } finally {
79
+ var _perf$timers$key;
80
+ const duration = nowMs() - start;
81
+ perf.timers[key] = ((_perf$timers$key = perf.timers[key]) !== null && _perf$timers$key !== void 0 ? _perf$timers$key : 0) + duration;
82
+ }
83
+ }
@@ -0,0 +1,57 @@
1
+ import { execSync } from 'child_process';
2
+ import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from 'fs';
3
+
4
+ /**
5
+ * Directory entry returned by readdirSync with withFileTypes option.
6
+ */
7
+
8
+ /**
9
+ * State for the package registry cache.
10
+ * This is used to cache package name to directory mappings for efficient lookups.
11
+ */
12
+
13
+ /**
14
+ * Cache structure for file system operations.
15
+ * Contains both package registry cache and workspace root cache.
16
+ */
17
+
18
+ /**
19
+ * File system abstraction for testability.
20
+ * This interface allows the core logic to be tested with mock file systems.
21
+ * The cache property holds package resolution state and can be passed as an empty
22
+ * object for tests to ensure fresh state for each test case.
23
+ */
24
+
25
+ /**
26
+ * Real file system implementation using Node.js fs module.
27
+ */
28
+ export const realFileSystem = {
29
+ existsSync,
30
+ readFileSync,
31
+ realpathSync,
32
+ statSync,
33
+ readdirSync: (path, options) => readdirSync(path, options),
34
+ execSync: (command, options) => {
35
+ try {
36
+ return execSync(command, {
37
+ ...options,
38
+ encoding: 'utf-8'
39
+ }).trim();
40
+ } catch {
41
+ return null;
42
+ }
43
+ },
44
+ cache: {}
45
+ };
46
+
47
+ /**
48
+ * Information about cross-package re-export origin.
49
+ */
50
+
51
+ /**
52
+ * Information about where an export originates.
53
+ */
54
+
55
+ /**
56
+ * Context for package resolution operations.
57
+ */
@@ -1,6 +1,6 @@
1
1
  // eslint-disable-next-line import/no-extraneous-dependencies
2
2
 
3
- const STORYBOOK_DECORATOR_IDENTIFIER = 'withPlatformFeatureFlags';
3
+ const STORYBOOK_DECORATOR_IDENTIFIER = 'withPlatformFeatureGates';
4
4
  const rule = {
5
5
  meta: {
6
6
  hasSuggestions: false,