diff --git a/apps/oxlint/src-js/generated/deserialize.js b/apps/oxlint/src-js/generated/deserialize.js index 99612460a6750..4ab9afba16db0 100644 --- a/apps/oxlint/src-js/generated/deserialize.js +++ b/apps/oxlint/src-js/generated/deserialize.js @@ -48,6 +48,7 @@ function deserializeProgram(pos) { body: null, sourceType: deserializeModuleKind(pos + 125), hashbang: null, + comments: deserializeVecComment(pos + 24), start: 0, end, range: [0, end], diff --git a/apps/oxlint/src-js/plugins/source_code.ts b/apps/oxlint/src-js/plugins/source_code.ts index 49a2d29e6facb..d306e0a44a6e2 100644 --- a/apps/oxlint/src-js/plugins/source_code.ts +++ b/apps/oxlint/src-js/plugins/source_code.ts @@ -159,7 +159,11 @@ export const SOURCE_CODE = Object.freeze({ * @returns Array of `Comment`s in occurrence order. */ getAllComments(): Comment[] { - throw new Error('`sourceCode.getAllComments` not implemented yet'); // TODO + if (ast === null) initAst(); + // TODO: Deserializing strings is expensive, make this access lazy + // @ts-expect-error types are generated from `Program` attributes + // which are also twinned with ESTree generation so can't touch it + return ast.comments; }, /** diff --git a/apps/oxlint/test/e2e.test.ts b/apps/oxlint/test/e2e.test.ts index 943cdc2158a44..f4ef6e785c397 100644 --- a/apps/oxlint/test/e2e.test.ts +++ b/apps/oxlint/test/e2e.test.ts @@ -216,4 +216,8 @@ describe('oxlint CLI', () => { await fs.writeFile(fixtureFilePath, codeBefore); } }); + + it('SourceCode.getAllComments() should return all comments', async () => { + await testFixture('getAllComments'); + }); }); diff --git a/apps/oxlint/test/fixtures/getAllComments/.oxlintrc.json b/apps/oxlint/test/fixtures/getAllComments/.oxlintrc.json new file mode 100644 index 0000000000000..54872bf630aa5 --- /dev/null +++ b/apps/oxlint/test/fixtures/getAllComments/.oxlintrc.json @@ -0,0 +1,6 @@ +{ + "jsPlugins": ["./plugin.ts"], + "rules": { + "test-getAllComments/test-getAllComments": "error" + } +} diff --git a/apps/oxlint/test/fixtures/getAllComments/files/test.js b/apps/oxlint/test/fixtures/getAllComments/files/test.js new file mode 100644 index 0000000000000..176b3574f98e4 --- /dev/null +++ b/apps/oxlint/test/fixtures/getAllComments/files/test.js @@ -0,0 +1,12 @@ +// Line comment 1 +const x = 1; /* Block comment 1 */ + +/** + * JSDoc comment + */ +export function foo() { + // Line comment 2 + return x; // Line comment 3 +} + +/* Block comment 2 */ diff --git a/apps/oxlint/test/fixtures/getAllComments/output.snap.md b/apps/oxlint/test/fixtures/getAllComments/output.snap.md new file mode 100644 index 0000000000000..574bde8adcfb7 --- /dev/null +++ b/apps/oxlint/test/fixtures/getAllComments/output.snap.md @@ -0,0 +1,36 @@ +# Exit code +1 + +# stdout +``` + x test-getAllComments(test-getAllComments): getAllComments() returned 6 comments: + | [0] Line: " Line comment 1" at [0, 17] + | [1] Block: " Block comment 1 " at [31, 52] + | [2] Block: "*\n * JSDoc comment\n " at [54, 78] + | [3] Line: " Line comment 2" at [105, 122] + | [4] Line: " Line comment 3" at [135, 152] + | [5] Block: " Block comment 2 " at [156, 177] + ,-[files/test.js:2:1] + 1 | // Line comment 1 + 2 | ,-> const x = 1; /* Block comment 1 */ + 3 | | + 4 | | /** + 5 | | * JSDoc comment + 6 | | */ + 7 | | export function foo() { + 8 | | // Line comment 2 + 9 | | return x; // Line comment 3 + 10 | | } + 11 | | + 12 | `-> /* Block comment 2 */ + `---- + +Found 0 warnings and 1 error. +Finished in Xms on 1 file using X threads. +``` + +# stderr +``` +WARNING: JS plugins are experimental and not subject to semver. +Breaking changes are possible while JS plugins support is under development. +``` diff --git a/apps/oxlint/test/fixtures/getAllComments/plugin.ts b/apps/oxlint/test/fixtures/getAllComments/plugin.ts new file mode 100644 index 0000000000000..d830b8a65382d --- /dev/null +++ b/apps/oxlint/test/fixtures/getAllComments/plugin.ts @@ -0,0 +1,27 @@ +import { definePlugin } from '../../../dist/index.js'; +import type { Rule } from '../../../dist/index.js'; + +const getAllCommentsRule: Rule = { + create(context) { + const comments = context.sourceCode.getAllComments(); + + context.report({ + message: `getAllComments() returned ${comments.length} comments:\n` + + comments.map((c, i) => ` [${i}] ${c.type}: ${JSON.stringify(c.value)} at [${c.range[0]}, ${c.range[1]}]`).join( + '\n', + ), + node: context.sourceCode.ast, + }); + + return {}; + }, +}; + +export default definePlugin({ + meta: { + name: 'test-getAllComments', + }, + rules: { + 'test-getAllComments': getAllCommentsRule, + }, +}); diff --git a/crates/oxc_ast/src/serialize/mod.rs b/crates/oxc_ast/src/serialize/mod.rs index 0a63c12e1a686..50da684accdb2 100644 --- a/crates/oxc_ast/src/serialize/mod.rs +++ b/crates/oxc_ast/src/serialize/mod.rs @@ -129,6 +129,7 @@ impl Program<'_> { body: null, sourceType: DESER[ModuleKind](POS_OFFSET.source_type.module_kind), hashbang: null, + ...(COMMENTS && { comments: DESER[Vec](POS_OFFSET.comments) }), start, end, ...(RANGE && { range: [start, end] }), diff --git a/tasks/ast_tools/src/generators/raw_transfer.rs b/tasks/ast_tools/src/generators/raw_transfer.rs index 5643aaafba9c5..232e3b2486270 100644 --- a/tasks/ast_tools/src/generators/raw_transfer.rs +++ b/tasks/ast_tools/src/generators/raw_transfer.rs @@ -209,18 +209,21 @@ fn generate_deserializers( } } - // Create deserializers with various settings, by setting `IS_TS`, `RANGE`, `LOC`, `PARENT` - // and `PRESERVE_PARENS` consts, and running through minifier to shake out irrelevant code + // Create deserializers with various settings, by setting `IS_TS`, `RANGE`, `LOC`, `PARENT`, + // `PRESERVE_PARENS`, and `COMMENTS` consts, and running through minifier to shake out + // irrelevant code struct VariantGen { variant_paths: Vec, } - impl VariantGenerator<5> for VariantGen { - const FLAG_NAMES: [&str; 5] = ["IS_TS", "RANGE", "LOC", "PARENT", "PRESERVE_PARENS"]; + impl VariantGenerator<6> for VariantGen { + const FLAG_NAMES: [&str; 6] = + ["IS_TS", "RANGE", "LOC", "PARENT", "PRESERVE_PARENS", "COMMENTS"]; - fn variants(&mut self) -> Vec<[bool; 5]> { + fn variants(&mut self) -> Vec<[bool; 6]> { let mut variants = Vec::with_capacity(9); + // Parser deserializers for is_ts in [false, true] { for range in [false, true] { for parent in [false, true] { @@ -233,16 +236,17 @@ fn generate_deserializers( variants.push([ is_ts, range, /* loc */ false, parent, - /* preserve_parens */ true, + /* preserve_parens */ true, /* comments */ false, ]); } } } + // Linter deserializer self.variant_paths.push(format!("{OXLINT_APP_PATH}/src-js/generated/deserialize.js")); variants.push([ /* is_ts */ true, /* range */ true, /* loc */ true, - /* parent */ true, /* preserve_parens */ false, + /* parent */ true, /* preserve_parens */ false, /* comments */ true, ]); variants @@ -251,7 +255,7 @@ fn generate_deserializers( fn pre_process_variant<'a>( &mut self, program: &mut Program<'a>, - flags: [bool; 5], + flags: [bool; 6], allocator: &'a Allocator, ) { if flags[2] {