Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add astro/no-set-html-directive rule #5

Merged
merged 4 commits into from
May 23, 2022
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
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
/node_modules
!/.vscode
!/.github
/tests/fixtures/rules/*/invalid/template-attr-*.astro
/tests/fixtures/rules/*/invalid/template-attr-*.astro
/docs-build/src/md
/docs-build/src/pages
/docs-build/README.md
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,13 @@ The `--fix` option on the [command line](https://eslint.org/docs/user-guide/comm

<!--RULES_TABLE_START-->

*No rules have been provided yet.*
## Security Vulnerability

These rules relate to security vulnerabilities in Astro component code:

| Rule ID | Description | |
|:--------|:------------|:---|
| [astro/no-set-html-directive](https://ota-meshi.github.io/eslint-plugin-astro/rules/no-set-html-directive/) | disallow use of `set:html` to prevent XSS attack | |

<!--RULES_TABLE_END-->
<!--RULES_SECTION_END-->
Expand Down
13 changes: 12 additions & 1 deletion astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { defineConfig } from "astro/config"
import svelte from "@astrojs/svelte"
import emoji from "remark-emoji"
import gfm from "remark-gfm"
import replaceLink from "./docs-build/remark-replace-link.mjs"
import "./docs-build/setup-docs.mjs"
import path from "path"
Expand All @@ -17,7 +18,17 @@ export default defineConfig({
root: dirname,
integrations: [svelte()],
markdown: {
remarkPlugins: [emoji, replaceLink],
remarkPlugins: [
emoji,
gfm,
[
replaceLink,
{
srcDir: "./docs-build/src",
base: "/eslint-plugin-astro",
},
],
],
},
vite: {
server: {
Expand Down
33 changes: 29 additions & 4 deletions docs-build/remark-replace-link.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
import { visit } from "unist-util-visit"
import path from "path"

export default () => {
return (tree) => {
visit(tree, "link", (node) => {
node.url = node.url.replace(/\.md$/u, "/")
export default (options = {}) => {
const base =
// eslint-disable-next-line no-process-env -- ignore
(process?.env?.NODE_ENV === "production" ? options.base : "") || ""
return (tree, file) => {
const srcDir = path.resolve(
file.cwd,
options.srcDir ? options.srcDir : "./src",
)
const pagesDir = path.join(srcDir, "pages")
let markdownPath
visit(tree, "html", (node, _i, parent) => {
if (node.value.startsWith("<!-- markdownPath:")) {
markdownPath = node.value.slice(18, -3).trim()
parent.children.splice(parent.children.indexOf(node), 1)
}
})
if (markdownPath) {
visit(tree, "link", (node) => {
if (node.url.startsWith(".")) {
const linkPath = path.resolve(path.dirname(markdownPath), node.url)
const relativeLinkPath = path.relative(pagesDir, linkPath)
const absoluteLinkPath = path.join(base, relativeLinkPath)
node.url = `/${absoluteLinkPath
.replace(/^\//u, "")
.replace(/\.md$/u, "/")}`
}
})
}
return tree
}
}
63 changes: 22 additions & 41 deletions docs-build/setup-docs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fs from "fs"
import path from "path"
import { URL } from "url"
import "./build-system/build.js"
import { load, dump } from "js-yaml"

const dirname = path.dirname(new URL(import.meta.url).pathname)
setupDocs()
Expand All @@ -16,50 +17,30 @@ function setupDocs() {
}
const docsDir = path.resolve(dirname, "../docs")
for (const md of listup(docsDir, ".md")) {
const from = (
md.endsWith("README.md") ? md.replace(/README.md$/u, "index.md") : md
).replace(/\.md$/u, ".astro")
const to = path.resolve(buildDocsDir, path.relative(docsDir, from))
const to = path.resolve(
buildDocsDir,
path.relative(
docsDir,
md.endsWith("README.md") ? md.replace(/README.md$/u, "index.md") : md,
),
)
mkDirs(path.dirname(to))

if (md.endsWith("/playground.md")) {
fs.writeFileSync(
to,
`---
import ESLintPlayground from '../components/ESLintPlaygroundWrap.astro'
import MainLayout from '../layouts/MainLayout.astro'
const content = {
title: 'Playground',
astro: { headers: [] }
}
---
<MainLayout {content} hiddenRight>
<ESLintPlayground />
</MainLayout>
`,
"utf8",
)
continue
}
const content = fs.readFileSync(md, "utf8")
const frontmatter = /^---\n([\s\S]*?)\n---\n/u.exec(content)?.[1]
const data = frontmatter ? load(frontmatter) : {}
data.layout = `./${path.relative(path.dirname(to), mainLayoutPath)}`
data.markdownPath = `${to}`
const newFrontmatter = `---
${dump(data)}---

fs.writeFileSync(
to,
`---
import * as all from '${path.relative(path.dirname(to), md)}'
import MainLayout from '${path.relative(path.dirname(to), mainLayoutPath)}'
const content = await all.getHeaders().then(headers=>{
return {
astro: {headers}
}
})
const Md = all.Content
---
<MainLayout {content}>
<Md />
</MainLayout>
`,
"utf8",
)
<!-- markdownPath: ${to} -->
`
const pageContent = frontmatter
? content.replace(/^---\n[\s\S]*?\n---\n/u, newFrontmatter)
: `${newFrontmatter}\n${content}`

fs.writeFileSync(to, pageContent, "utf8")
}
}

Expand Down
94 changes: 94 additions & 0 deletions docs-build/src/components/ESLintCodeBlock.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<script>
import { onMount } from "svelte"
import ESLintEditor from "./eslint/ESLintEditor.svelte"
import { loadMonacoEditor } from "./eslint/scripts/monaco-loader.mjs"
import {
createLinter,
preprocess,
postprocess,
} from "./eslint/scripts/linter.mjs"

const linter = loadMonacoEditor()
.then(async () => {
const parser = await import("@typescript-eslint/parser")
if (typeof window !== "undefined") {
window.require.define("@typescript-eslint/parser", parser)
}
return window.waitSetupForAstroCompilerWasm
})
.then(() => {
return createLinter()
})

let code = ""
export let rules = {}
export let fix = false
let time = ""
let options = {
filename: "example.astro",
preprocess,
postprocess,
}
let showDiff = fix

function onLintedResult(evt) {
time = `${evt.detail.time}ms`
}

let slotRoot
onMount(() => {
code = slotRoot.textContent.trim()
})
$: blockHeight = `${Math.max(
120,
20 * (1 + code.split("\n").length) + 100,
)}px`
</script>

<div class="eslint-code-block-default-content" bind:this={slotRoot}>
<slot />
</div>

<div class="eslint-code-block-root" style:height={blockHeight}>
<ESLintEditor
{linter}
bind:code
config={{
parser: "astro-auto-eslint-parser",
parserOptions: {
ecmaVersion: 2020,
sourceType: "module",
parser: "@typescript-eslint/parser",
},
rules,
env: {
browser: true,
es2021: true,
},
}}
{options}
on:result={onLintedResult}
showDiff={showDiff && fix}
/>
<div class="eslint-code-block-tools">
{#if fix}
<label>
<input bind:checked={showDiff} type="checkbox" />
Show Diff
</label>
{/if}
<span style:margin-left="16px">{time}</span>
</div>
</div>

<style>
.eslint-code-block-default-content {
display: none;
}
.eslint-code-block-root {
height: 300px;
}
.eslint-code-block-tools {
text-align: end;
}
</style>
6 changes: 6 additions & 0 deletions docs-build/src/components/ESLintCodeBlockWrap.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
import ESLintCodeBlock from './ESLintCodeBlock.svelte'
---
<ESLintCodeBlock client:only="svelte" >
<slot></slot>
</ESLintCodeBlock>
2 changes: 1 addition & 1 deletion docs-build/src/components/ESLintPlaygroundWrap.astro
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
---
import ESLintPlayground from './ESLintPlayground.svelte'
---
<ESLintPlayground client:only />
<ESLintPlayground client:only="svelte" />
10 changes: 2 additions & 8 deletions docs-build/src/components/LeftSidebar/LeftSidebar.astro
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,7 @@ const sidebarSections = SIDEBAR.en.reduce((col, item, i) => {
<li>
<div class="nav-group">
<h2 class="nav-group-title">
{section.link
? <a href={`${baseUrl}${section.link}`} aria-current={`${currentPageMatch === section.link ? 'page' : 'false'}`} >
{section.text}
</a>
: section.text
}
{section.text}
</h2>
<ul>
{section.children.map((child) => (
Expand Down Expand Up @@ -97,8 +92,7 @@ const sidebarSections = SIDEBAR.en.reduce((col, item, i) => {
margin: 1px;
padding: 0.3rem 1rem;
}
.nav-link a,
.nav-group-title a
.nav-link a
{
font: inherit;
color: inherit;
Expand Down
13 changes: 8 additions & 5 deletions docs-build/src/components/PageContent/PageContent.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@
import MoreMenu from '../RightSidebar/MoreMenu.astro';
import TableOfContents from '../RightSidebar/TableOfContents.svelte';

const { content, githubEditUrl, hiddenRight } = Astro.props;
const { content, githubEditUrl, hiddenRight, hiddenTOC } = Astro.props;
const title = content.title;
const headers = content.astro.headers;
---

<article id="article" class:list={["content", { 'hidden-right': hiddenRight}]}>
<article id="article" class:list={["content", { 'hidden-right': hiddenRight, hiddenTOC}]}>
<section class="main-section">
<h1 class="content-title" id="overview">{title}</h1>
<nav class="block sm:hidden">
<TableOfContents client:media="(max-width: 50em)" {headers} />
</nav>
{
!hiddenTOC &&
<nav class="block sm:hidden">
<TableOfContents client:media="(max-width: 50em)" {headers} />
</nav>
}
<slot />
</section>
<nav class="block sm:hidden">
Expand Down
4 changes: 2 additions & 2 deletions docs-build/src/components/RightSidebar/RightSidebar.astro
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
import TableOfContents from './TableOfContents.svelte';
import MoreMenu from './MoreMenu.astro';
const { content, githubEditUrl } = Astro.props;
const { content, githubEditUrl, hiddenTOC } = Astro.props;
const headers = content.astro.headers;
---

<nav class="sidebar-nav" aria-labelledby="grid-right">
<div class="sidebar-nav-inner">
<TableOfContents client:media="(min-width: 50em)" {headers} />
{!hiddenTOC && <TableOfContents client:media="(min-width: 50em)" {headers} />}
<MoreMenu editHref={githubEditUrl} />
</div>
</nav>
Expand Down
31 changes: 29 additions & 2 deletions docs-build/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
import { rules } from "../../src/utils/rules"

const categories = [
"Possible Errors",
"Security Vulnerability",
"Best Practices",
"Stylistic Issues",
"Extension Rules",
"System",
] as const

export const SITE = {
title: "eslint-plugin-astro",
description: "ESLint plugin for Astro component.",
Expand Down Expand Up @@ -33,7 +44,23 @@ export const SIDEBAR = {
{ text: "Introduction", link: "" },
{ text: "User Guide", link: "user-guide/" },
{ text: "Demo", link: "playground/" },

{ text: "Rules", header: true, link: "rules/" },
{ text: "Rules", link: "rules/" },
...categories.flatMap((category) => {
const categoryRules = rules.filter(
(rule) => rule.meta.docs.category === category,
)
if (!categoryRules.length) {
return []
}
return [
{ text: category, header: true },
...categoryRules.map((rule) => {
return {
text: rule.meta.docs.ruleId,
link: `rules/${rule.meta.docs.ruleName}/`,
}
}),
]
}),
],
}
Loading