@atlaskit/eslint-plugin-platform 2.5.0 → 2.7.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 (47) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/afm-cc/tsconfig.json +1 -1
  3. package/dist/cjs/index.js +43 -3
  4. package/dist/cjs/rules/ensure-native-and-af-exports-synced/index.js +3 -0
  5. package/dist/cjs/rules/ensure-valid-platform-yarn-protocol-usage/index.js +1 -1
  6. package/dist/cjs/rules/feature-gating/no-preconditioning/index.js +1 -1
  7. package/dist/cjs/rules/no-direct-document-usage/index.js +103 -0
  8. package/dist/cjs/rules/no-set-immediate/index.js +39 -0
  9. package/dist/cjs/rules/no-sparse-checkout/index.js +43 -0
  10. package/dist/cjs/rules/util/file-exclusions.js +45 -0
  11. package/dist/es2019/index.js +43 -3
  12. package/dist/es2019/rules/ensure-native-and-af-exports-synced/index.js +3 -0
  13. package/dist/es2019/rules/ensure-valid-platform-yarn-protocol-usage/index.js +1 -1
  14. package/dist/es2019/rules/feature-gating/no-preconditioning/index.js +1 -1
  15. package/dist/es2019/rules/no-direct-document-usage/index.js +95 -0
  16. package/dist/es2019/rules/no-set-immediate/index.js +33 -0
  17. package/dist/es2019/rules/no-sparse-checkout/index.js +35 -0
  18. package/dist/es2019/rules/util/file-exclusions.js +37 -0
  19. package/dist/esm/index.js +43 -3
  20. package/dist/esm/rules/ensure-native-and-af-exports-synced/index.js +3 -0
  21. package/dist/esm/rules/ensure-valid-platform-yarn-protocol-usage/index.js +1 -1
  22. package/dist/esm/rules/feature-gating/no-preconditioning/index.js +1 -1
  23. package/dist/esm/rules/no-direct-document-usage/index.js +97 -0
  24. package/dist/esm/rules/no-set-immediate/index.js +33 -0
  25. package/dist/esm/rules/no-sparse-checkout/index.js +37 -0
  26. package/dist/esm/rules/util/file-exclusions.js +39 -0
  27. package/dist/types/index.d.ts +22 -0
  28. package/dist/types/rules/no-direct-document-usage/index.d.ts +3 -0
  29. package/dist/types/rules/no-set-immediate/index.d.ts +3 -0
  30. package/dist/types/rules/no-sparse-checkout/index.d.ts +3 -0
  31. package/dist/types/rules/util/file-exclusions.d.ts +13 -0
  32. package/dist/types-ts4.5/index.d.ts +22 -0
  33. package/dist/types-ts4.5/rules/no-direct-document-usage/index.d.ts +3 -0
  34. package/dist/types-ts4.5/rules/no-set-immediate/index.d.ts +3 -0
  35. package/dist/types-ts4.5/rules/no-sparse-checkout/index.d.ts +3 -0
  36. package/dist/types-ts4.5/rules/util/file-exclusions.d.ts +13 -0
  37. package/package.json +10 -1
  38. package/src/index.tsx +47 -2
  39. package/src/rules/ensure-native-and-af-exports-synced/index.tsx +3 -0
  40. package/src/rules/ensure-valid-bin-values/__tests__/unit/rule.test.ts +3 -2
  41. package/src/rules/ensure-valid-platform-yarn-protocol-usage/index.ts +1 -1
  42. package/src/rules/feature-gating/no-preconditioning/index.tsx +1 -1
  43. package/src/rules/no-direct-document-usage/index.tsx +111 -0
  44. package/src/rules/no-set-immediate/index.tsx +43 -0
  45. package/src/rules/no-sparse-checkout/__tests__/unit/rule.test.tsx +48 -0
  46. package/src/rules/no-sparse-checkout/index.tsx +54 -0
  47. package/src/rules/util/file-exclusions.ts +39 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @atlaskit/eslint-plugin-platform
2
2
 
3
+ ## 2.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#157255](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/157255)
8
+ [`334db48ef3650`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/334db48ef3650) -
9
+ Linting rule to forbid use of non standard setImediate
10
+
11
+ ## 2.6.0
12
+
13
+ ### Minor Changes
14
+
15
+ - [#168806](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/168806)
16
+ [`e4f94f422b9f1`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/e4f94f422b9f1) -
17
+ New linting rule to prevent direct "document" usage. This prevents failed tests when document is
18
+ not defined in the testing environment
19
+
3
20
  ## 2.5.0
4
21
 
5
22
  ### Minor Changes
@@ -3,7 +3,7 @@
3
3
  "compilerOptions": {
4
4
  "target": "es5",
5
5
  "composite": true,
6
- "outDir": "../dist",
6
+ "outDir": "../../../../../confluence/tsDist/@atlaskit__eslint-plugin-platform",
7
7
  "rootDir": "../"
8
8
  },
9
9
  "include": [
package/dist/cjs/index.js CHANGED
@@ -34,8 +34,29 @@ var _useEntrypointsInExamples = _interopRequireDefault(require("./rules/use-entr
34
34
  var _useRecommendedUtils = _interopRequireDefault(require("./rules/feature-gating/use-recommended-utils"));
35
35
  var _expandBackgroundShorthand = _interopRequireDefault(require("./rules/compiled/expand-background-shorthand"));
36
36
  var _expandSpacingShorthand = _interopRequireDefault(require("./rules/compiled/expand-spacing-shorthand"));
37
+ var _noSparseCheckout = _interopRequireDefault(require("./rules/no-sparse-checkout"));
38
+ var _noDirectDocumentUsage = _interopRequireDefault(require("./rules/no-direct-document-usage"));
39
+ var _noSetImmediate = _interopRequireDefault(require("./rules/no-set-immediate"));
40
+ var _nodePath = require("node:path");
41
+ var _nodeFs = require("node:fs");
37
42
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
38
43
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } // eslint-disable-next-line import/no-extraneous-dependencies
44
+ var jiraRoot;
45
+ try {
46
+ var findUp = require('find-up');
47
+ findUp.sync(function (dir) {
48
+ var productsJsonPath = (0, _nodePath.join)(dir, 'products.json');
49
+ if (findUp.sync.exists(productsJsonPath)) {
50
+ var productJson = JSON.parse((0, _nodeFs.readFileSync)(productsJsonPath, 'utf-8'));
51
+ if (productJson.Jira) {
52
+ jiraRoot = (0, _nodePath.normalize)((0, _nodePath.join)(dir, productJson.Jira.path));
53
+ return findUp.stop;
54
+ }
55
+ }
56
+ });
57
+ } catch (_unused) {
58
+ // we aren't running inside of AFM, so we can ignore this.
59
+ }
39
60
  var packageJson = require('@atlaskit/eslint-plugin-platform/package.json');
40
61
  var rules = exports.rules = {
41
62
  'ensure-feature-flag-registration': _ensureFeatureFlagRegistration.default,
@@ -64,7 +85,10 @@ var rules = exports.rules = {
64
85
  'prefer-fg': _preferFg.default,
65
86
  'no-alias': _noAlias.default,
66
87
  'use-entrypoints-in-examples': _useEntrypointsInExamples.default,
67
- 'use-recommended-utils': _useRecommendedUtils.default
88
+ 'use-recommended-utils': _useRecommendedUtils.default,
89
+ 'no-sparse-checkout': _noSparseCheckout.default,
90
+ 'no-direct-document-usage': _noDirectDocumentUsage.default,
91
+ 'no-set-immediate': _noSetImmediate.default
68
92
  };
69
93
  var commonConfig = {
70
94
  '@atlaskit/platform/ensure-test-runner-arguments': 'error',
@@ -73,6 +97,8 @@ var commonConfig = {
73
97
  '@atlaskit/platform/no-invalid-storybook-decorator-usage': 'error',
74
98
  '@atlaskit/platform/ensure-atlassian-team': 'error',
75
99
  '@atlaskit/platform/no-module-level-eval-nav4': 'error',
100
+ '@atlaskit/platform/no-direct-document-usage': 'warn',
101
+ '@atlaskit/platform/no-set-immediate': 'error',
76
102
  // Compiled: rules that are not included via `@compiled/recommended
77
103
  '@atlaskit/platform/expand-border-shorthand': 'error',
78
104
  '@atlaskit/platform/expand-background-shorthand': 'error',
@@ -101,6 +127,7 @@ var recommendedRules = _objectSpread(_objectSpread({}, commonConfig), {}, {
101
127
  var jiraRules = commonConfig;
102
128
  var jsonPrefix = '/* eslint-disable quote-props, comma-dangle, quotes, semi, eol-last, @typescript-eslint/semi, no-template-curly-in-string */ module.exports = ';
103
129
  var jsonPrefixForFlatConfig = '/* eslint-disable quote-props, comma-dangle, quotes, semi, eol-last, no-template-curly-in-string */ module.exports = ';
130
+ var jsonPrefixForJira = 'module.exports = ';
104
131
  var name = packageJson.name,
105
132
  version = packageJson.version;
106
133
  var plugin = exports.plugin = {
@@ -147,7 +174,14 @@ var plugin = exports.plugin = {
147
174
  },
148
175
  processors: {
149
176
  'package-json-processor': {
150
- preprocess: function preprocess(source) {
177
+ preprocess: function preprocess(source, filename) {
178
+ // we only need to check for jiraRoot because it uses a different
179
+ // ESLint version and produces fake errors due to how this processor handles JSON
180
+ if (jiraRoot && filename.startsWith(jiraRoot)) {
181
+ // augment the json into a js file
182
+ return [jsonPrefixForJira + source.trim()];
183
+ }
184
+
151
185
  // augment the json into a js file
152
186
  return [jsonPrefix + source.trim()];
153
187
  },
@@ -170,7 +204,13 @@ var plugin = exports.plugin = {
170
204
  // This processor is used for ESLint FlatConfig,
171
205
  // once we roll out FlatConfig, we can remove the above processor
172
206
  'package-json-processor-for-flat-config': {
173
- preprocess: function preprocess(source) {
207
+ // we only need to check for jiraRoot because it uses a different
208
+ // ESLint version and produces fake errors due to how this processor handles JSON
209
+ preprocess: function preprocess(source, filename) {
210
+ if (jiraRoot && filename.startsWith(jiraRoot)) {
211
+ // augment the json into a js file
212
+ return [jsonPrefixForJira + source.trim()];
213
+ }
174
214
  // augment the json into a js file
175
215
  return [jsonPrefixForFlatConfig + source.trim()];
176
216
  },
@@ -13,6 +13,9 @@ var _registrationUtils = require("../util/registration-utils");
13
13
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
14
14
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
15
15
  var exportsValidationExceptions = {
16
+ '@af/yarn-workspace': {
17
+ ignoredAfExportKeys: ['./lock-parser']
18
+ },
16
19
  '@atlaskit/tokens': {
17
20
  ignoredAfExportKeys: ['./babel-plugin']
18
21
  },
@@ -53,7 +53,7 @@ var rule = {
53
53
  },
54
54
  hasSuggestions: false,
55
55
  messages: {
56
- invalidWorkspaceProtocolUsage: "The 'workspace:^' or 'workspace:~' protocol is Used. To resolve this error, please use the 'workspace:*' protocol instead.",
56
+ invalidWorkspaceProtocolUsage: "The 'workspace:^'protocol is Used. To resolve this error, please use the 'workspace:*' protocol instead.",
57
57
  invalidRootProtocolUsage: "The 'root:' protocol is not allowed in platform packages. To resolve this error, replace the 'root:' protocol with specific package versions (e.g. '^1.0.0')."
58
58
  }
59
59
  },
@@ -55,7 +55,7 @@ var rule = {
55
55
  url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/no-preconditioning/README.md'
56
56
  },
57
57
  messages: {
58
- useConfig: 'Do not precondition gates or experiments with another gate. Configure this in Statsig instead to reduce unnecessary code and simplify cleanup.',
58
+ useConfig: 'Do not precondition gates or experiments with another gate. Configure this in Statsig instead to reduce unnecessary code, simplify cleanup and to ensure accurate exposures in Statsig.',
59
59
  incorrectExposure: 'Evaluate gates or experiments at the end of your logical expression to ensure exposure is tracked correctly.'
60
60
  }
61
61
  },
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _fileExclusions = require("../util/file-exclusions");
8
+ var rule = {
9
+ meta: {
10
+ type: 'problem',
11
+ docs: {
12
+ description: 'Enforce using getDocument from @atlaskit/browser-apis instead of direct document usage',
13
+ recommended: true
14
+ },
15
+ messages: {
16
+ useGetDocument: 'Use getDocument from @atlaskit/browser-apis instead of direct document usage'
17
+ },
18
+ schema: []
19
+ },
20
+ create: function create(context) {
21
+ var hasGetDocumentImport = false;
22
+ var filename = context.filename;
23
+
24
+ // Skip test files
25
+ var skipResult = (0, _fileExclusions.skipForTestFiles)(context);
26
+ if (skipResult) {
27
+ return skipResult;
28
+ }
29
+
30
+ // Skip example files
31
+ var skipResult2 = (0, _fileExclusions.skipForExampleFiles)(context);
32
+ if (skipResult2) {
33
+ return skipResult2;
34
+ }
35
+
36
+ // Skip the getDocument.ts file itself
37
+ if (filename.endsWith('getDocument.ts')) {
38
+ return {};
39
+ }
40
+ return {
41
+ ImportDeclaration: function ImportDeclaration(node) {
42
+ if (node.source.value === '@atlaskit/browser-apis' && node.specifiers.some(function (specifier) {
43
+ return specifier.type === 'ImportSpecifier' && specifier.imported.type === 'Identifier' && specifier.imported.name === 'getDocument';
44
+ })) {
45
+ hasGetDocumentImport = true;
46
+ }
47
+ },
48
+ Identifier: function Identifier(node) {
49
+ if (node.name === 'document' && !hasGetDocumentImport) {
50
+ var parent = node.parent;
51
+
52
+ // Skip if 'document' is used as a property key in an object literal
53
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'Property' && parent.key === node) {
54
+ return;
55
+ }
56
+
57
+ // Skip if 'document' is used as a shorthand property value
58
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'Property' && parent.value === node && parent.shorthand) {
59
+ return;
60
+ }
61
+
62
+ // Skip if 'document' is used as a property being accessed in a member expression
63
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'MemberExpression' && parent.property === node && !parent.computed) {
64
+ return;
65
+ }
66
+
67
+ // Skip if 'document' is being declared as a variable
68
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'VariableDeclarator' && parent.id === node) {
69
+ return;
70
+ }
71
+
72
+ // Skip if 'document' is a function name
73
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'FunctionDeclaration' && 'id' in parent && parent.id === node) {
74
+ return;
75
+ }
76
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'FunctionExpression' && 'id' in parent && parent.id === node) {
77
+ return;
78
+ }
79
+
80
+ // Skip if 'document' is a method name in a class or object
81
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'MethodDefinition' && parent.key === node) {
82
+ return;
83
+ }
84
+
85
+ // Skip if 'document' is being assigned to (shadowing the global)
86
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'AssignmentExpression' && parent.left === node) {
87
+ return;
88
+ }
89
+
90
+ // Skip if 'document' is in a destructuring pattern (could be destructuring from an object)
91
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'ObjectPattern' || (parent === null || parent === void 0 ? void 0 : parent.type) === 'ArrayPattern') {
92
+ return;
93
+ }
94
+ context.report({
95
+ node: node,
96
+ messageId: 'useGetDocument'
97
+ });
98
+ }
99
+ }
100
+ };
101
+ }
102
+ };
103
+ var _default = exports.default = rule;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var rule = {
8
+ meta: {
9
+ docs: {
10
+ description: "Prevent usage of setImmediate in favor of React Testing Library's `waitFor` or similar",
11
+ recommended: true
12
+ },
13
+ type: 'problem',
14
+ messages: {
15
+ noSetImmediate: "Avoid using setImmediate. Use React Testing Library's waitFor or similar instead for better test reliability.",
16
+ suggestWaitFor: 'Replace with waitFor from @testing-library/react or similar'
17
+ },
18
+ hasSuggestions: true
19
+ },
20
+ create: function create(context) {
21
+ return {
22
+ CallExpression: function CallExpression(node) {
23
+ if (node.callee.type === 'Identifier' && node.callee.name === 'setImmediate') {
24
+ context.report({
25
+ node: node,
26
+ messageId: 'noSetImmediate',
27
+ suggest: [{
28
+ messageId: 'suggestWaitFor',
29
+ fix: function fix(fixer) {
30
+ return fixer.replaceText(node, 'await waitFor(() => { /* your assertion here */ })');
31
+ }
32
+ }]
33
+ });
34
+ }
35
+ }
36
+ };
37
+ }
38
+ };
39
+ var _default = exports.default = rule;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ // We will be removing sparse checkout from pipelines in CI completely due to the load it causes on BBC.
8
+ // We will be incrementally removing sparse-checkout from the files below as it is probably unnecessasry.
9
+ // If you must add an exception below, please go through the chopper process before doing so
10
+ var sparseCheckoutExceptions = ['bitbucket-pipelines/pipelines/custom/run-issue-automat.ts', 'bitbucket-pipelines/pipelines/custom/marketplace/utils.ts', 'bitbucket-pipelines/pipelines/custom/confluence/utils/index.ts', 'bitbucket-pipelines/pipelines/custom/afm-tools/upload-afm-dependency-graph-cache.ts', 'bitbucket-pipelines/pipelines/custom/afm-tools/default-afm-tools.ts', 'bitbucket-pipelines/pipelines/custom/marketplace/utils.ts', 'bitbucket-pipelines/pipelines/custom/afm-git-hooks.ts', 'bitbucket-pipelines/pipelines/custom/update-codeowners-and-teams-gen.ts', 'bitbucket-pipelines/pipelines/custom/run-issue-automat.ts'];
11
+ var rule = {
12
+ meta: {
13
+ docs: {
14
+ recommended: false
15
+ },
16
+ type: 'problem',
17
+ messages: {
18
+ noSparseCheckout: 'Sparse checkout is not allowed in pipeline configurations. Use git-alternates instead by setting sparseCheckout to false or add this file to exceptions.'
19
+ }
20
+ },
21
+ create: function create(context) {
22
+ var fileName = context.filename;
23
+ if (sparseCheckoutExceptions.some(function (exception) {
24
+ return fileName.endsWith(exception);
25
+ })) {
26
+ return {};
27
+ }
28
+ return {
29
+ // Look for calls to afmClone or objects that match AFMCloneConfig type
30
+ 'CallExpression[callee.object.name=alias][callee.property.name=afmClone] ObjectExpression Property': function CallExpressionCalleeObjectNameAliasCalleePropertyNameAfmClone_ObjectExpression_Property(node) {
31
+ if (node.key.type === 'Identifier' && node.key.name === 'sparseCheckout') {
32
+ if (node.value.type === 'Literal' && node.value.value === true) {
33
+ context.report({
34
+ node: node,
35
+ messageId: 'noSparseCheckout'
36
+ });
37
+ }
38
+ }
39
+ }
40
+ };
41
+ }
42
+ };
43
+ var _default = exports.default = rule;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.skipForTestFiles = exports.skipForExampleFiles = void 0;
7
+ /**
8
+ * Common patterns for test files that should be excluded from rules
9
+ */
10
+ var TEST_FILE_PATTERNS = ['__tests__', 'test', 'spec'];
11
+
12
+ /**
13
+ * Checks if a file should be excluded from rules based on test file patterns
14
+ * @param filename The filename to check
15
+ * @returns true if the file should be excluded, false otherwise
16
+ */
17
+ var isTestFile = function isTestFile(filename) {
18
+ return TEST_FILE_PATTERNS.some(function (pattern) {
19
+ return filename.includes(pattern);
20
+ });
21
+ };
22
+
23
+ /**
24
+ * Helper function to skip rules for test files
25
+ * @param context The ESLint rule context
26
+ * @returns An empty RuleListener if the file is a test file, undefined otherwise
27
+ */
28
+ var skipForTestFiles = exports.skipForTestFiles = function skipForTestFiles(context) {
29
+ if (isTestFile(context.filename)) {
30
+ return {};
31
+ }
32
+ return undefined;
33
+ };
34
+
35
+ /**
36
+ * Helper function to skip rules for example files
37
+ * @param context The ESLint rule context
38
+ * @returns An empty RuleListener if the file is an example file, undefined otherwise
39
+ */
40
+ var skipForExampleFiles = exports.skipForExampleFiles = function skipForExampleFiles(context) {
41
+ if (context.filename.includes('example')) {
42
+ return {};
43
+ }
44
+ return undefined;
45
+ };
@@ -27,6 +27,27 @@ import useEntrypointsInExamples from './rules/use-entrypoints-in-examples';
27
27
  import useRecommendedUtils from './rules/feature-gating/use-recommended-utils';
28
28
  import expandBackgroundShorthand from './rules/compiled/expand-background-shorthand';
29
29
  import expandSpacingShorthand from './rules/compiled/expand-spacing-shorthand';
30
+ import noSparseCheckout from './rules/no-sparse-checkout';
31
+ import noDirectDocumentUsage from './rules/no-direct-document-usage';
32
+ import noSetImmediate from './rules/no-set-immediate';
33
+ import { join, normalize } from 'node:path';
34
+ import { readFileSync } from 'node:fs';
35
+ let jiraRoot;
36
+ try {
37
+ const findUp = require('find-up');
38
+ findUp.sync(dir => {
39
+ const productsJsonPath = join(dir, 'products.json');
40
+ if (findUp.sync.exists(productsJsonPath)) {
41
+ const productJson = JSON.parse(readFileSync(productsJsonPath, 'utf-8'));
42
+ if (productJson.Jira) {
43
+ jiraRoot = normalize(join(dir, productJson.Jira.path));
44
+ return findUp.stop;
45
+ }
46
+ }
47
+ });
48
+ } catch {
49
+ // we aren't running inside of AFM, so we can ignore this.
50
+ }
30
51
  const packageJson = require('@atlaskit/eslint-plugin-platform/package.json');
31
52
  const rules = {
32
53
  'ensure-feature-flag-registration': ensureFeatureFlagRegistration,
@@ -55,7 +76,10 @@ const rules = {
55
76
  'prefer-fg': preferFG,
56
77
  'no-alias': noAlias,
57
78
  'use-entrypoints-in-examples': useEntrypointsInExamples,
58
- 'use-recommended-utils': useRecommendedUtils
79
+ 'use-recommended-utils': useRecommendedUtils,
80
+ 'no-sparse-checkout': noSparseCheckout,
81
+ 'no-direct-document-usage': noDirectDocumentUsage,
82
+ 'no-set-immediate': noSetImmediate
59
83
  };
60
84
  const commonConfig = {
61
85
  '@atlaskit/platform/ensure-test-runner-arguments': 'error',
@@ -64,6 +88,8 @@ const commonConfig = {
64
88
  '@atlaskit/platform/no-invalid-storybook-decorator-usage': 'error',
65
89
  '@atlaskit/platform/ensure-atlassian-team': 'error',
66
90
  '@atlaskit/platform/no-module-level-eval-nav4': 'error',
91
+ '@atlaskit/platform/no-direct-document-usage': 'warn',
92
+ '@atlaskit/platform/no-set-immediate': 'error',
67
93
  // Compiled: rules that are not included via `@compiled/recommended
68
94
  '@atlaskit/platform/expand-border-shorthand': 'error',
69
95
  '@atlaskit/platform/expand-background-shorthand': 'error',
@@ -93,6 +119,7 @@ const recommendedRules = {
93
119
  const jiraRules = commonConfig;
94
120
  const jsonPrefix = '/* eslint-disable quote-props, comma-dangle, quotes, semi, eol-last, @typescript-eslint/semi, no-template-curly-in-string */ module.exports = ';
95
121
  const jsonPrefixForFlatConfig = '/* eslint-disable quote-props, comma-dangle, quotes, semi, eol-last, no-template-curly-in-string */ module.exports = ';
122
+ const jsonPrefixForJira = 'module.exports = ';
96
123
  const {
97
124
  name,
98
125
  version
@@ -141,7 +168,14 @@ const plugin = {
141
168
  },
142
169
  processors: {
143
170
  'package-json-processor': {
144
- preprocess: source => {
171
+ preprocess: (source, filename) => {
172
+ // we only need to check for jiraRoot because it uses a different
173
+ // ESLint version and produces fake errors due to how this processor handles JSON
174
+ if (jiraRoot && filename.startsWith(jiraRoot)) {
175
+ // augment the json into a js file
176
+ return [jsonPrefixForJira + source.trim()];
177
+ }
178
+
145
179
  // augment the json into a js file
146
180
  return [jsonPrefix + source.trim()];
147
181
  },
@@ -168,7 +202,13 @@ const plugin = {
168
202
  // This processor is used for ESLint FlatConfig,
169
203
  // once we roll out FlatConfig, we can remove the above processor
170
204
  'package-json-processor-for-flat-config': {
171
- preprocess: source => {
205
+ // we only need to check for jiraRoot because it uses a different
206
+ // ESLint version and produces fake errors due to how this processor handles JSON
207
+ preprocess: (source, filename) => {
208
+ if (jiraRoot && filename.startsWith(jiraRoot)) {
209
+ // augment the json into a js file
210
+ return [jsonPrefixForJira + source.trim()];
211
+ }
172
212
  // augment the json into a js file
173
213
  return [jsonPrefixForFlatConfig + source.trim()];
174
214
  },
@@ -1,6 +1,9 @@
1
1
  import path from 'path';
2
2
  import { getMetadataForFilename } from '../util/registration-utils';
3
3
  const exportsValidationExceptions = {
4
+ '@af/yarn-workspace': {
5
+ ignoredAfExportKeys: ['./lock-parser']
6
+ },
4
7
  '@atlaskit/tokens': {
5
8
  ignoredAfExportKeys: ['./babel-plugin']
6
9
  },
@@ -36,7 +36,7 @@ const rule = {
36
36
  },
37
37
  hasSuggestions: false,
38
38
  messages: {
39
- invalidWorkspaceProtocolUsage: `The 'workspace:^' or 'workspace:~' protocol is Used. To resolve this error, please use the 'workspace:*' protocol instead.`,
39
+ invalidWorkspaceProtocolUsage: `The 'workspace:^'protocol is Used. To resolve this error, please use the 'workspace:*' protocol instead.`,
40
40
  invalidRootProtocolUsage: `The 'root:' protocol is not allowed in platform packages. To resolve this error, replace the 'root:' protocol with specific package versions (e.g. '^1.0.0').`
41
41
  }
42
42
  },
@@ -50,7 +50,7 @@ const rule = {
50
50
  url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/no-preconditioning/README.md'
51
51
  },
52
52
  messages: {
53
- useConfig: 'Do not precondition gates or experiments with another gate. Configure this in Statsig instead to reduce unnecessary code and simplify cleanup.',
53
+ useConfig: 'Do not precondition gates or experiments with another gate. Configure this in Statsig instead to reduce unnecessary code, simplify cleanup and to ensure accurate exposures in Statsig.',
54
54
  incorrectExposure: 'Evaluate gates or experiments at the end of your logical expression to ensure exposure is tracked correctly.'
55
55
  }
56
56
  },
@@ -0,0 +1,95 @@
1
+ import { skipForExampleFiles, skipForTestFiles } from '../util/file-exclusions';
2
+ const rule = {
3
+ meta: {
4
+ type: 'problem',
5
+ docs: {
6
+ description: 'Enforce using getDocument from @atlaskit/browser-apis instead of direct document usage',
7
+ recommended: true
8
+ },
9
+ messages: {
10
+ useGetDocument: 'Use getDocument from @atlaskit/browser-apis instead of direct document usage'
11
+ },
12
+ schema: []
13
+ },
14
+ create(context) {
15
+ let hasGetDocumentImport = false;
16
+ const filename = context.filename;
17
+
18
+ // Skip test files
19
+ const skipResult = skipForTestFiles(context);
20
+ if (skipResult) {
21
+ return skipResult;
22
+ }
23
+
24
+ // Skip example files
25
+ const skipResult2 = skipForExampleFiles(context);
26
+ if (skipResult2) {
27
+ return skipResult2;
28
+ }
29
+
30
+ // Skip the getDocument.ts file itself
31
+ if (filename.endsWith('getDocument.ts')) {
32
+ return {};
33
+ }
34
+ return {
35
+ ImportDeclaration(node) {
36
+ if (node.source.value === '@atlaskit/browser-apis' && node.specifiers.some(specifier => specifier.type === 'ImportSpecifier' && specifier.imported.type === 'Identifier' && specifier.imported.name === 'getDocument')) {
37
+ hasGetDocumentImport = true;
38
+ }
39
+ },
40
+ Identifier(node) {
41
+ if (node.name === 'document' && !hasGetDocumentImport) {
42
+ const parent = node.parent;
43
+
44
+ // Skip if 'document' is used as a property key in an object literal
45
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'Property' && parent.key === node) {
46
+ return;
47
+ }
48
+
49
+ // Skip if 'document' is used as a shorthand property value
50
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'Property' && parent.value === node && parent.shorthand) {
51
+ return;
52
+ }
53
+
54
+ // Skip if 'document' is used as a property being accessed in a member expression
55
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'MemberExpression' && parent.property === node && !parent.computed) {
56
+ return;
57
+ }
58
+
59
+ // Skip if 'document' is being declared as a variable
60
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'VariableDeclarator' && parent.id === node) {
61
+ return;
62
+ }
63
+
64
+ // Skip if 'document' is a function name
65
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'FunctionDeclaration' && 'id' in parent && parent.id === node) {
66
+ return;
67
+ }
68
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'FunctionExpression' && 'id' in parent && parent.id === node) {
69
+ return;
70
+ }
71
+
72
+ // Skip if 'document' is a method name in a class or object
73
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'MethodDefinition' && parent.key === node) {
74
+ return;
75
+ }
76
+
77
+ // Skip if 'document' is being assigned to (shadowing the global)
78
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'AssignmentExpression' && parent.left === node) {
79
+ return;
80
+ }
81
+
82
+ // Skip if 'document' is in a destructuring pattern (could be destructuring from an object)
83
+ if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'ObjectPattern' || (parent === null || parent === void 0 ? void 0 : parent.type) === 'ArrayPattern') {
84
+ return;
85
+ }
86
+ context.report({
87
+ node,
88
+ messageId: 'useGetDocument'
89
+ });
90
+ }
91
+ }
92
+ };
93
+ }
94
+ };
95
+ export default rule;
@@ -0,0 +1,33 @@
1
+ const rule = {
2
+ meta: {
3
+ docs: {
4
+ description: "Prevent usage of setImmediate in favor of React Testing Library's `waitFor` or similar",
5
+ recommended: true
6
+ },
7
+ type: 'problem',
8
+ messages: {
9
+ noSetImmediate: "Avoid using setImmediate. Use React Testing Library's waitFor or similar instead for better test reliability.",
10
+ suggestWaitFor: 'Replace with waitFor from @testing-library/react or similar'
11
+ },
12
+ hasSuggestions: true
13
+ },
14
+ create(context) {
15
+ return {
16
+ CallExpression(node) {
17
+ if (node.callee.type === 'Identifier' && node.callee.name === 'setImmediate') {
18
+ context.report({
19
+ node,
20
+ messageId: 'noSetImmediate',
21
+ suggest: [{
22
+ messageId: 'suggestWaitFor',
23
+ fix(fixer) {
24
+ return fixer.replaceText(node, 'await waitFor(() => { /* your assertion here */ })');
25
+ }
26
+ }]
27
+ });
28
+ }
29
+ }
30
+ };
31
+ }
32
+ };
33
+ export default rule;