@atlaskit/eslint-plugin-platform 0.4.1 → 0.6.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @atlaskit/eslint-plugin-platform
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#82550](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/82550) [`f0948af9e586`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/f0948af9e586) - Allow typescript upgrade to 5.x
8
+
9
+ ## 0.5.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#81166](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/81166) [`a249a1bd29a6`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/a249a1bd29a6) - Upgrade ESLint to version 8
14
+
3
15
  ## 0.4.1
4
16
 
5
17
  ### Patch Changes
package/dist/cjs/index.js CHANGED
@@ -17,6 +17,7 @@ var _ensureFeatureFlagPrefix = _interopRequireDefault(require("./rules/ensure-fe
17
17
  var _ensureCriticalDependencyResolutions = _interopRequireDefault(require("./rules/ensure-critical-dependency-resolutions"));
18
18
  var _noInvalidStorybookDecoratorUsage = _interopRequireDefault(require("./rules/no-invalid-storybook-decorator-usage"));
19
19
  var _ensurePublishValid = _interopRequireDefault(require("./rules/ensure-publish-valid"));
20
+ var _ensureNativeAndAfExportsSynced = _interopRequireDefault(require("./rules/ensure-native-and-af-exports-synced"));
20
21
  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; }
21
22
  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
22
23
  var rules = exports.rules = {
@@ -30,7 +31,8 @@ var rules = exports.rules = {
30
31
  'no-invalid-feature-flag-usage': _noInvalidFeatureFlagUsage.default,
31
32
  'no-pre-post-install-scripts': _noPrePostInstalls.default,
32
33
  'no-invalid-storybook-decorator-usage': _noInvalidStorybookDecoratorUsage.default,
33
- 'ensure-publish-valid': _ensurePublishValid.default
34
+ 'ensure-publish-valid': _ensurePublishValid.default,
35
+ 'ensure-native-and-af-exports-synced': _ensureNativeAndAfExportsSynced.default
34
36
  };
35
37
  var configs = exports.configs = {
36
38
  recommended: {
@@ -13,7 +13,7 @@ var _handleAstObject = require("../util/handle-ast-object");
13
13
  // Here we only need to specify the major and minor versions
14
14
  // In matchMinorVersion, we will check if the versions in resolutions fall in the right ranges.
15
15
  var DESIRED_PKG_VERSIONS = {
16
- typescript: ['4.9'],
16
+ typescript: ['4.9', '5.4'],
17
17
  '@types/react': ['16.14', '18.2']
18
18
  };
19
19
  var matchMinorVersion = function matchMinorVersion(desiredVersion, versionInResolutions) {
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
10
+ var _path = _interopRequireDefault(require("path"));
11
+ var _registrationUtils = require("../util/registration-utils");
12
+ 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; }
13
+ 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; }
14
+ var exportsValidationExceptions = {
15
+ '@atlassian/sizemap': {
16
+ ignoredAfExportKeys: ['.', './lmdb-cache-manager']
17
+ },
18
+ '@atlaskit/tokens': {
19
+ ignoredAfExportKeys: ['./babel-plugin']
20
+ }
21
+ };
22
+ var rule = {
23
+ meta: {
24
+ docs: {
25
+ recommended: false
26
+ },
27
+ type: 'problem',
28
+ messages: {
29
+ missingExportsProperty: "The exports property must be defined for {{pkgName}}; it most likely can just be a duplicate of the \"af:exports\" property. See http://go/eslint-exports for details",
30
+ missingExportsKey: "Missing package.json exports key \"{{expectedKey}}\" in {{pkgName}}. The exports entry should be \"{{expectedKey}}\": \"{{expectedValue}}\". See http://go/eslint-exports for details",
31
+ unexpectedExportsKey: "Unexpected package.json exports key \"{{key}}\" in {{pkgName}}. The exports entry should be \"{{expectedKey}}\": \"{{expectedValue}}\". See http://go/eslint-exports for details",
32
+ unexpectedExportsValue: "Unexpected package.json exports value in {{pkgName}} for the \"{{key}}\" key. The exports entry should be \"{{key}}\": \"{{expectedValue}}\". See http://go/eslint-exports for details"
33
+ }
34
+ },
35
+ create: function create(context) {
36
+ var fileName = context.getFilename();
37
+ if (!fileName.endsWith('package.json')) {
38
+ return {};
39
+ }
40
+ var _getMetadataForFilena = (0, _registrationUtils.getMetadataForFilename)(fileName),
41
+ packageJson = _getMetadataForFilena.pkgJson;
42
+ var pkgName = packageJson.name;
43
+
44
+ // TODO: remove '|| !packageJson['exports']' once all package.json files have 'exports'
45
+ if (!pkgName || !packageJson['af:exports'] || !packageJson['exports']) {
46
+ return {};
47
+ }
48
+
49
+ // TODO: Add back in once all package.json files have 'exports'
50
+ // if (!packageJson['exports']) {
51
+ // context.report({
52
+ // node: context.getSourceCode().ast,
53
+ // messageId: 'missingExportsProperty',
54
+ // data: { pkgName },
55
+ // });
56
+ // return {};
57
+ // }
58
+
59
+ var afExports = packageJson['af:exports'];
60
+ var nativeExports = packageJson['exports'];
61
+ return {
62
+ Program: function Program(node) {
63
+ for (var _i = 0, _Object$entries = Object.entries(afExports); _i < _Object$entries.length; _i++) {
64
+ var _exportsValidationExc;
65
+ var _Object$entries$_i = (0, _slicedToArray2.default)(_Object$entries[_i], 2),
66
+ afExportsKey = _Object$entries$_i[0],
67
+ afExportsValue = _Object$entries$_i[1];
68
+ if ((_exportsValidationExc = exportsValidationExceptions[pkgName]) !== null && _exportsValidationExc !== void 0 && _exportsValidationExc.ignoredAfExportKeys.includes(afExportsKey)) {
69
+ continue;
70
+ }
71
+ var exportKeyViolations = getExportKeyViolation(afExportsKey, afExportsValue, nativeExports);
72
+ if (exportKeyViolations) {
73
+ context.report({
74
+ data: _objectSpread(_objectSpread({}, exportKeyViolations), {}, {
75
+ key: afExportsKey,
76
+ pkgName: pkgName
77
+ }),
78
+ node: node,
79
+ messageId: exportKeyViolations.messageId
80
+ });
81
+ continue;
82
+ }
83
+ var exportValueViolations = getExportValueViolation(pkgName, afExportsKey, afExportsValue, nativeExports);
84
+ if (exportValueViolations) {
85
+ context.report({
86
+ data: _objectSpread(_objectSpread({}, exportValueViolations), {}, {
87
+ pkgName: pkgName
88
+ }),
89
+ node: node,
90
+ messageId: 'unexpectedExportsValue'
91
+ });
92
+ continue;
93
+ }
94
+ }
95
+ }
96
+ };
97
+ }
98
+ };
99
+ function getExportKeyViolation(afExportsKey, afExportsValue, nativeExports) {
100
+ var afExportsValueHasExtension = _path.default.extname(afExportsValue);
101
+ if (afExportsValueHasExtension && !nativeExports.hasOwnProperty(afExportsKey)) {
102
+ return {
103
+ messageId: 'missingExportsKey',
104
+ expectedKey: afExportsKey,
105
+ expectedValue: afExportsValue
106
+ };
107
+ }
108
+ if (!afExportsValueHasExtension && nativeExports.hasOwnProperty(afExportsKey)) {
109
+ return {
110
+ messageId: 'unexpectedExportsKey',
111
+ expectedKey: "".concat(afExportsKey, "/*"),
112
+ expectedValue: "".concat(afExportsValue, "/*")
113
+ };
114
+ }
115
+ if (!afExportsValueHasExtension && !nativeExports.hasOwnProperty("".concat(afExportsKey, "/*"))) {
116
+ return {
117
+ messageId: 'missingExportsKey',
118
+ expectedKey: "".concat(afExportsKey, "/*"),
119
+ expectedValue: "".concat(afExportsValue, "/*")
120
+ };
121
+ }
122
+ }
123
+ function getExportValueViolation(pkgName, afExportsKey, afExportsValue, nativeExports) {
124
+ var afExportsValueHasExtension = _path.default.extname(afExportsValue);
125
+
126
+ // Some entrypoints have been updated to an index.js file that registers ts-node
127
+ // Use path.basename to get the file name to see if it is equal to 'index.js'
128
+ if (afExportsValueHasExtension && _path.default.basename(nativeExports[afExportsKey]) === 'index.js') {
129
+ return;
130
+ }
131
+ if (afExportsValueHasExtension && nativeExports[afExportsKey] !== afExportsValue) {
132
+ return {
133
+ key: afExportsKey,
134
+ expectedValue: afExportsValue
135
+ };
136
+ }
137
+
138
+ // af:exports entrypoints without a file extension export the whole directory so check to ensure the exports value includes the wildcard
139
+ if (!afExportsValueHasExtension && !nativeExports["".concat(afExportsKey, "/*")].startsWith("".concat(afExportsValue, "/*"))) {
140
+ return {
141
+ key: "".concat(afExportsKey, "/*"),
142
+ expectedValue: "".concat(afExportsValue, "/*")
143
+ };
144
+ }
145
+ }
146
+ var _default = exports.default = rule;
@@ -11,6 +11,7 @@ import ensureFeatureFlagPrefix from './rules/ensure-feature-flag-prefix';
11
11
  import ensureCriticalDependencyResolutions from './rules/ensure-critical-dependency-resolutions';
12
12
  import noInvalidStorybookDecoratorUsage from './rules/no-invalid-storybook-decorator-usage';
13
13
  import ensurePublishValid from './rules/ensure-publish-valid';
14
+ import ensureNativeAndAfExportsSynced from './rules/ensure-native-and-af-exports-synced';
14
15
  export const rules = {
15
16
  'ensure-feature-flag-registration': ensureFeatureFlagRegistration,
16
17
  'ensure-feature-flag-prefix': ensureFeatureFlagPrefix,
@@ -22,7 +23,8 @@ export const rules = {
22
23
  'no-invalid-feature-flag-usage': noInvalidFeatureFlagUsage,
23
24
  'no-pre-post-install-scripts': noPreAndPostInstallScripts,
24
25
  'no-invalid-storybook-decorator-usage': noInvalidStorybookDecoratorUsage,
25
- 'ensure-publish-valid': ensurePublishValid
26
+ 'ensure-publish-valid': ensurePublishValid,
27
+ 'ensure-native-and-af-exports-synced': ensureNativeAndAfExportsSynced
26
28
  };
27
29
  export const configs = {
28
30
  recommended: {
@@ -6,7 +6,7 @@ import { getObjectPropertyAsObject } from '../util/handle-ast-object';
6
6
  // Here we only need to specify the major and minor versions
7
7
  // In matchMinorVersion, we will check if the versions in resolutions fall in the right ranges.
8
8
  const DESIRED_PKG_VERSIONS = {
9
- typescript: ['4.9'],
9
+ typescript: ['4.9', '5.4'],
10
10
  '@types/react': ['16.14', '18.2']
11
11
  };
12
12
  const matchMinorVersion = (desiredVersion, versionInResolutions) => {
@@ -0,0 +1,135 @@
1
+ import path from 'path';
2
+ import { getMetadataForFilename } from '../util/registration-utils';
3
+ const exportsValidationExceptions = {
4
+ '@atlassian/sizemap': {
5
+ ignoredAfExportKeys: ['.', './lmdb-cache-manager']
6
+ },
7
+ '@atlaskit/tokens': {
8
+ ignoredAfExportKeys: ['./babel-plugin']
9
+ }
10
+ };
11
+ const rule = {
12
+ meta: {
13
+ docs: {
14
+ recommended: false
15
+ },
16
+ type: 'problem',
17
+ messages: {
18
+ missingExportsProperty: `The exports property must be defined for {{pkgName}}; it most likely can just be a duplicate of the "af:exports" property. See http://go/eslint-exports for details`,
19
+ missingExportsKey: `Missing package.json exports key "{{expectedKey}}" in {{pkgName}}. The exports entry should be "{{expectedKey}}": "{{expectedValue}}". See http://go/eslint-exports for details`,
20
+ unexpectedExportsKey: `Unexpected package.json exports key "{{key}}" in {{pkgName}}. The exports entry should be "{{expectedKey}}": "{{expectedValue}}". See http://go/eslint-exports for details`,
21
+ unexpectedExportsValue: `Unexpected package.json exports value in {{pkgName}} for the "{{key}}" key. The exports entry should be "{{key}}": "{{expectedValue}}". See http://go/eslint-exports for details`
22
+ }
23
+ },
24
+ create(context) {
25
+ const fileName = context.getFilename();
26
+ if (!fileName.endsWith('package.json')) {
27
+ return {};
28
+ }
29
+ const {
30
+ pkgJson: packageJson
31
+ } = getMetadataForFilename(fileName);
32
+ const pkgName = packageJson.name;
33
+
34
+ // TODO: remove '|| !packageJson['exports']' once all package.json files have 'exports'
35
+ if (!pkgName || !packageJson['af:exports'] || !packageJson['exports']) {
36
+ return {};
37
+ }
38
+
39
+ // TODO: Add back in once all package.json files have 'exports'
40
+ // if (!packageJson['exports']) {
41
+ // context.report({
42
+ // node: context.getSourceCode().ast,
43
+ // messageId: 'missingExportsProperty',
44
+ // data: { pkgName },
45
+ // });
46
+ // return {};
47
+ // }
48
+
49
+ const afExports = packageJson['af:exports'];
50
+ const nativeExports = packageJson['exports'];
51
+ return {
52
+ Program(node) {
53
+ for (const [afExportsKey, afExportsValue] of Object.entries(afExports)) {
54
+ var _exportsValidationExc;
55
+ if ((_exportsValidationExc = exportsValidationExceptions[pkgName]) !== null && _exportsValidationExc !== void 0 && _exportsValidationExc.ignoredAfExportKeys.includes(afExportsKey)) {
56
+ continue;
57
+ }
58
+ const exportKeyViolations = getExportKeyViolation(afExportsKey, afExportsValue, nativeExports);
59
+ if (exportKeyViolations) {
60
+ context.report({
61
+ data: {
62
+ ...exportKeyViolations,
63
+ key: afExportsKey,
64
+ pkgName
65
+ },
66
+ node,
67
+ messageId: exportKeyViolations.messageId
68
+ });
69
+ continue;
70
+ }
71
+ const exportValueViolations = getExportValueViolation(pkgName, afExportsKey, afExportsValue, nativeExports);
72
+ if (exportValueViolations) {
73
+ context.report({
74
+ data: {
75
+ ...exportValueViolations,
76
+ pkgName
77
+ },
78
+ node,
79
+ messageId: 'unexpectedExportsValue'
80
+ });
81
+ continue;
82
+ }
83
+ }
84
+ }
85
+ };
86
+ }
87
+ };
88
+ function getExportKeyViolation(afExportsKey, afExportsValue, nativeExports) {
89
+ const afExportsValueHasExtension = path.extname(afExportsValue);
90
+ if (afExportsValueHasExtension && !nativeExports.hasOwnProperty(afExportsKey)) {
91
+ return {
92
+ messageId: 'missingExportsKey',
93
+ expectedKey: afExportsKey,
94
+ expectedValue: afExportsValue
95
+ };
96
+ }
97
+ if (!afExportsValueHasExtension && nativeExports.hasOwnProperty(afExportsKey)) {
98
+ return {
99
+ messageId: 'unexpectedExportsKey',
100
+ expectedKey: `${afExportsKey}/*`,
101
+ expectedValue: `${afExportsValue}/*`
102
+ };
103
+ }
104
+ if (!afExportsValueHasExtension && !nativeExports.hasOwnProperty(`${afExportsKey}/*`)) {
105
+ return {
106
+ messageId: 'missingExportsKey',
107
+ expectedKey: `${afExportsKey}/*`,
108
+ expectedValue: `${afExportsValue}/*`
109
+ };
110
+ }
111
+ }
112
+ function getExportValueViolation(pkgName, afExportsKey, afExportsValue, nativeExports) {
113
+ const afExportsValueHasExtension = path.extname(afExportsValue);
114
+
115
+ // Some entrypoints have been updated to an index.js file that registers ts-node
116
+ // Use path.basename to get the file name to see if it is equal to 'index.js'
117
+ if (afExportsValueHasExtension && path.basename(nativeExports[afExportsKey]) === 'index.js') {
118
+ return;
119
+ }
120
+ if (afExportsValueHasExtension && nativeExports[afExportsKey] !== afExportsValue) {
121
+ return {
122
+ key: afExportsKey,
123
+ expectedValue: afExportsValue
124
+ };
125
+ }
126
+
127
+ // af:exports entrypoints without a file extension export the whole directory so check to ensure the exports value includes the wildcard
128
+ if (!afExportsValueHasExtension && !nativeExports[`${afExportsKey}/*`].startsWith(`${afExportsValue}/*`)) {
129
+ return {
130
+ key: `${afExportsKey}/*`,
131
+ expectedValue: `${afExportsValue}/*`
132
+ };
133
+ }
134
+ }
135
+ export default rule;
package/dist/esm/index.js CHANGED
@@ -14,6 +14,7 @@ import ensureFeatureFlagPrefix from './rules/ensure-feature-flag-prefix';
14
14
  import ensureCriticalDependencyResolutions from './rules/ensure-critical-dependency-resolutions';
15
15
  import noInvalidStorybookDecoratorUsage from './rules/no-invalid-storybook-decorator-usage';
16
16
  import ensurePublishValid from './rules/ensure-publish-valid';
17
+ import ensureNativeAndAfExportsSynced from './rules/ensure-native-and-af-exports-synced';
17
18
  export var rules = {
18
19
  'ensure-feature-flag-registration': ensureFeatureFlagRegistration,
19
20
  'ensure-feature-flag-prefix': ensureFeatureFlagPrefix,
@@ -25,7 +26,8 @@ export var rules = {
25
26
  'no-invalid-feature-flag-usage': noInvalidFeatureFlagUsage,
26
27
  'no-pre-post-install-scripts': noPreAndPostInstallScripts,
27
28
  'no-invalid-storybook-decorator-usage': noInvalidStorybookDecoratorUsage,
28
- 'ensure-publish-valid': ensurePublishValid
29
+ 'ensure-publish-valid': ensurePublishValid,
30
+ 'ensure-native-and-af-exports-synced': ensureNativeAndAfExportsSynced
29
31
  };
30
32
  export var configs = {
31
33
  recommended: {
@@ -7,7 +7,7 @@ import { getObjectPropertyAsObject } from '../util/handle-ast-object';
7
7
  // Here we only need to specify the major and minor versions
8
8
  // In matchMinorVersion, we will check if the versions in resolutions fall in the right ranges.
9
9
  var DESIRED_PKG_VERSIONS = {
10
- typescript: ['4.9'],
10
+ typescript: ['4.9', '5.4'],
11
11
  '@types/react': ['16.14', '18.2']
12
12
  };
13
13
  var matchMinorVersion = function matchMinorVersion(desiredVersion, versionInResolutions) {
@@ -0,0 +1,139 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
+ 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; }
4
+ 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) { _defineProperty(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; }
5
+ import path from 'path';
6
+ import { getMetadataForFilename } from '../util/registration-utils';
7
+ var exportsValidationExceptions = {
8
+ '@atlassian/sizemap': {
9
+ ignoredAfExportKeys: ['.', './lmdb-cache-manager']
10
+ },
11
+ '@atlaskit/tokens': {
12
+ ignoredAfExportKeys: ['./babel-plugin']
13
+ }
14
+ };
15
+ var rule = {
16
+ meta: {
17
+ docs: {
18
+ recommended: false
19
+ },
20
+ type: 'problem',
21
+ messages: {
22
+ missingExportsProperty: "The exports property must be defined for {{pkgName}}; it most likely can just be a duplicate of the \"af:exports\" property. See http://go/eslint-exports for details",
23
+ missingExportsKey: "Missing package.json exports key \"{{expectedKey}}\" in {{pkgName}}. The exports entry should be \"{{expectedKey}}\": \"{{expectedValue}}\". See http://go/eslint-exports for details",
24
+ unexpectedExportsKey: "Unexpected package.json exports key \"{{key}}\" in {{pkgName}}. The exports entry should be \"{{expectedKey}}\": \"{{expectedValue}}\". See http://go/eslint-exports for details",
25
+ unexpectedExportsValue: "Unexpected package.json exports value in {{pkgName}} for the \"{{key}}\" key. The exports entry should be \"{{key}}\": \"{{expectedValue}}\". See http://go/eslint-exports for details"
26
+ }
27
+ },
28
+ create: function create(context) {
29
+ var fileName = context.getFilename();
30
+ if (!fileName.endsWith('package.json')) {
31
+ return {};
32
+ }
33
+ var _getMetadataForFilena = getMetadataForFilename(fileName),
34
+ packageJson = _getMetadataForFilena.pkgJson;
35
+ var pkgName = packageJson.name;
36
+
37
+ // TODO: remove '|| !packageJson['exports']' once all package.json files have 'exports'
38
+ if (!pkgName || !packageJson['af:exports'] || !packageJson['exports']) {
39
+ return {};
40
+ }
41
+
42
+ // TODO: Add back in once all package.json files have 'exports'
43
+ // if (!packageJson['exports']) {
44
+ // context.report({
45
+ // node: context.getSourceCode().ast,
46
+ // messageId: 'missingExportsProperty',
47
+ // data: { pkgName },
48
+ // });
49
+ // return {};
50
+ // }
51
+
52
+ var afExports = packageJson['af:exports'];
53
+ var nativeExports = packageJson['exports'];
54
+ return {
55
+ Program: function Program(node) {
56
+ for (var _i = 0, _Object$entries = Object.entries(afExports); _i < _Object$entries.length; _i++) {
57
+ var _exportsValidationExc;
58
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
59
+ afExportsKey = _Object$entries$_i[0],
60
+ afExportsValue = _Object$entries$_i[1];
61
+ if ((_exportsValidationExc = exportsValidationExceptions[pkgName]) !== null && _exportsValidationExc !== void 0 && _exportsValidationExc.ignoredAfExportKeys.includes(afExportsKey)) {
62
+ continue;
63
+ }
64
+ var exportKeyViolations = getExportKeyViolation(afExportsKey, afExportsValue, nativeExports);
65
+ if (exportKeyViolations) {
66
+ context.report({
67
+ data: _objectSpread(_objectSpread({}, exportKeyViolations), {}, {
68
+ key: afExportsKey,
69
+ pkgName: pkgName
70
+ }),
71
+ node: node,
72
+ messageId: exportKeyViolations.messageId
73
+ });
74
+ continue;
75
+ }
76
+ var exportValueViolations = getExportValueViolation(pkgName, afExportsKey, afExportsValue, nativeExports);
77
+ if (exportValueViolations) {
78
+ context.report({
79
+ data: _objectSpread(_objectSpread({}, exportValueViolations), {}, {
80
+ pkgName: pkgName
81
+ }),
82
+ node: node,
83
+ messageId: 'unexpectedExportsValue'
84
+ });
85
+ continue;
86
+ }
87
+ }
88
+ }
89
+ };
90
+ }
91
+ };
92
+ function getExportKeyViolation(afExportsKey, afExportsValue, nativeExports) {
93
+ var afExportsValueHasExtension = path.extname(afExportsValue);
94
+ if (afExportsValueHasExtension && !nativeExports.hasOwnProperty(afExportsKey)) {
95
+ return {
96
+ messageId: 'missingExportsKey',
97
+ expectedKey: afExportsKey,
98
+ expectedValue: afExportsValue
99
+ };
100
+ }
101
+ if (!afExportsValueHasExtension && nativeExports.hasOwnProperty(afExportsKey)) {
102
+ return {
103
+ messageId: 'unexpectedExportsKey',
104
+ expectedKey: "".concat(afExportsKey, "/*"),
105
+ expectedValue: "".concat(afExportsValue, "/*")
106
+ };
107
+ }
108
+ if (!afExportsValueHasExtension && !nativeExports.hasOwnProperty("".concat(afExportsKey, "/*"))) {
109
+ return {
110
+ messageId: 'missingExportsKey',
111
+ expectedKey: "".concat(afExportsKey, "/*"),
112
+ expectedValue: "".concat(afExportsValue, "/*")
113
+ };
114
+ }
115
+ }
116
+ function getExportValueViolation(pkgName, afExportsKey, afExportsValue, nativeExports) {
117
+ var afExportsValueHasExtension = path.extname(afExportsValue);
118
+
119
+ // Some entrypoints have been updated to an index.js file that registers ts-node
120
+ // Use path.basename to get the file name to see if it is equal to 'index.js'
121
+ if (afExportsValueHasExtension && path.basename(nativeExports[afExportsKey]) === 'index.js') {
122
+ return;
123
+ }
124
+ if (afExportsValueHasExtension && nativeExports[afExportsKey] !== afExportsValue) {
125
+ return {
126
+ key: afExportsKey,
127
+ expectedValue: afExportsValue
128
+ };
129
+ }
130
+
131
+ // af:exports entrypoints without a file extension export the whole directory so check to ensure the exports value includes the wildcard
132
+ if (!afExportsValueHasExtension && !nativeExports["".concat(afExportsKey, "/*")].startsWith("".concat(afExportsValue, "/*"))) {
133
+ return {
134
+ key: "".concat(afExportsKey, "/*"),
135
+ expectedValue: "".concat(afExportsValue, "/*")
136
+ };
137
+ }
138
+ }
139
+ export default rule;
@@ -11,6 +11,7 @@ export declare const rules: {
11
11
  'no-pre-post-install-scripts': import("eslint").Rule.RuleModule;
12
12
  'no-invalid-storybook-decorator-usage': import("eslint").Rule.RuleModule;
13
13
  'ensure-publish-valid': import("eslint").Rule.RuleModule;
14
+ 'ensure-native-and-af-exports-synced': import("eslint").Rule.RuleModule;
14
15
  };
15
16
  export declare const configs: {
16
17
  recommended: {
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -11,6 +11,7 @@ export declare const rules: {
11
11
  'no-pre-post-install-scripts': import("eslint").Rule.RuleModule;
12
12
  'no-invalid-storybook-decorator-usage': import("eslint").Rule.RuleModule;
13
13
  'ensure-publish-valid': import("eslint").Rule.RuleModule;
14
+ 'ensure-native-and-af-exports-synced': import("eslint").Rule.RuleModule;
14
15
  };
15
16
  export declare const configs: {
16
17
  recommended: {
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@atlaskit/eslint-plugin-platform",
3
3
  "description": "The essential plugin for use with Atlassian frontend platform tools",
4
- "version": "0.4.1",
4
+ "version": "0.6.0",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "atlassian": {
7
7
  "team": "UIP - Platform Integration Trust (PITa)",
@@ -38,8 +38,8 @@
38
38
  "devDependencies": {
39
39
  "@atlassian/atlassian-frontend-prettier-config-1.0.1": "npm:@atlassian/atlassian-frontend-prettier-config@1.0.1",
40
40
  "@types/eslint": "^8.4.5",
41
- "eslint": "^7.7.0",
41
+ "eslint": "^8.49.0",
42
42
  "tsconfig-paths": "^4.2.0"
43
43
  },
44
44
  "prettier": "@atlassian/atlassian-frontend-prettier-config-1.0.1"
45
- }
45
+ }
@@ -18,7 +18,7 @@ import { RuleTester } from 'eslint';
18
18
  };
19
19
 
20
20
  export const tester = new RuleTester({
21
- parser: require.resolve('babel-eslint'),
21
+ parser: require.resolve('@babel/eslint-parser'),
22
22
  parserOptions: {
23
23
  ecmaVersion: 6,
24
24
  sourceType: 'module',
package/src/index.tsx CHANGED
@@ -11,6 +11,7 @@ import ensureFeatureFlagPrefix from './rules/ensure-feature-flag-prefix';
11
11
  import ensureCriticalDependencyResolutions from './rules/ensure-critical-dependency-resolutions';
12
12
  import noInvalidStorybookDecoratorUsage from './rules/no-invalid-storybook-decorator-usage';
13
13
  import ensurePublishValid from './rules/ensure-publish-valid';
14
+ import ensureNativeAndAfExportsSynced from './rules/ensure-native-and-af-exports-synced';
14
15
 
15
16
  export const rules = {
16
17
  'ensure-feature-flag-registration': ensureFeatureFlagRegistration,
@@ -24,6 +25,7 @@ export const rules = {
24
25
  'no-pre-post-install-scripts': noPreAndPostInstallScripts,
25
26
  'no-invalid-storybook-decorator-usage': noInvalidStorybookDecoratorUsage,
26
27
  'ensure-publish-valid': ensurePublishValid,
28
+ 'ensure-native-and-af-exports-synced': ensureNativeAndAfExportsSynced,
27
29
  };
28
30
 
29
31
  export const configs = {
@@ -7,7 +7,7 @@ import { getObjectPropertyAsObject } from '../util/handle-ast-object';
7
7
  // Here we only need to specify the major and minor versions
8
8
  // In matchMinorVersion, we will check if the versions in resolutions fall in the right ranges.
9
9
  const DESIRED_PKG_VERSIONS: Record<string, string[]> = {
10
- typescript: ['4.9'],
10
+ typescript: ['4.9', '5.4'],
11
11
  '@types/react': ['16.14', '18.2'],
12
12
  };
13
13
 
@@ -0,0 +1,199 @@
1
+ import { tester } from '../../../../__tests__/utils/_tester';
2
+ import rule from '../../index';
3
+ import { PackageJson } from 'read-pkg-up';
4
+
5
+ let mockPath = 'packages/test/package.json';
6
+
7
+ let mockPackageJson: PackageJson = {};
8
+ jest.mock('read-pkg-up', () => ({
9
+ sync: () => ({
10
+ path: mockPath,
11
+ packageJson: mockPackageJson,
12
+ }),
13
+ }));
14
+
15
+ describe('valid test cases', () => {
16
+ describe('allows @atlaskit/tokens babel-plugin entrypoint', () => {
17
+ beforeEach(() => {
18
+ mockPackageJson = {
19
+ name: '@atlaskit/tokens',
20
+ 'af:exports': {
21
+ './babel-plugin': './src/entry-points/babel-plugin.tsx',
22
+ },
23
+ exports: {
24
+ './babel-plugin': './babel-plugin.js',
25
+ },
26
+ };
27
+ });
28
+ tester.run('ensure-native-and-af-exports-synced', rule, {
29
+ valid: [{ code: '' }],
30
+ invalid: [],
31
+ });
32
+ });
33
+
34
+ describe('passes for valid directory export', () => {
35
+ beforeEach(() => {
36
+ mockPackageJson = {
37
+ name: '@atlaskit/tokens',
38
+ 'af:exports': {
39
+ './glyph': './glyph',
40
+ './test/icon': './test/icon',
41
+ './button': './button',
42
+ '.': './src',
43
+ },
44
+ exports: {
45
+ './glyph/*': './glyph/*',
46
+ './test/icon/*': './test/icon/*',
47
+ './button/*': './button/*.js',
48
+ './*': './src/*',
49
+ },
50
+ };
51
+ });
52
+ tester.run('ensure-native-and-af-exports-synced', rule, {
53
+ valid: [
54
+ {
55
+ code: '',
56
+ filename: 'packages/test/package.json',
57
+ },
58
+ ],
59
+ invalid: [],
60
+ });
61
+ });
62
+
63
+ describe('passes for index.js files', () => {
64
+ beforeEach(() => {
65
+ mockPackageJson = {
66
+ name: '@atlaskit/tokens',
67
+ 'af:exports': {
68
+ '.': './src/index.tsx',
69
+ './gas-v3': './src/gas-v3/index.ts',
70
+ './reader': './src/reader/reader.ts',
71
+ './writer': './src/writer/writer.ts',
72
+ './report': './src/report/report.ts',
73
+ },
74
+ exports: {
75
+ '.': './index.js',
76
+ './gas-v3': './gas-v3/index.js',
77
+ './reader': './reader/index.js',
78
+ './writer': './writer/index.js',
79
+ './report': './report/index.js',
80
+ },
81
+ };
82
+ });
83
+ tester.run('ensure-native-and-af-exports-synced', rule, {
84
+ valid: [
85
+ {
86
+ code: '',
87
+ filename: 'packages/test/package.json',
88
+ },
89
+ ],
90
+ invalid: [],
91
+ });
92
+ });
93
+
94
+ describe('should pass for multiple valid entrypoints', () => {
95
+ beforeEach(() => {
96
+ mockPackageJson = {
97
+ name: '@atlaskit/tokens',
98
+ 'af:exports': {
99
+ '.': './src/index.tsx',
100
+ './rename-mapping': './src/entry-points/rename-mapping.tsx',
101
+ './babel-plugin': './src/entry-points/babel-plugin.tsx',
102
+ './glyph': './glyph',
103
+ './button': './button',
104
+ },
105
+ exports: {
106
+ '.': './index.js',
107
+ './rename-mapping': './src/entry-points/rename-mapping.tsx',
108
+ './babel-plugin': './src/entry-points/babel-plugin.tsx',
109
+ './glyph/*': './glyph/*',
110
+ './button/*': './button/*.js',
111
+ },
112
+ };
113
+ });
114
+ tester.run('ensure-native-and-af-exports-synced', rule, {
115
+ valid: [
116
+ {
117
+ code: '',
118
+ filename: 'packages/test/package.json',
119
+ },
120
+ ],
121
+ invalid: [],
122
+ });
123
+ });
124
+ });
125
+
126
+ describe('invalid test cases', () => {
127
+ describe('should fail for mismatched invalid entrypoints', () => {
128
+ beforeEach(() => {
129
+ mockPackageJson = {
130
+ name: '@atlaskit/test',
131
+ 'af:exports': {
132
+ '.': './src/index.ts',
133
+ },
134
+ exports: {
135
+ '.': './test/index.ts',
136
+ },
137
+ };
138
+ });
139
+ tester.run('ensure-native-and-af-exports-synced', rule, {
140
+ valid: [],
141
+ invalid: [
142
+ {
143
+ code: '',
144
+ filename: 'packages/test/package.json',
145
+ errors: [{ messageId: 'unexpectedExportsValue' }],
146
+ },
147
+ ],
148
+ });
149
+ });
150
+
151
+ describe('should fail for missing entrypoints', () => {
152
+ beforeEach(() => {
153
+ mockPackageJson = {
154
+ name: '@atlaskit/tokens',
155
+ 'af:exports': {
156
+ '.': './src/index.ts',
157
+ './button': './button',
158
+ },
159
+ exports: {
160
+ '.': './src/index.ts',
161
+ },
162
+ };
163
+ });
164
+ tester.run('ensure-native-and-af-exports-synced', rule, {
165
+ valid: [],
166
+ invalid: [
167
+ {
168
+ code: '',
169
+ filename: 'packages/test/package.json',
170
+ errors: [{ messageId: 'missingExportsKey' }],
171
+ },
172
+ ],
173
+ });
174
+ });
175
+
176
+ describe('should fail for invalid directory export', () => {
177
+ beforeEach(() => {
178
+ mockPackageJson = {
179
+ name: '@atlaskit/tokens',
180
+ 'af:exports': {
181
+ './button': './button',
182
+ },
183
+ exports: {
184
+ './button/*': './src/button/*',
185
+ },
186
+ };
187
+ });
188
+ tester.run('ensure-native-and-af-exports-synced', rule, {
189
+ valid: [],
190
+ invalid: [
191
+ {
192
+ code: '',
193
+ filename: 'packages/test/package.json',
194
+ errors: [{ messageId: 'unexpectedExportsValue' }],
195
+ },
196
+ ],
197
+ });
198
+ });
199
+ });
@@ -0,0 +1,193 @@
1
+ import type { Rule } from 'eslint';
2
+ import path from 'path';
3
+
4
+ import { getMetadataForFilename } from '../util/registration-utils';
5
+
6
+ interface ExportsValidationExceptions {
7
+ [key: string]: { ignoredAfExportKeys: string[] };
8
+ }
9
+
10
+ const exportsValidationExceptions: ExportsValidationExceptions = {
11
+ '@atlassian/sizemap': {
12
+ ignoredAfExportKeys: ['.', './lmdb-cache-manager'],
13
+ },
14
+ '@atlaskit/tokens': {
15
+ ignoredAfExportKeys: ['./babel-plugin'],
16
+ },
17
+ };
18
+
19
+ const rule: Rule.RuleModule = {
20
+ meta: {
21
+ docs: {
22
+ recommended: false,
23
+ },
24
+ type: 'problem',
25
+ messages: {
26
+ missingExportsProperty: `The exports property must be defined for {{pkgName}}; it most likely can just be a duplicate of the "af:exports" property. See http://go/eslint-exports for details`,
27
+ missingExportsKey: `Missing package.json exports key "{{expectedKey}}" in {{pkgName}}. The exports entry should be "{{expectedKey}}": "{{expectedValue}}". See http://go/eslint-exports for details`,
28
+ unexpectedExportsKey: `Unexpected package.json exports key "{{key}}" in {{pkgName}}. The exports entry should be "{{expectedKey}}": "{{expectedValue}}". See http://go/eslint-exports for details`,
29
+ unexpectedExportsValue: `Unexpected package.json exports value in {{pkgName}} for the "{{key}}" key. The exports entry should be "{{key}}": "{{expectedValue}}". See http://go/eslint-exports for details`,
30
+ },
31
+ },
32
+
33
+ create(context) {
34
+ const fileName = context.getFilename();
35
+
36
+ if (!fileName.endsWith('package.json')) {
37
+ return {};
38
+ }
39
+
40
+ const { pkgJson: packageJson } = getMetadataForFilename(fileName);
41
+
42
+ const pkgName = packageJson.name;
43
+
44
+ // TODO: remove '|| !packageJson['exports']' once all package.json files have 'exports'
45
+ if (!pkgName || !packageJson['af:exports'] || !packageJson['exports']) {
46
+ return {};
47
+ }
48
+
49
+ // TODO: Add back in once all package.json files have 'exports'
50
+ // if (!packageJson['exports']) {
51
+ // context.report({
52
+ // node: context.getSourceCode().ast,
53
+ // messageId: 'missingExportsProperty',
54
+ // data: { pkgName },
55
+ // });
56
+ // return {};
57
+ // }
58
+
59
+ const afExports: { [key: string]: any } = packageJson['af:exports'];
60
+ const nativeExports: { [key: string]: any } = packageJson['exports'];
61
+
62
+ return {
63
+ Program(node) {
64
+ for (const [afExportsKey, afExportsValue] of Object.entries(
65
+ afExports,
66
+ )) {
67
+ if (
68
+ exportsValidationExceptions[pkgName]?.ignoredAfExportKeys.includes(
69
+ afExportsKey,
70
+ )
71
+ ) {
72
+ continue;
73
+ }
74
+
75
+ const exportKeyViolations = getExportKeyViolation(
76
+ afExportsKey,
77
+ afExportsValue,
78
+ nativeExports,
79
+ );
80
+
81
+ if (exportKeyViolations) {
82
+ context.report({
83
+ data: { ...exportKeyViolations, key: afExportsKey, pkgName },
84
+ node,
85
+ messageId: exportKeyViolations.messageId,
86
+ });
87
+
88
+ continue;
89
+ }
90
+
91
+ const exportValueViolations = getExportValueViolation(
92
+ pkgName,
93
+ afExportsKey,
94
+ afExportsValue,
95
+ nativeExports,
96
+ );
97
+
98
+ if (exportValueViolations) {
99
+ context.report({
100
+ data: { ...exportValueViolations, pkgName },
101
+ node,
102
+ messageId: 'unexpectedExportsValue',
103
+ });
104
+
105
+ continue;
106
+ }
107
+ }
108
+ },
109
+ };
110
+ },
111
+ };
112
+
113
+ function getExportKeyViolation(
114
+ afExportsKey: string,
115
+ afExportsValue: string,
116
+ nativeExports: { [key: string]: any },
117
+ ) {
118
+ const afExportsValueHasExtension = path.extname(afExportsValue);
119
+
120
+ if (
121
+ afExportsValueHasExtension &&
122
+ !nativeExports.hasOwnProperty(afExportsKey)
123
+ ) {
124
+ return {
125
+ messageId: 'missingExportsKey',
126
+ expectedKey: afExportsKey,
127
+ expectedValue: afExportsValue,
128
+ };
129
+ }
130
+
131
+ if (
132
+ !afExportsValueHasExtension &&
133
+ nativeExports.hasOwnProperty(afExportsKey)
134
+ ) {
135
+ return {
136
+ messageId: 'unexpectedExportsKey',
137
+ expectedKey: `${afExportsKey}/*`,
138
+ expectedValue: `${afExportsValue}/*`,
139
+ };
140
+ }
141
+
142
+ if (
143
+ !afExportsValueHasExtension &&
144
+ !nativeExports.hasOwnProperty(`${afExportsKey}/*`)
145
+ ) {
146
+ return {
147
+ messageId: 'missingExportsKey',
148
+ expectedKey: `${afExportsKey}/*`,
149
+ expectedValue: `${afExportsValue}/*`,
150
+ };
151
+ }
152
+ }
153
+
154
+ function getExportValueViolation(
155
+ pkgName: string,
156
+ afExportsKey: string,
157
+ afExportsValue: string,
158
+ nativeExports: { [key: string]: any },
159
+ ) {
160
+ const afExportsValueHasExtension = path.extname(afExportsValue);
161
+
162
+ // Some entrypoints have been updated to an index.js file that registers ts-node
163
+ // Use path.basename to get the file name to see if it is equal to 'index.js'
164
+ if (
165
+ afExportsValueHasExtension &&
166
+ path.basename(nativeExports[afExportsKey]) === 'index.js'
167
+ ) {
168
+ return;
169
+ }
170
+
171
+ if (
172
+ afExportsValueHasExtension &&
173
+ nativeExports[afExportsKey] !== afExportsValue
174
+ ) {
175
+ return {
176
+ key: afExportsKey,
177
+ expectedValue: afExportsValue,
178
+ };
179
+ }
180
+
181
+ // af:exports entrypoints without a file extension export the whole directory so check to ensure the exports value includes the wildcard
182
+ if (
183
+ !afExportsValueHasExtension &&
184
+ !nativeExports[`${afExportsKey}/*`].startsWith(`${afExportsValue}/*`)
185
+ ) {
186
+ return {
187
+ key: `${afExportsKey}/*`,
188
+ expectedValue: `${afExportsValue}/*`,
189
+ };
190
+ }
191
+ }
192
+
193
+ export default rule;