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
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"request": "launch",
"name": "Run Current File Tests",
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
"args": ["${fileBasenameNoExtension}"],
"args": ["${fileDirname}/${fileBasenameNoExtension}"],
"sourceMaps": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
Expand Down
39 changes: 39 additions & 0 deletions packages/astro-language/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{

Check failure on line 1 in packages/astro-language/package.json

View workflow job for this annotation

GitHub Actions / Lint

Property 'sideEffects' is required

Check failure on line 1 in packages/astro-language/package.json

View workflow job for this annotation

GitHub Actions / Lint

Property 'files' is required
"name": "@flint.fyi/astro-language",
"version": "0.0.1",
"description": "[Experimental] TypeScript language plugin for Flint.",
"repository": {
"type": "git",
"url": "https://github.com/JoshuaKGoldberg/flint",
"directory": "packages/ts"

Check failure on line 8 in packages/astro-language/package.json

View workflow job for this annotation

GitHub Actions / Lint

Directory does not match package.json directory
},
"license": "MIT",
"author": {
"name": "JoshuaKGoldberg",
"email": "npm@joshuakgoldberg.com"
},
"type": "module",
"exports": {
".": "./lib/index.js"
},
"dependencies": {
"@astrojs/compiler": "^2.13.0",
"@astrojs/ts-plugin": "^1.10.6",
"@flint.fyi/core": "workspace:",
"@flint.fyi/ts": "workspace:",
"@flint.fyi/ts-patch": "workspace:",
"@flint.fyi/volar-language": "workspace:",
"ts-api-utils": "^2.1.0",
"typescript": ">=5.9.3"
},
"devDependencies": {
"@flint.fyi/rule-tester": "workspace:"
},
"engines": {
"node": ">=24.0.0"
},
"publishConfig": {
"access": "public",
"provenance": true
}
}
50 changes: 50 additions & 0 deletions packages/astro-language/src/language.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { getLanguagePlugin } from "@astrojs/ts-plugin/dist/language.js";
import { parse } from "@astrojs/compiler/sync";

Check failure on line 2 in packages/astro-language/src/language.ts

View workflow job for this annotation

GitHub Actions / Lint

Expected "@astrojs/compiler/sync" to come before "@astrojs/ts-plugin/dist/language.js"
import { RootNode } from "@astrojs/compiler/types";

import { RuleReporter } from "@flint.fyi/core";

Check failure on line 5 in packages/astro-language/src/language.ts

View workflow job for this annotation

GitHub Actions / Lint

Extra spacing between "@astrojs/compiler/types" and "@flint.fyi/core" objects

import { setTSExtraSupportedExtensions } from "@flint.fyi/ts-patch";

Check failure on line 7 in packages/astro-language/src/language.ts

View workflow job for this annotation

GitHub Actions / Lint

Extra spacing between "@flint.fyi/core" and "@flint.fyi/ts-patch" objects
import { createVolarBasedLanguage } from "@flint.fyi/volar-language";

setTSExtraSupportedExtensions([".astro"]);

export interface AstroServices {
astroServices?: {
ast: RootNode;
reportComponent: RuleReporter<string>;
};
}

export const astroLanguage = createVolarBasedLanguage<AstroServices>(
(ts, options) => {

Check failure on line 20 in packages/astro-language/src/language.ts

View workflow job for this annotation

GitHub Actions / Lint

'options' is defined but never used

Check failure on line 20 in packages/astro-language/src/language.ts

View workflow job for this annotation

GitHub Actions / Lint

'ts' is defined but never used
return {
languagePlugins: getLanguagePlugin(),
prepareFile(
filePathAbsolute,
{ program, sourceFile },

Check failure on line 25 in packages/astro-language/src/language.ts

View workflow job for this annotation

GitHub Actions / Lint

'sourceFile' is defined but never used

Check failure on line 25 in packages/astro-language/src/language.ts

View workflow job for this annotation

GitHub Actions / Lint

'program' is defined but never used
volarLanguage,
sourceScript,
) {
const sourceText = sourceScript.snapshot.getText(
0,
sourceScript.snapshot.getLength(),
);
// TODO: report parsing errors?
const { ast } = parse(sourceText, { position: true });
return {
// TODO: first statement
firstStatementPosition: sourceText.length,
extraContext(reportTranslated) {
return {
astroServices: {
ast,
reportComponent: reportTranslated,
},
};
},
};
},
};
},
);
53 changes: 53 additions & 0 deletions packages/astro-language/src/rules/anyReturns.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import rule from "../../../ts/lib/rules/anyReturns.js";
import { astroLanguage } from "../language.js";
import { ruleTester } from "./ruleTester.js";

ruleTester.describe(astroLanguage.createRule(rule), {
invalid: [
{
code: `
---
function foo() {
return 1 as any
}
---
`,
snapshot: `
---
function foo() {
return 1 as any
~~~~~~~~~~~~~~~
Unsafe return of a value of type \`any\`.
}
---
`,
},
{
code: `
{
function foo() {
return 1 as any
}
}
`,
snapshot: `
{
function foo() {
return 1 as any
~~~~~~~~~~~~~~~
Unsafe return of a value of type \`any\`.
}
}
`,
},
],
valid: [
`
---
function foo() {
return 1
}
---
`,
],
});
11 changes: 11 additions & 0 deletions packages/astro-language/src/rules/ruleTester.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createRuleTesterTSHost, RuleTester } from "@flint.fyi/rule-tester";
import { describe, it } from "vitest";

export const ruleTester = new RuleTester({
describe,
it,
defaults: {
fileName: "file.astro",
},
host: createRuleTesterTSHost(import.meta.dirname),
});
57 changes: 57 additions & 0 deletions packages/astro-language/src/rules/setHtmlDirectives.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import rule from "./setHtmlDirectives.js";
import { ruleTester } from "./ruleTester.js";

ruleTester.describe(rule, {
invalid: [
{
code: `
---
let string = 'this string contains some <strong>HTML!!!</strong>'
---

<p set:html={string}></p>
`,
snapshot: `
---
let string = 'this string contains some <strong>HTML!!!</strong>'
---

<p set:html={string}></p>
~~~~~~~~
TODO: don't use set:html to reduce XSS risk
`,
},
{
code: `
<div>
<p set:html=\`this string contains some <strong>HTML!!!</strong>\`></p>
</div>
`,
snapshot: `
<div>
<p set:html=\`this string contains some <strong>HTML!!!</strong>\`></p>
~~~~~~~~
TODO: don't use set:html to reduce XSS risk
</div>
`,
},
],
valid: [
`
---
let string = 'this string contains some <strong>HTML!!!</strong>'
---

<p set:text={string}></p>
`,
"<p set:text=`this string contains some <strong>HTML!!!</strong>`></p>",
`
---
let string = 'this string contains some <strong>HTML!!!</strong>'
---

<p>{string}</p>
`,
"<p>{`this string contains some <strong>HTML!!!</strong>`}</p>",
],
});
42 changes: 42 additions & 0 deletions packages/astro-language/src/rules/setHtmlDirectives.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { astroLanguage } from "../language.js";

export default astroLanguage.createRule({
about: {
description: "TODO",
id: "setHtmlDirectives",
preset: "logical",
},
messages: {
setHtml: {
primary: `TODO: don't use set:html to reduce XSS risk`,
secondary: ["TODO"],
suggestions: ["TODO"],
},
},
setup(context) {
const { astroServices } = context;
if (astroServices == null) {
return undefined;
}
astroServices.ast.children.forEach(function visit(node) {
if ("attributes" in node) {
for (const attr of node.attributes) {
if (attr.name === "set:html") {
const begin = attr.position!.start.offset;
astroServices.reportComponent({
message: "setHtml",
range: {
begin,
end: begin + attr.name.length,
},
});
}
}
}
if ("children" in node) {
node.children.forEach(visit);
}
});
return undefined;
},
});
15 changes: 15 additions & 0 deletions packages/astro-language/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"outDir": "lib",
"rootDir": "src"
},
"extends": "../../tsconfig.base.json",
"include": ["src"],
"references": [
{ "path": "../core" },
{ "path": "../ts" },
{ "path": "../rule-tester" },
{ "path": "../ts-patch" },
{ "path": "../volar-language" }
]
}
65 changes: 65 additions & 0 deletions packages/core/src/host/createFSBackedLinterHost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import fs from "node:fs";
import path from "node:path";
import { LinterHost, LinterHostDirectoryEntry } from "../types/host.js";
import { normalizePath } from "./normalizePath.js";

export function createFSBackedLinterHost(cwd: string): LinterHost {
cwd = normalizePath(cwd);

return {
getCurrentDirectory() {
return cwd;
},
stat(pathAbsolute) {
try {
const stat = fs.statSync(pathAbsolute);
if (stat.isDirectory()) {
return "directory";
}
if (stat.isFile()) {
return "file";
}
} catch {}
return undefined;
},
readDirectory(directoryPathAbsolute) {
const result: LinterHostDirectoryEntry[] = [];
const dirents = fs.readdirSync(directoryPathAbsolute, {
withFileTypes: true,
});

for (let entry of dirents) {
let stat = entry as Pick<typeof entry, "isFile" | "isDirectory">;
if (entry.isSymbolicLink()) {
try {
stat = fs.statSync(path.join(directoryPathAbsolute, entry.name));
} catch {
continue;
}
}
if (stat.isDirectory()) {
result.push({ type: "directory", name: entry.name });
}
if (stat.isFile()) {
result.push({ type: "file", name: entry.name });
}
}

return result;
},
readFile(filePathAbsolute) {
return fs.readFileSync(filePathAbsolute, "utf8");
},
// TODO
watchFile(filePathAbsolute, callback) {
return {
[Symbol.dispose]() {},
};
},
watchDirectory(directoryPathAbsolute, recursive, callback) {
return {
[Symbol.dispose]() {},
};
},
};
}
Loading
Loading