@atlaskit/eslint-plugin-platform 0.0.3

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 ADDED
@@ -0,0 +1,13 @@
1
+ # @atlaskit/eslint-plugin-platform
2
+
3
+ ## 0.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [`11706c3e7c5`](https://bitbucket.org/atlassian/atlassian-frontend/commits/11706c3e7c5) - Publish platform eslint rules to npm to be consumed in other products
8
+
9
+ ## 0.0.2
10
+
11
+ ### Patch Changes
12
+
13
+ - [`85dc0230439`](https://bitbucket.org/atlassian/atlassian-frontend/commits/85dc0230439) - Add eslint rule to allow for platform feature flag usage
package/README.md ADDED
@@ -0,0 +1,6 @@
1
+ # Atlassian platform ESLint Plugin
2
+
3
+ This plugin contains eslint plugins that are to be used when working on Atlassian platform
4
+
5
+ ## Installation
6
+ Not intended for use outside the Atlassian frontend repository.
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "../tsconfig",
3
+ "compilerOptions": {
4
+ "target": "es5",
5
+ "paths": {}
6
+ },
7
+ "include": [
8
+ "../src/**/*.ts",
9
+ "../src/**/*.tsx"
10
+ ],
11
+ "exclude": [
12
+ "../src/**/__tests__/*",
13
+ "../__tests__/*",
14
+ "../src/**/*.test.*",
15
+ "../src/**/test.*"
16
+ ]
17
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.rules = exports.configs = void 0;
8
+ var _ensureFeatureFlagRegistration = _interopRequireDefault(require("./rules/ensure-feature-flag-registration"));
9
+ var rules = {
10
+ 'ensure-feature-flag-registration': _ensureFeatureFlagRegistration.default
11
+ };
12
+ exports.rules = rules;
13
+ var configs = {
14
+ recommended: {
15
+ plugins: ['@atlaskit/platform'],
16
+ rules: {
17
+ '@atlaskit/platform/ensure-feature-flag-registration': 'error'
18
+ }
19
+ }
20
+ };
21
+ exports.configs = configs;
@@ -0,0 +1,102 @@
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 _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
+ var _readPkgUp = _interopRequireDefault(require("read-pkg-up"));
10
+ var _path = _interopRequireDefault(require("path"));
11
+ // make sure we cache reading the package.json so we don't end up reading it for every instance of this rule.
12
+ var pkgJsonCache = new Map();
13
+
14
+ // get the ancestor package.json for a given file
15
+ var getPackageJsonForFileName = function getPackageJsonForFileName(filename) {
16
+ var splitFilename = filename.split(_path.default.sep);
17
+ for (var i = 0; i < splitFilename.length; i++) {
18
+ // attempt to search using the filename in the cache to see if we've read the package.json for a sibling file before
19
+ var searchPath = _path.default.join.apply(_path.default, (0, _toConsumableArray2.default)(splitFilename.splice(0, i)));
20
+ var cachedPkgJson = pkgJsonCache.get(searchPath);
21
+ if (cachedPkgJson) {
22
+ return cachedPkgJson;
23
+ }
24
+ }
25
+ var _ref = _readPkgUp.default.sync({
26
+ cwd: filename,
27
+ normalize: false
28
+ }),
29
+ packageJson = _ref.packageJson,
30
+ pkgJsonPath = _ref.path;
31
+ pkgJsonCache.set(pkgJsonPath, packageJson);
32
+ return packageJson;
33
+ };
34
+ var rule = {
35
+ meta: {
36
+ hasSuggestions: false,
37
+ docs: {
38
+ recommended: false
39
+ },
40
+ type: 'problem',
41
+ messages: {
42
+ onlyInlineIf: "Only call feature flags as part of an expression, don't assign to a variable! See http://go/pff-eslint for more details",
43
+ onlyStringLiteral: "Only get feature flags by string literal, don't use variables! See http://go/pff-eslint for more details",
44
+ registrationSectionMissing: 'Please add a "platform-feature-flags" section to your package.json! See http://go/pff-eslint for more details',
45
+ featureFlagMissing: "Please add a \"{{ featureFlag }}\" section to the \"platform-feature-flags\" section in your package.json. See http://go/pff-eslint for more details"
46
+ }
47
+ },
48
+ create: function create(context) {
49
+ return {
50
+ 'CallExpression[callee.name=/getBooleanFF/]': function CallExpressionCalleeNameGetBooleanFF(node) {
51
+ // to make typescript happy
52
+ if (node.type === 'CallExpression') {
53
+ var _node$parent;
54
+ var args = node.arguments;
55
+ if (args.length === 1 && args[0].type !== 'Literal') {
56
+ return context.report({
57
+ node: node,
58
+ messageId: 'onlyStringLiteral'
59
+ });
60
+ }
61
+ switch ((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.type) {
62
+ case 'IfStatement':
63
+ case 'ConditionalExpression':
64
+ case 'LogicalExpression':
65
+ break;
66
+ default:
67
+ return context.report({
68
+ node: node,
69
+ messageId: 'onlyInlineIf'
70
+ });
71
+ break;
72
+ }
73
+ var filename = context.getFilename();
74
+ var packageJson = getPackageJsonForFileName(filename);
75
+ var platformFeatureFlags = packageJson['platform-feature-flags'];
76
+ if (!platformFeatureFlags) {
77
+ return context.report({
78
+ node: node,
79
+ messageId: 'registrationSectionMissing'
80
+ });
81
+ }
82
+ if (args.length === 1 && args[0].type === 'Literal' && args[0].raw) {
83
+ var featureFlag = args[0].value;
84
+ var featureFlagRegistration = platformFeatureFlags[featureFlag];
85
+ if (!featureFlagRegistration) {
86
+ return context.report({
87
+ node: node,
88
+ messageId: 'featureFlagMissing',
89
+ data: {
90
+ featureFlag: featureFlag
91
+ }
92
+ });
93
+ }
94
+ }
95
+ }
96
+ return {};
97
+ }
98
+ };
99
+ }
100
+ };
101
+ var _default = rule;
102
+ exports.default = _default;
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@atlaskit/eslint-plugin-platform",
3
+ "version": "0.0.3",
4
+ "sideEffects": false
5
+ }
@@ -0,0 +1,12 @@
1
+ import ensureFeatureFlagRegistration from './rules/ensure-feature-flag-registration';
2
+ export const rules = {
3
+ 'ensure-feature-flag-registration': ensureFeatureFlagRegistration
4
+ };
5
+ export const configs = {
6
+ recommended: {
7
+ plugins: ['@atlaskit/platform'],
8
+ rules: {
9
+ '@atlaskit/platform/ensure-feature-flag-registration': 'error'
10
+ }
11
+ }
12
+ };
@@ -0,0 +1,94 @@
1
+ import readPkgUp from 'read-pkg-up';
2
+ import path from 'path';
3
+ // make sure we cache reading the package.json so we don't end up reading it for every instance of this rule.
4
+ const pkgJsonCache = new Map();
5
+
6
+ // get the ancestor package.json for a given file
7
+ const getPackageJsonForFileName = filename => {
8
+ const splitFilename = filename.split(path.sep);
9
+ for (let i = 0; i < splitFilename.length; i++) {
10
+ // attempt to search using the filename in the cache to see if we've read the package.json for a sibling file before
11
+ const searchPath = path.join(...splitFilename.splice(0, i));
12
+ const cachedPkgJson = pkgJsonCache.get(searchPath);
13
+ if (cachedPkgJson) {
14
+ return cachedPkgJson;
15
+ }
16
+ }
17
+ const {
18
+ packageJson,
19
+ path: pkgJsonPath
20
+ } = readPkgUp.sync({
21
+ cwd: filename,
22
+ normalize: false
23
+ });
24
+ pkgJsonCache.set(pkgJsonPath, packageJson);
25
+ return packageJson;
26
+ };
27
+ const rule = {
28
+ meta: {
29
+ hasSuggestions: false,
30
+ docs: {
31
+ recommended: false
32
+ },
33
+ type: 'problem',
34
+ messages: {
35
+ onlyInlineIf: "Only call feature flags as part of an expression, don't assign to a variable! See http://go/pff-eslint for more details",
36
+ onlyStringLiteral: "Only get feature flags by string literal, don't use variables! See http://go/pff-eslint for more details",
37
+ registrationSectionMissing: 'Please add a "platform-feature-flags" section to your package.json! See http://go/pff-eslint for more details',
38
+ featureFlagMissing: `Please add a "{{ featureFlag }}" section to the "platform-feature-flags" section in your package.json. See http://go/pff-eslint for more details`
39
+ }
40
+ },
41
+ create(context) {
42
+ return {
43
+ 'CallExpression[callee.name=/getBooleanFF/]': node => {
44
+ // to make typescript happy
45
+ if (node.type === 'CallExpression') {
46
+ var _node$parent;
47
+ const args = node.arguments;
48
+ if (args.length === 1 && args[0].type !== 'Literal') {
49
+ return context.report({
50
+ node,
51
+ messageId: 'onlyStringLiteral'
52
+ });
53
+ }
54
+ switch ((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.type) {
55
+ case 'IfStatement':
56
+ case 'ConditionalExpression':
57
+ case 'LogicalExpression':
58
+ break;
59
+ default:
60
+ return context.report({
61
+ node,
62
+ messageId: 'onlyInlineIf'
63
+ });
64
+ break;
65
+ }
66
+ const filename = context.getFilename();
67
+ const packageJson = getPackageJsonForFileName(filename);
68
+ const platformFeatureFlags = packageJson['platform-feature-flags'];
69
+ if (!platformFeatureFlags) {
70
+ return context.report({
71
+ node,
72
+ messageId: 'registrationSectionMissing'
73
+ });
74
+ }
75
+ if (args.length === 1 && args[0].type === 'Literal' && args[0].raw) {
76
+ const featureFlag = args[0].value;
77
+ const featureFlagRegistration = platformFeatureFlags[featureFlag];
78
+ if (!featureFlagRegistration) {
79
+ return context.report({
80
+ node,
81
+ messageId: 'featureFlagMissing',
82
+ data: {
83
+ featureFlag
84
+ }
85
+ });
86
+ }
87
+ }
88
+ }
89
+ return {};
90
+ }
91
+ };
92
+ }
93
+ };
94
+ export default rule;
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@atlaskit/eslint-plugin-platform",
3
+ "version": "0.0.3",
4
+ "sideEffects": false
5
+ }
@@ -0,0 +1,12 @@
1
+ import ensureFeatureFlagRegistration from './rules/ensure-feature-flag-registration';
2
+ export var rules = {
3
+ 'ensure-feature-flag-registration': ensureFeatureFlagRegistration
4
+ };
5
+ export var configs = {
6
+ recommended: {
7
+ plugins: ['@atlaskit/platform'],
8
+ rules: {
9
+ '@atlaskit/platform/ensure-feature-flag-registration': 'error'
10
+ }
11
+ }
12
+ };
@@ -0,0 +1,94 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
+ import readPkgUp from 'read-pkg-up';
3
+ import path from 'path';
4
+ // make sure we cache reading the package.json so we don't end up reading it for every instance of this rule.
5
+ var pkgJsonCache = new Map();
6
+
7
+ // get the ancestor package.json for a given file
8
+ var getPackageJsonForFileName = function getPackageJsonForFileName(filename) {
9
+ var splitFilename = filename.split(path.sep);
10
+ for (var i = 0; i < splitFilename.length; i++) {
11
+ // attempt to search using the filename in the cache to see if we've read the package.json for a sibling file before
12
+ var searchPath = path.join.apply(path, _toConsumableArray(splitFilename.splice(0, i)));
13
+ var cachedPkgJson = pkgJsonCache.get(searchPath);
14
+ if (cachedPkgJson) {
15
+ return cachedPkgJson;
16
+ }
17
+ }
18
+ var _ref = readPkgUp.sync({
19
+ cwd: filename,
20
+ normalize: false
21
+ }),
22
+ packageJson = _ref.packageJson,
23
+ pkgJsonPath = _ref.path;
24
+ pkgJsonCache.set(pkgJsonPath, packageJson);
25
+ return packageJson;
26
+ };
27
+ var rule = {
28
+ meta: {
29
+ hasSuggestions: false,
30
+ docs: {
31
+ recommended: false
32
+ },
33
+ type: 'problem',
34
+ messages: {
35
+ onlyInlineIf: "Only call feature flags as part of an expression, don't assign to a variable! See http://go/pff-eslint for more details",
36
+ onlyStringLiteral: "Only get feature flags by string literal, don't use variables! See http://go/pff-eslint for more details",
37
+ registrationSectionMissing: 'Please add a "platform-feature-flags" section to your package.json! See http://go/pff-eslint for more details',
38
+ featureFlagMissing: "Please add a \"{{ featureFlag }}\" section to the \"platform-feature-flags\" section in your package.json. See http://go/pff-eslint for more details"
39
+ }
40
+ },
41
+ create: function create(context) {
42
+ return {
43
+ 'CallExpression[callee.name=/getBooleanFF/]': function CallExpressionCalleeNameGetBooleanFF(node) {
44
+ // to make typescript happy
45
+ if (node.type === 'CallExpression') {
46
+ var _node$parent;
47
+ var args = node.arguments;
48
+ if (args.length === 1 && args[0].type !== 'Literal') {
49
+ return context.report({
50
+ node: node,
51
+ messageId: 'onlyStringLiteral'
52
+ });
53
+ }
54
+ switch ((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.type) {
55
+ case 'IfStatement':
56
+ case 'ConditionalExpression':
57
+ case 'LogicalExpression':
58
+ break;
59
+ default:
60
+ return context.report({
61
+ node: node,
62
+ messageId: 'onlyInlineIf'
63
+ });
64
+ break;
65
+ }
66
+ var filename = context.getFilename();
67
+ var packageJson = getPackageJsonForFileName(filename);
68
+ var platformFeatureFlags = packageJson['platform-feature-flags'];
69
+ if (!platformFeatureFlags) {
70
+ return context.report({
71
+ node: node,
72
+ messageId: 'registrationSectionMissing'
73
+ });
74
+ }
75
+ if (args.length === 1 && args[0].type === 'Literal' && args[0].raw) {
76
+ var featureFlag = args[0].value;
77
+ var featureFlagRegistration = platformFeatureFlags[featureFlag];
78
+ if (!featureFlagRegistration) {
79
+ return context.report({
80
+ node: node,
81
+ messageId: 'featureFlagMissing',
82
+ data: {
83
+ featureFlag: featureFlag
84
+ }
85
+ });
86
+ }
87
+ }
88
+ }
89
+ return {};
90
+ }
91
+ };
92
+ }
93
+ };
94
+ export default rule;
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@atlaskit/eslint-plugin-platform",
3
+ "version": "0.0.3",
4
+ "sideEffects": false
5
+ }
@@ -0,0 +1,11 @@
1
+ export declare const rules: {
2
+ 'ensure-feature-flag-registration': import("eslint").Rule.RuleModule;
3
+ };
4
+ export declare const configs: {
5
+ recommended: {
6
+ plugins: string[];
7
+ rules: {
8
+ '@atlaskit/platform/ensure-feature-flag-registration': string;
9
+ };
10
+ };
11
+ };
@@ -0,0 +1,3 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
package/index.js ADDED
@@ -0,0 +1,24 @@
1
+ /* eslint-disable import/no-extraneous-dependencies */
2
+ /* eslint-disable global-require */
3
+ // Used only for internal repo usage.
4
+ const path = require('path');
5
+
6
+ const paths = require('tsconfig-paths');
7
+
8
+ if (!require.extensions['.ts']) {
9
+ // ts-node can only handle being registered once, see https://github.com/TypeStrong/ts-node/issues/409
10
+ require('ts-node').register({
11
+ project: path.join(__dirname, 'tsconfig.json'),
12
+ });
13
+ }
14
+
15
+ try {
16
+ // We programatically register tsconfig paths here so it picks up the tsconfig here
17
+ // instead of in root CWD.
18
+ paths.register(paths.loadConfig(__dirname));
19
+ } catch (e) {
20
+ // eslint-disable-next-line no-console
21
+ console.log(e);
22
+ }
23
+
24
+ module.exports = require('./src/index');
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@atlaskit/eslint-plugin-platform",
3
+ "description": "The essential plugin for use with Atlassian frontend platform tools",
4
+ "version": "0.0.3",
5
+ "author": "Atlassian Pty Ltd",
6
+ "atlassian": {
7
+ "team": "UIP - Platform Integration Trust (PITa)",
8
+ "releaseModel": "continuous",
9
+ "inPublicMirror": true
10
+ },
11
+ "publishConfig": {
12
+ "registry": "https://registry.npmjs.org/"
13
+ },
14
+ "repository": "https://bitbucket.org/atlassian/atlassian-frontend-mirror",
15
+ "main": "dist/cjs/index.js",
16
+ "module": "dist/esm/index.js",
17
+ "module:es2019": "dist/es2019/index.js",
18
+ "types": "dist/types/index.d.ts",
19
+ "sideEffects": false,
20
+ "atlaskit:src": "src/index.tsx",
21
+ "af:exports": {
22
+ ".": "./src/index.tsx"
23
+ },
24
+ "dependencies": {
25
+ "@babel/runtime": "^7.0.0",
26
+ "read-pkg-up": "^7.0.1"
27
+ },
28
+ "devDependencies": {
29
+ "@atlassian/atlassian-frontend-prettier-config-1.0.1": "npm:@atlassian/atlassian-frontend-prettier-config@1.0.1",
30
+ "eslint": "^7.7.0",
31
+ "tsconfig-paths": "^3.9.0"
32
+ },
33
+ "prettier": "@atlassian/atlassian-frontend-prettier-config-1.0.1"
34
+ }
package/report.api.md ADDED
@@ -0,0 +1,48 @@
1
+ <!-- API Report Version: 2.3 -->
2
+
3
+ ## API Report File for "@atlaskit/eslint-plugin-platform"
4
+
5
+ > Do not edit this file. This report is auto-generated using [API Extractor](https://api-extractor.com/).
6
+ > [Learn more about API reports](https://hello.atlassian.net/wiki/spaces/UR/pages/1825484529/Package+API+Reports)
7
+
8
+ ### Table of contents
9
+
10
+ - [Main Entry Types](#main-entry-types)
11
+ - [Peer Dependencies](#peer-dependencies)
12
+
13
+ ### Main Entry Types
14
+
15
+ <!--SECTION START: Main Entry Types-->
16
+
17
+ ```ts
18
+ import { Rule } from 'eslint';
19
+
20
+ // @public (undocumented)
21
+ export const configs: {
22
+ recommended: {
23
+ plugins: string[];
24
+ rules: {
25
+ '@atlaskit/platform/ensure-feature-flag-registration': string;
26
+ };
27
+ };
28
+ };
29
+
30
+ // @public (undocumented)
31
+ export const rules: {
32
+ 'ensure-feature-flag-registration': Rule.RuleModule;
33
+ };
34
+
35
+ // (No @packageDocumentation comment for this package)
36
+ ```
37
+
38
+ <!--SECTION END: Main Entry Types-->
39
+
40
+ ### Peer Dependencies
41
+
42
+ <!--SECTION START: Peer Dependencies-->
43
+
44
+ ```json
45
+ {}
46
+ ```
47
+
48
+ <!--SECTION END: Peer Dependencies-->
@@ -0,0 +1,25 @@
1
+ /* eslint-disable @repo/internal/fs/filename-pattern-match */
2
+ /* eslint-disable no-undef */
3
+ import { RuleTester } from 'eslint';
4
+
5
+ (RuleTester as any).describe = (text: string, method: Function) => {
6
+ const origHasAssertions = expect.hasAssertions;
7
+ describe(text, () => {
8
+ beforeAll(() => {
9
+ // Stub out expect.hasAssertions beforeEach from jest-presetup.js
10
+ expect.hasAssertions = () => {};
11
+ });
12
+ afterAll(() => {
13
+ expect.hasAssertions = origHasAssertions;
14
+ });
15
+
16
+ method();
17
+ });
18
+ };
19
+ export const tester = new RuleTester({
20
+ parser: require.resolve('babel-eslint'),
21
+ parserOptions: {
22
+ ecmaVersion: 6,
23
+ sourceType: 'module',
24
+ },
25
+ });
package/src/index.tsx ADDED
@@ -0,0 +1,14 @@
1
+ import ensureFeatureFlagRegistration from './rules/ensure-feature-flag-registration';
2
+
3
+ export const rules = {
4
+ 'ensure-feature-flag-registration': ensureFeatureFlagRegistration,
5
+ };
6
+
7
+ export const configs = {
8
+ recommended: {
9
+ plugins: ['@atlaskit/platform'],
10
+ rules: {
11
+ '@atlaskit/platform/ensure-feature-flag-registration': 'error',
12
+ },
13
+ },
14
+ };
@@ -0,0 +1,87 @@
1
+ import { tester } from '../../../../__tests__/utils/_tester';
2
+ import rule from '../../index';
3
+ import { PackageJson } from 'read-pkg-up';
4
+
5
+ let mockPath = 'test/package.json';
6
+
7
+ let mockPackageJson: PackageJson = {
8
+ 'platform-feature-flags': {
9
+ 'test-flag': {
10
+ type: 'boolean',
11
+ },
12
+ },
13
+ };
14
+ jest.mock('read-pkg-up', () => ({
15
+ sync: () => ({
16
+ path: mockPath,
17
+ packageJson: mockPackageJson,
18
+ }),
19
+ }));
20
+
21
+ describe('with existing platform-feature-flags section', () => {
22
+ beforeEach(() => {
23
+ mockPath = 'test/package.json';
24
+
25
+ mockPackageJson = {
26
+ 'platform-feature-flags': {
27
+ 'test-flag': {
28
+ type: 'boolean',
29
+ },
30
+ },
31
+ };
32
+ });
33
+
34
+ tester.run('ensure-feature-flag-registration', rule, {
35
+ valid: [
36
+ {
37
+ // IfStatement
38
+ code: `if(getBooleanFF('test-flag')) { }`,
39
+ },
40
+ {
41
+ // ConditionalExpression
42
+ code: `const val = getBooleanFF('test-flag') ? 'yay' : 'no';`,
43
+ },
44
+ {
45
+ // LogicalExpression
46
+ code: `const val = 100 + (getBooleanFF('test-flag') && 50 || 10);`,
47
+ },
48
+ ],
49
+ invalid: [
50
+ {
51
+ code: `getBooleanFF('test-flag')`,
52
+ errors: [{ messageId: 'onlyInlineIf' }],
53
+ },
54
+ {
55
+ code: `const val = getBooleanFF('test-flag')`,
56
+ errors: [{ messageId: 'onlyInlineIf' }],
57
+ },
58
+ {
59
+ code: `if(getBooleanFF('invalid-flag')) { }`,
60
+ errors: [{ messageId: 'featureFlagMissing' }],
61
+ },
62
+ {
63
+ code: `const ff = "test-flag"; if(getBooleanFF(ff)) { }`,
64
+ errors: [{ messageId: 'onlyStringLiteral' }],
65
+ },
66
+ ],
67
+ });
68
+ });
69
+
70
+ describe('with missing platform-feature-flags section', () => {
71
+ beforeEach(() => {
72
+ // change path to bust cache
73
+ mockPath = 'invalid-pkg/package.json';
74
+ mockPackageJson = {};
75
+ });
76
+
77
+ tester.run('ensure-feature-flag-registration', rule, {
78
+ valid: [],
79
+ invalid: [
80
+ {
81
+ filename: 'other-directory/index.ts',
82
+ code: `if(getBooleanFF('test-flag')) { }`,
83
+ errors: [{ messageId: 'registrationSectionMissing' }],
84
+ },
85
+ ],
86
+ });
87
+ });
@@ -0,0 +1,115 @@
1
+ import type { Rule } from 'eslint';
2
+ import readPkgUp from 'read-pkg-up';
3
+ import path from 'path';
4
+
5
+ type PlatformFeatureFlagRegistrationSection = {
6
+ [key: string]: {
7
+ type: 'boolean';
8
+ };
9
+ };
10
+
11
+ // 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, readPkgUp.PackageJson>();
13
+
14
+ // get the ancestor package.json for a given file
15
+ const getPackageJsonForFileName = (filename: string): readPkgUp.PackageJson => {
16
+ const splitFilename = filename.split(path.sep);
17
+ for (let i = 0; i < splitFilename.length; i++) {
18
+ // attempt to search using the filename in the cache to see if we've read the package.json for a sibling file before
19
+ const searchPath = path.join(...splitFilename.splice(0, i));
20
+ const cachedPkgJson = pkgJsonCache.get(searchPath);
21
+
22
+ if (cachedPkgJson) {
23
+ return cachedPkgJson;
24
+ }
25
+ }
26
+
27
+ const { packageJson, path: pkgJsonPath } = readPkgUp.sync({
28
+ cwd: filename,
29
+ normalize: false,
30
+ })!;
31
+
32
+ pkgJsonCache.set(pkgJsonPath, packageJson);
33
+ return packageJson;
34
+ };
35
+
36
+ const rule: Rule.RuleModule = {
37
+ meta: {
38
+ hasSuggestions: false,
39
+ docs: {
40
+ recommended: false,
41
+ },
42
+ type: 'problem',
43
+ messages: {
44
+ onlyInlineIf:
45
+ "Only call feature flags as part of an expression, don't assign to a variable! See http://go/pff-eslint for more details",
46
+ onlyStringLiteral:
47
+ "Only get feature flags by string literal, don't use variables! See http://go/pff-eslint for more details",
48
+ registrationSectionMissing:
49
+ 'Please add a "platform-feature-flags" section to your package.json! See http://go/pff-eslint for more details',
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
+ },
52
+ },
53
+ create(context) {
54
+ return {
55
+ 'CallExpression[callee.name=/getBooleanFF/]': (node: Rule.Node) => {
56
+ // to make typescript happy
57
+ if (node.type === 'CallExpression') {
58
+ const args = node.arguments;
59
+
60
+ if (args.length === 1 && args[0].type !== 'Literal') {
61
+ return context.report({
62
+ node,
63
+ messageId: 'onlyStringLiteral',
64
+ });
65
+ }
66
+
67
+ switch (node.parent?.type) {
68
+ case 'IfStatement':
69
+ case 'ConditionalExpression':
70
+ case 'LogicalExpression':
71
+ break;
72
+ default:
73
+ return context.report({
74
+ node,
75
+ messageId: 'onlyInlineIf',
76
+ });
77
+ break;
78
+ }
79
+
80
+ const filename = context.getFilename();
81
+ const packageJson = getPackageJsonForFileName(filename);
82
+ const platformFeatureFlags = packageJson[
83
+ 'platform-feature-flags'
84
+ ] as PlatformFeatureFlagRegistrationSection;
85
+
86
+ if (!platformFeatureFlags) {
87
+ return context.report({
88
+ node,
89
+ messageId: 'registrationSectionMissing',
90
+ });
91
+ }
92
+
93
+ if (args.length === 1 && args[0].type === 'Literal' && args[0].raw) {
94
+ const featureFlag = args[0].value as string;
95
+ const featureFlagRegistration = platformFeatureFlags[featureFlag];
96
+
97
+ if (!featureFlagRegistration) {
98
+ return context.report({
99
+ node,
100
+ messageId: 'featureFlagMissing',
101
+ data: {
102
+ featureFlag,
103
+ },
104
+ });
105
+ }
106
+ }
107
+ }
108
+
109
+ return {};
110
+ },
111
+ };
112
+ },
113
+ };
114
+
115
+ export default rule;
@@ -0,0 +1,26 @@
1
+ ## API Report File for "@atlaskit/eslint-plugin-platform"
2
+
3
+ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4
+
5
+ ```ts
6
+
7
+ import { Rule } from 'eslint';
8
+
9
+ // @public (undocumented)
10
+ export const configs: {
11
+ recommended: {
12
+ plugins: string[];
13
+ rules: {
14
+ '@atlaskit/platform/ensure-feature-flag-registration': string;
15
+ };
16
+ };
17
+ };
18
+
19
+ // @public (undocumented)
20
+ export const rules: {
21
+ 'ensure-feature-flag-registration': Rule.RuleModule;
22
+ };
23
+
24
+ // (No @packageDocumentation comment for this package)
25
+
26
+ ```
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "extends": "../../../tsconfig.node.json",
3
+ "include": [
4
+ "__tests__/**/*.ts",
5
+ "__tests__/**/*.tsx",
6
+ "docs/**/*.ts",
7
+ "docs/**/*.tsx",
8
+ "examples/**/*.ts",
9
+ "examples/**/*.tsx",
10
+ "src/**/*.ts",
11
+ "src/**/*.tsx",
12
+ "scripts"
13
+ ],
14
+ "compilerOptions": {
15
+ "baseUrl": "./",
16
+ "lib": ["ES2021.String"]
17
+ },
18
+ "ts-node": {
19
+ // It is faster to skip typechecking.
20
+ "transpileOnly": true,
21
+ // We must load files for third party defs - https://typestrong.org/ts-node/docs/types
22
+ "files": true,
23
+ }
24
+ }