@atlaskit/eslint-plugin-platform 0.6.1 → 0.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 (126) hide show
  1. package/CHANGELOG.md +103 -28
  2. package/afm-jira/tsconfig.json +20 -0
  3. package/dist/cjs/index.js +48 -2
  4. package/dist/cjs/rules/constants.js +11 -0
  5. package/dist/cjs/rules/ensure-critical-dependency-resolutions/index.js +54 -6
  6. package/dist/cjs/rules/ensure-native-and-af-exports-synced/index.js +16 -7
  7. package/dist/cjs/rules/ensure-valid-emotion-css-prop/index.js +91 -0
  8. package/dist/cjs/rules/inline-usage/index.js +94 -0
  9. package/dist/cjs/rules/no-alias/index.js +64 -0
  10. package/dist/cjs/rules/no-module-level-eval/index.js +45 -0
  11. package/dist/cjs/rules/no-preconditioning/index.js +108 -0
  12. package/dist/cjs/rules/prefer-fg/index.js +106 -0
  13. package/dist/cjs/rules/static-feature-flags/index.js +63 -0
  14. package/dist/cjs/rules/use-recommended-utils/index.js +47 -0
  15. package/dist/cjs/rules/util/registration-utils.js +2 -1
  16. package/dist/cjs/rules/utils.js +53 -0
  17. package/dist/es2019/index.js +52 -2
  18. package/dist/es2019/rules/constants.js +5 -0
  19. package/dist/es2019/rules/ensure-critical-dependency-resolutions/index.js +52 -6
  20. package/dist/es2019/rules/ensure-native-and-af-exports-synced/index.js +15 -7
  21. package/dist/es2019/rules/ensure-valid-emotion-css-prop/index.js +87 -0
  22. package/dist/es2019/rules/inline-usage/index.js +90 -0
  23. package/dist/es2019/rules/no-alias/index.js +58 -0
  24. package/dist/es2019/rules/no-module-level-eval/index.js +39 -0
  25. package/dist/es2019/rules/no-preconditioning/index.js +105 -0
  26. package/dist/es2019/rules/prefer-fg/index.js +81 -0
  27. package/dist/es2019/rules/static-feature-flags/index.js +54 -0
  28. package/dist/es2019/rules/use-recommended-utils/index.js +41 -0
  29. package/dist/es2019/rules/util/registration-utils.js +2 -1
  30. package/dist/es2019/rules/utils.js +29 -0
  31. package/dist/esm/index.js +48 -2
  32. package/dist/esm/rules/constants.js +5 -0
  33. package/dist/esm/rules/ensure-critical-dependency-resolutions/index.js +54 -6
  34. package/dist/esm/rules/ensure-native-and-af-exports-synced/index.js +16 -7
  35. package/dist/esm/rules/ensure-valid-emotion-css-prop/index.js +85 -0
  36. package/dist/esm/rules/inline-usage/index.js +87 -0
  37. package/dist/esm/rules/no-alias/index.js +57 -0
  38. package/dist/esm/rules/no-module-level-eval/index.js +39 -0
  39. package/dist/esm/rules/no-preconditioning/index.js +102 -0
  40. package/dist/esm/rules/prefer-fg/index.js +99 -0
  41. package/dist/esm/rules/static-feature-flags/index.js +56 -0
  42. package/dist/esm/rules/use-recommended-utils/index.js +41 -0
  43. package/dist/esm/rules/util/registration-utils.js +2 -1
  44. package/dist/esm/rules/utils.js +45 -0
  45. package/dist/types/index.d.ts +15 -0
  46. package/dist/types/rules/constants.d.ts +3 -0
  47. package/dist/types/rules/ensure-valid-emotion-css-prop/index.d.ts +3 -0
  48. package/dist/types/rules/inline-usage/index.d.ts +3 -0
  49. package/dist/types/rules/no-alias/index.d.ts +3 -0
  50. package/dist/types/rules/no-module-level-eval/index.d.ts +3 -0
  51. package/dist/types/rules/no-preconditioning/index.d.ts +3 -0
  52. package/dist/types/rules/prefer-fg/index.d.ts +3 -0
  53. package/dist/types/rules/static-feature-flags/index.d.ts +3 -0
  54. package/dist/types/rules/use-recommended-utils/index.d.ts +3 -0
  55. package/dist/types/rules/util/registration-utils.d.ts +1 -0
  56. package/dist/types/rules/utils.d.ts +7 -0
  57. package/dist/types-ts4.5/index.d.ts +15 -0
  58. package/dist/types-ts4.5/rules/constants.d.ts +3 -0
  59. package/dist/types-ts4.5/rules/ensure-valid-emotion-css-prop/index.d.ts +3 -0
  60. package/dist/types-ts4.5/rules/inline-usage/index.d.ts +3 -0
  61. package/dist/types-ts4.5/rules/no-alias/index.d.ts +3 -0
  62. package/dist/types-ts4.5/rules/no-module-level-eval/index.d.ts +3 -0
  63. package/dist/types-ts4.5/rules/no-preconditioning/index.d.ts +3 -0
  64. package/dist/types-ts4.5/rules/prefer-fg/index.d.ts +3 -0
  65. package/dist/types-ts4.5/rules/static-feature-flags/index.d.ts +3 -0
  66. package/dist/types-ts4.5/rules/use-recommended-utils/index.d.ts +3 -0
  67. package/dist/types-ts4.5/rules/util/registration-utils.d.ts +1 -0
  68. package/dist/types-ts4.5/rules/utils.d.ts +7 -0
  69. package/index.js +9 -9
  70. package/package.json +43 -44
  71. package/report.api.md +31 -30
  72. package/src/__tests__/utils/_tester.tsx +16 -16
  73. package/src/index.tsx +102 -51
  74. package/src/rules/constants.tsx +20 -0
  75. package/src/rules/ensure-atlassian-team/__tests__/unit/rule.test.ts +19 -19
  76. package/src/rules/ensure-atlassian-team/index.ts +39 -52
  77. package/src/rules/ensure-critical-dependency-resolutions/__test__/unit/rule.test.tsx +146 -81
  78. package/src/rules/ensure-critical-dependency-resolutions/index.tsx +152 -97
  79. package/src/rules/ensure-feature-flag-prefix/__tests__/unit/rule.test.tsx +51 -51
  80. package/src/rules/ensure-feature-flag-prefix/index.tsx +65 -80
  81. package/src/rules/ensure-feature-flag-registration/__tests__/unit/rule.test.tsx +97 -97
  82. package/src/rules/ensure-feature-flag-registration/index.tsx +88 -105
  83. package/src/rules/ensure-native-and-af-exports-synced/__tests__/unit/rule.test.tsx +180 -180
  84. package/src/rules/ensure-native-and-af-exports-synced/index.tsx +162 -168
  85. package/src/rules/ensure-publish-valid/__tests__/unit/rule.test.ts +34 -36
  86. package/src/rules/ensure-publish-valid/index.ts +66 -81
  87. package/src/rules/ensure-test-runner-arguments/__tests__/unit/rule.test.tsx +93 -93
  88. package/src/rules/ensure-test-runner-arguments/index.tsx +107 -121
  89. package/src/rules/ensure-test-runner-nested-count/__tests__/unit/rule.test.tsx +43 -43
  90. package/src/rules/ensure-test-runner-nested-count/index.tsx +68 -70
  91. package/src/rules/ensure-valid-emotion-css-prop/__tests__/unit/rule.test.ts +142 -0
  92. package/src/rules/ensure-valid-emotion-css-prop/index.ts +96 -0
  93. package/src/rules/inline-usage/README.md +53 -0
  94. package/src/rules/inline-usage/__tests__/rule.test.tsx +106 -0
  95. package/src/rules/inline-usage/index.tsx +130 -0
  96. package/src/rules/no-alias/README.md +29 -0
  97. package/src/rules/no-alias/__tests__/rule.test.tsx +76 -0
  98. package/src/rules/no-alias/index.tsx +75 -0
  99. package/src/rules/no-duplicate-dependencies/__tests__/unit/rule.test.ts +44 -44
  100. package/src/rules/no-duplicate-dependencies/index.ts +68 -73
  101. package/src/rules/no-invalid-feature-flag-usage/__tests__/unit/rule.test.tsx +64 -64
  102. package/src/rules/no-invalid-feature-flag-usage/index.tsx +105 -112
  103. package/src/rules/no-invalid-storybook-decorator-usage/__tests__/unit/rule.test.tsx +13 -13
  104. package/src/rules/no-invalid-storybook-decorator-usage/index.tsx +28 -30
  105. package/src/rules/no-module-level-eval/README.md +53 -0
  106. package/src/rules/no-module-level-eval/__tests__/test.tsx +133 -0
  107. package/src/rules/no-module-level-eval/index.tsx +52 -0
  108. package/src/rules/no-pre-post-installs/__tests__/unit/rule.test.ts +36 -36
  109. package/src/rules/no-pre-post-installs/index.ts +27 -27
  110. package/src/rules/no-preconditioning/README.md +69 -0
  111. package/src/rules/no-preconditioning/__tests__/rule.test.tsx +164 -0
  112. package/src/rules/no-preconditioning/index.tsx +138 -0
  113. package/src/rules/prefer-fg/README.md +3 -0
  114. package/src/rules/prefer-fg/__tests__/rule.test.tsx +83 -0
  115. package/src/rules/prefer-fg/index.tsx +108 -0
  116. package/src/rules/static-feature-flags/README.md +3 -0
  117. package/src/rules/static-feature-flags/__tests__/test.tsx +135 -0
  118. package/src/rules/static-feature-flags/index.tsx +103 -0
  119. package/src/rules/use-recommended-utils/README.md +67 -0
  120. package/src/rules/use-recommended-utils/__tests__/rule.test.tsx +78 -0
  121. package/src/rules/use-recommended-utils/index.tsx +57 -0
  122. package/src/rules/util/handle-ast-object.ts +21 -32
  123. package/src/rules/util/registration-utils.ts +31 -30
  124. package/src/rules/utils.tsx +46 -0
  125. package/tsconfig.app.json +35 -35
  126. package/tsconfig.dev.json +39 -39
@@ -0,0 +1,81 @@
1
+ import { FEATURE_API_IMPORT_SOURCES } from '../constants';
2
+ const validateUsage = (node, utilName, context, changeMap) => {
3
+ var _context$getScope$ref;
4
+ const resolved = (_context$getScope$ref = context.getScope().references.find(ref => ref.identifier.name === utilName)) === null || _context$getScope$ref === void 0 ? void 0 : _context$getScope$ref.resolved;
5
+ const importSpecifierDefinition = resolved === null || resolved === void 0 ? void 0 : resolved.defs.find(def => {
6
+ var _def$node, _def$parent;
7
+ return ((_def$node = def.node) === null || _def$node === void 0 ? void 0 : _def$node.type) === 'ImportSpecifier' && FEATURE_API_IMPORT_SOURCES.has((_def$parent = def.parent) === null || _def$parent === void 0 ? void 0 : _def$parent.source.value);
8
+ });
9
+ if (importSpecifierDefinition) {
10
+ const [flagNameArg] = node.arguments;
11
+ context.report({
12
+ messageId: 'preferFG',
13
+ node,
14
+ *fix(fixer) {
15
+ yield fixer.replaceText(node, `fg(${context.sourceCode.getText(flagNameArg)})`);
16
+ const importDeclaration = importSpecifierDefinition.parent;
17
+ if (changeMap.has(importDeclaration)) {
18
+ const changeCounts = changeMap.get(importDeclaration);
19
+ changeMap.set(importDeclaration, {
20
+ ...changeCounts,
21
+ [utilName]: changeCounts[utilName] + 1 || 1
22
+ });
23
+ } else {
24
+ changeMap.set(importDeclaration, {
25
+ [utilName]: 1
26
+ });
27
+ }
28
+ }
29
+ });
30
+ }
31
+ };
32
+ const rule = {
33
+ meta: {
34
+ docs: {
35
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/ff/prefer-fg/README.md',
36
+ description: 'Keep usages of boolean feature flags consistent'
37
+ },
38
+ fixable: 'code',
39
+ messages: {
40
+ preferFG: 'Use `fg` instead for boolean feature flags',
41
+ autoFixImports: 'Use `fg` instead for boolean feature flags'
42
+ }
43
+ },
44
+ create(context) {
45
+ let changeMap;
46
+ return {
47
+ 'CallExpression[callee.name="getBooleanFF"]': node => {
48
+ if (node.type !== 'CallExpression') {
49
+ return;
50
+ }
51
+ changeMap = changeMap || new Map();
52
+ validateUsage(node, 'getBooleanFF', context, changeMap);
53
+ },
54
+ 'Program:exit': () => {
55
+ var _changeMap;
56
+ if ((_changeMap = changeMap) !== null && _changeMap !== void 0 && _changeMap.size) {
57
+ changeMap.forEach((changeCounts, importDeclaration) => {
58
+ const [moduleScope] = context.getScope().childScopes;
59
+ const importSpecifiers = new Set(importDeclaration.specifiers.map(({
60
+ imported
61
+ }) => imported.name));
62
+ importSpecifiers.add('fg');
63
+ Object.keys(changeCounts).forEach(utilName => {
64
+ var _moduleScope$set$get;
65
+ if (changeCounts[utilName] === ((_moduleScope$set$get = moduleScope.set.get(utilName)) === null || _moduleScope$set$get === void 0 ? void 0 : _moduleScope$set$get.references.length)) {
66
+ importSpecifiers.delete(utilName);
67
+ }
68
+ });
69
+ context.report({
70
+ messageId: 'autoFixImports',
71
+ node: importDeclaration,
72
+ fix: fixer => fixer.replaceText(importDeclaration, `import { ${Array.from(importSpecifiers).join(', ')} } from '${importDeclaration.source.value}';`)
73
+ });
74
+ });
75
+ changeMap.clear();
76
+ }
77
+ }
78
+ };
79
+ }
80
+ };
81
+ export default rule;
@@ -0,0 +1,54 @@
1
+ import { FEATURE_API_IMPORT_SOURCES } from '../constants';
2
+ import { getDef, isIdentifierImportedFrom } from '../utils';
3
+ const IMPORT_SOURCES = new Set([...FEATURE_API_IMPORT_SOURCES, '@atlassian/jira-feature-flagging-utils', '@atlassian/jira-feature-gate-component', '@atlassian/jira-feature-gates-test-mocks', '@atlassian/jira-feature-gates-storybook-mocks']);
4
+
5
+ // Any functions not in this list should be skipped for performance.
6
+ const FUNCTION_NAMES = new Set(['ff', 'fg', 'getFeatureFlagValue', 'getMultivariateFeatureFlag', 'componentWithFF', 'componentWithFG', 'passGate', 'withGate', 'expVal', 'expValEquals', 'UNSAFE_noExposureExp', 'mockExp', 'withExp', 'wasExperimentManuallyExposed']);
7
+ const STATSIG_ONLY_FUNCTION_NAMES = new Set(['fg', 'componentWithFG', 'passGate', 'withGate', 'expVal', 'expValEquals', 'UNSAFE_noExposureExp', 'mockExp', 'withExp', 'wasExperimentManuallyExposed']);
8
+ const rule = {
9
+ meta: {
10
+ type: 'problem',
11
+ docs: {
12
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/ff/static-feature-flags/README.md',
13
+ description: 'Ensure feature flags or gates are static string literals'
14
+ },
15
+ fixable: 'code',
16
+ messages: {
17
+ FFLiteral: 'Use static string literal for `featureFlagName`. See https://team.atlassian.com/project/ATLAS-46997/about'
18
+ }
19
+ },
20
+ create(context) {
21
+ const targetedFunctionsSwitch = context.options[0] === 'ssOnly' ? STATSIG_ONLY_FUNCTION_NAMES : FUNCTION_NAMES;
22
+ return {
23
+ // When they're not literals, show a message
24
+ 'CallExpression[callee.type="Identifier"][arguments.length>0][arguments.0.type!="Literal"]': node => {
25
+ if (node.type !== 'CallExpression') {
26
+ return;
27
+ }
28
+ if (node.callee.type === 'Identifier' && (!targetedFunctionsSwitch.has(node.callee.name) || !isIdentifierImportedFrom(node.callee.name, IMPORT_SOURCES, context))) {
29
+ return;
30
+ }
31
+ const nameArgument = node.arguments[0];
32
+ if (nameArgument.type === 'Identifier') {
33
+ const def = getDef(nameArgument.name, context);
34
+ if (def != null && def.type === 'Variable') {
35
+ const {
36
+ value
37
+ } = def.node.init;
38
+ context.report({
39
+ node: nameArgument,
40
+ messageId: 'FFLiteral',
41
+ fix: fixer => fixer.replaceText(nameArgument, `'${value}'`)
42
+ });
43
+ return;
44
+ }
45
+ }
46
+ context.report({
47
+ node: nameArgument,
48
+ messageId: 'FFLiteral'
49
+ });
50
+ }
51
+ };
52
+ }
53
+ };
54
+ export default rule;
@@ -0,0 +1,41 @@
1
+ import { isIdentifierImportedFrom } from '../utils';
2
+ const BANNED_IMPORTS_SET = new Set(['@atlaskit/feature-gate-js-client']);
3
+ const rule = {
4
+ meta: {
5
+ docs: {
6
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/ff/use-recommended-utils/README.md',
7
+ description: 'Prefer using the feature flag abstraction over direct statsig library.'
8
+ },
9
+ messages: {
10
+ notSupported: 'Experimentation is not suported in platform feature flags, reach out to #help-statsig-switcheroo.',
11
+ useRecommended: 'Please do not use FeatureGates.{{util}}, use {{recommended}} from {{lib}} instead.'
12
+ },
13
+ type: 'problem'
14
+ },
15
+ create(context) {
16
+ return {
17
+ 'CallExpression > MemberExpression:matches([property.name="checkGate"])': node => {
18
+ if (node.object.type === 'Identifier' && isIdentifierImportedFrom(node.object.name, BANNED_IMPORTS_SET, context)) {
19
+ context.report({
20
+ messageId: 'useRecommended',
21
+ node,
22
+ data: {
23
+ lib: '`@atlaskit/platform-feature-flags`',
24
+ util: node.property.name,
25
+ recommended: '`fg`'
26
+ }
27
+ });
28
+ }
29
+ },
30
+ 'CallExpression > MemberExpression:matches([property.name="getExperimentValue"])': node => {
31
+ if (node.object.type === 'Identifier' && isIdentifierImportedFrom(node.object.name, BANNED_IMPORTS_SET, context)) {
32
+ context.report({
33
+ messageId: 'notSupported',
34
+ node
35
+ });
36
+ }
37
+ }
38
+ };
39
+ }
40
+ };
41
+ export default rule;
@@ -6,7 +6,8 @@ import Fuse from 'fuse.js';
6
6
  // if you don't want to verify the type use `null` as the value
7
7
  export const getterIdentifierToFlagTypeMap = {
8
8
  getBooleanFF: 'boolean',
9
- ffTest: 'boolean'
9
+ ffTest: 'boolean',
10
+ fg: 'boolean'
10
11
  };
11
12
  // make sure we cache reading the package.json so we don't end up reading it for every instance of this rule.
12
13
  const pkgJsonCache = new Map();
@@ -0,0 +1,29 @@
1
+ import { FEATURE_API_IMPORT_SOURCES } from './constants';
2
+ export function isIdentifierImportedFrom(identifierName, sources, context) {
3
+ if (sources.size > 0) {
4
+ var _context$getScope$ref, _context$getScope$ref2, _context$getScope$ref3;
5
+ return (_context$getScope$ref = (_context$getScope$ref2 = context.getScope().references.find(ref => ref.identifier.name === identifierName)) === null || _context$getScope$ref2 === void 0 ? void 0 : (_context$getScope$ref3 = _context$getScope$ref2.resolved) === null || _context$getScope$ref3 === void 0 ? void 0 : _context$getScope$ref3.defs.some(def => {
6
+ var _def$parent;
7
+ return ((_def$parent = def.parent) === null || _def$parent === void 0 ? void 0 : _def$parent.type) === 'ImportDeclaration' && sources.has(def.parent.source.value + '');
8
+ })) !== null && _context$getScope$ref !== void 0 ? _context$getScope$ref : false;
9
+ }
10
+ return false;
11
+ }
12
+ export function isAPIimport(functionName, context) {
13
+ return isIdentifierImportedFrom(functionName, FEATURE_API_IMPORT_SOURCES, context);
14
+ }
15
+
16
+ // returns the definition node of a variable if it's declared within the scope of the file
17
+ export function getDef(name, context) {
18
+ let scope = context.getScope();
19
+ while (scope && scope.type !== 'global') {
20
+ for (const variable of scope.variables) {
21
+ if (variable.name === name) {
22
+ const definition = variable.defs.find(def => def.node && def.node.type === 'VariableDeclarator');
23
+ return definition;
24
+ }
25
+ }
26
+ scope = scope.upper;
27
+ }
28
+ return null;
29
+ }
package/dist/esm/index.js CHANGED
@@ -12,9 +12,17 @@ import noDuplicateDependencies from './rules/no-duplicate-dependencies';
12
12
  import noInvalidFeatureFlagUsage from './rules/no-invalid-feature-flag-usage';
13
13
  import ensureFeatureFlagPrefix from './rules/ensure-feature-flag-prefix';
14
14
  import ensureCriticalDependencyResolutions from './rules/ensure-critical-dependency-resolutions';
15
+ import ensureValidEmotionCssProp from './rules/ensure-valid-emotion-css-prop';
15
16
  import noInvalidStorybookDecoratorUsage from './rules/no-invalid-storybook-decorator-usage';
16
17
  import ensurePublishValid from './rules/ensure-publish-valid';
17
18
  import ensureNativeAndAfExportsSynced from './rules/ensure-native-and-af-exports-synced';
19
+ import noModuleLevelEval from './rules/no-module-level-eval';
20
+ import staticFeatureFlags from './rules/static-feature-flags';
21
+ import noPreconditioning from './rules/no-preconditioning';
22
+ import inlineUsage from './rules/inline-usage';
23
+ import preferFG from './rules/prefer-fg';
24
+ import noAlias from './rules/no-alias';
25
+ import useRecommendedUtils from './rules/use-recommended-utils';
18
26
  export var rules = {
19
27
  'ensure-feature-flag-registration': ensureFeatureFlagRegistration,
20
28
  'ensure-feature-flag-prefix': ensureFeatureFlagPrefix,
@@ -22,12 +30,20 @@ export var rules = {
22
30
  'ensure-test-runner-nested-count': ensureTestRunnerNestedCount,
23
31
  'ensure-atlassian-team': ensureAtlassianTeam,
24
32
  'ensure-critical-dependency-resolutions': ensureCriticalDependencyResolutions,
33
+ 'ensure-valid-emotion-css-prop': ensureValidEmotionCssProp,
25
34
  'no-duplicate-dependencies': noDuplicateDependencies,
26
35
  'no-invalid-feature-flag-usage': noInvalidFeatureFlagUsage,
27
36
  'no-pre-post-install-scripts': noPreAndPostInstallScripts,
28
37
  'no-invalid-storybook-decorator-usage': noInvalidStorybookDecoratorUsage,
29
38
  'ensure-publish-valid': ensurePublishValid,
30
- 'ensure-native-and-af-exports-synced': ensureNativeAndAfExportsSynced
39
+ 'ensure-native-and-af-exports-synced': ensureNativeAndAfExportsSynced,
40
+ 'no-module-level-eval': noModuleLevelEval,
41
+ 'static-feature-flags': staticFeatureFlags,
42
+ 'no-preconditioning': noPreconditioning,
43
+ 'inline-usage': inlineUsage,
44
+ 'prefer-fg': preferFG,
45
+ 'no-alias': noAlias,
46
+ 'use-recommended-utils': useRecommendedUtils
31
47
  };
32
48
  export var configs = {
33
49
  recommended: {
@@ -41,11 +57,18 @@ export var configs = {
41
57
  '@atlaskit/platform/ensure-test-runner-nested-count': 'warn',
42
58
  '@atlaskit/platform/no-invalid-feature-flag-usage': 'error',
43
59
  '@atlaskit/platform/no-invalid-storybook-decorator-usage': 'error',
44
- '@atlaskit/platform/ensure-atlassian-team': 'error'
60
+ '@atlaskit/platform/ensure-atlassian-team': 'error',
61
+ '@atlaskit/platform/no-module-level-eval': 'error',
62
+ '@atlaskit/platform/static-feature-flags': 'error',
63
+ '@atlaskit/platform/no-preconditioning': 'error',
64
+ '@atlaskit/platform/inline-usage': 'error',
65
+ '@atlaskit/platform/prefer-fg': 'error',
66
+ '@atlaskit/platform/no-alias': 'error'
45
67
  }
46
68
  }
47
69
  };
48
70
  var jsonPrefix = '/* eslint-disable quote-props, comma-dangle, quotes, semi, eol-last, @typescript-eslint/semi, no-template-curly-in-string */ module.exports = ';
71
+ var jsonPrefixForFlatConfig = '/* eslint-disable quote-props, comma-dangle, quotes, semi, eol-last, no-template-curly-in-string */ module.exports = ';
49
72
  export var processors = {
50
73
  'package-json-processor': {
51
74
  preprocess: function preprocess(source) {
@@ -67,5 +90,28 @@ export var processors = {
67
90
  });
68
91
  },
69
92
  supportsAutofix: true
93
+ },
94
+ // This processor is used for ESLint FlatConfig,
95
+ // once we roll out FlatConfig, we can remove the above processor
96
+ 'package-json-processor-for-flat-config': {
97
+ preprocess: function preprocess(source) {
98
+ // augment the json into a js file
99
+ return [jsonPrefixForFlatConfig + source.trim()];
100
+ },
101
+ postprocess: function postprocess(messages) {
102
+ return messages[0].map(function (message) {
103
+ var fix = message.fix;
104
+ if (!fix) {
105
+ return message;
106
+ }
107
+ var offset = jsonPrefixForFlatConfig.length;
108
+ return _objectSpread(_objectSpread({}, message), {}, {
109
+ fix: _objectSpread(_objectSpread({}, fix), {}, {
110
+ range: [fix.range[0] - offset, fix.range[1] - offset]
111
+ })
112
+ });
113
+ });
114
+ },
115
+ supportsAutofix: true
70
116
  }
71
117
  };
@@ -0,0 +1,5 @@
1
+ // List of libraries that we maintain or have worked on
2
+ // - eg `@atlaskit/feature-gate-js-client` shouldn't be included in here
3
+ export var FEATURE_API_IMPORT_SOURCES = new Set(['@atlassian/jira-feature-flagging', '@atlassian/jira-feature-flagging-using-meta', '@atlassian/jira-feature-gating', '@atlassian/jira-feature-experiments', '@atlaskit/platform-feature-flags']);
4
+ export var FEATURE_MOCKS_IMPORT_SOURCES = new Set(['@atlassian/jira-feature-flagging-mocks', '@atlassian/jira-feature-gates-test-mocks', '@atlassian/jira-feature-gates-storybook-mocks']);
5
+ export var FEATURE_UTILS_IMPORT_SOURCES = new Set(['@atlassian/jira-feature-flagging-utils', '@atlassian/jira-feature-gate-component']);
@@ -9,30 +9,69 @@ import { getObjectPropertyAsObject } from '../util/handle-ast-object';
9
9
  //
10
10
  var DESIRED_PKG_VERSIONS = {
11
11
  typescript: ['5.4'],
12
- '@types/react': ['16.14', '18.2']
12
+ tslib: ['2.6'],
13
+ '@types/react': ['16.14', '18.2'],
14
+ 'react-relay': ['npm:atl-react-relay@0.0.0-main-2ccd6998'],
15
+ 'relay-compiler': ['npm:atl-relay-compiler@0.0.0-main-2ccd6998'],
16
+ 'relay-runtime': ['npm:atl-relay-runtime@0.0.0-main-2ccd6998'],
17
+ 'relay-test-utils': ['npm:atl-relay-test-utils@0.0.0-main-2ccd6998']
13
18
  };
14
19
  var matchMinorVersion = function matchMinorVersion(desiredVersion, versionInResolutions) {
15
20
  var firstChar = versionInResolutions[0];
16
21
  // The version is invalid if it doesn't start with a number or ~
17
- if (!/^\d$/.test(firstChar) && firstChar !== '~') {
22
+ if (!/^\d$/.test(firstChar) && firstChar !== '~' && !versionInResolutions.startsWith('npm:')) {
18
23
  return false;
19
24
  }
20
25
  return versionInResolutions.startsWith(desiredVersion) || versionInResolutions.startsWith('~' + desiredVersion);
21
26
  };
22
- var verifyResolutionFromObject = function verifyResolutionFromObject(node, pkg, version, optional) {
27
+ var verifyResolutionFromObject = function verifyResolutionFromObject(_ref) {
28
+ var resolutions = _ref.resolutions,
29
+ dependencies = _ref.dependencies,
30
+ devDependencies = _ref.devDependencies,
31
+ pkg = _ref.pkg,
32
+ version = _ref.version,
33
+ optional = _ref.optional;
23
34
  // For root package.json, we require the critical packages' resolutions exist and with matching version
24
35
  // For individual package's package.json, it's ok if resolutions don't exist. But if they do, the version should match
25
- var resolutionExist = node.properties.some(function (p) {
36
+ var resolutionExist = resolutions.properties.some(function (p) {
26
37
  return p.type === 'Property' && p.key.type === 'Literal' && p.key.value === pkg;
27
38
  });
39
+ isDependencyPresent({
40
+ resolutions: resolutions,
41
+ dependencies: dependencies,
42
+ devDependencies: devDependencies,
43
+ pkg: pkg
44
+ });
28
45
  if (!resolutionExist) {
46
+ // when package is not a part of dependencies/devDependencies
47
+ if (optional === false && !isDependencyPresent({
48
+ resolutions: resolutions,
49
+ dependencies: dependencies,
50
+ devDependencies: devDependencies,
51
+ pkg: pkg
52
+ })) {
53
+ return true;
54
+ }
29
55
  return optional;
30
56
  }
31
- var resolutionExistAndMatch = node.properties.some(function (p) {
57
+ var resolutionExistAndMatch = resolutions.properties.some(function (p) {
32
58
  return p.type === 'Property' && p.key.type === 'Literal' && p.key.value === pkg && p.value.type === 'Literal' && matchMinorVersion(version, p.value.value);
33
59
  });
34
60
  return resolutionExistAndMatch;
35
61
  };
62
+ var isDependencyPresent = function isDependencyPresent(_ref2) {
63
+ var resolutions = _ref2.resolutions,
64
+ dependencies = _ref2.dependencies,
65
+ devDependencies = _ref2.devDependencies,
66
+ pkg = _ref2.pkg;
67
+ var dependencyExist = dependencies !== null && dependencies.properties.some(function (p) {
68
+ return p.type === 'Property' && p.key.type === 'Literal' && p.key.value === pkg;
69
+ });
70
+ var devDependencyExist = devDependencies !== null && devDependencies.properties.some(function (p) {
71
+ return p.type === 'Property' && p.key.type === 'Literal' && p.key.value === pkg;
72
+ });
73
+ return dependencyExist || devDependencyExist;
74
+ };
36
75
  var rule = {
37
76
  meta: {
38
77
  type: 'problem',
@@ -61,6 +100,8 @@ var rule = {
61
100
  return;
62
101
  }
63
102
  var packageResolutions = getObjectPropertyAsObject(node, 'resolutions');
103
+ var packageDependencies = getObjectPropertyAsObject(node, 'dependencies');
104
+ var packageDevDependencies = getObjectPropertyAsObject(node, 'devDependencies');
64
105
  var rootDir = findRootSync(process.cwd());
65
106
  var isRootPackageJson = fileName.endsWith("".concat(rootDir, "/package.json"));
66
107
  if (packageResolutions !== null) {
@@ -69,7 +110,14 @@ var rule = {
69
110
  key = _Object$entries$_i[0],
70
111
  values = _Object$entries$_i[1];
71
112
  if (!values.some(function (value) {
72
- return verifyResolutionFromObject(packageResolutions, key, value, !isRootPackageJson);
113
+ return verifyResolutionFromObject({
114
+ resolutions: packageResolutions,
115
+ dependencies: packageDependencies,
116
+ devDependencies: packageDevDependencies,
117
+ pkg: key,
118
+ version: value,
119
+ optional: !isRootPackageJson
120
+ });
73
121
  })) {
74
122
  return {
75
123
  v: context.report({
@@ -1,3 +1,4 @@
1
+ import _typeof from "@babel/runtime/helpers/typeof";
1
2
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
3
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
4
  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; }
@@ -70,7 +71,7 @@ var rule = {
70
71
  });
71
72
  continue;
72
73
  }
73
- var exportValueViolations = getExportValueViolation(pkgName, afExportsKey, afExportsValue, nativeExports);
74
+ var exportValueViolations = getExportValueViolation(afExportsKey, afExportsValue, nativeExports);
74
75
  if (exportValueViolations) {
75
76
  context.report({
76
77
  data: _objectSpread(_objectSpread({}, exportValueViolations), {}, {
@@ -87,7 +88,7 @@ var rule = {
87
88
  }
88
89
  };
89
90
  function getExportKeyViolation(afExportsKey, afExportsValue, nativeExports) {
90
- var afExportsValueHasExtension = path.extname(afExportsValue);
91
+ var afExportsValueHasExtension = path.extname(afExportsValue) !== '';
91
92
  if (afExportsValueHasExtension && !nativeExports.hasOwnProperty(afExportsKey)) {
92
93
  return {
93
94
  messageId: 'missingExportsKey',
@@ -110,15 +111,23 @@ function getExportKeyViolation(afExportsKey, afExportsValue, nativeExports) {
110
111
  };
111
112
  }
112
113
  }
113
- function getExportValueViolation(pkgName, afExportsKey, afExportsValue, nativeExports) {
114
- var afExportsValueHasExtension = path.extname(afExportsValue);
114
+ function getNativeExportsValue(afExportsKey, afExportsValueHasExtension, nativeExports) {
115
+ var nativeExportsKey = afExportsValueHasExtension ? afExportsKey : "".concat(afExportsKey, "/*");
116
+ if (_typeof(nativeExports[nativeExportsKey]) === 'object') {
117
+ return nativeExports[nativeExportsKey].default;
118
+ }
119
+ return nativeExports[nativeExportsKey];
120
+ }
121
+ function getExportValueViolation(afExportsKey, afExportsValue, nativeExports) {
122
+ var afExportsValueHasExtension = path.extname(afExportsValue) !== '';
123
+ var nativeExportsValue = getNativeExportsValue(afExportsKey, afExportsValueHasExtension, nativeExports);
115
124
 
116
125
  // Some entrypoints have been updated to an index.js file that registers ts-node
117
126
  // Use path.basename to get the file name to see if it is equal to 'index.js'
118
- if (afExportsValueHasExtension && path.basename(nativeExports[afExportsKey]) === 'index.js') {
127
+ if (afExportsValueHasExtension && path.basename(nativeExportsValue) === 'index.js') {
119
128
  return;
120
129
  }
121
- if (afExportsValueHasExtension && nativeExports[afExportsKey] !== afExportsValue) {
130
+ if (afExportsValueHasExtension && nativeExportsValue !== afExportsValue) {
122
131
  return {
123
132
  key: afExportsKey,
124
133
  expectedValue: afExportsValue
@@ -126,7 +135,7 @@ function getExportValueViolation(pkgName, afExportsKey, afExportsValue, nativeEx
126
135
  }
127
136
 
128
137
  // af:exports entrypoints without a file extension export the whole directory so check to ensure the exports value includes the wildcard
129
- if (!afExportsValueHasExtension && !nativeExports["".concat(afExportsKey, "/*")].startsWith("".concat(afExportsValue, "/*"))) {
138
+ if (!afExportsValueHasExtension && !nativeExportsValue.startsWith("".concat(afExportsValue, "/*"))) {
130
139
  return {
131
140
  key: "".concat(afExportsKey, "/*"),
132
141
  expectedValue: "".concat(afExportsValue, "/*")
@@ -0,0 +1,85 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
+
3
+ var rule = {
4
+ meta: {
5
+ type: 'problem',
6
+ docs: {
7
+ description: 'Ensure valid use of the `css` prop from `@emotion/react`',
8
+ recommended: true
9
+ },
10
+ messages: {
11
+ noEmotionCssImport: 'Must import `css` from `@emotion/react` when using the `css` prop.',
12
+ noEmotionCssPropFunctionCall: 'No function calls allowed when passing an object directly to the `css` prop with `@emotion/react`.'
13
+ }
14
+ },
15
+ create: function create(context) {
16
+ var emotionJsxImported = false;
17
+ var emotionJsxImportPosition;
18
+ var emotionCssImported = false;
19
+ var cssPropExpressonUsed = false;
20
+
21
+ // Ignore files in these directories
22
+ if (/example|__tests__|__fixtures__/.test(context.filename)) {
23
+ return {};
24
+ }
25
+ return {
26
+ ImportDeclaration: function ImportDeclaration(node) {
27
+ if (node.source.value === '@emotion/react') {
28
+ node.specifiers.forEach(function (specifier) {
29
+ if (specifier.type === 'ImportSpecifier') {
30
+ if (specifier.imported.name === 'jsx') {
31
+ var _specifier$loc;
32
+ emotionJsxImported = true;
33
+ emotionJsxImportPosition = (_specifier$loc = specifier.loc) === null || _specifier$loc === void 0 ? void 0 : _specifier$loc.start;
34
+ }
35
+ if (specifier.imported.name === 'css') {
36
+ emotionCssImported = true;
37
+ }
38
+ }
39
+ });
40
+ }
41
+ },
42
+ JSXAttribute: function JSXAttribute(node) {
43
+ var name = node.name,
44
+ value = node.value;
45
+
46
+ // Only run on emotion css props
47
+ if (!emotionJsxImported) return;
48
+ if (name.name !== 'css') return;
49
+ if (value.type === 'JSXExpressionContainer' && value.expression.type === 'ObjectExpression') {
50
+ cssPropExpressonUsed = true;
51
+ var containsFunctionExpression = false;
52
+
53
+ // Iterate over the properties of the object
54
+ value.expression.properties.forEach(function (prop) {
55
+ var _prop$value, _prop$value2, _prop$value3;
56
+ // Check for function expressions directly within the object literal
57
+ if (((_prop$value = prop.value) === null || _prop$value === void 0 ? void 0 : _prop$value.type) === 'ArrowFunctionExpression' || ((_prop$value2 = prop.value) === null || _prop$value2 === void 0 ? void 0 : _prop$value2.type) === 'FunctionExpression' || ((_prop$value3 = prop.value) === null || _prop$value3 === void 0 ? void 0 : _prop$value3.type) === 'CallExpression') {
58
+ containsFunctionExpression = true;
59
+ }
60
+ });
61
+
62
+ // If a function expression is found within the direct object literal, report an error
63
+ if (containsFunctionExpression) {
64
+ context.report({
65
+ node: node,
66
+ messageId: 'noEmotionCssPropFunctionCall'
67
+ });
68
+ }
69
+ }
70
+ },
71
+ 'Program:exit': function ProgramExit() {
72
+ if (emotionJsxImported && cssPropExpressonUsed && !emotionCssImported) {
73
+ context.report({
74
+ messageId: 'noEmotionCssImport',
75
+ loc: emotionJsxImportPosition || {
76
+ line: 1,
77
+ column: 0
78
+ }
79
+ });
80
+ }
81
+ }
82
+ };
83
+ }
84
+ };
85
+ export default rule;
@@ -0,0 +1,87 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
+ import { isAPIimport } from '../utils';
3
+ var FUNCTION_NAMES = new Set(['ff', 'fg', 'expVal', 'expValEquals', 'UNSAFE_noExposureExp']);
4
+ var STATSIG_ONLY_FUNCTION_NAMES = new Set(['fg', 'expVal', 'expValEquals', 'UNSAFE_noExposureExp']);
5
+ var findDefinitionDeclaration = function findDefinitionDeclaration(node) {
6
+ return node.type === 'VariableDeclaration' || node.type === 'FunctionDeclaration' ? node : findDefinitionDeclaration(node.parent);
7
+ };
8
+ var validateCallExpression = function validateCallExpression(node, context) {
9
+ var targetedFunctionsSwitch = context.options[0] === 'ssOnly' ? STATSIG_ONLY_FUNCTION_NAMES : FUNCTION_NAMES;
10
+ var callee = node.callee;
11
+ var shouldWarn = callee.type === 'Identifier' && targetedFunctionsSwitch.has(callee.name) && isAPIimport(callee.name, context);
12
+ if (shouldWarn) {
13
+ var defDeclaration = findDefinitionDeclaration(node.parent);
14
+ context.report({
15
+ messageId: 'inlineUsage',
16
+ node: defDeclaration.parent.type === 'ExportNamedDeclaration' ? defDeclaration.parent : defDeclaration
17
+ });
18
+ return true;
19
+ }
20
+ return false;
21
+ };
22
+ var validateBinaryExpression = function validateBinaryExpression(node, context) {
23
+ // Match all comparator operators i.e ===, >=, <
24
+ if (node.operator.match(/^[=|<|>]/)) {
25
+ if (node.left.type === 'CallExpression' && validateCallExpression(node.left, context)) {
26
+ return;
27
+ }
28
+ if (node.right.type === 'CallExpression') {
29
+ validateCallExpression(node.right, context);
30
+ }
31
+ }
32
+ };
33
+ var validateReturnExpression = function validateReturnExpression(_ref, context) {
34
+ var body = _ref.body;
35
+ if (body.length !== 1) {
36
+ return;
37
+ }
38
+ var _body = _slicedToArray(body, 1),
39
+ statement = _body[0];
40
+ if (statement.type === 'ReturnStatement') {
41
+ var argument = statement.argument;
42
+ if (argument && argument.type === 'CallExpression') {
43
+ validateCallExpression(argument, context);
44
+ } else if (argument && argument.type === 'BinaryExpression') {
45
+ validateBinaryExpression(argument, context);
46
+ }
47
+ }
48
+ };
49
+ var validateFunctionBody = function validateFunctionBody(body, context) {
50
+ switch (body.type) {
51
+ case 'CallExpression':
52
+ validateCallExpression(body, context);
53
+ break;
54
+ case 'BinaryExpression':
55
+ validateBinaryExpression(body, context);
56
+ break;
57
+ case 'BlockStatement':
58
+ validateReturnExpression(body, context);
59
+ break;
60
+ default:
61
+ }
62
+ };
63
+ var rule = {
64
+ meta: {
65
+ type: 'problem',
66
+ docs: {
67
+ description: 'Ensure feature flags/gates and experiments are inlined so that they can be statically analyzable.',
68
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/ff/inline-usage/README.md'
69
+ },
70
+ messages: {
71
+ inlineUsage: 'Do not export or wrap feature flags/gates and experiments usages. Inline calls at the callsite to ensure it is statically analyzable.'
72
+ }
73
+ },
74
+ create: function create(context) {
75
+ return {
76
+ 'VariableDeclaration[declarations.length=1] > VariableDeclarator[id.type="Identifier"]:matches([init.type="ArrowFunctionExpression"], [init.type="FunctionExpression"]) > *.init': function VariableDeclarationDeclarationsLength1VariableDeclaratorIdTypeIdentifierMatchesInitTypeArrowFunctionExpressionInitTypeFunctionExpressionInit(_ref2) {
77
+ var body = _ref2.body;
78
+ validateFunctionBody(body, context);
79
+ },
80
+ FunctionDeclaration: function FunctionDeclaration(_ref3) {
81
+ var body = _ref3.body;
82
+ validateFunctionBody(body, context);
83
+ }
84
+ };
85
+ }
86
+ };
87
+ export default rule;