package symbols import ( "fmt" "strings" "testing" ) // got renders symbols as "kind name key line", one per entry, key left // out when it equals the name. func got(syms []Symbol) []string { var out []string for _, s := range syms { if s.Key != s.Name { out = append(out, fmt.Sprintf("%s %s %s %d", s.Kind, s.Name, s.Key, s.Line)) continue } out = append(out, fmt.Sprintf("%s %s %d", s.Kind, s.Name, s.Line)) } return out } func TestExtract(t *testing.T) { tests := []struct { path string src string want []string }{ {"a.go", `package a // Doc. func Top() {} type Server struct{} type ( List[T any] struct{} ID int ) func (s *Server) Handle() {} func (l List[T]) Push(v T) {} func (List[T]) Len() int { return 0 } const Max = 3 const ( A, B = 1, 2 _ = 9 ) var global int `, []string{ "function Top 4", "type Server 6", "type List 9", "type ID 10", "method Server.Handle Handle 13", "method List.Push Push 14", "method List.Len Len 15", "const Max 17", "const A 19", "const B 19", "var global 23", }}, {"broken.go", "package a\n\nfunc Before() {}\n\nfunc (\n", []string{"function Before 3"}}, {"gen.go", "// Code generated by stringer; DO NOT EDIT.\n\npackage a\n\nfunc X() {}\n", nil}, {"a.swift", `import Foundation public final class Store { static func make() -> Store { Store() } class var shared: Store { make() } } struct Point { var x: Int } enum Mode { case a } protocol Drawable {} actor Cache {} typealias Handler = () -> Void @MainActor func onMain() {} `, []string{ "class Store 3", "function make 4", "struct Point 7", "enum Mode 8", "interface Drawable 9", "class Cache 10", "type Handler 11", "function onMain 12", }}, {"lib.rs", `pub fn open() {} pub(crate) async fn fetch() {} struct Inner; pub enum Kind { A } pub trait Store {} type Result = std::result::Result; mod tests {} pub const LIMIT: usize = 3; static mut COUNT: u32 = 0; macro_rules! bail {} impl Store for Inner {} `, []string{ "function open 1", "function fetch 2", "struct Inner 3", "enum Kind 4", "interface Store 5", "type Result 6", "module tests 7", "const LIMIT 8", "const COUNT 9", "macro bail 10", }}, {"app.py", `import os def main(): pass class Client: async def get(self): return helper() async def run(): pass `, []string{"function main 3", "class Client 6", "method get 7", "function run 10"}}, {"web.ts", `export function render() {} export default async function load() {} function* gen() {} export class View {} export interface Props {} export type ID = string; export type Map = Record; export const enum Color { Red } export const LIMIT = 10; const handler = () => {}; const inner = 1; `, []string{ "function render 1", "function load 2", "function gen 3", "class View 4", "interface Props 5", "type ID 6", "type Map 7", "enum Color 8", "const LIMIT 9", "const handler 10", }}, {"api.h", `#ifndef API_H #define API_H #define MAX_LEN 64 struct buffer { int len; }; typedef struct buffer buffer_t; typedef enum mode { MODE_A } mode_t; int buffer_open(const char *path); static inline void *buffer_data(buffer_t *b); typedef struct { int x; } point; #endif `, []string{ "macro API_H 2", "macro MAX_LEN 3", "struct buffer 4", "type buffer_t 7", "enum mode 8", "function buffer_open 9", "function buffer_data 10", "type point 13", }}, {"deploy.sh", `#!/bin/sh build() { go build ./... } function release { build } ship_it () { release } `, []string{"function build 2", "function release 5", "function ship_it 8"}}, {"README.md", "# Title\n\nText\n\n```sh\n# not a heading\n```\n\n## Usage ##\n", []string{"section Title 1", "section Usage 9"}}, {"notes.org", "* Top\n#+begin_src sh\n* not a heading\n#+end_src\n** Child heading\n", []string{"section Top 1", "section Child heading 5"}}, {"data.json", `{"a": 1}`, nil}, } for _, tc := range tests { t.Run(tc.path, func(t *testing.T) { g := got(Extract(tc.path, []byte(tc.src))) if strings.Join(g, "\n") != strings.Join(tc.want, "\n") { t.Errorf("got:\n%s\nwant:\n%s", strings.Join(g, "\n"), strings.Join(tc.want, "\n")) } }) } } func TestSkip(t *testing.T) { tests := []struct { path string size int64 skip bool }{ {"main.go", 100, false}, {"main.go", MaxFileBytes + 1, true}, {"main.go", MaxFileBytes, false}, {"vendor/x/y.go", 10, true}, {"web/node_modules/a/index.js", 10, true}, {"internal/vendors/y.go", 10, false}, {"zz_gen.go", 10, true}, {"api.pb.go", 10, true}, {"static/app.min.js", 10, true}, {"static/app.js", 10, false}, {"image.png", 10, true}, {"Makefile", 10, true}, } for _, tc := range tests { if got := Skip(tc.path, tc.size); got != tc.skip { t.Errorf("Skip(%q, %d) = %v, want %v", tc.path, tc.size, got, tc.skip) } } }