@atlaskit/eslint-plugin-platform 0.4.1 → 0.5.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 +6 -0
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/rules/ensure-native-and-af-exports-synced/index.js +146 -0
- package/dist/es2019/index.js +3 -1
- package/dist/es2019/rules/ensure-native-and-af-exports-synced/index.js +135 -0
- package/dist/esm/index.js +3 -1
- package/dist/esm/rules/ensure-native-and-af-exports-synced/index.js +139 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/rules/ensure-native-and-af-exports-synced/index.d.ts +3 -0
- package/dist/types-ts4.5/index.d.ts +1 -0
- package/dist/types-ts4.5/rules/ensure-native-and-af-exports-synced/index.d.ts +3 -0
- package/package.json +2 -2
- package/src/__tests__/utils/_tester.tsx +1 -1
- package/src/index.tsx +2 -0
- package/src/rules/ensure-native-and-af-exports-synced/__tests__/unit/rule.test.tsx +199 -0
- package/src/rules/ensure-native-and-af-exports-synced/index.tsx +193 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @atlaskit/eslint-plugin-platform
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#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
|
|
8
|
+
|
|
3
9
|
## 0.4.1
|
|
4
10
|
|
|
5
11
|
### 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: {
|
|
@@ -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;
|
package/dist/es2019/index.js
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
|
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: {
|
|
@@ -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: {
|
|
@@ -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;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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: {
|
|
@@ -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: {
|
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
|
+
"version": "0.5.0",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"atlassian": {
|
|
7
7
|
"team": "UIP - Platform Integration Trust (PITa)",
|
|
@@ -38,7 +38,7 @@
|
|
|
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": "^
|
|
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"
|
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 = {
|
|
@@ -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;
|