Skip to content

Commit 4c32995

Browse files
authored
[8.18] ESLint Rule for non-kbn-handlebars detection (#233190) (#236211)
# Backport This will backport the following commits from `main` to `8.18`: - [ESLint Rule for non-kbn-handlebars detection (#233190)](#233190) <!--- Backport version: 10.0.2 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Kurt","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-09-19T19:08:27Z","message":"ESLint Rule for non-kbn-handlebars detection (#233190)\n\n## Summary\n\nCloses https://github.com/elastic/kibana/issues/229853\n\nThis adds an ESLint rule to prevent teams from directly importing\n\"Handlebars\" into their files and instead using @kbn-handlebars.\n\nThe @kbn-handlebars package is allows to directly import \"Handlebars\"\nfor patching\n\n## Testing\n\nOpen up a file of your choice and try to import Handlebars, check that\nESLint recognizes it:\n\n<img width=\"334\" height=\"103\" alt=\"Screenshot 2025-09-02 at 8 04 23 AM\"\nsrc=\"https://github.com/user-attachments/assets/20d0cc4b-d353-47a5-bf29-444c63425eda\"\n/>\n\n---------\n\nCo-authored-by: kibanamachine <[email protected]>","sha":"1525cead4835f03330e9c0681556ee2aed64e138","branchLabelMapping":{"^v9.2.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:Security","release_note:skip","backport missing","backport:version","v9.2.0","v9.1.4","v9.0.7","v8.18.7","v8.19.4"],"title":"ESLint Rule for non-kbn-handlebars detection","number":233190,"url":"https://github.com/elastic/kibana/pull/233190","mergeCommit":{"message":"ESLint Rule for non-kbn-handlebars detection (#233190)\n\n## Summary\n\nCloses https://github.com/elastic/kibana/issues/229853\n\nThis adds an ESLint rule to prevent teams from directly importing\n\"Handlebars\" into their files and instead using @kbn-handlebars.\n\nThe @kbn-handlebars package is allows to directly import \"Handlebars\"\nfor patching\n\n## Testing\n\nOpen up a file of your choice and try to import Handlebars, check that\nESLint recognizes it:\n\n<img width=\"334\" height=\"103\" alt=\"Screenshot 2025-09-02 at 8 04 23 AM\"\nsrc=\"https://github.com/user-attachments/assets/20d0cc4b-d353-47a5-bf29-444c63425eda\"\n/>\n\n---------\n\nCo-authored-by: kibanamachine <[email protected]>","sha":"1525cead4835f03330e9c0681556ee2aed64e138"}},"sourceBranch":"main","suggestedTargetBranches":["9.1","9.0","8.18","8.19"],"targetPullRequestStates":[{"branch":"main","label":"v9.2.0","branchLabelMappingKey":"^v9.2.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/233190","number":233190,"mergeCommit":{"message":"ESLint Rule for non-kbn-handlebars detection (#233190)\n\n## Summary\n\nCloses https://github.com/elastic/kibana/issues/229853\n\nThis adds an ESLint rule to prevent teams from directly importing\n\"Handlebars\" into their files and instead using @kbn-handlebars.\n\nThe @kbn-handlebars package is allows to directly import \"Handlebars\"\nfor patching\n\n## Testing\n\nOpen up a file of your choice and try to import Handlebars, check that\nESLint recognizes it:\n\n<img width=\"334\" height=\"103\" alt=\"Screenshot 2025-09-02 at 8 04 23 AM\"\nsrc=\"https://github.com/user-attachments/assets/20d0cc4b-d353-47a5-bf29-444c63425eda\"\n/>\n\n---------\n\nCo-authored-by: kibanamachine <[email protected]>","sha":"1525cead4835f03330e9c0681556ee2aed64e138"}},{"branch":"9.1","label":"v9.1.4","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"9.0","label":"v9.0.7","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.7","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.19","label":"v8.19.4","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT-->
1 parent 75f6817 commit 4c32995

File tree

7 files changed

+165
-3
lines changed

7 files changed

+165
-3
lines changed

packages/kbn-eslint-config/.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ module.exports = {
331331
'@kbn/imports/no_group_crossing_manifests': 'error',
332332
'@kbn/imports/no_group_crossing_imports': 'error',
333333
'@kbn/css/no_css_color': 'warn',
334+
'@kbn/imports/no_direct_handlebars_import': 'error',
334335
'no-new-func': 'error',
335336
'no-implied-eval': 'error',
336337
'no-prototype-builtins': 'error',

packages/kbn-eslint-plugin-imports/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { NoBoundaryCrossingRule } from './src/rules/no_boundary_crossing';
1616
import { NoGroupCrossingImportsRule } from './src/rules/no_group_crossing_imports';
1717
import { NoGroupCrossingManifestsRule } from './src/rules/no_group_crossing_manifests';
1818
import { RequireImportRule } from './src/rules/require_import';
19+
import { NoDirectHandlebarsImportRule } from './src/rules/no_direct_handlebars_import';
1920

2021
/**
2122
* Custom ESLint rules, add `'@kbn/eslint-plugin-imports'` to your eslint config to use them
@@ -30,4 +31,5 @@ export const rules = {
3031
no_group_crossing_imports: NoGroupCrossingImportsRule,
3132
no_group_crossing_manifests: NoGroupCrossingManifestsRule,
3233
require_import: RequireImportRule,
34+
no_direct_handlebars_import: NoDirectHandlebarsImportRule,
3335
};
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import { RuleTester } from 'eslint';
11+
import { NoDirectHandlebarsImportRule } from './no_direct_handlebars_import';
12+
13+
const ruleTester = new RuleTester({
14+
parser: require.resolve('@typescript-eslint/parser'),
15+
parserOptions: {
16+
sourceType: 'module',
17+
ecmaVersion: 2018,
18+
ecmaFeatures: {
19+
jsx: true,
20+
},
21+
},
22+
});
23+
24+
ruleTester.run('@kbn/imports/no_direct_handlebars_import', NoDirectHandlebarsImportRule, {
25+
valid: [
26+
{
27+
code: 'import Handlebars from "@kbn/handlebars";',
28+
},
29+
{
30+
code: 'import { compile } from "@kbn/handlebars";',
31+
},
32+
{
33+
code: 'import * as Handlebars from "@kbn/handlebars";',
34+
},
35+
{
36+
code: 'import something from "other-package";',
37+
},
38+
{
39+
code: 'const foo = require("@kbn/handlebars");',
40+
},
41+
],
42+
43+
invalid: [
44+
{
45+
code: 'import Handlebars from "handlebars";',
46+
errors: [
47+
{
48+
message:
49+
'Do not import directly from "handlebars". Use the custom Handlebars from "@kbn/handlebars" instead.',
50+
},
51+
],
52+
output: "import Handlebars from '@kbn/handlebars';",
53+
},
54+
{
55+
code: 'import { compile } from "handlebars";',
56+
errors: [
57+
{
58+
message:
59+
'Do not import directly from "handlebars". Use the custom Handlebars from "@kbn/handlebars" instead.',
60+
},
61+
],
62+
output: "import { compile } from '@kbn/handlebars';",
63+
},
64+
{
65+
code: 'import * as Handlebars from "handlebars";',
66+
errors: [
67+
{
68+
message:
69+
'Do not import directly from "handlebars". Use the custom Handlebars from "@kbn/handlebars" instead.',
70+
},
71+
],
72+
output: "import * as Handlebars from '@kbn/handlebars';",
73+
},
74+
{
75+
code: 'import "handlebars/lib/handlebars";',
76+
errors: [
77+
{
78+
message:
79+
'Do not import directly from "handlebars". Use the custom Handlebars from "@kbn/handlebars" instead.',
80+
},
81+
],
82+
output: "import '@kbn/handlebars/lib/handlebars';",
83+
},
84+
{
85+
code: 'const Handlebars = require("handlebars");',
86+
errors: [
87+
{
88+
message:
89+
'Do not import directly from "handlebars". Use the custom Handlebars from "@kbn/handlebars" instead.',
90+
},
91+
],
92+
output: "const Handlebars = require('@kbn/handlebars');",
93+
},
94+
],
95+
});
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import type { Rule } from 'eslint';
11+
import { visitAllImportStatements } from '../helpers/visit_all_import_statements';
12+
import { report } from '../helpers/report';
13+
14+
export const NoDirectHandlebarsImportRule: Rule.RuleModule = {
15+
meta: {
16+
type: 'problem',
17+
fixable: 'code',
18+
docs: {
19+
description: 'Disallow direct imports from handlebars package. Use @kbn/handlebars instead.',
20+
url: 'https://github.com/elastic/kibana/blob/main/packages/kbn-eslint-plugin-imports/README.mdx#kbnimportsno_direct_handlebars_import',
21+
},
22+
messages: {
23+
noDirectHandlebarsImport:
24+
'Do not import directly from "handlebars". Use the custom Handlebars from "@kbn/handlebars" instead.',
25+
},
26+
},
27+
28+
create(context) {
29+
return visitAllImportStatements((req, { node }) => {
30+
if (!req) {
31+
return;
32+
}
33+
34+
// Skip the rule for files within the kbn-handlebars package itself
35+
const filename = context.getFilename();
36+
if (filename.includes('/kbn-handlebars/') || filename.includes('\\kbn-handlebars\\')) {
37+
return;
38+
}
39+
40+
// Check for direct imports from 'handlebars' package
41+
if (req === 'handlebars' || req.startsWith('handlebars/')) {
42+
// Replace 'handlebars' with '@kbn/handlebars' in the import request
43+
const correctImport = req.replace(/^handlebars/, '@kbn/handlebars');
44+
45+
report(context, {
46+
node,
47+
message:
48+
'Do not import directly from "handlebars". Use the custom Handlebars from "@kbn/handlebars" instead.',
49+
correctImport,
50+
});
51+
}
52+
});
53+
},
54+
};

src/dev/eslint/security_eslint_rule_tests.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ require('lodash/fp/assoc'); // eslint-disable-line no-restricted-modules
5050
require('lodash/fp/assocPath'); // eslint-disable-line no-restricted-modules
5151
require('lodash/fp/template'); // eslint-disable-line no-restricted-modules
5252

53+
// Adding some additional tests for Handlebars here, for more tests, see packages/kbn-eslint-plugin-imports/src/rules/no_direct_handlebars_import.test.ts
54+
import * as hb from 'handlebars'; // eslint-disable-line @kbn/imports/no_direct_handlebars_import
55+
import Handlebars from 'handlebars'; // eslint-disable-line @kbn/imports/no_direct_handlebars_import
56+
import { compile as hc } from 'handlebars'; // eslint-disable-line @kbn/imports/no_direct_handlebars_import
57+
import { precompile as hp } from 'handlebars'; // eslint-disable-line @kbn/imports/no_direct_handlebars_import
58+
import { template as ht } from 'handlebars'; // eslint-disable-line @kbn/imports/no_direct_handlebars_import
59+
import { SafeString as hs } from 'handlebars'; // eslint-disable-line @kbn/imports/no_direct_handlebars_import
60+
61+
require('handlebars'); // eslint-disable-line @kbn/imports/no_direct_handlebars_import
62+
5363
const lodash = {
5464
set() {},
5565
setWith() {},
@@ -71,4 +81,4 @@ _.assocPath(); // eslint-disable-line no-restricted-properties
7181
_.template(); // eslint-disable-line no-restricted-properties
7282

7383
// hack to ensure all imported variables are used
74-
module.exports = [a, b, c, d, e, f, g, h, i, j];
84+
module.exports = [a, b, c, d, e, f, g, h, i, j, hb, Handlebars, hc, hp, ht, hs];

src/platform/packages/shared/kbn-openapi-generator/src/template_service/register_helpers.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66
* your election, the "Elastic License 2.0", the "GNU Affero General Public
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
9-
109
import type Handlebars from '@kbn/handlebars';
11-
import { HelperOptions } from 'handlebars';
10+
import type { HelperOptions } from '@kbn/handlebars';
1211
import { snakeCase, camelCase, upperCase } from 'lodash';
1312

1413
export function registerHelpers(handlebarsInstance: typeof Handlebars) {

src/platform/packages/shared/kbn-openapi-generator/src/template_service/template_service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10+
// eslint-disable-next-line @kbn/imports/no_direct_handlebars_import
1011
import Handlebars from 'handlebars';
1112
import { resolve } from 'path';
1213
import { BundleGenerationContext, GenerationContext } from '../parser/get_generation_context';

0 commit comments

Comments
 (0)