@atlaskit/eslint-plugin-platform 0.0.7 → 0.1.1
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 +12 -0
- package/dist/cjs/index.js +18 -3
- package/dist/cjs/rules/ensure-feature-flag-registration/index.js +53 -17
- package/dist/cjs/rules/no-pre-post-installs/index.js +34 -0
- package/dist/cjs/version.json +1 -1
- package/dist/es2019/index.js +13 -1
- package/dist/es2019/rules/ensure-feature-flag-registration/index.js +70 -35
- package/dist/es2019/rules/no-pre-post-installs/index.js +27 -0
- package/dist/es2019/version.json +1 -1
- package/dist/esm/index.js +15 -1
- package/dist/esm/rules/ensure-feature-flag-registration/index.js +54 -17
- package/dist/esm/rules/no-pre-post-installs/index.js +27 -0
- package/dist/esm/version.json +1 -1
- package/dist/types/index.d.ts +6 -0
- package/dist/types/rules/no-pre-post-installs/index.d.ts +3 -0
- package/package.json +2 -1
- package/report.api.md +8 -0
- package/src/__tests__/utils/_tester.tsx +1 -0
- package/src/index.tsx +16 -0
- package/src/rules/ensure-feature-flag-registration/__tests__/unit/rule.test.tsx +15 -1
- package/src/rules/ensure-feature-flag-registration/index.tsx +105 -40
- package/src/rules/no-pre-post-installs/__tests__/unit/rule.test.ts +41 -0
- package/src/rules/no-pre-post-installs/index.ts +34 -0
- package/tmp/api-report-tmp.d.ts +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @atlaskit/eslint-plugin-platform
|
|
2
2
|
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`7edd9e8b4b1`](https://bitbucket.org/atlassian/atlassian-frontend/commits/7edd9e8b4b1) - Add suggestion to change feature flag to the closest matching feature flag using fuzzy search
|
|
8
|
+
|
|
9
|
+
## 0.1.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [`6339334e3ac`](https://bitbucket.org/atlassian/atlassian-frontend/commits/6339334e3ac) - Adds new rule to disallow pre/post install scripts in package.json.
|
|
14
|
+
|
|
3
15
|
## 0.0.7
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/cjs/index.js
CHANGED
|
@@ -4,8 +4,9 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
|
-
exports.rules = exports.configs = void 0;
|
|
7
|
+
exports.rules = exports.processors = exports.configs = void 0;
|
|
8
8
|
var _ensureFeatureFlagRegistration = _interopRequireDefault(require("./rules/ensure-feature-flag-registration"));
|
|
9
|
+
var _noPrePostInstalls = _interopRequireDefault(require("./rules/no-pre-post-installs"));
|
|
9
10
|
var _ensureTestRunnerArguments = _interopRequireDefault(require("./rules/ensure-test-runner-arguments"));
|
|
10
11
|
var _ensureTestRunnerNestedCount = _interopRequireDefault(require("./rules/ensure-test-runner-nested-count"));
|
|
11
12
|
var _noInvalidFeatureFlagUsage = _interopRequireDefault(require("./rules/no-invalid-feature-flag-usage"));
|
|
@@ -13,7 +14,8 @@ var rules = {
|
|
|
13
14
|
'ensure-feature-flag-registration': _ensureFeatureFlagRegistration.default,
|
|
14
15
|
'ensure-test-runner-arguments': _ensureTestRunnerArguments.default,
|
|
15
16
|
'ensure-test-runner-nested-count': _ensureTestRunnerNestedCount.default,
|
|
16
|
-
'no-invalid-feature-flag-usage': _noInvalidFeatureFlagUsage.default
|
|
17
|
+
'no-invalid-feature-flag-usage': _noInvalidFeatureFlagUsage.default,
|
|
18
|
+
'no-pre-post-install-scripts': _noPrePostInstalls.default
|
|
17
19
|
};
|
|
18
20
|
exports.rules = rules;
|
|
19
21
|
var configs = {
|
|
@@ -21,10 +23,23 @@ var configs = {
|
|
|
21
23
|
plugins: ['@atlaskit/platform'],
|
|
22
24
|
rules: {
|
|
23
25
|
'@atlaskit/platform/ensure-feature-flag-registration': 'error',
|
|
26
|
+
'@atlaskit/platform/no-pre-post-install-scripts': 'error',
|
|
24
27
|
'@atlaskit/platform/ensure-test-runner-arguments': 'error',
|
|
25
28
|
'@atlaskit/platform/ensure-test-runner-nested-count': 'warn',
|
|
26
29
|
'@atlaskit/platform/no-invalid-feature-flag-usage': 'error'
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
32
|
};
|
|
30
|
-
exports.configs = configs;
|
|
33
|
+
exports.configs = configs;
|
|
34
|
+
var processors = {
|
|
35
|
+
'package-json-processor': {
|
|
36
|
+
preprocess: function preprocess(source) {
|
|
37
|
+
// augment the json into a js file
|
|
38
|
+
return ["/* eslint-disable quote-props, comma-dangle, quotes, semi, eol-last, @typescript-eslint/semi, no-template-curly-in-string */ module.exports = ".concat(source.trim())];
|
|
39
|
+
},
|
|
40
|
+
postprocess: function postprocess(errors) {
|
|
41
|
+
return errors[0];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
exports.processors = processors;
|
|
@@ -5,21 +5,29 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
7
|
exports.default = void 0;
|
|
8
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
8
9
|
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
9
10
|
var _readPkgUp = _interopRequireDefault(require("read-pkg-up"));
|
|
10
11
|
var _path = _interopRequireDefault(require("path"));
|
|
12
|
+
var _fuse = _interopRequireDefault(require("fuse.js"));
|
|
13
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
14
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
15
|
+
// defines a "getter" to "type" map, if more types are required for feature flags (like string) add it here!
|
|
16
|
+
var getterIdentifierToFlagTypeMap = {
|
|
17
|
+
getBooleanFF: 'boolean'
|
|
18
|
+
};
|
|
11
19
|
// make sure we cache reading the package.json so we don't end up reading it for every instance of this rule.
|
|
12
20
|
var pkgJsonCache = new Map();
|
|
13
21
|
|
|
14
22
|
// get the ancestor package.json for a given file
|
|
15
|
-
var
|
|
23
|
+
var getMetadataForFilename = function getMetadataForFilename(filename) {
|
|
16
24
|
var splitFilename = filename.split(_path.default.sep);
|
|
17
25
|
for (var i = 0; i < splitFilename.length; i++) {
|
|
18
26
|
// attempt to search using the filename in the cache to see if we've read the package.json for a sibling file before
|
|
19
27
|
var searchPath = _path.default.join.apply(_path.default, (0, _toConsumableArray2.default)(splitFilename.splice(0, i)));
|
|
20
|
-
var
|
|
21
|
-
if (
|
|
22
|
-
return
|
|
28
|
+
var cachedMetaData = pkgJsonCache.get(searchPath);
|
|
29
|
+
if (cachedMetaData) {
|
|
30
|
+
return cachedMetaData;
|
|
23
31
|
}
|
|
24
32
|
}
|
|
25
33
|
var _ref = _readPkgUp.default.sync({
|
|
@@ -28,29 +36,38 @@ var getPackageJsonForFileName = function getPackageJsonForFileName(filename) {
|
|
|
28
36
|
}),
|
|
29
37
|
packageJson = _ref.packageJson,
|
|
30
38
|
pkgJsonPath = _ref.path;
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
var pkgJson = packageJson;
|
|
40
|
+
var fuse = packageJson['platform-feature-flags'] == null ? null : new _fuse.default(Object.keys(pkgJson['platform-feature-flags']));
|
|
41
|
+
var metaData = {
|
|
42
|
+
pkgJson: pkgJson,
|
|
43
|
+
fuse: fuse
|
|
44
|
+
};
|
|
45
|
+
pkgJsonCache.set(pkgJsonPath, metaData);
|
|
46
|
+
return metaData;
|
|
33
47
|
};
|
|
34
48
|
var rule = {
|
|
35
49
|
meta: {
|
|
36
|
-
hasSuggestions: false,
|
|
37
50
|
docs: {
|
|
38
51
|
recommended: false
|
|
39
52
|
},
|
|
40
53
|
type: 'problem',
|
|
41
54
|
messages: {
|
|
42
55
|
registrationSectionMissing: 'Please add a "platform-feature-flags" section to your package.json! See http://go/pff-eslint for more details',
|
|
43
|
-
featureFlagMissing: "Please add a \"{{ featureFlag }}\" section to the \"platform-feature-flags\" section in your package.json. See http://go/pff-eslint for more details"
|
|
44
|
-
|
|
56
|
+
featureFlagMissing: "Please add a \"{{ featureFlag }}\" section to the \"platform-feature-flags\" section in your package.json. See http://go/pff-eslint for more details",
|
|
57
|
+
changeFeatureFlag: "Change flag key to \"{{ closestFlag }}\" already defined in package.json"
|
|
58
|
+
},
|
|
59
|
+
hasSuggestions: true
|
|
45
60
|
},
|
|
46
61
|
create: function create(context) {
|
|
47
|
-
return {
|
|
48
|
-
|
|
62
|
+
return Object.fromEntries(Object.keys(getterIdentifierToFlagTypeMap).map(function (getterIdentifier) {
|
|
63
|
+
return ["CallExpression[callee.name=/".concat(getterIdentifier, "/]"), function (node) {
|
|
49
64
|
// to make typescript happy
|
|
50
65
|
if (node.type === 'CallExpression') {
|
|
51
66
|
var args = node.arguments;
|
|
52
67
|
var filename = context.getFilename();
|
|
53
|
-
var
|
|
68
|
+
var _getMetadataForFilena = getMetadataForFilename(filename),
|
|
69
|
+
packageJson = _getMetadataForFilena.pkgJson,
|
|
70
|
+
fuse = _getMetadataForFilena.fuse;
|
|
54
71
|
var platformFeatureFlags = packageJson['platform-feature-flags'];
|
|
55
72
|
if (!platformFeatureFlags) {
|
|
56
73
|
return context.report({
|
|
@@ -62,19 +79,38 @@ var rule = {
|
|
|
62
79
|
var featureFlag = args[0].value;
|
|
63
80
|
var featureFlagRegistration = platformFeatureFlags[featureFlag];
|
|
64
81
|
if (!featureFlagRegistration) {
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
// find the closest match in existing section for suggestion text
|
|
83
|
+
var closestMatchFix = null;
|
|
84
|
+
if (fuse) {
|
|
85
|
+
var closestFlagMatches = fuse.search(featureFlag);
|
|
86
|
+
if (closestFlagMatches.length > 0) {
|
|
87
|
+
var closestFlag = closestFlagMatches[0].item;
|
|
88
|
+
closestMatchFix = {
|
|
89
|
+
messageId: 'changeFeatureFlag',
|
|
90
|
+
data: {
|
|
91
|
+
closestFlag: closestFlag
|
|
92
|
+
},
|
|
93
|
+
fix: function fix(fixer) {
|
|
94
|
+
return fixer.replaceText(node.arguments[0], "'".concat(closestFlag, "'"));
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return context.report(_objectSpread({
|
|
100
|
+
node: args[0],
|
|
67
101
|
messageId: 'featureFlagMissing',
|
|
68
102
|
data: {
|
|
69
103
|
featureFlag: featureFlag
|
|
70
104
|
}
|
|
71
|
-
}
|
|
105
|
+
}, closestMatchFix != null ? {
|
|
106
|
+
suggest: [closestMatchFix]
|
|
107
|
+
} : {}));
|
|
72
108
|
}
|
|
73
109
|
}
|
|
74
110
|
}
|
|
75
111
|
return {};
|
|
76
|
-
}
|
|
77
|
-
};
|
|
112
|
+
}];
|
|
113
|
+
}));
|
|
78
114
|
}
|
|
79
115
|
};
|
|
80
116
|
var _default = rule;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var rule = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'problem',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'This rule disallows public packages to have pre/post install scripts as installations can happen on different environments',
|
|
12
|
+
recommended: false
|
|
13
|
+
},
|
|
14
|
+
hasSuggestions: false,
|
|
15
|
+
messages: {
|
|
16
|
+
prePostInstallScriptsNotAllowed: 'pre/post install scripts not allowed in package.json'
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
create: function create(context) {
|
|
20
|
+
return {
|
|
21
|
+
'ObjectExpression Property[key.value=scripts] Property[key.value=/^(pre|post)install$/]': function ObjectExpressionPropertyKeyValueScriptsPropertyKeyValuePrePostInstall$(node) {
|
|
22
|
+
if (!context.getFilename().endsWith('/package.json')) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
return context.report({
|
|
26
|
+
node: node,
|
|
27
|
+
messageId: 'prePostInstallScriptsNotAllowed'
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var _default = rule;
|
|
34
|
+
exports.default = _default;
|
package/dist/cjs/version.json
CHANGED
package/dist/es2019/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ensureFeatureFlagRegistration from './rules/ensure-feature-flag-registration';
|
|
2
|
+
import noPreAndPostInstallScripts from './rules/no-pre-post-installs';
|
|
2
3
|
import ensureTestRunnerArguments from './rules/ensure-test-runner-arguments';
|
|
3
4
|
import ensureTestRunnerNestedCount from './rules/ensure-test-runner-nested-count';
|
|
4
5
|
import noInvalidFeatureFlagUsage from './rules/no-invalid-feature-flag-usage';
|
|
@@ -6,16 +7,27 @@ export const rules = {
|
|
|
6
7
|
'ensure-feature-flag-registration': ensureFeatureFlagRegistration,
|
|
7
8
|
'ensure-test-runner-arguments': ensureTestRunnerArguments,
|
|
8
9
|
'ensure-test-runner-nested-count': ensureTestRunnerNestedCount,
|
|
9
|
-
'no-invalid-feature-flag-usage': noInvalidFeatureFlagUsage
|
|
10
|
+
'no-invalid-feature-flag-usage': noInvalidFeatureFlagUsage,
|
|
11
|
+
'no-pre-post-install-scripts': noPreAndPostInstallScripts
|
|
10
12
|
};
|
|
11
13
|
export const configs = {
|
|
12
14
|
recommended: {
|
|
13
15
|
plugins: ['@atlaskit/platform'],
|
|
14
16
|
rules: {
|
|
15
17
|
'@atlaskit/platform/ensure-feature-flag-registration': 'error',
|
|
18
|
+
'@atlaskit/platform/no-pre-post-install-scripts': 'error',
|
|
16
19
|
'@atlaskit/platform/ensure-test-runner-arguments': 'error',
|
|
17
20
|
'@atlaskit/platform/ensure-test-runner-nested-count': 'warn',
|
|
18
21
|
'@atlaskit/platform/no-invalid-feature-flag-usage': 'error'
|
|
19
22
|
}
|
|
20
23
|
}
|
|
24
|
+
};
|
|
25
|
+
export const processors = {
|
|
26
|
+
'package-json-processor': {
|
|
27
|
+
preprocess: source => {
|
|
28
|
+
// augment the json into a js file
|
|
29
|
+
return [`/* eslint-disable quote-props, comma-dangle, quotes, semi, eol-last, @typescript-eslint/semi, no-template-curly-in-string */ module.exports = ${source.trim()}`];
|
|
30
|
+
},
|
|
31
|
+
postprocess: errors => errors[0]
|
|
32
|
+
}
|
|
21
33
|
};
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import readPkgUp from 'read-pkg-up';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import Fuse from 'fuse.js';
|
|
4
|
+
|
|
5
|
+
// defines a "getter" to "type" map, if more types are required for feature flags (like string) add it here!
|
|
6
|
+
const getterIdentifierToFlagTypeMap = {
|
|
7
|
+
getBooleanFF: 'boolean'
|
|
8
|
+
};
|
|
3
9
|
// make sure we cache reading the package.json so we don't end up reading it for every instance of this rule.
|
|
4
10
|
const pkgJsonCache = new Map();
|
|
5
11
|
|
|
6
12
|
// get the ancestor package.json for a given file
|
|
7
|
-
const
|
|
13
|
+
const getMetadataForFilename = filename => {
|
|
8
14
|
const splitFilename = filename.split(path.sep);
|
|
9
15
|
for (let i = 0; i < splitFilename.length; i++) {
|
|
10
16
|
// attempt to search using the filename in the cache to see if we've read the package.json for a sibling file before
|
|
11
17
|
const searchPath = path.join(...splitFilename.splice(0, i));
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
14
|
-
return
|
|
18
|
+
const cachedMetaData = pkgJsonCache.get(searchPath);
|
|
19
|
+
if (cachedMetaData) {
|
|
20
|
+
return cachedMetaData;
|
|
15
21
|
}
|
|
16
22
|
}
|
|
17
23
|
const {
|
|
@@ -21,53 +27,82 @@ const getPackageJsonForFileName = filename => {
|
|
|
21
27
|
cwd: filename,
|
|
22
28
|
normalize: false
|
|
23
29
|
});
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
const pkgJson = packageJson;
|
|
31
|
+
const fuse = packageJson['platform-feature-flags'] == null ? null : new Fuse(Object.keys(pkgJson['platform-feature-flags']));
|
|
32
|
+
const metaData = {
|
|
33
|
+
pkgJson,
|
|
34
|
+
fuse
|
|
35
|
+
};
|
|
36
|
+
pkgJsonCache.set(pkgJsonPath, metaData);
|
|
37
|
+
return metaData;
|
|
26
38
|
};
|
|
27
39
|
const rule = {
|
|
28
40
|
meta: {
|
|
29
|
-
hasSuggestions: false,
|
|
30
41
|
docs: {
|
|
31
42
|
recommended: false
|
|
32
43
|
},
|
|
33
44
|
type: 'problem',
|
|
34
45
|
messages: {
|
|
35
46
|
registrationSectionMissing: 'Please add a "platform-feature-flags" section to your package.json! See http://go/pff-eslint for more details',
|
|
36
|
-
featureFlagMissing: `Please add a "{{ featureFlag }}" section to the "platform-feature-flags" section in your package.json. See http://go/pff-eslint for more details
|
|
37
|
-
|
|
47
|
+
featureFlagMissing: `Please add a "{{ featureFlag }}" section to the "platform-feature-flags" section in your package.json. See http://go/pff-eslint for more details`,
|
|
48
|
+
changeFeatureFlag: `Change flag key to "{{ closestFlag }}" already defined in package.json`
|
|
49
|
+
},
|
|
50
|
+
hasSuggestions: true
|
|
38
51
|
},
|
|
39
52
|
create(context) {
|
|
40
|
-
return {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
return Object.fromEntries(Object.keys(getterIdentifierToFlagTypeMap).map(getterIdentifier => [`CallExpression[callee.name=/${getterIdentifier}/]`, node => {
|
|
54
|
+
// to make typescript happy
|
|
55
|
+
if (node.type === 'CallExpression') {
|
|
56
|
+
const args = node.arguments;
|
|
57
|
+
const filename = context.getFilename();
|
|
58
|
+
const {
|
|
59
|
+
pkgJson: packageJson,
|
|
60
|
+
fuse
|
|
61
|
+
} = getMetadataForFilename(filename);
|
|
62
|
+
const platformFeatureFlags = packageJson['platform-feature-flags'];
|
|
63
|
+
if (!platformFeatureFlags) {
|
|
64
|
+
return context.report({
|
|
65
|
+
node: node,
|
|
66
|
+
messageId: 'registrationSectionMissing'
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (args.length === 1 && args[0].type === 'Literal' && args[0].raw) {
|
|
70
|
+
const featureFlag = args[0].value;
|
|
71
|
+
const featureFlagRegistration = platformFeatureFlags[featureFlag];
|
|
72
|
+
if (!featureFlagRegistration) {
|
|
73
|
+
// find the closest match in existing section for suggestion text
|
|
74
|
+
let closestMatchFix = null;
|
|
75
|
+
if (fuse) {
|
|
76
|
+
const closestFlagMatches = fuse.search(featureFlag);
|
|
77
|
+
if (closestFlagMatches.length > 0) {
|
|
78
|
+
const closestFlag = closestFlagMatches[0].item;
|
|
79
|
+
closestMatchFix = {
|
|
80
|
+
messageId: 'changeFeatureFlag',
|
|
81
|
+
data: {
|
|
82
|
+
closestFlag
|
|
83
|
+
},
|
|
84
|
+
fix: fixer => {
|
|
85
|
+
return fixer.replaceText(node.arguments[0], `'${closestFlag}'`);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
49
90
|
return context.report({
|
|
50
|
-
node,
|
|
51
|
-
messageId: '
|
|
91
|
+
node: args[0],
|
|
92
|
+
messageId: 'featureFlagMissing',
|
|
93
|
+
data: {
|
|
94
|
+
featureFlag
|
|
95
|
+
},
|
|
96
|
+
// only suggest if we have a close flag to match
|
|
97
|
+
...(closestMatchFix != null ? {
|
|
98
|
+
suggest: [closestMatchFix]
|
|
99
|
+
} : {})
|
|
52
100
|
});
|
|
53
101
|
}
|
|
54
|
-
if (args.length === 1 && args[0].type === 'Literal' && args[0].raw) {
|
|
55
|
-
const featureFlag = args[0].value;
|
|
56
|
-
const featureFlagRegistration = platformFeatureFlags[featureFlag];
|
|
57
|
-
if (!featureFlagRegistration) {
|
|
58
|
-
return context.report({
|
|
59
|
-
node,
|
|
60
|
-
messageId: 'featureFlagMissing',
|
|
61
|
-
data: {
|
|
62
|
-
featureFlag
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
102
|
}
|
|
68
|
-
return {};
|
|
69
103
|
}
|
|
70
|
-
|
|
104
|
+
return {};
|
|
105
|
+
}]));
|
|
71
106
|
}
|
|
72
107
|
};
|
|
73
108
|
export default rule;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const rule = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'problem',
|
|
4
|
+
docs: {
|
|
5
|
+
description: 'This rule disallows public packages to have pre/post install scripts as installations can happen on different environments',
|
|
6
|
+
recommended: false
|
|
7
|
+
},
|
|
8
|
+
hasSuggestions: false,
|
|
9
|
+
messages: {
|
|
10
|
+
prePostInstallScriptsNotAllowed: 'pre/post install scripts not allowed in package.json'
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
create(context) {
|
|
14
|
+
return {
|
|
15
|
+
'ObjectExpression Property[key.value=scripts] Property[key.value=/^(pre|post)install$/]': node => {
|
|
16
|
+
if (!context.getFilename().endsWith('/package.json')) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
return context.report({
|
|
20
|
+
node,
|
|
21
|
+
messageId: 'prePostInstallScriptsNotAllowed'
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
export default rule;
|
package/dist/es2019/version.json
CHANGED
package/dist/esm/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ensureFeatureFlagRegistration from './rules/ensure-feature-flag-registration';
|
|
2
|
+
import noPreAndPostInstallScripts from './rules/no-pre-post-installs';
|
|
2
3
|
import ensureTestRunnerArguments from './rules/ensure-test-runner-arguments';
|
|
3
4
|
import ensureTestRunnerNestedCount from './rules/ensure-test-runner-nested-count';
|
|
4
5
|
import noInvalidFeatureFlagUsage from './rules/no-invalid-feature-flag-usage';
|
|
@@ -6,16 +7,29 @@ export var rules = {
|
|
|
6
7
|
'ensure-feature-flag-registration': ensureFeatureFlagRegistration,
|
|
7
8
|
'ensure-test-runner-arguments': ensureTestRunnerArguments,
|
|
8
9
|
'ensure-test-runner-nested-count': ensureTestRunnerNestedCount,
|
|
9
|
-
'no-invalid-feature-flag-usage': noInvalidFeatureFlagUsage
|
|
10
|
+
'no-invalid-feature-flag-usage': noInvalidFeatureFlagUsage,
|
|
11
|
+
'no-pre-post-install-scripts': noPreAndPostInstallScripts
|
|
10
12
|
};
|
|
11
13
|
export var configs = {
|
|
12
14
|
recommended: {
|
|
13
15
|
plugins: ['@atlaskit/platform'],
|
|
14
16
|
rules: {
|
|
15
17
|
'@atlaskit/platform/ensure-feature-flag-registration': 'error',
|
|
18
|
+
'@atlaskit/platform/no-pre-post-install-scripts': 'error',
|
|
16
19
|
'@atlaskit/platform/ensure-test-runner-arguments': 'error',
|
|
17
20
|
'@atlaskit/platform/ensure-test-runner-nested-count': 'warn',
|
|
18
21
|
'@atlaskit/platform/no-invalid-feature-flag-usage': 'error'
|
|
19
22
|
}
|
|
20
23
|
}
|
|
24
|
+
};
|
|
25
|
+
export var processors = {
|
|
26
|
+
'package-json-processor': {
|
|
27
|
+
preprocess: function preprocess(source) {
|
|
28
|
+
// augment the json into a js file
|
|
29
|
+
return ["/* eslint-disable quote-props, comma-dangle, quotes, semi, eol-last, @typescript-eslint/semi, no-template-curly-in-string */ module.exports = ".concat(source.trim())];
|
|
30
|
+
},
|
|
31
|
+
postprocess: function postprocess(errors) {
|
|
32
|
+
return errors[0];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
21
35
|
};
|
|
@@ -1,18 +1,27 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
1
2
|
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
3
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
4
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
2
5
|
import readPkgUp from 'read-pkg-up';
|
|
3
6
|
import path from 'path';
|
|
7
|
+
import Fuse from 'fuse.js';
|
|
8
|
+
|
|
9
|
+
// defines a "getter" to "type" map, if more types are required for feature flags (like string) add it here!
|
|
10
|
+
var getterIdentifierToFlagTypeMap = {
|
|
11
|
+
getBooleanFF: 'boolean'
|
|
12
|
+
};
|
|
4
13
|
// make sure we cache reading the package.json so we don't end up reading it for every instance of this rule.
|
|
5
14
|
var pkgJsonCache = new Map();
|
|
6
15
|
|
|
7
16
|
// get the ancestor package.json for a given file
|
|
8
|
-
var
|
|
17
|
+
var getMetadataForFilename = function getMetadataForFilename(filename) {
|
|
9
18
|
var splitFilename = filename.split(path.sep);
|
|
10
19
|
for (var i = 0; i < splitFilename.length; i++) {
|
|
11
20
|
// attempt to search using the filename in the cache to see if we've read the package.json for a sibling file before
|
|
12
21
|
var searchPath = path.join.apply(path, _toConsumableArray(splitFilename.splice(0, i)));
|
|
13
|
-
var
|
|
14
|
-
if (
|
|
15
|
-
return
|
|
22
|
+
var cachedMetaData = pkgJsonCache.get(searchPath);
|
|
23
|
+
if (cachedMetaData) {
|
|
24
|
+
return cachedMetaData;
|
|
16
25
|
}
|
|
17
26
|
}
|
|
18
27
|
var _ref = readPkgUp.sync({
|
|
@@ -21,29 +30,38 @@ var getPackageJsonForFileName = function getPackageJsonForFileName(filename) {
|
|
|
21
30
|
}),
|
|
22
31
|
packageJson = _ref.packageJson,
|
|
23
32
|
pkgJsonPath = _ref.path;
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
var pkgJson = packageJson;
|
|
34
|
+
var fuse = packageJson['platform-feature-flags'] == null ? null : new Fuse(Object.keys(pkgJson['platform-feature-flags']));
|
|
35
|
+
var metaData = {
|
|
36
|
+
pkgJson: pkgJson,
|
|
37
|
+
fuse: fuse
|
|
38
|
+
};
|
|
39
|
+
pkgJsonCache.set(pkgJsonPath, metaData);
|
|
40
|
+
return metaData;
|
|
26
41
|
};
|
|
27
42
|
var rule = {
|
|
28
43
|
meta: {
|
|
29
|
-
hasSuggestions: false,
|
|
30
44
|
docs: {
|
|
31
45
|
recommended: false
|
|
32
46
|
},
|
|
33
47
|
type: 'problem',
|
|
34
48
|
messages: {
|
|
35
49
|
registrationSectionMissing: 'Please add a "platform-feature-flags" section to your package.json! See http://go/pff-eslint for more details',
|
|
36
|
-
featureFlagMissing: "Please add a \"{{ featureFlag }}\" section to the \"platform-feature-flags\" section in your package.json. See http://go/pff-eslint for more details"
|
|
37
|
-
|
|
50
|
+
featureFlagMissing: "Please add a \"{{ featureFlag }}\" section to the \"platform-feature-flags\" section in your package.json. See http://go/pff-eslint for more details",
|
|
51
|
+
changeFeatureFlag: "Change flag key to \"{{ closestFlag }}\" already defined in package.json"
|
|
52
|
+
},
|
|
53
|
+
hasSuggestions: true
|
|
38
54
|
},
|
|
39
55
|
create: function create(context) {
|
|
40
|
-
return {
|
|
41
|
-
|
|
56
|
+
return Object.fromEntries(Object.keys(getterIdentifierToFlagTypeMap).map(function (getterIdentifier) {
|
|
57
|
+
return ["CallExpression[callee.name=/".concat(getterIdentifier, "/]"), function (node) {
|
|
42
58
|
// to make typescript happy
|
|
43
59
|
if (node.type === 'CallExpression') {
|
|
44
60
|
var args = node.arguments;
|
|
45
61
|
var filename = context.getFilename();
|
|
46
|
-
var
|
|
62
|
+
var _getMetadataForFilena = getMetadataForFilename(filename),
|
|
63
|
+
packageJson = _getMetadataForFilena.pkgJson,
|
|
64
|
+
fuse = _getMetadataForFilena.fuse;
|
|
47
65
|
var platformFeatureFlags = packageJson['platform-feature-flags'];
|
|
48
66
|
if (!platformFeatureFlags) {
|
|
49
67
|
return context.report({
|
|
@@ -55,19 +73,38 @@ var rule = {
|
|
|
55
73
|
var featureFlag = args[0].value;
|
|
56
74
|
var featureFlagRegistration = platformFeatureFlags[featureFlag];
|
|
57
75
|
if (!featureFlagRegistration) {
|
|
58
|
-
|
|
59
|
-
|
|
76
|
+
// find the closest match in existing section for suggestion text
|
|
77
|
+
var closestMatchFix = null;
|
|
78
|
+
if (fuse) {
|
|
79
|
+
var closestFlagMatches = fuse.search(featureFlag);
|
|
80
|
+
if (closestFlagMatches.length > 0) {
|
|
81
|
+
var closestFlag = closestFlagMatches[0].item;
|
|
82
|
+
closestMatchFix = {
|
|
83
|
+
messageId: 'changeFeatureFlag',
|
|
84
|
+
data: {
|
|
85
|
+
closestFlag: closestFlag
|
|
86
|
+
},
|
|
87
|
+
fix: function fix(fixer) {
|
|
88
|
+
return fixer.replaceText(node.arguments[0], "'".concat(closestFlag, "'"));
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return context.report(_objectSpread({
|
|
94
|
+
node: args[0],
|
|
60
95
|
messageId: 'featureFlagMissing',
|
|
61
96
|
data: {
|
|
62
97
|
featureFlag: featureFlag
|
|
63
98
|
}
|
|
64
|
-
}
|
|
99
|
+
}, closestMatchFix != null ? {
|
|
100
|
+
suggest: [closestMatchFix]
|
|
101
|
+
} : {}));
|
|
65
102
|
}
|
|
66
103
|
}
|
|
67
104
|
}
|
|
68
105
|
return {};
|
|
69
|
-
}
|
|
70
|
-
};
|
|
106
|
+
}];
|
|
107
|
+
}));
|
|
71
108
|
}
|
|
72
109
|
};
|
|
73
110
|
export default rule;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
var rule = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'problem',
|
|
4
|
+
docs: {
|
|
5
|
+
description: 'This rule disallows public packages to have pre/post install scripts as installations can happen on different environments',
|
|
6
|
+
recommended: false
|
|
7
|
+
},
|
|
8
|
+
hasSuggestions: false,
|
|
9
|
+
messages: {
|
|
10
|
+
prePostInstallScriptsNotAllowed: 'pre/post install scripts not allowed in package.json'
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
create: function create(context) {
|
|
14
|
+
return {
|
|
15
|
+
'ObjectExpression Property[key.value=scripts] Property[key.value=/^(pre|post)install$/]': function ObjectExpressionPropertyKeyValueScriptsPropertyKeyValuePrePostInstall$(node) {
|
|
16
|
+
if (!context.getFilename().endsWith('/package.json')) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
return context.report({
|
|
20
|
+
node: node,
|
|
21
|
+
messageId: 'prePostInstallScriptsNotAllowed'
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
export default rule;
|
package/dist/esm/version.json
CHANGED
package/dist/types/index.d.ts
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
|
+
import type { Linter } from 'eslint';
|
|
1
2
|
export declare const rules: {
|
|
2
3
|
'ensure-feature-flag-registration': import("eslint").Rule.RuleModule;
|
|
3
4
|
'ensure-test-runner-arguments': import("eslint").Rule.RuleModule;
|
|
4
5
|
'ensure-test-runner-nested-count': import("eslint").Rule.RuleModule;
|
|
5
6
|
'no-invalid-feature-flag-usage': import("eslint").Rule.RuleModule;
|
|
7
|
+
'no-pre-post-install-scripts': import("eslint").Rule.RuleModule;
|
|
6
8
|
};
|
|
7
9
|
export declare const configs: {
|
|
8
10
|
recommended: {
|
|
9
11
|
plugins: string[];
|
|
10
12
|
rules: {
|
|
11
13
|
'@atlaskit/platform/ensure-feature-flag-registration': string;
|
|
14
|
+
'@atlaskit/platform/no-pre-post-install-scripts': string;
|
|
12
15
|
'@atlaskit/platform/ensure-test-runner-arguments': string;
|
|
13
16
|
'@atlaskit/platform/ensure-test-runner-nested-count': string;
|
|
14
17
|
'@atlaskit/platform/no-invalid-feature-flag-usage': string;
|
|
15
18
|
};
|
|
16
19
|
};
|
|
17
20
|
};
|
|
21
|
+
export declare const processors: {
|
|
22
|
+
'package-json-processor': Linter.Processor<string | Linter.ProcessorFile>;
|
|
23
|
+
};
|
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.1.1",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"atlassian": {
|
|
7
7
|
"team": "UIP - Platform Integration Trust (PITa)",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@babel/runtime": "^7.0.0",
|
|
26
|
+
"fuse.js": "^6.6.2",
|
|
26
27
|
"read-pkg-up": "^7.0.1"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
package/report.api.md
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
<!--SECTION START: Main Entry Types-->
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
|
+
import type { Linter } from 'eslint';
|
|
18
19
|
import { Rule } from 'eslint';
|
|
19
20
|
|
|
20
21
|
// @public (undocumented)
|
|
@@ -23,6 +24,7 @@ export const configs: {
|
|
|
23
24
|
plugins: string[];
|
|
24
25
|
rules: {
|
|
25
26
|
'@atlaskit/platform/ensure-feature-flag-registration': string;
|
|
27
|
+
'@atlaskit/platform/no-pre-post-install-scripts': string;
|
|
26
28
|
'@atlaskit/platform/ensure-test-runner-arguments': string;
|
|
27
29
|
'@atlaskit/platform/ensure-test-runner-nested-count': string;
|
|
28
30
|
'@atlaskit/platform/no-invalid-feature-flag-usage': string;
|
|
@@ -30,12 +32,18 @@ export const configs: {
|
|
|
30
32
|
};
|
|
31
33
|
};
|
|
32
34
|
|
|
35
|
+
// @public (undocumented)
|
|
36
|
+
export const processors: {
|
|
37
|
+
'package-json-processor': Linter.Processor<Linter.ProcessorFile | string>;
|
|
38
|
+
};
|
|
39
|
+
|
|
33
40
|
// @public (undocumented)
|
|
34
41
|
export const rules: {
|
|
35
42
|
'ensure-feature-flag-registration': Rule.RuleModule;
|
|
36
43
|
'ensure-test-runner-arguments': Rule.RuleModule;
|
|
37
44
|
'ensure-test-runner-nested-count': Rule.RuleModule;
|
|
38
45
|
'no-invalid-feature-flag-usage': Rule.RuleModule;
|
|
46
|
+
'no-pre-post-install-scripts': Rule.RuleModule;
|
|
39
47
|
};
|
|
40
48
|
|
|
41
49
|
// (No @packageDocumentation comment for this package)
|
package/src/index.tsx
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { Linter } from 'eslint';
|
|
1
2
|
import ensureFeatureFlagRegistration from './rules/ensure-feature-flag-registration';
|
|
3
|
+
import noPreAndPostInstallScripts from './rules/no-pre-post-installs';
|
|
2
4
|
import ensureTestRunnerArguments from './rules/ensure-test-runner-arguments';
|
|
3
5
|
import ensureTestRunnerNestedCount from './rules/ensure-test-runner-nested-count';
|
|
4
6
|
import noInvalidFeatureFlagUsage from './rules/no-invalid-feature-flag-usage';
|
|
@@ -8,6 +10,7 @@ export const rules = {
|
|
|
8
10
|
'ensure-test-runner-arguments': ensureTestRunnerArguments,
|
|
9
11
|
'ensure-test-runner-nested-count': ensureTestRunnerNestedCount,
|
|
10
12
|
'no-invalid-feature-flag-usage': noInvalidFeatureFlagUsage,
|
|
13
|
+
'no-pre-post-install-scripts': noPreAndPostInstallScripts,
|
|
11
14
|
};
|
|
12
15
|
|
|
13
16
|
export const configs = {
|
|
@@ -15,9 +18,22 @@ export const configs = {
|
|
|
15
18
|
plugins: ['@atlaskit/platform'],
|
|
16
19
|
rules: {
|
|
17
20
|
'@atlaskit/platform/ensure-feature-flag-registration': 'error',
|
|
21
|
+
'@atlaskit/platform/no-pre-post-install-scripts': 'error',
|
|
18
22
|
'@atlaskit/platform/ensure-test-runner-arguments': 'error',
|
|
19
23
|
'@atlaskit/platform/ensure-test-runner-nested-count': 'warn',
|
|
20
24
|
'@atlaskit/platform/no-invalid-feature-flag-usage': 'error',
|
|
21
25
|
},
|
|
22
26
|
},
|
|
23
27
|
};
|
|
28
|
+
|
|
29
|
+
export const processors = {
|
|
30
|
+
'package-json-processor': {
|
|
31
|
+
preprocess: (source: string) => {
|
|
32
|
+
// augment the json into a js file
|
|
33
|
+
return [
|
|
34
|
+
`/* eslint-disable quote-props, comma-dangle, quotes, semi, eol-last, @typescript-eslint/semi, no-template-curly-in-string */ module.exports = ${source.trim()}`,
|
|
35
|
+
];
|
|
36
|
+
},
|
|
37
|
+
postprocess: (errors) => errors[0],
|
|
38
|
+
} as Linter.Processor,
|
|
39
|
+
};
|
|
@@ -31,6 +31,7 @@ describe('with existing platform-feature-flags section', () => {
|
|
|
31
31
|
};
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
+
// this isolates the invalid case so we can test the suggestion properly
|
|
34
35
|
tester.run('ensure-feature-flag-registration', rule, {
|
|
35
36
|
valid: [
|
|
36
37
|
{
|
|
@@ -40,7 +41,20 @@ describe('with existing platform-feature-flags section', () => {
|
|
|
40
41
|
invalid: [
|
|
41
42
|
{
|
|
42
43
|
code: `getBooleanFF('invalid-flag')`,
|
|
43
|
-
errors: [
|
|
44
|
+
errors: [
|
|
45
|
+
{
|
|
46
|
+
messageId: 'featureFlagMissing',
|
|
47
|
+
suggestions: [
|
|
48
|
+
{
|
|
49
|
+
messageId: 'changeFeatureFlag',
|
|
50
|
+
data: {
|
|
51
|
+
closestFlag: 'test-flag',
|
|
52
|
+
},
|
|
53
|
+
output: `getBooleanFF('test-flag')`,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
],
|
|
44
58
|
},
|
|
45
59
|
],
|
|
46
60
|
});
|
|
@@ -1,26 +1,42 @@
|
|
|
1
1
|
import type { Rule } from 'eslint';
|
|
2
2
|
import readPkgUp from 'read-pkg-up';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import Fuse from 'fuse.js';
|
|
5
|
+
|
|
6
|
+
// defines a "getter" to "type" map, if more types are required for feature flags (like string) add it here!
|
|
7
|
+
const getterIdentifierToFlagTypeMap = {
|
|
8
|
+
getBooleanFF: 'boolean' as const,
|
|
9
|
+
} as const;
|
|
4
10
|
|
|
5
11
|
type PlatformFeatureFlagRegistrationSection = {
|
|
6
12
|
[key: string]: {
|
|
7
|
-
|
|
13
|
+
// get the values of the object above
|
|
14
|
+
type: typeof getterIdentifierToFlagTypeMap[keyof typeof getterIdentifierToFlagTypeMap];
|
|
8
15
|
};
|
|
9
16
|
};
|
|
10
17
|
|
|
18
|
+
type EnhancedPackageJson = readPkgUp.PackageJson & {
|
|
19
|
+
'platform-feature-flags'?: PlatformFeatureFlagRegistrationSection;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type PkgJsonMetaData = {
|
|
23
|
+
pkgJson: EnhancedPackageJson;
|
|
24
|
+
fuse: Fuse<string> | null;
|
|
25
|
+
};
|
|
26
|
+
|
|
11
27
|
// make sure we cache reading the package.json so we don't end up reading it for every instance of this rule.
|
|
12
|
-
const pkgJsonCache = new Map<string,
|
|
28
|
+
const pkgJsonCache = new Map<string, PkgJsonMetaData>();
|
|
13
29
|
|
|
14
30
|
// get the ancestor package.json for a given file
|
|
15
|
-
const
|
|
31
|
+
const getMetadataForFilename = (filename: string): PkgJsonMetaData => {
|
|
16
32
|
const splitFilename = filename.split(path.sep);
|
|
17
33
|
for (let i = 0; i < splitFilename.length; i++) {
|
|
18
34
|
// attempt to search using the filename in the cache to see if we've read the package.json for a sibling file before
|
|
19
35
|
const searchPath = path.join(...splitFilename.splice(0, i));
|
|
20
|
-
const
|
|
36
|
+
const cachedMetaData = pkgJsonCache.get(searchPath);
|
|
21
37
|
|
|
22
|
-
if (
|
|
23
|
-
return
|
|
38
|
+
if (cachedMetaData) {
|
|
39
|
+
return cachedMetaData;
|
|
24
40
|
}
|
|
25
41
|
}
|
|
26
42
|
|
|
@@ -29,13 +45,21 @@ const getPackageJsonForFileName = (filename: string): readPkgUp.PackageJson => {
|
|
|
29
45
|
normalize: false,
|
|
30
46
|
})!;
|
|
31
47
|
|
|
32
|
-
|
|
33
|
-
|
|
48
|
+
const pkgJson = packageJson as EnhancedPackageJson;
|
|
49
|
+
|
|
50
|
+
const fuse =
|
|
51
|
+
packageJson['platform-feature-flags'] == null
|
|
52
|
+
? null
|
|
53
|
+
: new Fuse(Object.keys(pkgJson['platform-feature-flags']!));
|
|
54
|
+
|
|
55
|
+
const metaData = { pkgJson, fuse };
|
|
56
|
+
|
|
57
|
+
pkgJsonCache.set(pkgJsonPath, metaData);
|
|
58
|
+
return metaData;
|
|
34
59
|
};
|
|
35
60
|
|
|
36
61
|
const rule: Rule.RuleModule = {
|
|
37
62
|
meta: {
|
|
38
|
-
hasSuggestions: false,
|
|
39
63
|
docs: {
|
|
40
64
|
recommended: false,
|
|
41
65
|
},
|
|
@@ -44,47 +68,88 @@ const rule: Rule.RuleModule = {
|
|
|
44
68
|
registrationSectionMissing:
|
|
45
69
|
'Please add a "platform-feature-flags" section to your package.json! See http://go/pff-eslint for more details',
|
|
46
70
|
featureFlagMissing: `Please add a "{{ featureFlag }}" section to the "platform-feature-flags" section in your package.json. See http://go/pff-eslint for more details`,
|
|
71
|
+
changeFeatureFlag: `Change flag key to "{{ closestFlag }}" already defined in package.json`,
|
|
47
72
|
},
|
|
73
|
+
|
|
74
|
+
hasSuggestions: true,
|
|
48
75
|
},
|
|
49
76
|
create(context) {
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (!platformFeatureFlags) {
|
|
63
|
-
return context.report({
|
|
64
|
-
node,
|
|
65
|
-
messageId: 'registrationSectionMissing',
|
|
66
|
-
});
|
|
67
|
-
}
|
|
77
|
+
return Object.fromEntries(
|
|
78
|
+
(
|
|
79
|
+
Object.keys(
|
|
80
|
+
getterIdentifierToFlagTypeMap,
|
|
81
|
+
) as (keyof typeof getterIdentifierToFlagTypeMap)[]
|
|
82
|
+
).map((getterIdentifier) => [
|
|
83
|
+
`CallExpression[callee.name=/${getterIdentifier}/]`,
|
|
84
|
+
(node: Rule.Node) => {
|
|
85
|
+
// to make typescript happy
|
|
86
|
+
if (node.type === 'CallExpression') {
|
|
87
|
+
const args = node.arguments;
|
|
68
88
|
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
89
|
+
const filename = context.getFilename();
|
|
90
|
+
const { pkgJson: packageJson, fuse } =
|
|
91
|
+
getMetadataForFilename(filename);
|
|
92
|
+
const platformFeatureFlags = packageJson['platform-feature-flags'];
|
|
72
93
|
|
|
73
|
-
if (!
|
|
94
|
+
if (!platformFeatureFlags) {
|
|
74
95
|
return context.report({
|
|
75
|
-
node,
|
|
76
|
-
messageId: '
|
|
77
|
-
data: {
|
|
78
|
-
featureFlag,
|
|
79
|
-
},
|
|
96
|
+
node: node,
|
|
97
|
+
messageId: 'registrationSectionMissing',
|
|
80
98
|
});
|
|
81
99
|
}
|
|
100
|
+
|
|
101
|
+
if (
|
|
102
|
+
args.length === 1 &&
|
|
103
|
+
args[0].type === 'Literal' &&
|
|
104
|
+
args[0].raw
|
|
105
|
+
) {
|
|
106
|
+
const featureFlag = args[0].value as string;
|
|
107
|
+
const featureFlagRegistration = platformFeatureFlags[featureFlag];
|
|
108
|
+
|
|
109
|
+
if (!featureFlagRegistration) {
|
|
110
|
+
// find the closest match in existing section for suggestion text
|
|
111
|
+
let closestMatchFix: Rule.SuggestionReportDescriptor | null =
|
|
112
|
+
null;
|
|
113
|
+
|
|
114
|
+
if (fuse) {
|
|
115
|
+
const closestFlagMatches = fuse.search(featureFlag);
|
|
116
|
+
if (closestFlagMatches.length > 0) {
|
|
117
|
+
const closestFlag = closestFlagMatches[0].item;
|
|
118
|
+
|
|
119
|
+
closestMatchFix = {
|
|
120
|
+
messageId: 'changeFeatureFlag',
|
|
121
|
+
data: {
|
|
122
|
+
closestFlag,
|
|
123
|
+
},
|
|
124
|
+
fix: (fixer) => {
|
|
125
|
+
return fixer.replaceText(
|
|
126
|
+
node.arguments[0],
|
|
127
|
+
`'${closestFlag}'`,
|
|
128
|
+
);
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return context.report({
|
|
135
|
+
node: args[0],
|
|
136
|
+
messageId: 'featureFlagMissing',
|
|
137
|
+
data: {
|
|
138
|
+
featureFlag,
|
|
139
|
+
},
|
|
140
|
+
// only suggest if we have a close flag to match
|
|
141
|
+
...(closestMatchFix != null
|
|
142
|
+
? { suggest: [closestMatchFix] }
|
|
143
|
+
: {}),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
82
147
|
}
|
|
83
|
-
}
|
|
84
148
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
149
|
+
return {};
|
|
150
|
+
},
|
|
151
|
+
]),
|
|
152
|
+
);
|
|
88
153
|
},
|
|
89
154
|
};
|
|
90
155
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { tester } from '../../../../__tests__/utils/_tester';
|
|
2
|
+
import rule from '../../index';
|
|
3
|
+
|
|
4
|
+
describe('test no-pre-post-installs rule', () => {
|
|
5
|
+
tester.run('no-pre-post-installs', rule, {
|
|
6
|
+
valid: [
|
|
7
|
+
{
|
|
8
|
+
code: `const foo = { "scripts": { "preinstall": 1, "postinstall": 2 }}`,
|
|
9
|
+
filename: 'hello/foo.ts',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
code: `const foo = { "scripts": { "preinstall": 1, "postinstall": 2 }}`,
|
|
13
|
+
filename: 'foo/dummy.json',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
code: `const foo = { "scripts": { "bar": 1, "dummy": 'echo 1' }}`,
|
|
17
|
+
filename: 'foo/package.json',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
code: `module.exports = { "scripts": { "fakePreinstall": 1 }};`,
|
|
21
|
+
filename: 'bar/package.json',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
code: `module.exports = { "scripts": { "fakePostinstall": 1 }};`,
|
|
25
|
+
filename: 'bar/package.json',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
invalid: [
|
|
29
|
+
{
|
|
30
|
+
code: `module.exports = { "scripts": { "preinstall": 1 }};`,
|
|
31
|
+
filename: 'bar/package.json',
|
|
32
|
+
errors: [{ messageId: 'prePostInstallScriptsNotAllowed' }],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
code: `const foo = { "scripts": { "postinstall": 1 }}`,
|
|
36
|
+
filename: 'baz/package.json',
|
|
37
|
+
errors: [{ messageId: 'prePostInstallScriptsNotAllowed' }],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Rule } from 'eslint';
|
|
2
|
+
|
|
3
|
+
const rule: Rule.RuleModule = {
|
|
4
|
+
meta: {
|
|
5
|
+
type: 'problem',
|
|
6
|
+
docs: {
|
|
7
|
+
description:
|
|
8
|
+
'This rule disallows public packages to have pre/post install scripts as installations can happen on different environments',
|
|
9
|
+
recommended: false,
|
|
10
|
+
},
|
|
11
|
+
hasSuggestions: false,
|
|
12
|
+
messages: {
|
|
13
|
+
prePostInstallScriptsNotAllowed:
|
|
14
|
+
'pre/post install scripts not allowed in package.json',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
create(context) {
|
|
18
|
+
return {
|
|
19
|
+
'ObjectExpression Property[key.value=scripts] Property[key.value=/^(pre|post)install$/]':
|
|
20
|
+
(node: Rule.Node) => {
|
|
21
|
+
if (!context.getFilename().endsWith('/package.json')) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return context.report({
|
|
26
|
+
node,
|
|
27
|
+
messageId: 'prePostInstallScriptsNotAllowed',
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default rule;
|
package/tmp/api-report-tmp.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
6
|
|
|
7
|
+
import type { Linter } from 'eslint';
|
|
7
8
|
import { Rule } from 'eslint';
|
|
8
9
|
|
|
9
10
|
// @public (undocumented)
|
|
@@ -12,6 +13,7 @@ export const configs: {
|
|
|
12
13
|
plugins: string[];
|
|
13
14
|
rules: {
|
|
14
15
|
'@atlaskit/platform/ensure-feature-flag-registration': string;
|
|
16
|
+
'@atlaskit/platform/no-pre-post-install-scripts': string;
|
|
15
17
|
'@atlaskit/platform/ensure-test-runner-arguments': string;
|
|
16
18
|
'@atlaskit/platform/ensure-test-runner-nested-count': string;
|
|
17
19
|
'@atlaskit/platform/no-invalid-feature-flag-usage': string;
|
|
@@ -19,12 +21,18 @@ export const configs: {
|
|
|
19
21
|
};
|
|
20
22
|
};
|
|
21
23
|
|
|
24
|
+
// @public (undocumented)
|
|
25
|
+
export const processors: {
|
|
26
|
+
'package-json-processor': Linter.Processor<Linter.ProcessorFile | string>;
|
|
27
|
+
};
|
|
28
|
+
|
|
22
29
|
// @public (undocumented)
|
|
23
30
|
export const rules: {
|
|
24
31
|
'ensure-feature-flag-registration': Rule.RuleModule;
|
|
25
32
|
'ensure-test-runner-arguments': Rule.RuleModule;
|
|
26
33
|
'ensure-test-runner-nested-count': Rule.RuleModule;
|
|
27
34
|
'no-invalid-feature-flag-usage': Rule.RuleModule;
|
|
35
|
+
'no-pre-post-install-scripts': Rule.RuleModule;
|
|
28
36
|
};
|
|
29
37
|
|
|
30
38
|
// (No @packageDocumentation comment for this package)
|