Skip to content
Closed
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
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,12 +315,13 @@ rules in templates can be disabled with eslint directives with mustache or html

### Stylistic Issues

| Name | Description | 💼 | 🔧 | 💡 |
| :--------------------------------------------------------- | :------------------------------------------------ | :- | :- | :- |
| [order-in-components](docs/rules/order-in-components.md) | enforce proper order of properties in components | | 🔧 | |
| [order-in-controllers](docs/rules/order-in-controllers.md) | enforce proper order of properties in controllers | | 🔧 | |
| [order-in-models](docs/rules/order-in-models.md) | enforce proper order of properties in models | | 🔧 | |
| [order-in-routes](docs/rules/order-in-routes.md) | enforce proper order of properties in routes | | 🔧 | |
| Name | Description | 💼 | 🔧 | 💡 |
| :--------------------------------------------------------------- | :-------------------------------------------------- | :- | :- | :- |
| [order-in-components](docs/rules/order-in-components.md) | enforce proper order of properties in components | | 🔧 | |
| [order-in-controllers](docs/rules/order-in-controllers.md) | enforce proper order of properties in controllers | | 🔧 | |
| [order-in-models](docs/rules/order-in-models.md) | enforce proper order of properties in models | | 🔧 | |
| [order-in-routes](docs/rules/order-in-routes.md) | enforce proper order of properties in routes | | 🔧 | |
| [template-inline-link-to](docs/rules/template-inline-link-to.md) | disallow inline link-to, use the block form instead | | 🔧 | |

### Testing

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

🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

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

Disallows the inline form of the `link-to` component and enforces the block form instead.

Ember's `link-to` component has both an inline form and a block form. This rule forbids the inline form.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this form still exist?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it does not! yay!


## Examples

This rule **forbids** the following (inline form):

```hbs
{{link-to 'Link text' 'routeName' prop1 prop2}}
```

This rule **allows** the following (block form):

```hbs
{{#link-to 'routeName' prop1 prop2}}Link text{{/link-to}}
```

## Rationale

The block form is a little longer but has advantages over the inline form:

- It maps closer to the use of HTML anchor tags which wrap their inner content.
- It provides an obvious way for developers to put nested markup and components inside of their link.
- The block form's argument order is more direct: "link to route". The inline form's argument order is somewhat ambiguous (link text then link target). This is opposite of the order in HTML (`href` then link text).

## References

- [Ember guides/routing](https://guides.emberjs.com/release/routing/linking-between-routes/#toc_the-linkto--component)
- [Ember api/LinkTo component](https://api.emberjs.com/ember/release/classes/Ember.Templates.components/methods/LinkTo?anchor=LinkTo)
76 changes: 76 additions & 0 deletions lib/rules/template-inline-link-to.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow inline link-to, use the block form instead',
category: 'Stylistic Issues',
strictGjs: true,
strictGts: true,
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-inline-link-to.md',
},
fixable: 'code',
schema: [],
messages: {},
},

create(context) {
const MESSAGE = 'The inline form of link-to is not allowed. Use the block form instead.';

return {
GlimmerMustacheStatement(node) {
if (
node.path &&
node.path.type === 'GlimmerPathExpression' &&
node.path.original === 'link-to'
) {
const titleNode = node.params?.[0];
const isFixable =
titleNode &&
(titleNode.type === 'GlimmerSubExpression' ||
titleNode.type === 'GlimmerStringLiteral');

context.report({
node,
message: MESSAGE,
fix: isFixable
? (fixer) => {
const sourceCode = context.getSourceCode();
const text = sourceCode.getText(node);

// Convert {{link-to 'text' 'route' ...}} to {{#link-to 'route' ...}}text{{/link-to}}
let blockBody;
if (titleNode.type === 'GlimmerSubExpression') {
// {{link-to (helper ...) 'route'}} -> {{#link-to 'route'}}{{helper ...}}{{/link-to}}
const helperText = sourceCode.getText(titleNode);
blockBody = helperText.replace(/^\(/, '{{').replace(/\)$/, '}}');
} else if (titleNode.type === 'GlimmerStringLiteral') {
// {{link-to 'text' 'route'}} -> {{#link-to 'route'}}text{{/link-to}}
blockBody = titleNode.value;
}

// Get remaining params (everything after the first param)
const remainingParams = node.params.slice(1);
const remainingParamsText = remainingParams
.map((param) => sourceCode.getText(param))
.join(' ');

// Get hash if present
const hashText =
node.hash && node.hash.pairs && node.hash.pairs.length > 0
? ` ${node.hash.pairs
.map((pair) => `${pair.key}=${sourceCode.getText(pair.value)}`)
.join(' ')}`
: '';

const fixedText = `{{#link-to ${remainingParamsText}${hashText}}}${blockBody}{{/link-to}}`;

return fixer.replaceText(node, fixedText);
}
: null,
});
}
},
};
},
};
53 changes: 53 additions & 0 deletions tests/lib/rules/template-inline-link-to.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const rule = require('../../../lib/rules/template-inline-link-to');
const RuleTester = require('eslint').RuleTester;

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

ruleTester.run('template-inline-link-to', rule, {
valid: [
"<template>{{#link-to 'routeName' prop}}Link text{{/link-to}}</template>",
"<template>{{#link-to 'routeName'}}Link text{{/link-to}}</template>",
],
invalid: [
{
code: "<template>{{link-to 'Link text' 'routeName'}}</template>",
output: "<template>{{#link-to 'routeName'}}Link text{{/link-to}}</template>",
errors: [
{
message: 'The inline form of link-to is not allowed. Use the block form instead.',
},
],
},
{
code: "<template>{{link-to 'Link text' 'routeName' one two}}</template>",
output: "<template>{{#link-to 'routeName' one two}}Link text{{/link-to}}</template>",
errors: [
{
message: 'The inline form of link-to is not allowed. Use the block form instead.',
},
],
},
{
code: "<template>{{link-to (concat 'Hello' @username) 'routeName' one two}}</template>",
output:
"<template>{{#link-to 'routeName' one two}}{{concat 'Hello' @username}}{{/link-to}}</template>",
errors: [
{
message: 'The inline form of link-to is not allowed. Use the block form instead.',
},
],
},
{
code: "<template>{{link-to 1234 'routeName' one two}}</template>",
output: null,
errors: [
{
message: 'The inline form of link-to is not allowed. Use the block form instead.',
},
],
},
],
});
Loading