Skip to content

Commit

Permalink
feat: add seo-tags
Browse files Browse the repository at this point in the history
  • Loading branch information
DanSnow committed Jun 29, 2021
1 parent c376d09 commit cd9d539
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 0 deletions.
45 changes: 45 additions & 0 deletions docs/rules/seo-tags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# warning about `component` not matching SEO spec

Because this rule is guessing block type via finding `HeroBlock` tag, please notice that it may suggest a wrong fix.

This rule has suggested fixes.

## Fail

For standard blocks:

```vue
<template>
<TextElement component="h4">{{ article.title }}</TextElement>
<TextElement component="h3">{{ desk }}</TextElement>
</template>
```

For hero blocks:

```vue
<template>
<TextElement component="h3">{{ article.title }}</TextElement>
<TextElement component="h3">{{ desk }}</TextElement>
</template>
```

## Pass

For standard blocks:

```vue
<template>
<TextElement component="h3">{{ article.title }}</TextElement>
<TextElement component="h4">{{ desk }}</TextElement>
</template>
```

For hero blocks:

```vue
<template>
<TextElement component="h2">{{ article.title }}</TextElement>
<TextElement component="h4">{{ desk }}</TextElement>
</template>
```
1 change: 1 addition & 0 deletions lib/configs/recommended.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ module.exports = {
'@storipress/block/headline-image-missing-alt': 'error',
'@storipress/block/link-element-no-literal-href': 'warn',
'@storipress/block/image-with-literal-src': 'warn',
'@storipress/block/seo-tags': 'warn',
},
}
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
'link-element-no-literal-href': require('./rules/link-element-no-literal-href'),
'image-with-literal-src': require('./rules/image-with-literal-src'),
'image-without-src': require('./rules/image-without-src'),
'seo-tags': require('./rules/seo-tags'),
},
configs: {
recommended: require('./configs/recommended'),
Expand Down
71 changes: 71 additions & 0 deletions lib/rules/seo-tags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
const { findLiteralAttr } = require('../helpers/find-attr')

const TAGS = {
hero: ['h4', 'h2'],
block: ['h4', 'h3'],
}

module.exports = {
meta: {
type: 'problem',

docs: {
description: 'checking seo tag to follow SEO spec',
category: 'Possible Errors',
recommended: true,
},
messages: {
tagNotMatch: 'here possible should be `{{ tag }}`, please check again',
changeTag: 'change this to {{ tag }}',
},
schema: [], // no options
},
create(context) {
const filename = context.getFilename()

// guessing block type
let blockType = filename.startsWith('navbar') ? 'hero' : 'block'
const sourceCode = context.getSourceCode()

function checkSEOTag(node) {
const isTitle = sourceCode.getText(node).includes('article.title')
const attr = findLiteralAttr(node, 'component')

if (!attr) {
return
}

const expectedTag = TAGS[blockType][isTitle ? 1 : 0]

const data = { tag: expectedTag }

if (attr.value.value !== expectedTag) {
context.report({
node: attr,
messageId: 'tagNotMatch',
data,
suggest: [
{
messageId: 'changeTag',
data,
fix(fixer) {
return fixer.replaceText(attr, `component="${expectedTag}"`)
},
},
],
})
}
}

return context.parserServices.defineTemplateBodyVisitor({
'VElement[rawName=Block]'() {
blockType = 'block'
},
'VElement[rawName=HeroBlock]'() {
blockType = 'hero'
},
'VElement[rawName=TextInput]': checkSEOTag,
'VElement[rawName=TextElement]': checkSEOTag,
})
},
}
156 changes: 156 additions & 0 deletions tests/rules/seo-tags.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
const { ruleTester } = require('../_helper')
const rule = require('../../lib/rules/seo-tags')

ruleTester.run('seo-tags', rule, {
valid: [
{
code: `
<template>
<TextElement kind="foo" component="h3">{{ article.title }}</TextElement>
</template>
`,
filename: 'test.vue',
},
{
code: `
<template>
<TextInput kind="foo" component="h4" :defaultValue="desk" />
</template>
`,
filename: 'test.vue',
},
{
code: `
<template>
<Block>
<TextElement kind="foo" component="h3">{{ article.title }}</TextElement>
</Block>
</template>
`,
filename: 'test.vue',
},
{
code: `
<template>
<Block>
<TextInput kind="foo" component="h4" :defaultValue="desk" />
</Block>
</template>
`,
filename: 'test.vue',
},
{
code: `
<template>
<HeroBlock>
<TextElement kind="foo" component="h2">{{ article.title }}</TextElement>
</HeroBlock>
</template>
`,
filename: 'test.vue',
},
{
code: `
<template>
<HeroBlock>
<TextInput kind="foo" component="h4" :defaultValue="desk" />
</HeroBlock>
</template>
`,
filename: 'test.vue',
},
],
invalid: [
{
code: `
<template>
<Block>
<TextElement kind="foo" component="h2">
{{ article.title }}
</TextElement>
</Block>
</template>
`,
filename: 'test.vue',
errors: [
{
messageId: 'tagNotMatch',
data: { tag: 'h3' },
suggestions: [
{
messageId: 'changeTag',
data: { tag: 'h3' },
output: `
<template>
<Block>
<TextElement kind="foo" component="h3">
{{ article.title }}
</TextElement>
</Block>
</template>
`,
},
],
},
],
},
{
code: `
<template>
<Block>
<TextInput kind="foo" component="h2" :defaultValue="desk" />
</Block>
</template>
`,
filename: 'test.vue',
errors: [
{
messageId: 'tagNotMatch',
data: { tag: 'h4' },
suggestions: [
{
messageId: 'changeTag',
data: { tag: 'h4' },
output: `
<template>
<Block>
<TextInput kind="foo" component="h4" :defaultValue="desk" />
</Block>
</template>
`,
},
],
},
],
},
{
code: `
<template>
<HeroBlock>
<TextInput kind="foo" component="h1" :defaultValue="desk" />
</HeroBlock>
</template>
`,
filename: 'test.vue',
errors: [
{
messageId: 'tagNotMatch',
data: { tag: 'h4' },
suggestions: [
{
messageId: 'changeTag',
data: { tag: 'h4' },
output: `
<template>
<HeroBlock>
<TextInput kind="foo" component="h4" :defaultValue="desk" />
</HeroBlock>
</template>
`,
},
],
},
],
},
],
})

0 comments on commit cd9d539

Please sign in to comment.