@atlaskit/eslint-plugin-platform 0.8.0 → 0.10.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,23 @@
1
1
  # @atlaskit/eslint-plugin-platform
2
2
 
3
+ ## 0.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#151601](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/151601)
8
+ [`d619298dc0279`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/d619298dc0279) -
9
+ Adds `use-entrypoints-in-examples` rule which prevents the use of relative imports from `src` in
10
+ examples. Instead they should use public entrypoints to ensure they reflect public API.
11
+
12
+ ## 0.9.0
13
+
14
+ ### Minor Changes
15
+
16
+ - [#146603](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/146603)
17
+ [`73a0361be46a2`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/73a0361be46a2) -
18
+ Created new rule `@atlaskit/platform/ensure-valid-bin-values` which validates bin values in
19
+ package.json files are valid point to files, not directories.
20
+
3
21
  ## 0.8.0
4
22
 
5
23
  ### Minor Changes
package/dist/cjs/index.js CHANGED
@@ -16,6 +16,7 @@ var _noInvalidFeatureFlagUsage = _interopRequireDefault(require("./rules/no-inva
16
16
  var _ensureFeatureFlagPrefix = _interopRequireDefault(require("./rules/ensure-feature-flag-prefix"));
17
17
  var _ensureCriticalDependencyResolutions = _interopRequireDefault(require("./rules/ensure-critical-dependency-resolutions"));
18
18
  var _ensureValidWorkspaceProtocolUsage = _interopRequireDefault(require("./rules/ensure-valid-workspace-protocol-usage"));
19
+ var _ensureValidBinValues = _interopRequireDefault(require("./rules/ensure-valid-bin-values"));
19
20
  var _noInvalidStorybookDecoratorUsage = _interopRequireDefault(require("./rules/no-invalid-storybook-decorator-usage"));
20
21
  var _ensurePublishValid = _interopRequireDefault(require("./rules/ensure-publish-valid"));
21
22
  var _ensureNativeAndAfExportsSynced = _interopRequireDefault(require("./rules/ensure-native-and-af-exports-synced"));
@@ -26,6 +27,7 @@ var _noPreconditioning = _interopRequireDefault(require("./rules/no-precondition
26
27
  var _inlineUsage = _interopRequireDefault(require("./rules/inline-usage"));
27
28
  var _preferFg = _interopRequireDefault(require("./rules/prefer-fg"));
28
29
  var _noAlias = _interopRequireDefault(require("./rules/no-alias"));
30
+ var _useEntrypointsInExamples = _interopRequireDefault(require("./rules/use-entrypoints-in-examples"));
29
31
  var _useRecommendedUtils = _interopRequireDefault(require("./rules/use-recommended-utils"));
30
32
  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; }
31
33
  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
@@ -37,6 +39,7 @@ var rules = exports.rules = {
37
39
  'ensure-atlassian-team': _ensureAtlassianTeam.default,
38
40
  'ensure-critical-dependency-resolutions': _ensureCriticalDependencyResolutions.default,
39
41
  'ensure-valid-workspace-protocol-usage': _ensureValidWorkspaceProtocolUsage.default,
42
+ 'ensure-valid-bin-values': _ensureValidBinValues.default,
40
43
  'no-duplicate-dependencies': _noDuplicateDependencies.default,
41
44
  'no-invalid-feature-flag-usage': _noInvalidFeatureFlagUsage.default,
42
45
  'no-pre-post-install-scripts': _noPrePostInstalls.default,
@@ -50,6 +53,7 @@ var rules = exports.rules = {
50
53
  'inline-usage': _inlineUsage.default,
51
54
  'prefer-fg': _preferFg.default,
52
55
  'no-alias': _noAlias.default,
56
+ 'use-entrypoints-in-examples': _useEntrypointsInExamples.default,
53
57
  'use-recommended-utils': _useRecommendedUtils.default
54
58
  };
55
59
  var configs = exports.configs = {
@@ -0,0 +1,68 @@
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 _fs = _interopRequireDefault(require("fs"));
9
+ var _path = require("path");
10
+ var _handleAstObject = require("../util/handle-ast-object");
11
+ // eslint-disable-next-line import/no-extraneous-dependencies
12
+
13
+ var cwd = process.cwd();
14
+ function checkIsAllBinValuesAreValid(node, packageDir) {
15
+ var binObj = (0, _handleAstObject.getObjectPropertyAsObject)(node, 'bin');
16
+ if (!binObj || !Array.isArray(binObj.properties)) {
17
+ return true;
18
+ }
19
+ return binObj.properties.every(function (p) {
20
+ if (p.type === 'Property' && p.value.type === 'Literal') {
21
+ try {
22
+ var binValue = String(p.value.value);
23
+ var pathToBin = (0, _path.resolve)(cwd, packageDir, binValue);
24
+ // Ignore bin values that point to dist as these files don't always exist
25
+ if (binValue.startsWith('./dist/')) {
26
+ return true;
27
+ }
28
+ return _fs.default.statSync(pathToBin).isFile();
29
+ } catch (err) {
30
+ return false;
31
+ }
32
+ }
33
+ // If it's not a property or doesn't have a literal value, consider it invalid
34
+ return false;
35
+ });
36
+ }
37
+ var rule = {
38
+ meta: {
39
+ type: 'problem',
40
+ docs: {
41
+ description: "Ensures bin values in package.json files are valid.",
42
+ recommended: true
43
+ },
44
+ hasSuggestions: false,
45
+ messages: {
46
+ invalidBinValue: "Invalid bin value. Ensure that the value points to a file and not a directory."
47
+ }
48
+ },
49
+ create: function create(context) {
50
+ var fileName = context.getFilename();
51
+ return {
52
+ ObjectExpression: function ObjectExpression(node) {
53
+ if (!fileName.endsWith('package.json') || node.type !== 'ObjectExpression') {
54
+ return;
55
+ }
56
+ var isAllBinValuesValid = checkIsAllBinValuesAreValid(node, (0, _path.dirname)(fileName));
57
+ if (isAllBinValuesValid) {
58
+ return;
59
+ }
60
+ return context.report({
61
+ node: node,
62
+ messageId: 'invalidBinValue'
63
+ });
64
+ }
65
+ };
66
+ }
67
+ };
68
+ var _default = exports.default = rule;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var rule = {
8
+ meta: {
9
+ docs: {
10
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/use-entrypoints-in-examples/README.md',
11
+ description: 'Encourage usage of package entrypoints in examples.'
12
+ },
13
+ messages: {
14
+ useEntrypointsInExamples: 'Use the package entrypoints instead of importing from src. This ensures examples reflect public API.\n\nFor example, use `@atlaskit/button/new` instead of `../../src/new`'
15
+ },
16
+ type: 'problem'
17
+ },
18
+ create: function create(context) {
19
+ /**
20
+ * Even if it's enabled on non-example files it will ignore them.
21
+ *
22
+ * This is a defensive check, the rule should be configured to only run on examples.
23
+ */
24
+ if (!context.filename.includes('/examples/')) {
25
+ return {};
26
+ }
27
+ return {
28
+ ImportDeclaration: function ImportDeclaration(node) {
29
+ var moduleName = node.source.value;
30
+ if (typeof moduleName !== 'string') {
31
+ return;
32
+ }
33
+ if (/^(\.\.\/)+src(\/|$)/.test(moduleName)) {
34
+ context.report({
35
+ node: node.source,
36
+ messageId: 'useEntrypointsInExamples'
37
+ });
38
+ }
39
+ }
40
+ };
41
+ }
42
+ };
43
+ var _default = exports.default = rule;
@@ -10,6 +10,7 @@ import noInvalidFeatureFlagUsage from './rules/no-invalid-feature-flag-usage';
10
10
  import ensureFeatureFlagPrefix from './rules/ensure-feature-flag-prefix';
11
11
  import ensureCriticalDependencyResolutions from './rules/ensure-critical-dependency-resolutions';
12
12
  import ensureValidWorkspaceProtocolUsage from './rules/ensure-valid-workspace-protocol-usage';
13
+ import ensureValidBinValues from './rules/ensure-valid-bin-values';
13
14
  import noInvalidStorybookDecoratorUsage from './rules/no-invalid-storybook-decorator-usage';
14
15
  import ensurePublishValid from './rules/ensure-publish-valid';
15
16
  import ensureNativeAndAfExportsSynced from './rules/ensure-native-and-af-exports-synced';
@@ -20,6 +21,7 @@ import noPreconditioning from './rules/no-preconditioning';
20
21
  import inlineUsage from './rules/inline-usage';
21
22
  import preferFG from './rules/prefer-fg';
22
23
  import noAlias from './rules/no-alias';
24
+ import useEntrypointsInExamples from './rules/use-entrypoints-in-examples';
23
25
  import useRecommendedUtils from './rules/use-recommended-utils';
24
26
  export const rules = {
25
27
  'ensure-feature-flag-registration': ensureFeatureFlagRegistration,
@@ -29,6 +31,7 @@ export const rules = {
29
31
  'ensure-atlassian-team': ensureAtlassianTeam,
30
32
  'ensure-critical-dependency-resolutions': ensureCriticalDependencyResolutions,
31
33
  'ensure-valid-workspace-protocol-usage': ensureValidWorkspaceProtocolUsage,
34
+ 'ensure-valid-bin-values': ensureValidBinValues,
32
35
  'no-duplicate-dependencies': noDuplicateDependencies,
33
36
  'no-invalid-feature-flag-usage': noInvalidFeatureFlagUsage,
34
37
  'no-pre-post-install-scripts': noPreAndPostInstallScripts,
@@ -42,6 +45,7 @@ export const rules = {
42
45
  'inline-usage': inlineUsage,
43
46
  'prefer-fg': preferFG,
44
47
  'no-alias': noAlias,
48
+ 'use-entrypoints-in-examples': useEntrypointsInExamples,
45
49
  'use-recommended-utils': useRecommendedUtils
46
50
  };
47
51
  export const configs = {
@@ -0,0 +1,60 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
+ import fs from 'fs';
3
+ import { resolve, dirname } from 'path';
4
+ import { getObjectPropertyAsObject } from '../util/handle-ast-object';
5
+ const cwd = process.cwd();
6
+ function checkIsAllBinValuesAreValid(node, packageDir) {
7
+ const binObj = getObjectPropertyAsObject(node, 'bin');
8
+ if (!binObj || !Array.isArray(binObj.properties)) {
9
+ return true;
10
+ }
11
+ return binObj.properties.every(p => {
12
+ if (p.type === 'Property' && p.value.type === 'Literal') {
13
+ try {
14
+ const binValue = String(p.value.value);
15
+ const pathToBin = resolve(cwd, packageDir, binValue);
16
+ // Ignore bin values that point to dist as these files don't always exist
17
+ if (binValue.startsWith('./dist/')) {
18
+ return true;
19
+ }
20
+ return fs.statSync(pathToBin).isFile();
21
+ } catch (err) {
22
+ return false;
23
+ }
24
+ }
25
+ // If it's not a property or doesn't have a literal value, consider it invalid
26
+ return false;
27
+ });
28
+ }
29
+ const rule = {
30
+ meta: {
31
+ type: 'problem',
32
+ docs: {
33
+ description: `Ensures bin values in package.json files are valid.`,
34
+ recommended: true
35
+ },
36
+ hasSuggestions: false,
37
+ messages: {
38
+ invalidBinValue: `Invalid bin value. Ensure that the value points to a file and not a directory.`
39
+ }
40
+ },
41
+ create(context) {
42
+ const fileName = context.getFilename();
43
+ return {
44
+ ObjectExpression: node => {
45
+ if (!fileName.endsWith('package.json') || node.type !== 'ObjectExpression') {
46
+ return;
47
+ }
48
+ const isAllBinValuesValid = checkIsAllBinValuesAreValid(node, dirname(fileName));
49
+ if (isAllBinValuesValid) {
50
+ return;
51
+ }
52
+ return context.report({
53
+ node,
54
+ messageId: 'invalidBinValue'
55
+ });
56
+ }
57
+ };
58
+ }
59
+ };
60
+ export default rule;
@@ -0,0 +1,37 @@
1
+ const rule = {
2
+ meta: {
3
+ docs: {
4
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/use-entrypoints-in-examples/README.md',
5
+ description: 'Encourage usage of package entrypoints in examples.'
6
+ },
7
+ messages: {
8
+ useEntrypointsInExamples: 'Use the package entrypoints instead of importing from src. This ensures examples reflect public API.\n\nFor example, use `@atlaskit/button/new` instead of `../../src/new`'
9
+ },
10
+ type: 'problem'
11
+ },
12
+ create(context) {
13
+ /**
14
+ * Even if it's enabled on non-example files it will ignore them.
15
+ *
16
+ * This is a defensive check, the rule should be configured to only run on examples.
17
+ */
18
+ if (!context.filename.includes('/examples/')) {
19
+ return {};
20
+ }
21
+ return {
22
+ ImportDeclaration(node) {
23
+ const moduleName = node.source.value;
24
+ if (typeof moduleName !== 'string') {
25
+ return;
26
+ }
27
+ if (/^(\.\.\/)+src(\/|$)/.test(moduleName)) {
28
+ context.report({
29
+ node: node.source,
30
+ messageId: 'useEntrypointsInExamples'
31
+ });
32
+ }
33
+ }
34
+ };
35
+ }
36
+ };
37
+ export default rule;
package/dist/esm/index.js CHANGED
@@ -13,6 +13,7 @@ import noInvalidFeatureFlagUsage from './rules/no-invalid-feature-flag-usage';
13
13
  import ensureFeatureFlagPrefix from './rules/ensure-feature-flag-prefix';
14
14
  import ensureCriticalDependencyResolutions from './rules/ensure-critical-dependency-resolutions';
15
15
  import ensureValidWorkspaceProtocolUsage from './rules/ensure-valid-workspace-protocol-usage';
16
+ import ensureValidBinValues from './rules/ensure-valid-bin-values';
16
17
  import noInvalidStorybookDecoratorUsage from './rules/no-invalid-storybook-decorator-usage';
17
18
  import ensurePublishValid from './rules/ensure-publish-valid';
18
19
  import ensureNativeAndAfExportsSynced from './rules/ensure-native-and-af-exports-synced';
@@ -23,6 +24,7 @@ import noPreconditioning from './rules/no-preconditioning';
23
24
  import inlineUsage from './rules/inline-usage';
24
25
  import preferFG from './rules/prefer-fg';
25
26
  import noAlias from './rules/no-alias';
27
+ import useEntrypointsInExamples from './rules/use-entrypoints-in-examples';
26
28
  import useRecommendedUtils from './rules/use-recommended-utils';
27
29
  export var rules = {
28
30
  'ensure-feature-flag-registration': ensureFeatureFlagRegistration,
@@ -32,6 +34,7 @@ export var rules = {
32
34
  'ensure-atlassian-team': ensureAtlassianTeam,
33
35
  'ensure-critical-dependency-resolutions': ensureCriticalDependencyResolutions,
34
36
  'ensure-valid-workspace-protocol-usage': ensureValidWorkspaceProtocolUsage,
37
+ 'ensure-valid-bin-values': ensureValidBinValues,
35
38
  'no-duplicate-dependencies': noDuplicateDependencies,
36
39
  'no-invalid-feature-flag-usage': noInvalidFeatureFlagUsage,
37
40
  'no-pre-post-install-scripts': noPreAndPostInstallScripts,
@@ -45,6 +48,7 @@ export var rules = {
45
48
  'inline-usage': inlineUsage,
46
49
  'prefer-fg': preferFG,
47
50
  'no-alias': noAlias,
51
+ 'use-entrypoints-in-examples': useEntrypointsInExamples,
48
52
  'use-recommended-utils': useRecommendedUtils
49
53
  };
50
54
  export var configs = {
@@ -0,0 +1,60 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
+ import fs from 'fs';
3
+ import { resolve, dirname } from 'path';
4
+ import { getObjectPropertyAsObject } from '../util/handle-ast-object';
5
+ var cwd = process.cwd();
6
+ function checkIsAllBinValuesAreValid(node, packageDir) {
7
+ var binObj = getObjectPropertyAsObject(node, 'bin');
8
+ if (!binObj || !Array.isArray(binObj.properties)) {
9
+ return true;
10
+ }
11
+ return binObj.properties.every(function (p) {
12
+ if (p.type === 'Property' && p.value.type === 'Literal') {
13
+ try {
14
+ var binValue = String(p.value.value);
15
+ var pathToBin = resolve(cwd, packageDir, binValue);
16
+ // Ignore bin values that point to dist as these files don't always exist
17
+ if (binValue.startsWith('./dist/')) {
18
+ return true;
19
+ }
20
+ return fs.statSync(pathToBin).isFile();
21
+ } catch (err) {
22
+ return false;
23
+ }
24
+ }
25
+ // If it's not a property or doesn't have a literal value, consider it invalid
26
+ return false;
27
+ });
28
+ }
29
+ var rule = {
30
+ meta: {
31
+ type: 'problem',
32
+ docs: {
33
+ description: "Ensures bin values in package.json files are valid.",
34
+ recommended: true
35
+ },
36
+ hasSuggestions: false,
37
+ messages: {
38
+ invalidBinValue: "Invalid bin value. Ensure that the value points to a file and not a directory."
39
+ }
40
+ },
41
+ create: function create(context) {
42
+ var fileName = context.getFilename();
43
+ return {
44
+ ObjectExpression: function ObjectExpression(node) {
45
+ if (!fileName.endsWith('package.json') || node.type !== 'ObjectExpression') {
46
+ return;
47
+ }
48
+ var isAllBinValuesValid = checkIsAllBinValuesAreValid(node, dirname(fileName));
49
+ if (isAllBinValuesValid) {
50
+ return;
51
+ }
52
+ return context.report({
53
+ node: node,
54
+ messageId: 'invalidBinValue'
55
+ });
56
+ }
57
+ };
58
+ }
59
+ };
60
+ export default rule;
@@ -0,0 +1,37 @@
1
+ var rule = {
2
+ meta: {
3
+ docs: {
4
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/use-entrypoints-in-examples/README.md',
5
+ description: 'Encourage usage of package entrypoints in examples.'
6
+ },
7
+ messages: {
8
+ useEntrypointsInExamples: 'Use the package entrypoints instead of importing from src. This ensures examples reflect public API.\n\nFor example, use `@atlaskit/button/new` instead of `../../src/new`'
9
+ },
10
+ type: 'problem'
11
+ },
12
+ create: function create(context) {
13
+ /**
14
+ * Even if it's enabled on non-example files it will ignore them.
15
+ *
16
+ * This is a defensive check, the rule should be configured to only run on examples.
17
+ */
18
+ if (!context.filename.includes('/examples/')) {
19
+ return {};
20
+ }
21
+ return {
22
+ ImportDeclaration: function ImportDeclaration(node) {
23
+ var moduleName = node.source.value;
24
+ if (typeof moduleName !== 'string') {
25
+ return;
26
+ }
27
+ if (/^(\.\.\/)+src(\/|$)/.test(moduleName)) {
28
+ context.report({
29
+ node: node.source,
30
+ messageId: 'useEntrypointsInExamples'
31
+ });
32
+ }
33
+ }
34
+ };
35
+ }
36
+ };
37
+ export default rule;
@@ -7,6 +7,7 @@ export declare const rules: {
7
7
  'ensure-atlassian-team': import("eslint").Rule.RuleModule;
8
8
  'ensure-critical-dependency-resolutions': import("eslint").Rule.RuleModule;
9
9
  'ensure-valid-workspace-protocol-usage': import("eslint").Rule.RuleModule;
10
+ 'ensure-valid-bin-values': import("eslint").Rule.RuleModule;
10
11
  'no-duplicate-dependencies': import("eslint").Rule.RuleModule;
11
12
  'no-invalid-feature-flag-usage': import("eslint").Rule.RuleModule;
12
13
  'no-pre-post-install-scripts': import("eslint").Rule.RuleModule;
@@ -20,6 +21,7 @@ export declare const rules: {
20
21
  'inline-usage': import("eslint").Rule.RuleModule;
21
22
  'prefer-fg': import("eslint").Rule.RuleModule;
22
23
  'no-alias': import("eslint").Rule.RuleModule;
24
+ 'use-entrypoints-in-examples': import("eslint").Rule.RuleModule;
23
25
  'use-recommended-utils': import("eslint").Rule.RuleModule;
24
26
  };
25
27
  export declare const configs: {
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -7,6 +7,7 @@ export declare const rules: {
7
7
  'ensure-atlassian-team': import("eslint").Rule.RuleModule;
8
8
  'ensure-critical-dependency-resolutions': import("eslint").Rule.RuleModule;
9
9
  'ensure-valid-workspace-protocol-usage': import("eslint").Rule.RuleModule;
10
+ 'ensure-valid-bin-values': import("eslint").Rule.RuleModule;
10
11
  'no-duplicate-dependencies': import("eslint").Rule.RuleModule;
11
12
  'no-invalid-feature-flag-usage': import("eslint").Rule.RuleModule;
12
13
  'no-pre-post-install-scripts': import("eslint").Rule.RuleModule;
@@ -20,6 +21,7 @@ export declare const rules: {
20
21
  'inline-usage': import("eslint").Rule.RuleModule;
21
22
  'prefer-fg': import("eslint").Rule.RuleModule;
22
23
  'no-alias': import("eslint").Rule.RuleModule;
24
+ 'use-entrypoints-in-examples': import("eslint").Rule.RuleModule;
23
25
  'use-recommended-utils': import("eslint").Rule.RuleModule;
24
26
  };
25
27
  export declare const configs: {
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
@@ -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.8.0",
4
+ "version": "0.10.0",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "atlassian": {
7
7
  "team": "UIP Dev Infra",
package/src/index.tsx CHANGED
@@ -10,6 +10,7 @@ import noInvalidFeatureFlagUsage from './rules/no-invalid-feature-flag-usage';
10
10
  import ensureFeatureFlagPrefix from './rules/ensure-feature-flag-prefix';
11
11
  import ensureCriticalDependencyResolutions from './rules/ensure-critical-dependency-resolutions';
12
12
  import ensureValidWorkspaceProtocolUsage from './rules/ensure-valid-workspace-protocol-usage';
13
+ import ensureValidBinValues from './rules/ensure-valid-bin-values';
13
14
  import noInvalidStorybookDecoratorUsage from './rules/no-invalid-storybook-decorator-usage';
14
15
  import ensurePublishValid from './rules/ensure-publish-valid';
15
16
  import ensureNativeAndAfExportsSynced from './rules/ensure-native-and-af-exports-synced';
@@ -20,6 +21,7 @@ import noPreconditioning from './rules/no-preconditioning';
20
21
  import inlineUsage from './rules/inline-usage';
21
22
  import preferFG from './rules/prefer-fg';
22
23
  import noAlias from './rules/no-alias';
24
+ import useEntrypointsInExamples from './rules/use-entrypoints-in-examples';
23
25
  import useRecommendedUtils from './rules/use-recommended-utils';
24
26
 
25
27
  export const rules = {
@@ -30,6 +32,7 @@ export const rules = {
30
32
  'ensure-atlassian-team': ensureAtlassianTeam,
31
33
  'ensure-critical-dependency-resolutions': ensureCriticalDependencyResolutions,
32
34
  'ensure-valid-workspace-protocol-usage': ensureValidWorkspaceProtocolUsage,
35
+ 'ensure-valid-bin-values': ensureValidBinValues,
33
36
  'no-duplicate-dependencies': noDuplicateDependencies,
34
37
  'no-invalid-feature-flag-usage': noInvalidFeatureFlagUsage,
35
38
  'no-pre-post-install-scripts': noPreAndPostInstallScripts,
@@ -43,6 +46,7 @@ export const rules = {
43
46
  'inline-usage': inlineUsage,
44
47
  'prefer-fg': preferFG,
45
48
  'no-alias': noAlias,
49
+ 'use-entrypoints-in-examples': useEntrypointsInExamples,
46
50
  'use-recommended-utils': useRecommendedUtils,
47
51
  };
48
52
 
@@ -0,0 +1,158 @@
1
+ import { tester } from '../../../../__tests__/utils/_tester';
2
+ import rule from '../../index';
3
+
4
+ const cwd = process.cwd();
5
+
6
+ const mockValidBinPaths = [
7
+ `${cwd}/packages/foo/run-ts.bin`,
8
+ `${cwd}/packages/foo/scripts/run-ts.bin`,
9
+ `${cwd}/packages/foo/bar/run-ts.ts`,
10
+ `${cwd}/packages/foo/bar/scripts/run-ts.ts`,
11
+ `${cwd}/packages/baz/run-ts.js`,
12
+ `${cwd}/packages/baz/scripts/run-ts.js`,
13
+ ];
14
+
15
+ jest.mock('fs', () => {
16
+ const actual = jest.requireActual('fs');
17
+ return {
18
+ ...actual,
19
+ statSync: jest.fn((stat: string) => ({
20
+ isFile: jest.fn(() => mockValidBinPaths.includes(stat)),
21
+ })),
22
+ };
23
+ });
24
+
25
+ describe('test ensure-valid-bin-values rule', () => {
26
+ tester.run('ensure-valid-bin-values', rule, {
27
+ valid: [
28
+ // .bin files are valid
29
+ {
30
+ code: `const foo = {
31
+ "bin": {
32
+ "run-ts": "./run-ts.bin",
33
+ }
34
+ }`,
35
+ filename: `${cwd}/packages/foo/package.json`,
36
+ },
37
+ {
38
+ code: `const foo = {
39
+ "bin": {
40
+ "run-ts": "./scripts/run-ts.bin",
41
+ }
42
+ }`,
43
+ filename: `${cwd}/packages/foo/package.json`,
44
+ },
45
+ // .ts files are valid
46
+ {
47
+ code: `const foo = {
48
+ "bin": {
49
+ "run-ts": "./run-ts.ts",
50
+ }
51
+ }`,
52
+ filename: `${cwd}/packages/foo/bar/package.json`,
53
+ },
54
+ {
55
+ code: `const foo = {
56
+ "bin": {
57
+ "run-ts": "./scripts/run-ts.ts",
58
+ }
59
+ }`,
60
+ filename: `${cwd}/packages/foo/bar/package.json`,
61
+ },
62
+ // .js files are valid
63
+ {
64
+ code: `const foo = {
65
+ "bin": {
66
+ "run-ts": "./run-ts.js",
67
+ }
68
+ }`,
69
+ filename: `${cwd}/packages/baz/package.json`,
70
+ },
71
+ {
72
+ code: `const foo = {
73
+ "bin": {
74
+ "run-ts": "./scripts/run-ts.js",
75
+ }
76
+ }`,
77
+ filename: `${cwd}/packages/baz/package.json`,
78
+ },
79
+ // dist paths are valid
80
+ {
81
+ code: `const foo = {
82
+ "bin": {
83
+ "run-ts": "./dist/run-ts.js",
84
+ }
85
+ }`,
86
+ filename: `${cwd}/packages/baz/package.json`,
87
+ },
88
+ ],
89
+ invalid: [
90
+ // Pointing to anything other than a file is invalid
91
+ {
92
+ code: `const foo = {
93
+ "bin": {
94
+ "run-ts": "./bin",
95
+ }
96
+ }`,
97
+ filename: `${cwd}/packages/foo/package.json`,
98
+ errors: [
99
+ {
100
+ messageId: 'invalidBinValue',
101
+ },
102
+ ],
103
+ },
104
+ {
105
+ code: `const foo = {
106
+ "bin": {
107
+ "run-ts": "./scripts/index",
108
+ }
109
+ }`,
110
+ filename: `${cwd}/packages/foo/package.json`,
111
+ errors: [
112
+ {
113
+ messageId: 'invalidBinValue',
114
+ },
115
+ ],
116
+ },
117
+ {
118
+ code: `const foo = {
119
+ "bin": {
120
+ "run-ts": "./scripts/bin",
121
+ }
122
+ }`,
123
+ filename: `${cwd}/packages/foo/package.json`,
124
+ errors: [
125
+ {
126
+ messageId: 'invalidBinValue',
127
+ },
128
+ ],
129
+ },
130
+ {
131
+ code: `const foo = {
132
+ "bin": {
133
+ "run-ts": "./",
134
+ }
135
+ }`,
136
+ filename: `${cwd}/packages/foo/package.json`,
137
+ errors: [
138
+ {
139
+ messageId: 'invalidBinValue',
140
+ },
141
+ ],
142
+ },
143
+ {
144
+ code: `const foo = {
145
+ "bin": {
146
+ "run-ts": "",
147
+ }
148
+ }`,
149
+ filename: `${cwd}/packages/foo/package.json`,
150
+ errors: [
151
+ {
152
+ messageId: 'invalidBinValue',
153
+ },
154
+ ],
155
+ },
156
+ ],
157
+ });
158
+ });
@@ -0,0 +1,70 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
+ import fs from 'fs';
3
+ import { resolve, dirname } from 'path';
4
+ import type { Rule } from 'eslint';
5
+ import type { ObjectExpression } from 'estree';
6
+ import { getObjectPropertyAsObject } from '../util/handle-ast-object';
7
+
8
+ const cwd = process.cwd();
9
+
10
+ function checkIsAllBinValuesAreValid(node: ObjectExpression, packageDir: string) {
11
+ const binObj = getObjectPropertyAsObject(node, 'bin');
12
+
13
+ if (!binObj || !Array.isArray(binObj.properties)) {
14
+ return true;
15
+ }
16
+
17
+ return binObj.properties.every((p) => {
18
+ if (p.type === 'Property' && p.value.type === 'Literal') {
19
+ try {
20
+ const binValue = String(p.value.value);
21
+ const pathToBin = resolve(cwd, packageDir, binValue);
22
+ // Ignore bin values that point to dist as these files don't always exist
23
+ if (binValue.startsWith('./dist/')) {
24
+ return true;
25
+ }
26
+ return fs.statSync(pathToBin).isFile();
27
+ } catch (err) {
28
+ return false;
29
+ }
30
+ }
31
+ // If it's not a property or doesn't have a literal value, consider it invalid
32
+ return false;
33
+ });
34
+ }
35
+
36
+ const rule: Rule.RuleModule = {
37
+ meta: {
38
+ type: 'problem',
39
+ docs: {
40
+ description: `Ensures bin values in package.json files are valid.`,
41
+ recommended: true,
42
+ },
43
+ hasSuggestions: false,
44
+ messages: {
45
+ invalidBinValue: `Invalid bin value. Ensure that the value points to a file and not a directory.`,
46
+ },
47
+ },
48
+ create(context) {
49
+ const fileName = context.getFilename();
50
+ return {
51
+ ObjectExpression: (node: Rule.Node) => {
52
+ if (!fileName.endsWith('package.json') || node.type !== 'ObjectExpression') {
53
+ return;
54
+ }
55
+
56
+ const isAllBinValuesValid = checkIsAllBinValuesAreValid(node, dirname(fileName));
57
+ if (isAllBinValuesValid) {
58
+ return;
59
+ }
60
+
61
+ return context.report({
62
+ node,
63
+ messageId: 'invalidBinValue',
64
+ });
65
+ },
66
+ };
67
+ },
68
+ };
69
+
70
+ export default rule;
@@ -0,0 +1,27 @@
1
+ Using public entrypoints in our examples ensures that they reflect public API.
2
+
3
+ It also has benefits for:
4
+
5
+ - readability
6
+ - bundle and code analysis
7
+
8
+ ## Examples
9
+
10
+ This rule marks imports as violations when they reach into the `src` folder through relative file
11
+ paths.
12
+
13
+ ### Incorrect
14
+
15
+ ```js
16
+ import Button from '../../../src';
17
+
18
+ import { IconButton } from '../../../src/new';
19
+ ```
20
+
21
+ ### Correct
22
+
23
+ ```js
24
+ import Button from '@atlaskit/button';
25
+
26
+ import { ExampleHelper } from '../not-src';
27
+ ```
@@ -0,0 +1,34 @@
1
+ import { tester } from '../../../__tests__/utils/_tester';
2
+ import rule from '../index';
3
+
4
+ const exampleFilename = 'packages/design-system/button/examples/0-basic.tsx';
5
+ const nonExampleFilename = 'packages/design-system/button/scripts/my-script.tsx';
6
+
7
+ tester.run('use-entrypoints-in-examples', rule, {
8
+ valid: [
9
+ {
10
+ code: `import Button from '@atlaskit/button';`,
11
+ filename: exampleFilename,
12
+ },
13
+ {
14
+ code: `import { ExampleHelper } from '../not-src';`,
15
+ filename: exampleFilename,
16
+ },
17
+ {
18
+ code: `import Button from '../src';`,
19
+ filename: nonExampleFilename,
20
+ },
21
+ ],
22
+ invalid: [
23
+ {
24
+ code: `import Button from '../../../src';`,
25
+ filename: exampleFilename,
26
+ errors: [{ messageId: 'useEntrypointsInExamples' }],
27
+ },
28
+ {
29
+ code: `import { IconButton } from '../../../src/new';`,
30
+ filename: exampleFilename,
31
+ errors: [{ messageId: 'useEntrypointsInExamples' }],
32
+ },
33
+ ],
34
+ });
@@ -0,0 +1,43 @@
1
+ import type { Rule } from 'eslint';
2
+
3
+ const rule: Rule.RuleModule = {
4
+ meta: {
5
+ docs: {
6
+ url: 'https://stash.atlassian.com/projects/ATLASSIAN/repos/atlassian-frontend-monorepo/browse/platform/packages/platform/eslint-plugin/src/rules/use-entrypoints-in-examples/README.md',
7
+ description: 'Encourage usage of package entrypoints in examples.',
8
+ },
9
+ messages: {
10
+ useEntrypointsInExamples:
11
+ 'Use the package entrypoints instead of importing from src. This ensures examples reflect public API.\n\nFor example, use `@atlaskit/button/new` instead of `../../src/new`',
12
+ },
13
+ type: 'problem',
14
+ },
15
+ create(context) {
16
+ /**
17
+ * Even if it's enabled on non-example files it will ignore them.
18
+ *
19
+ * This is a defensive check, the rule should be configured to only run on examples.
20
+ */
21
+ if (!context.filename.includes('/examples/')) {
22
+ return {};
23
+ }
24
+
25
+ return {
26
+ ImportDeclaration(node) {
27
+ const moduleName = node.source.value;
28
+ if (typeof moduleName !== 'string') {
29
+ return;
30
+ }
31
+
32
+ if (/^(\.\.\/)+src(\/|$)/.test(moduleName)) {
33
+ context.report({
34
+ node: node.source,
35
+ messageId: 'useEntrypointsInExamples',
36
+ });
37
+ }
38
+ },
39
+ };
40
+ },
41
+ };
42
+
43
+ export default rule;