Skip to content

Commit fecdba7

Browse files
Spencerspalgerkibanamachine
authored
[eslint] add rule to prevent export* in plugin index files (elastic#109357)
* [eslint] add rule to prevent export* in plugin index files * deduplicate export names for types/instances with the same name * attempt to auto-fix duplicate exports too * capture exported enums too * enforce no_export_all for core too * disable rule by default, allow opting-in for help fixing * update tests * reduce yarn.lock duplication * add rule but no fixes * disable all existing violations * update api docs with new line numbers * revert unnecessary changes to yarn.lock which only had drawbacks * remove unnecessary eslint-disable * rework codegen to split type exports and use babel to generate valid code * check for "export types" deeply * improve test by using fixtures * add comments to some helper functions * disable fix for namespace exports including types * label all eslint-disable comments with related team-specific issue * ensure that child exports of `export type` are always tracked as types Co-authored-by: spalger <spalger@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent 4f0a63f commit fecdba7

File tree

89 files changed

+792
-4
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+792
-4
lines changed

.eslintrc.js

+19
Original file line numberDiff line numberDiff line change
@@ -1656,5 +1656,24 @@ module.exports = {
16561656
'@typescript-eslint/prefer-ts-expect-error': 'error',
16571657
},
16581658
},
1659+
1660+
/**
1661+
* Disallow `export *` syntax in plugin/core public/server/common index files and instead
1662+
* require that plugins/core explicitly export the APIs that should be accessible outside the plugin.
1663+
*
1664+
* To add your plugin to this list just update the relevant glob with the name of your plugin
1665+
*/
1666+
{
1667+
files: [
1668+
'src/core/{server,public,common}/index.ts',
1669+
'src/plugins/*/{server,public,common}/index.ts',
1670+
'src/plugins/*/*/{server,public,common}/index.ts',
1671+
'x-pack/plugins/*/{server,public,common}/index.ts',
1672+
'x-pack/plugins/*/*/{server,public,common}/index.ts',
1673+
],
1674+
rules: {
1675+
'@kbn/eslint/no_export_all': 'error',
1676+
},
1677+
},
16591678
],
16601679
};

packages/kbn-eslint-plugin-eslint/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ PKG_REQUIRE_NAME = "@kbn/eslint-plugin-eslint"
66
SOURCE_FILES = glob(
77
[
88
"rules/**/*.js",
9+
"helpers/**/*.js",
910
"index.js",
1011
"lib.js",
1112
],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
/* eslint-disable no-restricted-syntax */
10+
11+
export class Bar {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
/* eslint-disable no-restricted-syntax */
10+
11+
export const one = 1;
12+
export const two = 2;
13+
export const three = 3;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
/* eslint-disable no-restricted-syntax */
10+
11+
export type { Bar as ReexportedClass } from './bar';
12+
13+
export const someConst = 'bar';
14+
15+
// eslint-disable-next-line prefer-const
16+
export let someLet = 'bar';
17+
18+
export function someFunction() {}
19+
20+
export class SomeClass {}
21+
22+
export interface SomeInterface {
23+
prop: number;
24+
}
25+
26+
export enum SomeEnum {
27+
a = 'a',
28+
b = 'b',
29+
}
30+
31+
export type TypeAlias = string[];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
/* eslint-disable no-restricted-syntax */
10+
11+
export * from './foo';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
const t = require('@babel/types');
10+
const { default: generate } = require('@babel/generator');
11+
12+
/** @typedef {import('./export_set').ExportSet} ExportSet */
13+
14+
/**
15+
* Generate code for replacing a `export * from './path'`, ie.
16+
*
17+
* export type { foo } from './path'
18+
* export { bar } from './path'
19+
20+
* @param {ExportSet} exportSet
21+
* @param {string} source
22+
*/
23+
const getExportCode = (exportSet, source) => {
24+
const exportedTypes = exportSet.types.size
25+
? t.exportNamedDeclaration(
26+
undefined,
27+
Array.from(exportSet.types).map((n) => t.exportSpecifier(t.identifier(n), t.identifier(n))),
28+
t.stringLiteral(source)
29+
)
30+
: undefined;
31+
32+
if (exportedTypes) {
33+
exportedTypes.exportKind = 'type';
34+
}
35+
36+
const exportedValues = exportSet.values.size
37+
? t.exportNamedDeclaration(
38+
undefined,
39+
Array.from(exportSet.values).map((n) =>
40+
t.exportSpecifier(t.identifier(n), t.identifier(n))
41+
),
42+
t.stringLiteral(source)
43+
)
44+
: undefined;
45+
46+
return generate(t.program([exportedTypes, exportedValues].filter(Boolean))).code;
47+
};
48+
49+
/**
50+
* Generate code for replacing a `export * as name from './path'`, ie.
51+
*
52+
* import { foo, bar } from './path'
53+
* export const name = { foo, bar }
54+
*
55+
* @param {string} nsName
56+
* @param {string[]} exportNames
57+
* @param {string} source
58+
*/
59+
const getExportNamedNamespaceCode = (nsName, exportNames, source) => {
60+
return generate(
61+
t.program([
62+
t.importDeclaration(
63+
exportNames.map((n) => t.importSpecifier(t.identifier(n), t.identifier(n))),
64+
t.stringLiteral(source)
65+
),
66+
t.exportNamedDeclaration(
67+
t.variableDeclaration('const', [
68+
t.variableDeclarator(
69+
t.identifier(nsName),
70+
t.objectExpression(
71+
exportNames.map((n) =>
72+
t.objectProperty(t.identifier(n), t.identifier(n), false, true)
73+
)
74+
)
75+
),
76+
])
77+
),
78+
])
79+
).code;
80+
};
81+
82+
module.exports = { getExportCode, getExportNamedNamespaceCode };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
/**
10+
* Helper class to collect exports of different types, either "value" exports or "type" exports
11+
*/
12+
class ExportSet {
13+
constructor() {
14+
/** @type {Set<string>} */
15+
this.values = new Set();
16+
17+
/** @type {Set<string>} */
18+
this.types = new Set();
19+
}
20+
21+
get size() {
22+
return this.values.size + this.types.size;
23+
}
24+
25+
/**
26+
* @param {'value'|'type'} type
27+
* @param {string} value
28+
*/
29+
add(type, value) {
30+
this[type + 's'].add(value);
31+
}
32+
}
33+
34+
module.exports = { ExportSet };

0 commit comments

Comments
 (0)