Skip to content

Commit

Permalink
feat: add html stack
Browse files Browse the repository at this point in the history
Adds prettier as a formatter for .html and Vue files.
Uses ESLint and Stylelint for Vue linting, choosing ESLint as a default.

Resolves: #7
  • Loading branch information
joao10lima committed Sep 1, 2021
1 parent f2f73ad commit f91653a
Show file tree
Hide file tree
Showing 7 changed files with 334 additions and 2 deletions.
43 changes: 43 additions & 0 deletions plugins/stack/html/formatters.ts
@@ -0,0 +1,43 @@
import { IntrospectFn } from "../deps.ts";
import {
introspect as introspectPrettier,
Prettier,
} from "../_shared/prettier/mod.ts";

// deno-lint-ignore no-empty-interface
interface Deno {}

export type Formatters = {
prettier?: Prettier | null;
deno?: Deno;
} | null;

function anyValue(records: Record<string, unknown>): boolean {
return Object.values(records).some((v) => v);
}

export const introspect: IntrospectFn<Formatters> = async (context) => {
const logger = context.getLogger("html");
logger.debug("detecting formatter");

const prettier = await introspectPrettier(context);
if (prettier !== null) {
logger.debug("detected Prettier");
}

const formatters: Formatters = {
prettier,
};

if (anyValue(formatters)) return formatters;

if (context.suggestDefault) {
logger.warning("No Vue or Html formatter detected, using Prettier");
return {
prettier: { name: "prettier", hasIgnoreFile: false },
};
}

logger.debug("no supported formatter detected");
return null;
};
56 changes: 56 additions & 0 deletions plugins/stack/html/linters.ts
@@ -0,0 +1,56 @@
import { IntrospectFn } from "../deps.ts";

import {
ESLint,
introspect as introspectESLint,
} from "../_shared/eslint/mod.ts";

import {
introspect as introspectStylelint,
Stylelint,
} from "../_shared/stylelint/mod.ts";

// deno-lint-ignore no-empty-interface
interface Deno {}

export type Linters = {
eslint?: ESLint | null;
stylelint?: Stylelint | null;
deno?: Deno;
} | null;

function anyValue(records: Record<string, unknown>): boolean {
return Object.values(records).some((v) => v);
}

export const introspect: IntrospectFn<Linters> = async (context) => {
const logger = context.getLogger("html");
logger.debug("detecting linter");

const eslint = await introspectESLint(context);
if (eslint !== null) {
logger.debug("detected ESLint");
}

const styleLint = await introspectStylelint(context);
if (styleLint !== null) {
logger.debug("detected stylelint");
}

const linters = {
eslint,
styleLint,
};

if (anyValue(linters)) return linters;

if (context.suggestDefault) {
logger.warning("No Vue or HTML linter detected, using ESLint");
return {
eslint: { name: "eslint", hasIgnoreFile: false },
};
}

logger.debug("no supported linter detected");
return null;
};
88 changes: 88 additions & 0 deletions plugins/stack/html/mod.test.ts
@@ -0,0 +1,88 @@
import { context } from "../../../src/plugin/mod.ts";
import { assertEquals, deepMerge, WalkEntry } from "../../../deps.ts";

import { introspector } from "./mod.ts";

Deno.test("Plugins > Html has stylelint and eslint configured", async () => {
const fakeContext = deepMerge(
context,
{
files: {
// deno-lint-ignore require-await
includes: async (glob: string): Promise<boolean> => {
if (glob === "**/.eslintrc.{js,cjs,yaml,yml,json}") {
return true;
}
return false;
},
each: async function* (glob: string): AsyncIterableIterator<WalkEntry> {
if (glob === "**/*.{html,vue}") {
yield {
name: "test.html",
path: "fake-path",
isFile: true,
isSymlink: false,
isDirectory: false,
};
yield {
name: "test.vue",
path: "fake-path",
isFile: true,
isSymlink: false,
isDirectory: false,
};
}
if (glob === "**/package.json") {
yield {
name: "package.json",
path: "fake-path",
isFile: true,
isSymlink: false,
isDirectory: false,
};
}
if (glob === "**/.eslintrc.{js,cjs,yaml,yml,json}") {
yield {
name: ".eslintrc.js",
path: "fake-path",
isFile: true,
isSymlink: false,
isDirectory: false,
};
}
if (glob === "**/.eslintignore") {
yield {
name: ".eslintignore",
path: "fake-path",
isFile: true,
isSymlink: false,
isDirectory: false,
};
}
return;
},
// deno-lint-ignore require-await
readJSON: async (path: string): Promise<Record<string, unknown>> => {
const deps = { stylelint: "1.0.0", eslint: "7.2.2" };
if (path === "fake-path") {
return { devDependencies: deps };
}
return {};
},
},
},
);
const result = await introspector.introspect(
fakeContext,
);

assertEquals(result, {
runtime: { name: "node", version: "16" },
packageManager: { name: "npm" },
linters: {
eslint: { name: "eslint", hasIgnoreFile: false },
styleLint: { name: "stylelint" },
},
formatters: { prettier: { name: "prettier", hasIgnoreFile: false } },
});
});
81 changes: 81 additions & 0 deletions plugins/stack/html/mod.ts
@@ -0,0 +1,81 @@
import { Introspector } from "../deps.ts";
import { Formatters, introspect as introspectFormatter } from "./formatters.ts";
import { introspect as introspectLinter, Linters } from "./linters.ts";
import {
introspect as introspectRuntime,
Runtime,
} from "../javascript/runtime.ts";
import {
introspect as introspectPackageManager,
NodePackageManager,
} from "../_shared/node_package_manager/mod.ts";

// Available package managers
type PackageManager = NodePackageManager | null;

/**
* Introspected information about a project with JavaScript
*/
export default interface HtmlProject {
/**
* Which package manager is used in the project
*
* A package manager may not exist in a JavaScript project.
* For example, a project that uses Deno doesn't need to use
* npm, yarn or any other package manager.
*/
packageManager?: PackageManager;
/**
* Which runtime the project uses
*/
runtime: Runtime;
/**
* Which linter the project uses, if any
*/
linters: Linters;
/**
* Which formatter the project uses, if any
*/
formatters: Formatters;
}

export const introspector: Introspector<HtmlProject> = {
detect: async (context) => {
return await context.files.includes("**/*.{html,vue}");
},
introspect: async (context) => {
const logger = context.getLogger("html");

// Runtime
logger.debug("detecting runtime");
const runtime = await introspectRuntime(context);
logger.debug(`detected runtime "${runtime.name}"`);
if (runtime.name === "deno") {
return {
runtime,
linters: {
deno: {},
},
formatters: {
deno: {},
},
};
}

// Package manager
logger.debug("detecting package manager");
const packageManager = await introspectPackageManager(context);
logger.debug(`detected package manager "${packageManager.name}"`);
// Linter and Formatter
const linters = await introspectLinter(context);
logger.debug(`detecting linters for html`);
const formatters = await introspectFormatter(context);

return {
runtime,
packageManager,
linters,
formatters,
};
},
};
8 changes: 6 additions & 2 deletions plugins/stack/mod.ts
@@ -1,20 +1,24 @@
import { introspector as CssIntrospector } from "./css/mod.ts";
import { introspector as JavaScriptIntrospector } from "./javascript/mod.ts";
import { introspector as PythonIntrospector } from "./python/mod.ts";
import { introspector as HtmlIntrospector } from "./html/mod.ts";

import type CSSProject from "./css/mod.ts";
import type JavaScriptProject from "./javascript/mod.ts";
import type PythonProject from "./python/mod.ts";
import type HtmlProject from "./html/mod.ts";

export type ProjectData =
| CSSProject
| JavaScriptProject
| PythonProject;
| PythonProject
| HtmlProject;

export type { CSSProject, JavaScriptProject, PythonProject };
export type { CSSProject, HtmlProject, JavaScriptProject, PythonProject };

export const introspectors = [
{ name: "css", ...CssIntrospector },
{ name: "javascript", ...JavaScriptIntrospector },
{ name: "python", ...PythonIntrospector },
{ name: "html", ...HtmlIntrospector },
];
29 changes: 29 additions & 0 deletions templates/github/html/format.yml
@@ -0,0 +1,29 @@
<% if (it.runtime.name === "node" && it.formatters) { -%>
name: Format HTML
on:
pull_request:
paths:
- '**.html'
- '**.vue'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '<%= it.runtime.version %>'
cache: '<%= it.packageManager.name %>'

<%_ let installCmd; -%>
<%_ if (it.packageManager.name === "npm") { -%>
<%_ installCmd = "npm ci" %>
<%_ } else { -%>
<%_ installCmd = "yarn" %>
<% } -%>

- run: <%= installCmd %>
<%_ if (it.formatters.prettier) { %>
- run: npx prettier --no-error-on-unmatched-pattern --check "**/*.vue" "**/*.html"
<% } -%>
<% } -%>
31 changes: 31 additions & 0 deletions templates/github/html/lint.yml
@@ -0,0 +1,31 @@
<% if (it.runtime.name === "node" && it.linters) { -%>
name: Lint Vue
on:
pull_request:
paths:
- '**.vue'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '<%= it.runtime.version %>'
cache: '<%= it.packageManager.name %>'

<%_ let installCmd; -%>
<%_ if (it.packageManager.name === "npm") { -%>
<%_ installCmd = "npm ci" %>
<%_ } else { -%>
<%_ installCmd = "yarn" %>
<% } -%>

- run: <%= installCmd %>
<%_ if (it.linters.eslint) { %>
- run: npx eslint --ext .vue .
<% } -%>
<%_ if (it.linters.styleLint) { %>
- run: npx stylelint --aei **/*.vue
<% } -%>
<% } -%>

0 comments on commit f91653a

Please sign in to comment.