Skip to content

Commit

Permalink
[eslint] add rule to prevent export* in plugin index files (#109357)
Browse files Browse the repository at this point in the history
* [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 <[email protected]>
Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
3 people authored Sep 2, 2021
1 parent 4f0a63f commit fecdba7
Show file tree
Hide file tree
Showing 89 changed files with 792 additions and 4 deletions.
19 changes: 19 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -1656,5 +1656,24 @@ module.exports = {
'@typescript-eslint/prefer-ts-expect-error': 'error',
},
},

/**
* Disallow `export *` syntax in plugin/core public/server/common index files and instead
* require that plugins/core explicitly export the APIs that should be accessible outside the plugin.
*
* To add your plugin to this list just update the relevant glob with the name of your plugin
*/
{
files: [
'src/core/{server,public,common}/index.ts',
'src/plugins/*/{server,public,common}/index.ts',
'src/plugins/*/*/{server,public,common}/index.ts',
'x-pack/plugins/*/{server,public,common}/index.ts',
'x-pack/plugins/*/*/{server,public,common}/index.ts',
],
rules: {
'@kbn/eslint/no_export_all': 'error',
},
},
],
};
1 change: 1 addition & 0 deletions packages/kbn-eslint-plugin-eslint/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ PKG_REQUIRE_NAME = "@kbn/eslint-plugin-eslint"
SOURCE_FILES = glob(
[
"rules/**/*.js",
"helpers/**/*.js",
"index.js",
"lib.js",
],
Expand Down
11 changes: 11 additions & 0 deletions packages/kbn-eslint-plugin-eslint/__fixtures__/bar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

/* eslint-disable no-restricted-syntax */

export class Bar {}
13 changes: 13 additions & 0 deletions packages/kbn-eslint-plugin-eslint/__fixtures__/baz.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

/* eslint-disable no-restricted-syntax */

export const one = 1;
export const two = 2;
export const three = 3;
31 changes: 31 additions & 0 deletions packages/kbn-eslint-plugin-eslint/__fixtures__/foo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

/* eslint-disable no-restricted-syntax */

export type { Bar as ReexportedClass } from './bar';

export const someConst = 'bar';

// eslint-disable-next-line prefer-const
export let someLet = 'bar';

export function someFunction() {}

export class SomeClass {}

export interface SomeInterface {
prop: number;
}

export enum SomeEnum {
a = 'a',
b = 'b',
}

export type TypeAlias = string[];
11 changes: 11 additions & 0 deletions packages/kbn-eslint-plugin-eslint/__fixtures__/top.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

/* eslint-disable no-restricted-syntax */

export * from './foo';
82 changes: 82 additions & 0 deletions packages/kbn-eslint-plugin-eslint/helpers/codegen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

const t = require('@babel/types');
const { default: generate } = require('@babel/generator');

/** @typedef {import('./export_set').ExportSet} ExportSet */

/**
* Generate code for replacing a `export * from './path'`, ie.
*
* export type { foo } from './path'
* export { bar } from './path'
* @param {ExportSet} exportSet
* @param {string} source
*/
const getExportCode = (exportSet, source) => {
const exportedTypes = exportSet.types.size
? t.exportNamedDeclaration(
undefined,
Array.from(exportSet.types).map((n) => t.exportSpecifier(t.identifier(n), t.identifier(n))),
t.stringLiteral(source)
)
: undefined;

if (exportedTypes) {
exportedTypes.exportKind = 'type';
}

const exportedValues = exportSet.values.size
? t.exportNamedDeclaration(
undefined,
Array.from(exportSet.values).map((n) =>
t.exportSpecifier(t.identifier(n), t.identifier(n))
),
t.stringLiteral(source)
)
: undefined;

return generate(t.program([exportedTypes, exportedValues].filter(Boolean))).code;
};

/**
* Generate code for replacing a `export * as name from './path'`, ie.
*
* import { foo, bar } from './path'
* export const name = { foo, bar }
*
* @param {string} nsName
* @param {string[]} exportNames
* @param {string} source
*/
const getExportNamedNamespaceCode = (nsName, exportNames, source) => {
return generate(
t.program([
t.importDeclaration(
exportNames.map((n) => t.importSpecifier(t.identifier(n), t.identifier(n))),
t.stringLiteral(source)
),
t.exportNamedDeclaration(
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier(nsName),
t.objectExpression(
exportNames.map((n) =>
t.objectProperty(t.identifier(n), t.identifier(n), false, true)
)
)
),
])
),
])
).code;
};

module.exports = { getExportCode, getExportNamedNamespaceCode };
34 changes: 34 additions & 0 deletions packages/kbn-eslint-plugin-eslint/helpers/export_set.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

/**
* Helper class to collect exports of different types, either "value" exports or "type" exports
*/
class ExportSet {
constructor() {
/** @type {Set<string>} */
this.values = new Set();

/** @type {Set<string>} */
this.types = new Set();
}

get size() {
return this.values.size + this.types.size;
}

/**
* @param {'value'|'type'} type
* @param {string} value
*/
add(type, value) {
this[type + 's'].add(value);
}
}

module.exports = { ExportSet };
Loading

0 comments on commit fecdba7

Please sign in to comment.