analyze: add a -s option to search for identifier substring

for example, all expectXxx from the testing module:

    zdoc -s std.testing expect
0.10
alex 2 years ago
parent 3fa0f65b54
commit 802fda7cf7

@ -2,11 +2,12 @@
usage:
zdoc [source] <identifier>
zdoc [-s] [source] <identifier>
the program searches source code for matching public identifiers,
printing found types and their doc comments to stdout.
the search is case-insensitive and non-exhaustive.
if -s option is specified, any identifier substring matches.
for example, look up format function in std lib:

@ -2,11 +2,17 @@ const std = @import("std");
const output = @import("output.zig");
const Ast = std.zig.Ast;
/// Query specifies the kind of identifier matching to look for when search'ing.
pub const Query = union(enum) {
exact: []const u8, // exact match, case-insensitive
sub: []const u8, // substring match, case-insensitive
};
/// search parses the source code into std.zig.Ast and walks over top level declarations,
/// optionally matching against the query.
///
/// results are printed using ais.
pub fn search(alloc: std.mem.Allocator, ais: *output.Ais, source: [:0]const u8, query: ?[]const u8) !void {
pub fn search(alloc: std.mem.Allocator, ais: *output.Ais, source: [:0]const u8, query: ?Query) !void {
var tree = try std.zig.parse(alloc, source);
defer tree.deinit(alloc);
var insert_newline = false;
@ -51,8 +57,14 @@ pub fn isPublic(tree: Ast, decl: Ast.Node.Index) bool {
}
/// reports whether the given name matches decl identifier, case-insensitive.
pub fn identifierMatch(tree: Ast, decl: Ast.Node.Index, name: []const u8) bool {
return if (identifier(tree, decl)) |ident| std.ascii.eqlIgnoreCase(name, ident) else false;
pub fn identifierMatch(tree: Ast, decl: Ast.Node.Index, name: Query) bool {
if (identifier(tree, decl)) |id| {
switch (name) {
.exact => |exact| return std.ascii.eqlIgnoreCase(id, exact),
.sub => |sub| return std.ascii.indexOfIgnoreCase(id, sub) != null,
}
}
return false;
}
/// identifier returns node's identifier, if any.
@ -96,14 +108,14 @@ fn identifier(tree: Ast, node: Ast.Node.Index) ?[]const u8 {
return null;
}
test "identifier matches" {
test "identifier exact match" {
const alloc = std.testing.allocator;
const print = std.debug.print;
const src =
\\const foo: u32 = 1;
\\pub const bar: i32 = 2;
\\pub const baz = struct { z: u32 };
\\pub const Baz = struct { z: u32 };
\\fn quix() void { }
;
const tt = [_]struct { name: []const u8 }{
@ -116,15 +128,45 @@ test "identifier matches" {
defer tree.deinit(alloc);
for (tree.rootDecls()) |decl, i| {
const tc = tt[i];
if (!identifierMatch(tree, decl, tc.name)) {
const exact = Query{ .exact = tc.name };
if (!identifierMatch(tree, decl, exact)) {
const id = identifier(tree, decl);
print("{d}: identifierMatch({s}): false; identifier({d}): {?s}\n", .{ i, tc.name, decl, id });
return error.NoMatch;
return error.NoExactMatch;
}
}
}
test "no identifier match" {
test "identifier sub match" {
const alloc = std.testing.allocator;
const print = std.debug.print;
const src =
\\const foo: u32 = 1;
\\pub const bar: i32 = 2;
\\pub const Baz = struct { z: u32 };
\\fn quix() void { }
;
const tt = [_]struct { name: []const u8 }{
.{ .name = "fo" },
.{ .name = "ar" },
.{ .name = "baz" },
.{ .name = "UI" },
};
var tree = try std.zig.parse(alloc, src);
defer tree.deinit(alloc);
for (tree.rootDecls()) |decl, i| {
const tc = tt[i];
const sub = Query{ .sub = tc.name };
if (!identifierMatch(tree, decl, sub)) {
const id = identifier(tree, decl);
print("{d}: identifierMatch({s}): false; identifier({d}): {?s}\n", .{ i, tc.name, decl, id });
return error.NoSubMatch;
}
}
}
test "no identifier exact match" {
const alloc = std.testing.allocator;
const print = std.debug.print;
@ -134,14 +176,37 @@ test "no identifier match" {
\\pub const baz = struct { z: u32 };
\\fn quix() void { }
;
const z = "z";
var tree = try std.zig.parse(alloc, src);
defer tree.deinit(alloc);
const z = Query{ .exact = "z" };
for (tree.rootDecls()) |decl, i| {
if (identifierMatch(tree, decl, z)) {
const id = identifier(tree, decl);
print("{d}: identifierMatch({s}): true; identifier({d}): {?s}\n", .{ i, z.exact, decl, id });
return error.ExactMatch;
}
}
}
test "no identifier sub match" {
const alloc = std.testing.allocator;
const print = std.debug.print;
const src =
\\const foo: u32 = 1;
\\pub const bar: i32 = 2;
\\fn quix() void { }
;
var tree = try std.zig.parse(alloc, src);
defer tree.deinit(alloc);
const z = Query{ .sub = "z" };
for (tree.rootDecls()) |decl, i| {
if (identifierMatch(tree, decl, z)) {
const id = identifier(tree, decl);
print("{d}: identifierMatch({s}): true; identifier({d}): {?s}\n", .{ i, z, decl, id });
return error.Match;
print("{d}: identifierMatch({s}): true; identifier({d}): {?s}\n", .{ i, z.sub, decl, id });
return error.SubMatch;
}
}
}

@ -18,10 +18,19 @@ pub fn main() !void {
var args = try std.process.ArgIterator.initWithAllocator(alloc);
defer args.deinit();
const progname = args.next().?;
var zquery: ?[:0]const u8 = null;
var zsource: [:0]const u8 = undefined;
var nargs: u8 = 0;
var zquery: ?[:0]const u8 = null; // identifier name to search for
var zsource: [:0]const u8 = undefined; // std.xxx or an fs path
const opts = struct { // cmd line options with defaults
var sub: bool = false; // -s substr option
};
var nargs: u8 = 0; // excluding opts
while (args.next()) |a| {
if (std.mem.eql(u8, a, "-s")) {
opts.sub = true;
continue;
}
switch (nargs) {
0 => {
zsource = a;
@ -47,7 +56,11 @@ pub fn main() !void {
const ais = &auto_indenting_stream;
// run the search, one file at a time
var query: ?[]const u8 = if (zquery) |q| q[0..] else null;
var query: ?analyze.Query = null;
if (zquery) |q| switch (opts.sub) {
true => query = .{ .sub = q[0..] },
false => query = .{ .exact = q[0..] },
};
const list = try expandSourcePath(alloc, zsource);
for (list.items) |src| {
// todo: consider replacing arena with something else to dealloc already
@ -59,11 +72,12 @@ pub fn main() !void {
fn usage(prog: []const u8) !void {
try stderr.print(
\\usage: {s} [source] <identifier>
\\usage: {s} [-s] [source] <identifier>
\\
\\the program searches source code for matching public identifiers,
\\printing found types and their doc comments to stdout.
\\the search is case-insensitive and non-exhaustive.
\\if -s option is specified, any identifier substring matches.
\\
\\for example, look up format function in std lib:
\\

Loading…
Cancel
Save