[ESLint] Add rule to prevent self closing custom elements Bug: None Change-Id: I082b787fb56159ef9465fe8f0b5c3d5623ab9890 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2955365 Commit-Queue: Jan Scheffler <janscheffler@chromium.org> Reviewed-by: Paul Lewis <aerotwist@chromium.org> Reviewed-by: Tim van der Lippe <tvanderlippe@chromium.org>
diff --git a/front_end/.eslintrc.js b/front_end/.eslintrc.js index a0cb477..7d56e7b 100644 --- a/front_end/.eslintrc.js +++ b/front_end/.eslintrc.js
@@ -27,6 +27,7 @@ 'rulesdir/lit_html_data_as_type': 2, 'rulesdir/lit_no_style_interpolation': 2, 'rulesdir/ban_literal_devtools_component_tag_names': 2, + 'rulesdir/ban_self_closing_custom_element_tagnames': 2, 'rulesdir/check_component_naming': 2, '@typescript-eslint/naming-convention': [ 'error', {
diff --git a/scripts/eslint_rules/lib/ban_self_closing_custom_element_tagnames.js b/scripts/eslint_rules/lib/ban_self_closing_custom_element_tagnames.js new file mode 100644 index 0000000..c976c4e --- /dev/null +++ b/scripts/eslint_rules/lib/ban_self_closing_custom_element_tagnames.js
@@ -0,0 +1,46 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +'use strict'; + +function isLitHtmlTemplateCall(taggedTemplateExpression) { + if (taggedTemplateExpression.name) { + // Call to html`` and we assume that html = LitHtml's html function. + return taggedTemplateExpression.name === 'html'; + } + + // Match calls to LitHtml.html`` + return taggedTemplateExpression.object && taggedTemplateExpression.object.name === 'LitHtml' && + taggedTemplateExpression.property.name === 'html'; +} + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Check for self closing custom element tag names in Lit templates.', + category: 'Possible Errors', + }, + fixable: 'code', + schema: [] // no options + }, + create: function(context) { + return { + TaggedTemplateExpression(node) { + const isLitHtmlCall = isLitHtmlTemplateCall(node.tag); + if (!isLitHtmlCall) { + return; + } + + const text = node.quasi.quasis.map(templatePart => templatePart.value.raw).join('@TEMPLATE_EXPRESSION()'); + + if (text.match(/<@TEMPLATE_EXPRESSION\(\)([^>]*?)\/>/)) { + context.report({ + node, + message: 'Custom elements should not be self closing.', + }); + } + }, + }; + } +};
diff --git a/scripts/eslint_rules/tests/ban_self_closing_custom_element_tagnames_test.js b/scripts/eslint_rules/tests/ban_self_closing_custom_element_tagnames_test.js new file mode 100644 index 0000000..0e7896e --- /dev/null +++ b/scripts/eslint_rules/tests/ban_self_closing_custom_element_tagnames_test.js
@@ -0,0 +1,64 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +'use strict'; + +const rule = require('../lib/ban_self_closing_custom_element_tagnames.js'); +const ruleTester = new (require('eslint').RuleTester)({ + parserOptions: {ecmaVersion: 9, sourceType: 'module'}, + parser: require.resolve('@typescript-eslint/parser'), +}); + +const EXPECTED_ERROR_MESSAGE = 'Custom elements should not be self closing.'; + +ruleTester.run('ban_self_closing_custom_element_tagnames', rule, { + valid: [ + { + code: 'LitHtml.html`<p></p>`', + filename: 'front_end/components/test.ts', + }, + { + code: 'LitHtml.html`<input />`', + filename: 'front_end/components/test.ts', + }, + { + code: 'LitHtml.html`<${DataGrid.litTagName}></${DataGrid.litTagName}>`', + filename: 'front_end/components/test.ts', + }, + { + code: 'LitHtml.html`<p><${DataGrid.litTagName}></${DataGrid.litTagName}></p>`', + filename: 'front_end/components/test.ts', + }, + { + code: + 'LitHtml.html`<${DataGrid1.litTagName}><${DataGrid2.litTagName}></${DataGrid2.litTagName}></${DataGrid1.litTagName}>`', + filename: 'front_end/components/test.ts', + }, + { + code: 'LitHtml.html`<${DataGrid1.litTagName}>\n</${DataGrid1.litTagName}>`', + filename: 'front_end/components/test.ts', + }, + ], + invalid: [ + { + code: 'LitHtml.html`<${DataGrid.litTagName} />`', + filename: 'front_end/components/test.ts', + errors: [{message: EXPECTED_ERROR_MESSAGE}] + }, + { + code: 'LitHtml.html`<p><${DataGrid.litTagName} /></p>`', + filename: 'front_end/components/test.ts', + errors: [{message: EXPECTED_ERROR_MESSAGE}] + }, + { + code: 'LitHtml.html`<${DataGrid1.litTagName}><${DataGrid2.litTagName} /></${DataGrid1.litTagName}>`', + filename: 'front_end/components/test.ts', + errors: [{message: EXPECTED_ERROR_MESSAGE}] + }, + { + code: 'LitHtml.html`<${DataGrid.litTagName} .data=${{test: "Hello World"}}/>`', + filename: 'front_end/components/test.ts', + errors: [{message: EXPECTED_ERROR_MESSAGE}] + }, + ] +});