mirror of
https://github.com/denoland/deno.git
synced 2025-07-07 21:35:07 +00:00
feat(unstable): support comments in lint plugin (#29189)
This PR adds support for comments in the AST for lint plugins. - The `Program` node has a `comments` field pointing to an array of all comments. - `SourceCode.getAllComments()`: Returns all comments (same as `program.comments`) - `SourceCode.getCommentsBefore(node)`: Get all comments before this Node - `SourceCode.getCommentsAfter(node)`: Get all comments after this Node - `SourceCode.getCommentsInside(node)`: Get all comments inside this Node ESLint docs: - https://eslint.org/docs/latest/extend/custom-rules#accessing-the-source-code - https://eslint.org/docs/latest/extend/custom-rules#accessing-comments
This commit is contained in:
parent
e1e67a703c
commit
c015b8affd
11 changed files with 352 additions and 7 deletions
|
@ -263,6 +263,79 @@ export class SourceCode {
|
|||
return ancestors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Array<Deno.lint.LineComment | Deno.lint.BlockComment>}
|
||||
*/
|
||||
getAllComments() {
|
||||
materializeComments(this.#ctx);
|
||||
return this.#ctx.comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Deno.lint.Node} node
|
||||
* @returns {Array<Deno.lint.LineComment | Deno.lint.BlockComment>}
|
||||
*/
|
||||
getCommentsBefore(node) {
|
||||
materializeComments(this.#ctx);
|
||||
|
||||
/** @type {Array<Deno.lint.LineComment | Deno.lint.BlockComment>} */
|
||||
const before = [];
|
||||
|
||||
const { comments } = this.#ctx;
|
||||
for (let i = 0; i < comments.length; i++) {
|
||||
const comment = comments[i];
|
||||
if (comment.range[0] <= node.range[0]) {
|
||||
before.push(comment);
|
||||
}
|
||||
}
|
||||
|
||||
return before;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Deno.lint.Node} node
|
||||
* @returns {Array<Deno.lint.LineComment | Deno.lint.BlockComment>}
|
||||
*/
|
||||
getCommentsAfter(node) {
|
||||
materializeComments(this.#ctx);
|
||||
|
||||
/** @type {Array<Deno.lint.LineComment | Deno.lint.BlockComment>} */
|
||||
const after = [];
|
||||
|
||||
const { comments } = this.#ctx;
|
||||
for (let i = 0; i < comments.length; i++) {
|
||||
const comment = comments[i];
|
||||
if (comment.range[0] >= node.range[1]) {
|
||||
after.push(comment);
|
||||
}
|
||||
}
|
||||
|
||||
return after;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Deno.lint.Node} node
|
||||
* @returns {Array<Deno.lint.LineComment | Deno.lint.BlockComment>}
|
||||
*/
|
||||
getCommentsInside(node) {
|
||||
materializeComments(this.#ctx);
|
||||
|
||||
/** @type {Array<Deno.lint.LineComment | Deno.lint.BlockComment>} */
|
||||
const inside = [];
|
||||
|
||||
const { comments } = this.#ctx;
|
||||
for (let i = 0; i < comments.length; i++) {
|
||||
const comment = comments[i];
|
||||
if (
|
||||
comment.range[0] >= node.range[0] && comment.range[1] <= node.range[1]
|
||||
) {
|
||||
inside.push(comment);
|
||||
}
|
||||
}
|
||||
|
||||
return inside;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
|
@ -345,6 +418,34 @@ export class Context {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AstContext} ctx
|
||||
*/
|
||||
function materializeComments(ctx) {
|
||||
const { buf, commentsOffset, comments, strTable } = ctx;
|
||||
|
||||
let offset = commentsOffset;
|
||||
const count = readU32(buf, offset);
|
||||
offset += 4;
|
||||
|
||||
if (comments.length === count) return;
|
||||
|
||||
while (offset < buf.length && comments.length < count) {
|
||||
const kind = buf[offset];
|
||||
offset++;
|
||||
const spanId = readU32(buf, offset);
|
||||
offset += 4;
|
||||
const strId = readU32(buf, offset);
|
||||
offset += 4;
|
||||
|
||||
comments.push({
|
||||
type: kind === 0 ? "Line" : "Block",
|
||||
range: readSpan(ctx, spanId),
|
||||
value: getString(strTable, strId),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Deno.lint.Plugin[]} plugins
|
||||
* @param {string[]} exclude
|
||||
|
@ -489,6 +590,7 @@ class FacadeNode {
|
|||
|
||||
/** @type {Set<number>} */
|
||||
const appliedGetters = new Set();
|
||||
let hasCommenstGetter = false;
|
||||
|
||||
/**
|
||||
* Add getters for all potential properties found in the message.
|
||||
|
@ -515,6 +617,16 @@ function setNodeGetters(ctx) {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasCommenstGetter) {
|
||||
hasCommenstGetter = true;
|
||||
Object.defineProperty(FacadeNode.prototype, "comments", {
|
||||
get() {
|
||||
materializeComments(ctx);
|
||||
return ctx.comments;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -994,6 +1106,7 @@ function createAstContext(buf, token) {
|
|||
|
||||
// The buffer has a few offsets at the end which allows us to easily
|
||||
// jump to the relevant sections of the message.
|
||||
const commentsOffset = readU32(buf, buf.length - 28);
|
||||
const propsOffset = readU32(buf, buf.length - 24);
|
||||
const spansOffset = readU32(buf, buf.length - 20);
|
||||
const typeMapOffset = readU32(buf, buf.length - 16);
|
||||
|
@ -1060,7 +1173,9 @@ function createAstContext(buf, token) {
|
|||
rootOffset,
|
||||
spansOffset,
|
||||
propsOffset,
|
||||
commentsOffset,
|
||||
nodes: new Map(),
|
||||
comments: [],
|
||||
strTableOffset,
|
||||
strByProp,
|
||||
strByType,
|
||||
|
|
2
cli/js/40_lint_types.d.ts
vendored
2
cli/js/40_lint_types.d.ts
vendored
|
@ -8,6 +8,8 @@ export interface AstContext {
|
|||
nodes: Map<number, Deno.lint.Node>;
|
||||
spansOffset: number;
|
||||
propsOffset: number;
|
||||
commentsOffset: number;
|
||||
comments: Array<Deno.lint.LineComment | Deno.lint.BlockComment>;
|
||||
strByType: number[];
|
||||
strByProp: number[];
|
||||
typeByStr: Map<string, number>;
|
||||
|
|
|
@ -142,6 +142,19 @@ struct Node {
|
|||
parent: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CommentKind {
|
||||
Line,
|
||||
Block,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Comment {
|
||||
kind: CommentKind,
|
||||
str_id: usize,
|
||||
span_id: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SerializeCtx {
|
||||
root_idx: Index,
|
||||
|
@ -161,6 +174,9 @@ pub struct SerializeCtx {
|
|||
kind_name_map: Vec<usize>,
|
||||
/// Maps prop id to string id
|
||||
prop_name_map: Vec<usize>,
|
||||
|
||||
/// Comments
|
||||
comments: Vec<Comment>,
|
||||
}
|
||||
|
||||
/// This is the internal context used to allocate and fill the buffer. The point
|
||||
|
@ -185,6 +201,7 @@ impl SerializeCtx {
|
|||
str_table: StringTable::new(),
|
||||
kind_name_map: vec![0; kind_size],
|
||||
prop_name_map: vec![0; prop_size],
|
||||
comments: vec![],
|
||||
};
|
||||
|
||||
let empty_str = ctx.str_table.insert("");
|
||||
|
@ -285,12 +302,7 @@ impl SerializeCtx {
|
|||
where
|
||||
K: Into<u8> + Display + Clone,
|
||||
{
|
||||
let (start, end) = if *span == DUMMY_SP {
|
||||
(0, 0)
|
||||
} else {
|
||||
// -1 is because swc stores spans 1-indexed
|
||||
(span.lo.0 - 1, span.hi.0 - 1)
|
||||
};
|
||||
let (start, end) = span_to_value(span);
|
||||
self.append_inner(kind, start, end)
|
||||
}
|
||||
|
||||
|
@ -559,6 +571,21 @@ impl SerializeCtx {
|
|||
self.write_ref_vec(prop, parent_ref, actual)
|
||||
}
|
||||
|
||||
pub fn write_comment(&mut self, kind: CommentKind, value: &str, span: &Span) {
|
||||
let str_id = self.str_table.insert(value);
|
||||
|
||||
let span_id = self.spans.len() / 2;
|
||||
let (span_lo, span_hi) = span_to_value(span);
|
||||
self.spans.push(span_lo);
|
||||
self.spans.push(span_hi);
|
||||
|
||||
self.comments.push(Comment {
|
||||
kind,
|
||||
str_id,
|
||||
span_id,
|
||||
});
|
||||
}
|
||||
|
||||
/// Serialize all information we have into a buffer that can be sent to JS.
|
||||
/// It has the following structure:
|
||||
///
|
||||
|
@ -629,10 +656,24 @@ impl SerializeCtx {
|
|||
let offset_props = buf.len();
|
||||
buf.append(&mut self.field_buf);
|
||||
|
||||
// Serialize comments
|
||||
let offset_comments = buf.len();
|
||||
append_usize(&mut buf, self.comments.len());
|
||||
for comment in &self.comments {
|
||||
let kind = match comment.kind {
|
||||
CommentKind::Line => 0,
|
||||
CommentKind::Block => 1,
|
||||
};
|
||||
buf.push(kind);
|
||||
append_usize(&mut buf, comment.span_id);
|
||||
append_usize(&mut buf, comment.str_id);
|
||||
}
|
||||
|
||||
// Putting offsets of relevant parts of the buffer at the end. This
|
||||
// allows us to hop to the relevant part by merely looking at the last
|
||||
// for values in the message. Each value represents an offset into the
|
||||
// buffer.
|
||||
append_usize(&mut buf, offset_comments);
|
||||
append_usize(&mut buf, offset_props);
|
||||
append_usize(&mut buf, offset_spans);
|
||||
append_usize(&mut buf, offset_kind_map);
|
||||
|
@ -643,3 +684,12 @@ impl SerializeCtx {
|
|||
buf
|
||||
}
|
||||
}
|
||||
|
||||
fn span_to_value(span: &Span) -> (u32, u32) {
|
||||
if *span == DUMMY_SP {
|
||||
(0, 0)
|
||||
} else {
|
||||
// -1 is because swc stores spans 1-indexed
|
||||
(span.lo.0 - 1, span.hi.0 - 1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ use deno_ast::view::VarDeclKind;
|
|||
use deno_ast::ParsedSource;
|
||||
|
||||
use super::buffer::AstBufSerializer;
|
||||
use super::buffer::CommentKind;
|
||||
use super::buffer::NodeRef;
|
||||
use super::ts_estree::AstNode;
|
||||
use super::ts_estree::MethodKind as TsEstreeMethodKind;
|
||||
|
@ -134,6 +135,14 @@ pub fn serialize_swc_to_buffer(
|
|||
}
|
||||
}
|
||||
|
||||
for comment in parsed_source.comments().get_vec() {
|
||||
let kind = match comment.kind {
|
||||
deno_ast::swc::common::comments::CommentKind::Line => CommentKind::Line,
|
||||
deno_ast::swc::common::comments::CommentKind::Block => CommentKind::Block,
|
||||
};
|
||||
ctx.write_comment(kind, &comment.text, &comment.span);
|
||||
}
|
||||
|
||||
ctx.map_utf8_spans_to_utf16(utf16_map);
|
||||
ctx.serialize()
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ use deno_ast::swc::common::Span;
|
|||
use deno_ast::view::TruePlusMinus;
|
||||
|
||||
use super::buffer::AstBufSerializer;
|
||||
use super::buffer::CommentKind;
|
||||
use super::buffer::NodeRef;
|
||||
use super::buffer::SerializeCtx;
|
||||
use crate::util::text_encoding::Utf16Map;
|
||||
|
@ -2890,6 +2891,10 @@ impl TsEsTreeBuilder {
|
|||
_ => self.ctx.write_undefined(prop),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_comment(&mut self, kind: CommentKind, value: &str, span: &Span) {
|
||||
self.ctx.write_comment(kind, value, span);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
48
cli/tsc/dts/lib.deno.unstable.d.ts
vendored
48
cli/tsc/dts/lib.deno.unstable.d.ts
vendored
|
@ -1406,6 +1406,27 @@ declare namespace Deno {
|
|||
* current node.
|
||||
*/
|
||||
getAncestors(node: Node): Node[];
|
||||
|
||||
/**
|
||||
* Get all comments inside the source.
|
||||
*/
|
||||
getAllComments(): Array<LineComment | BlockComment>;
|
||||
|
||||
/**
|
||||
* Get leading comments before a node.
|
||||
*/
|
||||
getCommentsBefore(node: Node): Array<LineComment | BlockComment>;
|
||||
|
||||
/**
|
||||
* Get trailing comments after a node.
|
||||
*/
|
||||
getCommentsAfter(node: Node): Array<LineComment | BlockComment>;
|
||||
|
||||
/**
|
||||
* Get comments inside a node.
|
||||
*/
|
||||
getCommentsInside(node: Node): Array<LineComment | BlockComment>;
|
||||
|
||||
/**
|
||||
* Get the full source code.
|
||||
*/
|
||||
|
@ -1532,6 +1553,7 @@ declare namespace Deno {
|
|||
range: Range;
|
||||
sourceType: "module" | "script";
|
||||
body: Statement[];
|
||||
comments: Array<LineComment | BlockComment>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4335,6 +4357,28 @@ declare namespace Deno {
|
|||
| TSUnknownKeyword
|
||||
| TSVoidKeyword;
|
||||
|
||||
/**
|
||||
* A single line comment
|
||||
* @category Linter
|
||||
* @experimental
|
||||
*/
|
||||
export interface LineComment {
|
||||
type: "Line";
|
||||
range: Range;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A potentially multi-line block comment
|
||||
* @category Linter
|
||||
* @experimental
|
||||
*/
|
||||
export interface BlockComment {
|
||||
type: "Block";
|
||||
range: Range;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Union type of all possible AST nodes
|
||||
* @category Linter
|
||||
|
@ -4394,7 +4438,9 @@ declare namespace Deno {
|
|||
| TSIndexSignature
|
||||
| TSTypeAnnotation
|
||||
| TSTypeParameterDeclaration
|
||||
| TSTypeParameter;
|
||||
| TSTypeParameter
|
||||
| LineComment
|
||||
| BlockComment;
|
||||
|
||||
export {}; // only export exports
|
||||
}
|
||||
|
|
6
tests/specs/lint/lint_plugin_comments/__test__.jsonc
Normal file
6
tests/specs/lint/lint_plugin_comments/__test__.jsonc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"tempDir": true,
|
||||
"args": "lint main.ts",
|
||||
"output": "comments.out",
|
||||
"exitCode": 0
|
||||
}
|
67
tests/specs/lint/lint_plugin_comments/comments.out
Normal file
67
tests/specs/lint/lint_plugin_comments/comments.out
Normal file
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
program: [
|
||||
{ type: "Line", range: [ 0, 14 ], value: " before line" },
|
||||
{
|
||||
type: "Block",
|
||||
range: [ 15, 38 ],
|
||||
value: "*\n * before block\n "
|
||||
},
|
||||
{ type: "Line", range: [ 58, 72 ], value: " inside line" },
|
||||
{
|
||||
type: "Block",
|
||||
range: [ 75, 102 ],
|
||||
value: "*\n * inside block\n "
|
||||
},
|
||||
{ type: "Line", range: [ 106, 119 ], value: " after line" },
|
||||
{
|
||||
type: "Block",
|
||||
range: [ 120, 142 ],
|
||||
value: "*\n * after block\n "
|
||||
}
|
||||
],
|
||||
all: [
|
||||
{ type: "Line", range: [ 0, 14 ], value: " before line" },
|
||||
{
|
||||
type: "Block",
|
||||
range: [ 15, 38 ],
|
||||
value: "*\n * before block\n "
|
||||
},
|
||||
{ type: "Line", range: [ 58, 72 ], value: " inside line" },
|
||||
{
|
||||
type: "Block",
|
||||
range: [ 75, 102 ],
|
||||
value: "*\n * inside block\n "
|
||||
},
|
||||
{ type: "Line", range: [ 106, 119 ], value: " after line" },
|
||||
{
|
||||
type: "Block",
|
||||
range: [ 120, 142 ],
|
||||
value: "*\n * after block\n "
|
||||
}
|
||||
],
|
||||
before: [
|
||||
{ type: "Line", range: [ 0, 14 ], value: " before line" },
|
||||
{
|
||||
type: "Block",
|
||||
range: [ 15, 38 ],
|
||||
value: "*\n * before block\n "
|
||||
}
|
||||
],
|
||||
after: [
|
||||
{ type: "Line", range: [ 106, 119 ], value: " after line" },
|
||||
{
|
||||
type: "Block",
|
||||
range: [ 120, 142 ],
|
||||
value: "*\n * after block\n "
|
||||
}
|
||||
],
|
||||
inside: [
|
||||
{ type: "Line", range: [ 58, 72 ], value: " inside line" },
|
||||
{
|
||||
type: "Block",
|
||||
range: [ 75, 102 ],
|
||||
value: "*\n * inside block\n "
|
||||
}
|
||||
]
|
||||
}
|
||||
Checked 1 file
|
5
tests/specs/lint/lint_plugin_comments/deno.json
Normal file
5
tests/specs/lint/lint_plugin_comments/deno.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"lint": {
|
||||
"plugins": ["./plugin.ts"]
|
||||
}
|
||||
}
|
17
tests/specs/lint/lint_plugin_comments/main.ts
Normal file
17
tests/specs/lint/lint_plugin_comments/main.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
// before line
|
||||
/**
|
||||
* before block
|
||||
*/
|
||||
function foo() {
|
||||
// inside line
|
||||
/**
|
||||
* inside block
|
||||
*/
|
||||
}
|
||||
|
||||
// after line
|
||||
/**
|
||||
* after block
|
||||
*/
|
||||
|
||||
foo();
|
23
tests/specs/lint/lint_plugin_comments/plugin.ts
Normal file
23
tests/specs/lint/lint_plugin_comments/plugin.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
export default {
|
||||
name: "foo",
|
||||
rules: {
|
||||
foo: {
|
||||
create(ctx) {
|
||||
let program: Array<Deno.lint.LineComment | Deno.lint.BlockComment> = [];
|
||||
return {
|
||||
Program(node) {
|
||||
program = node.comments;
|
||||
},
|
||||
FunctionDeclaration(node) {
|
||||
const all = ctx.sourceCode.getAllComments();
|
||||
const before = ctx.sourceCode.getCommentsBefore(node);
|
||||
const after = ctx.sourceCode.getCommentsAfter(node);
|
||||
const inside = ctx.sourceCode.getCommentsInside(node);
|
||||
|
||||
console.log({ program, all, before, after, inside });
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies Deno.lint.Plugin;
|
Loading…
Add table
Add a link
Reference in a new issue