Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ rules in templates can be disabled with eslint directives with mustache or html
| [template-linebreak-style](docs/rules/template-linebreak-style.md) | enforce consistent linebreaks in templates | | 🔧 | |
| [template-modifier-name-case](docs/rules/template-modifier-name-case.md) | require dasherized names for modifiers | | 🔧 | |
| [template-no-only-default-slot](docs/rules/template-no-only-default-slot.md) | disallow using only the default slot | | 🔧 | |
| [template-template-length](docs/rules/template-template-length.md) | enforce template size constraints | | | |

### Testing

Expand Down
57 changes: 57 additions & 0 deletions docs/rules/template-template-length.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# ember/template-template-length

<!-- end auto-generated rule header -->

Enforce template size constraints.

Very long templates can indicate that markup should be split into smaller components.
Very short templates can indicate that markup might be better inlined.

## Config

This rule accepts either:

- `true` / `false`
- an object with:
- `max` (integer): maximum allowed template length in lines
- `min` (integer): minimum allowed template length in lines

Using `true` defaults to:

- `max: 200`
- `min: 5`

## Examples

Examples of **incorrect** code for this rule:

```gjs
<template>
<div>short</div>
</template>
```

with config:

```json
{ "min": 10 }
```

Examples of **correct** code for this rule:

```gjs
<template>
<div>line 1</div>
<div>line 2</div>
<div>line 3</div>
</template>
```

## Related rules

- [eslint/max-lines](https://eslint.org/docs/rules/max-lines)

## References

- [eslint-plugin-ember template-length](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-template-length.md)
- [eslint/max-lines](https://eslint.org/docs/latest/rules/max-lines)
119 changes: 119 additions & 0 deletions lib/rules/template-template-length.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
const DEFAULT_MAX_LENGTH = 200;
const DEFAULT_MIN_LENGTH = 5;

const DEFAULT_CONFIG = {
max: DEFAULT_MAX_LENGTH,
min: DEFAULT_MIN_LENGTH,
};

function isValidConfigObjectFormat(config) {
for (const key of Object.keys(config)) {
const value = config[key];

if (key !== 'min' && key !== 'max') {
return false;
}

if (!Number.isInteger(value)) {
return false;
}
}

return true;
}

function parseConfig(config) {
const configType = typeof config;

switch (configType) {
case 'boolean': {
return config ? DEFAULT_CONFIG : {};
}
case 'object': {
if (config === null) {
break;
}

if (isValidConfigObjectFormat(config)) {
return config;
}
break;
}
case 'undefined': {
return {};
}
// No default
}

throw new Error(
'The ember/template-template-length rule accepts: boolean, or object with integer "min" and/or "max" keys.'
);
}

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'enforce template size constraints',
category: 'Stylistic Issues',
recommendedGjs: false,
recommendedGts: false,
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-template-length.md',
templateMode: 'both',
},
schema: [
{
oneOf: [
{ type: 'boolean' },
{
type: 'object',
properties: {
min: { type: 'integer' },
max: { type: 'integer' },
},
additionalProperties: false,
},
],
},
],
messages: {
tooLong: 'Template length of {{length}} exceeds {{max}}',
tooShort: 'Template length of {{length}} is smaller than {{min}}',
},
originallyFrom: {
name: 'ember-template-lint',
rule: 'lib/rules/template-length.js',
docs: 'docs/rule/template-length.md',
tests: 'test/unit/rules/template-length-test.js',
},
},

create(context) {
const config = parseConfig(context.options[0]);

if (!config.min && !config.max) {
return {};
}

return {
'GlimmerTemplate:exit'(node) {
const length = node.loc.end.line - node.loc.start.line + 1;

if (config.max && length > config.max) {
context.report({
node,
messageId: 'tooLong',
data: { length, max: config.max },
});
} else if (config.min && length < config.min) {
context.report({
node,
messageId: 'tooShort',
data: { length, min: config.min },
});
}
},
};
},
};
116 changes: 116 additions & 0 deletions tests/lib/rules/template-template-length.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
const rule = require('../../../lib/rules/template-template-length');
const RuleTester = require('eslint').RuleTester;

const ruleTester = new RuleTester({
parser: require.resolve('ember-eslint-parser'),
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
});

ruleTester.run('template-template-length', rule, {
valid: [
{
code: `<template>
one
two
</template>`,
options: [{ max: 5 }],
},
{
code: `<template>
one
two
three
</template>`,
options: [{ min: 3 }],
},
{
code: `<template>
one
</template>`,
options: [false],
},

`<template>testing this
and
this
and his</template>`,
],
invalid: [
{
code: `<template>
one
two
</template>`,
output: null,
options: [{ min: 10 }],
errors: [{ message: 'Template length of 4 is smaller than 10' }],
},
{
code: `<template>
one
two
three
</template>`,
output: null,
options: [{ max: 3 }],
errors: [{ message: 'Template length of 5 exceeds 3' }],
},
],
});

const hbsRuleTester = new RuleTester({
parser: require.resolve('ember-eslint-parser/hbs'),
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
});

hbsRuleTester.run('template-template-length', rule, {
valid: [
`testing this
and
this
and his`,
`testing
this
`,
`testing
this
and his
`,
`testing
this
andthis
`,
// Config: max option
{
code: 'testing\nthis\n',
options: [{ max: 10 }],
},
// Config: min option
{
code: 'testing\nthis\nand\this\n',
options: [{ min: 1 }],
},
// Config: min + max options
{
code: 'testing\nthis\nandthis\n',
options: [{ min: 1, max: 5 }],
},
],
invalid: [
{
code: 'testing\ntoo-short template\n',
output: null,
options: [{ min: 10 }],
errors: [{ message: 'Template length of 3 is smaller than 10' }],
},
{
code: 'test\nthis\nand\nthis\n',
output: null,
options: [{ max: 3 }],
errors: [{ message: 'Template length of 5 exceeds 3' }],
},
],
});
Loading