diff options
Diffstat (limited to 'nimsuggest')
82 files changed, 3949 insertions, 0 deletions
diff --git a/nimsuggest/config.nims b/nimsuggest/config.nims new file mode 100644 index 000000000..ee19f9893 --- /dev/null +++ b/nimsuggest/config.nims @@ -0,0 +1,2 @@ +# xxx not sure why this flag isn't needed: switch("processing", "filenames") +switch("filenames", "canonical") diff --git a/nimsuggest/crashtester.nim b/nimsuggest/crashtester.nim new file mode 100644 index 000000000..ead97adec --- /dev/null +++ b/nimsuggest/crashtester.nim @@ -0,0 +1,52 @@ + + +import strutils, os, osproc, streams + +const + DummyEof = "!EOF!" + +proc getPosition(s: string): (int, int) = + result = (1, 1) + var col = 0 + for i in 0..<s.len: + if s[i] == '\L': + inc result[0] + col = 0 + else: + inc col + result[1] = col+1 + +proc callNimsuggest() = + let cl = parseCmdLine("nimsuggest --tester temp000.nim") + var p = startProcess(command=cl[0], args=cl[1 .. ^1], + options={poStdErrToStdOut, poUsePath, + poInteractive, poDaemon}) + let outp = p.outputStream + let inp = p.inputStream + var report = "" + var a = newStringOfCap(120) + let contents = readFile("tools/nimsuggest/crashtester.nim") + try: + # read and ignore anything nimsuggest says at startup: + while outp.readLine(a): + if a == DummyEof: break + + var line = 0 + for i in 0 ..< contents.len: + let slic = contents[0..i] + writeFile("temp000.nim", slic) + let (line, col) = getPosition(slic) + inp.writeLine("sug temp000.nim:$#:$#" % [$line, $col]) + inp.flush() + var answer = "" + while outp.readLine(a): + if a == DummyEof: break + answer.add a + answer.add '\L' + echo answer + finally: + inp.writeLine("quit") + inp.flush() + close(p) + +callNimsuggest() diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim new file mode 100644 index 000000000..04bae08c1 --- /dev/null +++ b/nimsuggest/nimsuggest.nim @@ -0,0 +1,1392 @@ +# +# +# The Nim Compiler +# (c) Copyright 2017 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +import compiler/renderer +import compiler/types +import compiler/trees +import compiler/wordrecg +import compiler/sempass2 +import strformat +import algorithm +import tables +import times +import procmonitor + +template tryImport(module) = import module + +when compiles tryImport ../dist/checksums/src/checksums/sha1: + import ../dist/checksums/src/checksums/sha1 +else: + import checksums/sha1 + +## Nimsuggest is a tool that helps to give editors IDE like capabilities. + +when not defined(nimcore): + {.error: "nimcore MUST be defined for Nim's core tooling".} + +import strutils, os, parseopt, parseutils, sequtils, net, rdstdin, sexp +# Do NOT import suggest. It will lead to weird bugs with +# suggestionResultHook, because suggest.nim is included by sigmatch. +# So we import that one instead. +import compiler / [options, commands, modules, + passes, passaux, msgs, + sigmatch, ast, + idents, modulegraphs, prefixmatches, lineinfos, cmdlinehelper, + pathutils, condsyms, syntaxes, suggestsymdb] + +when defined(nimPreviewSlimSystem): + import std/typedthreads + +when defined(windows): + import winlean +else: + import posix + +const HighestSuggestProtocolVersion = 4 +const DummyEof = "!EOF!" +const Usage = """ +Nimsuggest - Tool to give every editor IDE like capabilities for Nim +Usage: + nimsuggest [options] projectfile.nim + +Options: + --autobind automatically binds into a free port + --port:PORT port, by default 6000 + --address:HOST binds to that address, by default "" + --stdin read commands from stdin and write results to + stdout instead of using sockets + --clientProcessId:PID shutdown nimsuggest in case this process dies + --epc use emacs epc mode + --debug enable debug output + --log enable verbose logging to nimsuggest.log file + --v1 use version 1 of the protocol; for backwards compatibility + --v2 use version 2(default) of the protocol + --v3 use version 3 of the protocol + --v4 use version 4 of the protocol + --info:X information + --info:nimVer return the Nim compiler version that nimsuggest uses internally + --info:protocolVer return the newest protocol version that is supported + --info:capabilities return the capabilities supported by nimsuggest + --refresh perform automatic refreshes to keep the analysis precise + --maxresults:N limit the number of suggestions to N + --tester implies --stdin and outputs a line + '""" & DummyEof & """' for the tester + --find attempts to find the project file of the current project + --exceptionInlayHints:on|off + globally turn exception inlay hints on|off + +The server then listens to the connection and takes line-based commands. + +If --autobind is used, the binded port number will be printed to stdout. + +In addition, all command line options of Nim that do not affect code generation +are supported. +""" +type + Mode = enum mstdin, mtcp, mepc, mcmdsug, mcmdcon + CachedMsg = object + info: TLineInfo + msg: string + sev: Severity + CachedMsgs = seq[CachedMsg] + +var + gPort = 6000.Port + gAddress = "127.0.0.1" + gMode: Mode + gEmitEof: bool # whether we write '!EOF!' dummy lines + gLogging = defined(logging) + gRefresh: bool + gAutoBind = false + + requests: Channel[string] + results: Channel[Suggest] + +proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile, line, col: int; tag: string, + graph: ModuleGraph); + +proc writelnToChannel(line: string) = + results.send(Suggest(section: ideMsg, doc: line)) + +proc sugResultHook(s: Suggest) = + results.send(s) + +proc errorHook(conf: ConfigRef; info: TLineInfo; msg: string; sev: Severity) = + results.send(Suggest(section: ideChk, filePath: toFullPath(conf, info), + line: toLinenumber(info), column: toColumn(info), doc: msg, + forth: $sev)) + +proc myLog(s: string) = + if gLogging: log(s) + +const + seps = {':', ';', ' ', '\t'} + Help = "usage: sug|con|def|use|dus|chk|mod|highlight|outline|known|project file.nim[;dirtyfile.nim]:line:col\n" & + "type 'quit' to quit\n" & + "type 'debug' to toggle debug mode on/off\n" & + "type 'terse' to toggle terse mode on/off" + #List of currently supported capabilities. So lang servers/ides can iterate over and check for what's enabled + Capabilities = [ + "con", #current NimSuggest supports the `con` commmand + "exceptionInlayHints", + "unknownFile", #current NimSuggest can handle unknown files + ] + +proc parseQuoted(cmd: string; outp: var string; start: int): int = + var i = start + i += skipWhitespace(cmd, i) + if i < cmd.len and cmd[i] == '"': + i += parseUntil(cmd, outp, '"', i+1)+2 + else: + i += parseUntil(cmd, outp, seps, i) + result = i + +proc sexp(s: IdeCmd|TSymKind|PrefixMatch): SexpNode = sexp($s) + +proc sexp(s: Suggest): SexpNode = + # If you change the order here, make sure to change it over in + # nim-mode.el too. + let qp = if s.qualifiedPath.len == 0: @[] else: s.qualifiedPath + result = convertSexp([ + s.section, + TSymKind s.symkind, + qp.map(newSString), + s.filePath, + s.forth, + s.line, + s.column, + s.doc, + s.quality + ]) + if s.section == ideSug: + result.add convertSexp(s.prefix) + if s.section in {ideOutline, ideExpand} and s.version == 3: + result.add convertSexp(s.endLine.int) + result.add convertSexp(s.endCol) + +proc sexp(s: seq[Suggest]): SexpNode = + result = newSList() + for sug in s: + result.add(sexp(sug)) + +proc listEpc(): SexpNode = + # This function is called from Emacs to show available options. + let + argspecs = sexp("file line column dirtyfile".split(" ").map(newSSymbol)) + docstring = sexp("line starts at 1, column at 0, dirtyfile is optional") + result = newSList() + for command in ["sug", "con", "def", "use", "dus", "chk", "mod", "globalSymbols", "recompile", "saved", "chkFile", "declaration", "inlayHints"]: + let + cmd = sexp(command) + methodDesc = newSList() + methodDesc.add(cmd) + methodDesc.add(argspecs) + methodDesc.add(docstring) + result.add(methodDesc) + +proc findNode(n: PNode; trackPos: TLineInfo): PSym = + #echo "checking node ", n.info + if n.kind == nkSym: + if isTracked(n.info, trackPos, n.sym.name.s.len): return n.sym + else: + for i in 0 ..< safeLen(n): + let res = findNode(n[i], trackPos) + if res != nil: return res + +proc symFromInfo(graph: ModuleGraph; trackPos: TLineInfo): PSym = + let m = graph.getModule(trackPos.fileIndex) + if m != nil and m.ast != nil: + result = findNode(m.ast, trackPos) + +template benchmark(benchmarkName: untyped, code: untyped) = + block: + myLog "Started [" & benchmarkName & "]..." + let t0 = epochTime() + code + let elapsed = epochTime() - t0 + let elapsedStr = elapsed.formatFloat(format = ffDecimal, precision = 3) + myLog "CPU Time [" & benchmarkName & "] " & elapsedStr & "s" + +proc clearInstCache(graph: ModuleGraph, projectFileIdx: FileIndex) = + if projectFileIdx == InvalidFileIdx: + graph.typeInstCache.clear() + graph.procInstCache.clear() + return + var typeIdsToDelete = newSeq[ItemId]() + for id in graph.typeInstCache.keys: + if id.module == projectFileIdx.int: + typeIdsToDelete.add id + for id in typeIdsToDelete: + graph.typeInstCache.del id + var procIdsToDelete = newSeq[ItemId]() + for id in graph.procInstCache.keys: + if id.module == projectFileIdx.int: + procIdsToDelete.add id + for id in procIdsToDelete: + graph.procInstCache.del id + + for tbl in mitems(graph.attachedOps): + var attachedOpsToDelete = newSeq[ItemId]() + for id in tbl.keys: + if id.module == projectFileIdx.int and sfOverridden in resolveAttachedOp(graph, tbl[id]).flags: + attachedOpsToDelete.add id + for id in attachedOpsToDelete: + tbl.del id + +proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, tag: string, + graph: ModuleGraph) = + let conf = graph.config + + if conf.suggestVersion >= 3: + let command = fmt "cmd = {cmd} {file}:{line}:{col}" + benchmark command: + executeNoHooksV3(cmd, file, dirtyfile, line, col, tag, graph) + return + + myLog("cmd: " & $cmd & ", file: " & file.string & + ", dirtyFile: " & dirtyfile.string & + "[" & $line & ":" & $col & "]") + conf.ideCmd = cmd + if cmd == ideUse and conf.suggestVersion != 0: + graph.resetAllModules() + var isKnownFile = true + let dirtyIdx = fileInfoIdx(conf, file, isKnownFile) + + if not dirtyfile.isEmpty: msgs.setDirtyFile(conf, dirtyIdx, dirtyfile) + else: msgs.setDirtyFile(conf, dirtyIdx, AbsoluteFile"") + + conf.m.trackPos = newLineInfo(dirtyIdx, line, col) + conf.m.trackPosAttached = false + conf.errorCounter = 0 + if conf.suggestVersion == 1: + graph.usageSym = nil + if not isKnownFile: + graph.clearInstCache(dirtyIdx) + graph.compileProject(dirtyIdx) + if conf.suggestVersion == 0 and conf.ideCmd in {ideUse, ideDus} and + dirtyfile.isEmpty: + discard "no need to recompile anything" + else: + let modIdx = graph.parentModule(dirtyIdx) + graph.markDirty dirtyIdx + graph.markClientsDirty dirtyIdx + if conf.ideCmd != ideMod: + if isKnownFile: + graph.clearInstCache(modIdx) + graph.compileProject(modIdx) + if conf.ideCmd in {ideUse, ideDus}: + let u = if conf.suggestVersion != 1: graph.symFromInfo(conf.m.trackPos) else: graph.usageSym + if u != nil: + listUsages(graph, u) + else: + localError(conf, conf.m.trackPos, "found no symbol at this position " & (conf $ conf.m.trackPos)) + +proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, graph: ModuleGraph) = + executeNoHooks(cmd, file, dirtyfile, line, col, "", graph) + +proc execute(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; tag: string, + graph: ModuleGraph) = + if cmd == ideChk: + graph.config.structuredErrorHook = errorHook + graph.config.writelnHook = myLog + else: + graph.config.structuredErrorHook = nil + graph.config.writelnHook = myLog + executeNoHooks(cmd, file, dirtyfile, line, col, tag, graph) + +proc executeEpc(cmd: IdeCmd, args: SexpNode; + graph: ModuleGraph) = + let + file = AbsoluteFile args[0].getStr + line = args[1].getNum + column = args[2].getNum + var dirtyfile = AbsoluteFile"" + if len(args) > 3: + dirtyfile = AbsoluteFile args[3].getStr("") + execute(cmd, file, dirtyfile, int(line), int(column), args[3].getStr, graph) + +proc returnEpc(socket: Socket, uid: BiggestInt, s: SexpNode|string, + returnSymbol = "return") = + let response = $convertSexp([newSSymbol(returnSymbol), uid, s]) + socket.send(toHex(len(response), 6)) + socket.send(response) + +template checkSanity(client, sizeHex, size, messageBuffer: typed) = + if client.recv(sizeHex, 6) != 6: + raise newException(ValueError, "didn't get all the hexbytes") + if parseHex(sizeHex, size) == 0: + raise newException(ValueError, "invalid size hex: " & $sizeHex) + if client.recv(messageBuffer, size) != size: + raise newException(ValueError, "didn't get all the bytes") + +proc toStdout() {.gcsafe.} = + while true: + let res = results.recv() + case res.section + of ideNone: break + of ideMsg: echo res.doc + of ideKnown: echo res.quality == 1 + of ideProject: echo res.filePath + else: echo res + +proc toSocket(stdoutSocket: Socket) {.gcsafe.} = + while true: + let res = results.recv() + case res.section + of ideNone: break + of ideMsg: stdoutSocket.send(res.doc & "\c\L") + of ideKnown: stdoutSocket.send($(res.quality == 1) & "\c\L") + of ideProject: stdoutSocket.send(res.filePath & "\c\L") + else: stdoutSocket.send($res & "\c\L") + +proc toEpc(client: Socket; uid: BiggestInt) {.gcsafe.} = + var list = newSList() + while true: + let res = results.recv() + case res.section + of ideNone: break + of ideMsg: + list.add sexp(res.doc) + of ideKnown: + list.add sexp(res.quality == 1) + of ideProject: + list.add sexp(res.filePath) + else: + list.add sexp(res) + returnEpc(client, uid, list) + +template setVerbosity(level: typed) = + gVerbosity = level + conf.notes = NotesVerbosity[gVerbosity] + +proc connectToNextFreePort(server: Socket, host: string): Port = + server.bindAddr(Port(0), host) + let (_, port) = server.getLocalAddr + result = port + +type + ThreadParams = tuple[port: Port; address: string] + +proc replStdinSingleCmd(line: string) = + requests.send line + toStdout() + echo "" + flushFile(stdout) + +proc replStdin(x: ThreadParams) {.thread.} = + if gEmitEof: + echo DummyEof + while true: + let line = readLine(stdin) + requests.send line + if line == "quit": break + toStdout() + echo DummyEof + flushFile(stdout) + else: + echo Help + var line = "" + while readLineFromStdin("> ", line): + replStdinSingleCmd(line) + requests.send "quit" + +proc replCmdline(x: ThreadParams) {.thread.} = + replStdinSingleCmd(x.address) + requests.send "quit" + +proc replTcp(x: ThreadParams) {.thread.} = + var server = newSocket() + if gAutoBind: + let port = server.connectToNextFreePort(x.address) + server.listen() + echo port + stdout.flushFile() + else: + server.bindAddr(x.port, x.address) + server.listen() + var inp = "" + var stdoutSocket: Socket + while true: + accept(server, stdoutSocket) + + stdoutSocket.readLine(inp) + requests.send inp + toSocket(stdoutSocket) + stdoutSocket.send("\c\L") + stdoutSocket.close() + +proc argsToStr(x: SexpNode): string = + if x.kind != SList: return x.getStr + doAssert x.kind == SList + doAssert x.len >= 4 + let file = x[0].getStr + let line = x[1].getNum + let col = x[2].getNum + let dirty = x[3].getStr + result = x[0].getStr.escape + if dirty.len > 0: + result.add ';' + result.add dirty.escape + result.add ':' + result.addInt line + result.add ':' + result.addInt col + +proc replEpc(x: ThreadParams) {.thread.} = + var server = newSocket() + let port = connectToNextFreePort(server, "localhost") + server.listen() + echo port + stdout.flushFile() + + var client: Socket + # Wait for connection + accept(server, client) + while true: + var + sizeHex = "" + size = 0 + messageBuffer = "" + checkSanity(client, sizeHex, size, messageBuffer) + let + message = parseSexp($messageBuffer) + epcApi = message[0].getSymbol + case epcApi + of "call": + let + uid = message[1].getNum + cmd = message[2].getSymbol + args = message[3] + + when false: + x.ideCmd[] = parseIdeCmd(message[2].getSymbol) + case x.ideCmd[] + of ideSug, ideCon, ideDef, ideUse, ideDus, ideOutline, ideHighlight: + setVerbosity(0) + else: discard + let fullCmd = cmd & " " & args.argsToStr + myLog "MSG CMD: " & fullCmd + requests.send(fullCmd) + toEpc(client, uid) + of "methods": + returnEpc(client, message[1].getNum, listEpc()) + of "epc-error": + # an unhandled exception forces down the whole process anyway, so we + # use 'quit' here instead of 'raise' + quit("received epc error: " & $messageBuffer) + else: + let errMessage = case epcApi + of "return", "return-error": + "no return expected" + else: + "unexpected call: " & epcApi + quit errMessage + +proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) = + let conf = graph.config + + template sentinel() = + # send sentinel for the input reading thread: + results.send(Suggest(section: ideNone)) + + template toggle(sw) = + if sw in conf.globalOptions: + excl(conf.globalOptions, sw) + else: + incl(conf.globalOptions, sw) + sentinel() + return + + template err() = + echo Help + sentinel() + return + + var opc = "" + var i = parseIdent(cmd, opc, 0) + case opc.normalize + of "sug": conf.ideCmd = ideSug + of "con": conf.ideCmd = ideCon + of "def": conf.ideCmd = ideDef + of "use": conf.ideCmd = ideUse + of "dus": conf.ideCmd = ideDus + of "mod": conf.ideCmd = ideMod + of "chk": conf.ideCmd = ideChk + of "highlight": conf.ideCmd = ideHighlight + of "outline": conf.ideCmd = ideOutline + of "quit": + sentinel() + quit() + of "debug": toggle optIdeDebug + of "terse": toggle optIdeTerse + of "known": conf.ideCmd = ideKnown + of "project": conf.ideCmd = ideProject + of "changed": conf.ideCmd = ideChanged + of "globalsymbols": conf.ideCmd = ideGlobalSymbols + of "declaration": conf.ideCmd = ideDeclaration + of "expand": conf.ideCmd = ideExpand + of "chkfile": conf.ideCmd = ideChkFile + of "recompile": conf.ideCmd = ideRecompile + of "type": conf.ideCmd = ideType + of "inlayhints": + if conf.suggestVersion >= 4: + conf.ideCmd = ideInlayHints + else: + err() + else: err() + var dirtyfile = "" + var orig = "" + i += skipWhitespace(cmd, i) + if i < cmd.len and cmd[i] in {'0'..'9'}: + orig = string conf.projectFull + else: + i = parseQuoted(cmd, orig, i) + if i < cmd.len and cmd[i] == ';': + i = parseQuoted(cmd, dirtyfile, i+1) + i += skipWhile(cmd, seps, i) + var line = 0 + var col = -1 + i += parseInt(cmd, line, i) + i += skipWhile(cmd, seps, i) + i += parseInt(cmd, col, i) + let tag = substr(cmd, i) + + if conf.ideCmd == ideKnown: + results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(conf, AbsoluteFile orig)))) + elif conf.ideCmd == ideProject: + results.send(Suggest(section: ideProject, filePath: string conf.projectFull)) + else: + if conf.ideCmd == ideChk: + for cm in cachedMsgs: errorHook(conf, cm.info, cm.msg, cm.sev) + execute(conf.ideCmd, AbsoluteFile orig, AbsoluteFile dirtyfile, line, col, tag, graph) + sentinel() + +proc recompileFullProject(graph: ModuleGraph) = + benchmark "Recompilation(clean)": + graph.resetForBackend() + graph.resetSystemArtifacts() + graph.vm = nil + graph.resetAllModules() + GC_fullCollect() + graph.compileProject() + +proc mainThread(graph: ModuleGraph) = + let conf = graph.config + myLog "searchPaths: " + for it in conf.searchPaths: + myLog(" " & it.string) + + proc wrHook(line: string) {.closure.} = + if gMode == mepc: + if gLogging: log(line) + else: + writelnToChannel(line) + + conf.writelnHook = wrHook + conf.suggestionResultHook = sugResultHook + graph.doStopCompile = proc (): bool = requests.peek() > 0 + var idle = 0 + var cachedMsgs: CachedMsgs = @[] + while true: + let (hasData, req) = requests.tryRecv() + if hasData: + conf.writelnHook = wrHook + conf.suggestionResultHook = sugResultHook + execCmd(req, graph, cachedMsgs) + idle = 0 + else: + os.sleep 250 + idle += 1 + if idle == 20 and gRefresh and conf.suggestVersion < 3: + # we use some nimsuggest activity to enable a lazy recompile: + conf.ideCmd = ideChk + conf.writelnHook = proc (s: string) = discard + cachedMsgs.setLen 0 + conf.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo; msg: string; sev: Severity) = + cachedMsgs.add(CachedMsg(info: info, msg: msg, sev: sev)) + conf.suggestionResultHook = proc (s: Suggest) = discard + recompileFullProject(graph) + +var + inputThread: Thread[ThreadParams] + +proc mainCommand(graph: ModuleGraph) = + let conf = graph.config + clearPasses(graph) + registerPass graph, verbosePass + registerPass graph, semPass + conf.setCmd cmdIdeTools + defineSymbol(conf.symbols, $conf.backend) + wantMainModule(conf) + + if not fileExists(conf.projectFull): + quit "cannot find file: " & conf.projectFull.string + + add(conf.searchPaths, conf.libpath) + + conf.setErrorMaxHighMaybe # honor --errorMax even if it may not make sense here + # do not print errors, but log them + conf.writelnHook = proc (msg: string) = discard + + if graph.config.suggestVersion >= 3: + graph.config.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo; msg: string; sev: Severity) = + let suggest = Suggest(section: ideChk, filePath: toFullPath(conf, info), + line: toLinenumber(info), column: toColumn(info), doc: msg, forth: $sev) + graph.suggestErrors.mgetOrPut(info.fileIndex, @[]).add suggest + + # compile the project before showing any input so that we already + # can answer questions right away: + benchmark "Initial compilation": + compileProject(graph) + + open(requests) + open(results) + + if graph.config.clientProcessId != 0: + hookProcMonitor(graph.config.clientProcessId) + + case gMode + of mstdin: createThread(inputThread, replStdin, (gPort, gAddress)) + of mtcp: createThread(inputThread, replTcp, (gPort, gAddress)) + of mepc: createThread(inputThread, replEpc, (gPort, gAddress)) + of mcmdsug: createThread(inputThread, replCmdline, + (gPort, "sug \"" & conf.projectFull.string & "\":" & gAddress)) + of mcmdcon: createThread(inputThread, replCmdline, + (gPort, "con \"" & conf.projectFull.string & "\":" & gAddress)) + mainThread(graph) + joinThread(inputThread) + close(requests) + close(results) + +proc processCmdLine*(pass: TCmdLinePass, cmd: string; conf: ConfigRef) = + var p = parseopt.initOptParser(cmd) + var findProject = false + while true: + parseopt.next(p) + case p.kind + of cmdEnd: break + of cmdLongOption, cmdShortOption: + case p.key.normalize + of "help", "h": + stdout.writeLine(Usage) + quit() + of "autobind": + gMode = mtcp + gAutoBind = true + of "port": + gPort = parseInt(p.val).Port + gMode = mtcp + of "address": + gAddress = p.val + gMode = mtcp + of "stdin": gMode = mstdin + of "cmdsug": + gMode = mcmdsug + gAddress = p.val + incl(conf.globalOptions, optIdeDebug) + of "cmdcon": + gMode = mcmdcon + gAddress = p.val + incl(conf.globalOptions, optIdeDebug) + of "epc": + gMode = mepc + conf.verbosity = 0 # Port number gotta be first. + of "debug": incl(conf.globalOptions, optIdeDebug) + of "v1": conf.suggestVersion = 1 + of "v2": conf.suggestVersion = 0 + of "v3": conf.suggestVersion = 3 + of "v4": conf.suggestVersion = 4 + of "info": + case p.val.normalize + of "protocolver": + stdout.writeLine(HighestSuggestProtocolVersion) + quit 0 + of "nimver": + stdout.writeLine(system.NimVersion) + quit 0 + of "capabilities": + stdout.writeLine(Capabilities.toSeq.mapIt($it).join(" ")) + quit 0 + else: + processSwitch(pass, p, conf) + of "exceptioninlayhints": + case p.val.normalize + of "", "on": incl(conf.globalOptions, optIdeExceptionInlayHints) + of "off": excl(conf.globalOptions, optIdeExceptionInlayHints) + else: processSwitch(pass, p, conf) + of "tester": + gMode = mstdin + gEmitEof = true + gRefresh = false + of "log": gLogging = true + of "refresh": + if p.val.len > 0: + gRefresh = parseBool(p.val) + else: + gRefresh = true + of "maxresults": + conf.suggestMaxResults = parseInt(p.val) + of "find": + findProject = true + of "clientprocessid": + conf.clientProcessId = parseInt(p.val) + else: processSwitch(pass, p, conf) + of cmdArgument: + let a = unixToNativePath(p.key) + if dirExists(a) and not fileExists(a.addFileExt("nim")): + conf.projectName = findProjectNimFile(conf, a) + # don't make it worse, report the error the old way: + if conf.projectName.len == 0: conf.projectName = a + else: + if findProject: + conf.projectName = findProjectNimFile(conf, a.parentDir()) + if conf.projectName.len == 0: + conf.projectName = a + else: + conf.projectName = a + # if processArgument(pass, p, argsCount): break + +proc handleCmdLine(cache: IdentCache; conf: ConfigRef) = + let self = NimProg( + suggestMode: true, + processCmdLine: processCmdLine + ) + self.initDefinesProg(conf, "nimsuggest") + + if paramCount() == 0: + stdout.writeLine(Usage) + return + + self.processCmdLineAndProjectPath(conf) + + if gMode != mstdin: + conf.writelnHook = proc (msg: string) = discard + conf.prefixDir = conf.getPrefixDir() + #msgs.writelnHook = proc (line: string) = log(line) + myLog("START " & conf.projectFull.string) + + var graph = newModuleGraph(cache, conf) + if self.loadConfigsAndProcessCmdLine(cache, conf, graph): + + if conf.selectedGC == gcUnselected and + conf.backend != backendJs: + initOrcDefines(conf) + mainCommand(graph) + +# v3 start + +proc recompilePartially(graph: ModuleGraph, projectFileIdx = InvalidFileIdx) = + if projectFileIdx == InvalidFileIdx: + myLog "Recompiling partially from root" + else: + myLog fmt "Recompiling partially starting from {graph.getModule(projectFileIdx)}" + + # inst caches are breaking incremental compilation when the cache caches stuff + # from dirty buffer + graph.clearInstCache(projectFileIdx) + + GC_fullCollect() + + try: + benchmark "Recompilation": + graph.compileProject(projectFileIdx) + except Exception as e: + myLog fmt "Failed to recompile partially with the following error:\n {e.msg} \n\n {e.getStackTrace()}" + try: + graph.recompileFullProject() + except Exception as e: + myLog fmt "Failed clean recompilation:\n {e.msg} \n\n {e.getStackTrace()}" + +func deduplicateSymInfoPair[SymInfoPair](xs: seq[SymInfoPair]): seq[SymInfoPair] = + # xs contains duplicate items and we want to filter them by range because the + # sym may not match. This can happen when xs contains the same definition but + # with different signature because suggestSym might be called multiple times + # for the same symbol (e. g. including/excluding the pragma) + result = newSeqOfCap[SymInfoPair](xs.len) + for itm in xs.reversed: + var found = false + for res in result: + if res.info.exactEquals(itm.info): + found = true + break + if not found: + result.add(itm) + result.reverse() + +func deduplicateSymInfoPair(xs: SuggestFileSymbolDatabase): SuggestFileSymbolDatabase = + # xs contains duplicate items and we want to filter them by range because the + # sym may not match. This can happen when xs contains the same definition but + # with different signature because suggestSym might be called multiple times + # for the same symbol (e. g. including/excluding the pragma) + result = SuggestFileSymbolDatabase( + lineInfo: newSeqOfCap[TinyLineInfo](xs.lineInfo.len), + sym: newSeqOfCap[PSym](xs.sym.len), + isDecl: newPackedBoolArray(), + caughtExceptions: newSeqOfCap[seq[PType]](xs.caughtExceptions.len), + caughtExceptionsSet: newPackedBoolArray(), + fileIndex: xs.fileIndex, + trackCaughtExceptions: xs.trackCaughtExceptions, + isSorted: false + ) + var i = xs.lineInfo.high + while i >= 0: + let itm = xs.lineInfo[i] + var found = false + for res in result.lineInfo: + if res.exactEquals(itm): + found = true + break + if not found: + result.add(xs.getSymInfoPair(i)) + dec i + result.reverse() + +proc findSymData(graph: ModuleGraph, trackPos: TLineInfo): + ref SymInfoPair = + let db = graph.fileSymbols(trackPos.fileIndex).deduplicateSymInfoPair + doAssert(db.fileIndex == trackPos.fileIndex) + for i in db.lineInfo.low..db.lineInfo.high: + if isTracked(db.lineInfo[i], TinyLineInfo(line: trackPos.line, col: trackPos.col), db.sym[i].name.s.len): + var res = db.getSymInfoPair(i) + new(result) + result[] = res + break + +func isInRange*(current, startPos, endPos: TinyLineInfo, tokenLen: int): bool = + result = + (current.line > startPos.line or (current.line == startPos.line and current.col>=startPos.col)) and + (current.line < endPos.line or (current.line == endPos.line and current.col <= endPos.col)) + +proc findSymDataInRange(graph: ModuleGraph, startPos, endPos: TLineInfo): + seq[SymInfoPair] = + result = newSeq[SymInfoPair]() + let db = graph.fileSymbols(startPos.fileIndex).deduplicateSymInfoPair + for i in db.lineInfo.low..db.lineInfo.high: + if isInRange(db.lineInfo[i], TinyLineInfo(line: startPos.line, col: startPos.col), TinyLineInfo(line: endPos.line, col: endPos.col), db.sym[i].name.s.len): + result.add(db.getSymInfoPair(i)) + +proc findSymData(graph: ModuleGraph, file: AbsoluteFile; line, col: int): + ref SymInfoPair = + let + fileIdx = fileInfoIdx(graph.config, file) + trackPos = newLineInfo(fileIdx, line, col) + result = findSymData(graph, trackPos) + +proc findSymDataInRange(graph: ModuleGraph, file: AbsoluteFile; startLine, startCol, endLine, endCol: int): + seq[SymInfoPair] = + let + fileIdx = fileInfoIdx(graph.config, file) + startPos = newLineInfo(fileIdx, startLine, startCol) + endPos = newLineInfo(fileIdx, endLine, endCol) + result = findSymDataInRange(graph, startPos, endPos) + +proc markDirtyIfNeeded(graph: ModuleGraph, file: string, originalFileIdx: FileIndex) = + let sha = $sha1.secureHashFile(file) + if graph.config.m.fileInfos[originalFileIdx.int32].hash != sha or graph.config.ideCmd in {ideSug, ideCon}: + myLog fmt "{file} changed compared to last compilation" + graph.markDirty originalFileIdx + graph.markClientsDirty originalFileIdx + else: + myLog fmt "No changes in file {file} compared to last compilation" + +proc suggestResult(graph: ModuleGraph, sym: PSym, info: TLineInfo, + defaultSection = ideNone, endLine: uint16 = 0, endCol = 0) = + let section = if defaultSection != ideNone: + defaultSection + elif sym.info.exactEquals(info): + ideDef + else: + ideUse + let suggest = symToSuggest(graph, sym, isLocal=false, section, + info, 100, PrefixMatch.None, false, 0, + endLine = endLine, endCol = endCol) + suggestResult(graph.config, suggest) + +proc suggestInlayHintResultType(graph: ModuleGraph, sym: PSym, info: TLineInfo, + defaultSection = ideNone, endLine: uint16 = 0, endCol = 0) = + let section = if defaultSection != ideNone: + defaultSection + elif sym.info.exactEquals(info): + ideDef + else: + ideUse + var suggestDef = symToSuggest(graph, sym, isLocal=false, section, + info, 100, PrefixMatch.None, false, 0, true, + endLine = endLine, endCol = endCol) + suggestDef.inlayHintInfo = suggestToSuggestInlayTypeHint(suggestDef) + suggestDef.section = ideInlayHints + if sym.kind == skForVar: + suggestDef.inlayHintInfo.allowInsert = false + suggestResult(graph.config, suggestDef) + +proc suggestInlayHintResultException(graph: ModuleGraph, sym: PSym, info: TLineInfo, + defaultSection = ideNone, caughtExceptions: seq[PType], caughtExceptionsSet: bool, endLine: uint16 = 0, endCol = 0) = + if not caughtExceptionsSet: + return + + if sym.kind == skParam and sfEffectsDelayed in sym.flags: + return + + var raisesList: seq[PType] = @[getEbase(graph, info)] + + let t = sym.typ + if not isNil(t) and not isNil(t.n) and t.n.len > 0 and t.n[0].len > exceptionEffects: + let effects = t.n[0] + if effects.kind == nkEffectList and effects.len == effectListLen: + let effs = effects[exceptionEffects] + if not isNil(effs): + raisesList = @[] + for eff in items(effs): + if not isNil(eff): + raisesList.add(eff.typ) + + var propagatedExceptionList: seq[PType] = @[] + for re in raisesList: + var exceptionIsPropagated = true + for ce in caughtExceptions: + if isNil(ce) or safeInheritanceDiff(re, ce) <= 0: + exceptionIsPropagated = false + break + if exceptionIsPropagated: + propagatedExceptionList.add(re) + + if propagatedExceptionList.len == 0: + return + + let section = if defaultSection != ideNone: + defaultSection + elif sym.info.exactEquals(info): + ideDef + else: + ideUse + var suggestDef = symToSuggest(graph, sym, isLocal=false, section, + info, 100, PrefixMatch.None, false, 0, true, + endLine = endLine, endCol = endCol) + suggestDef.inlayHintInfo = suggestToSuggestInlayExceptionHintLeft(suggestDef, propagatedExceptionList) + suggestDef.section = ideInlayHints + suggestResult(graph.config, suggestDef) + suggestDef.inlayHintInfo = suggestToSuggestInlayExceptionHintRight(suggestDef, propagatedExceptionList) + suggestResult(graph.config, suggestDef) + +const + # kinds for ideOutline and ideGlobalSymbols + searchableSymKinds = {skField, skEnumField, skIterator, skMethod, skFunc, skProc, skConverter, skTemplate} + +proc symbolEqual(left, right: PSym): bool = + # More relaxed symbol comparison + return left.info.exactEquals(right.info) and left.name == right.name + +proc findDef(n: PNode, line: uint16, col: int16): PNode = + if n.kind in {nkProcDef, nkIteratorDef, nkTemplateDef, nkMethodDef, nkMacroDef}: + if n.info.line == line: + return n + else: + for i in 0 ..< safeLen(n): + let res = findDef(n[i], line, col) + if res != nil: return res + +proc findByTLineInfo(trackPos: TLineInfo, infoPairs: SuggestFileSymbolDatabase): + ref SymInfoPair = + result = nil + if infoPairs.fileIndex == trackPos.fileIndex: + for i in infoPairs.lineInfo.low..infoPairs.lineInfo.high: + let s = infoPairs.getSymInfoPair(i) + if s.info.exactEquals trackPos: + new(result) + result[] = s + break + +proc outlineNode(graph: ModuleGraph, n: PNode, endInfo: TLineInfo, infoPairs: SuggestFileSymbolDatabase): bool = + proc checkSymbol(sym: PSym, info: TLineInfo): bool = + result = (sym.owner.kind in {skModule, skType} or sym.kind in {skProc, skMethod, skIterator, skTemplate, skType}) + + if n.kind == nkSym and n.sym.checkSymbol(n.info): + graph.suggestResult(n.sym, n.sym.info, ideOutline, endInfo.line, endInfo.col) + return true + elif n.kind == nkIdent: + let symData = findByTLineInfo(n.info, infoPairs) + if symData != nil and symData.sym.checkSymbol(symData.info): + let sym = symData.sym + graph.suggestResult(sym, sym.info, ideOutline, endInfo.line, endInfo.col) + return true + +proc handleIdentOrSym(graph: ModuleGraph, n: PNode, endInfo: TLineInfo, infoPairs: SuggestFileSymbolDatabase): bool = + for child in n: + if child.kind in {nkIdent, nkSym}: + if graph.outlineNode(child, endInfo, infoPairs): + return true + elif child.kind == nkPostfix: + if graph.handleIdentOrSym(child, endInfo, infoPairs): + return true + +proc iterateOutlineNodes(graph: ModuleGraph, n: PNode, infoPairs: SuggestFileSymbolDatabase) = + var matched = true + if n.kind == nkIdent: + let symData = findByTLineInfo(n.info, infoPairs) + if symData != nil and symData.sym.kind == skEnumField and symData.info.exactEquals(symData.sym.info): + let sym = symData.sym + graph.suggestResult(sym, sym.info, ideOutline, n.endInfo.line, n.endInfo.col) + elif (n.kind in {nkFuncDef, nkProcDef, nkTypeDef, nkMacroDef, nkTemplateDef, nkConverterDef, nkEnumFieldDef, nkConstDef}): + matched = handleIdentOrSym(graph, n, n.endInfo, infoPairs) + else: + matched = false + + if n.kind != nkFormalParams: + for child in n: + graph.iterateOutlineNodes(child, infoPairs) + +proc calculateExpandRange(n: PNode, info: TLineInfo): TLineInfo = + if ((n.kind in {nkFuncDef, nkProcDef, nkIteratorDef, nkTemplateDef, nkMethodDef, nkConverterDef} and + n.info.exactEquals(info)) or + (n.kind in {nkCall, nkCommand} and n[0].info.exactEquals(info))): + result = n.endInfo + else: + for child in n: + result = child.calculateExpandRange(info) + if result != unknownLineInfo: + return result + result = unknownLineInfo + +proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile, line, col: int; tag: string, + graph: ModuleGraph) = + let conf = graph.config + conf.writelnHook = proc (s: string) = discard + conf.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo; + msg: string; sev: Severity) = + let suggest = Suggest(section: ideChk, filePath: toFullPath(conf, info), + line: toLinenumber(info), column: toColumn(info), doc: msg, forth: $sev) + graph.suggestErrors.mgetOrPut(info.fileIndex, @[]).add suggest + + conf.ideCmd = cmd + + myLog fmt "cmd: {cmd}, file: {file}[{line}:{col}], dirtyFile: {dirtyfile}, tag: {tag}" + + var fileIndex: FileIndex + + if not (cmd in {ideRecompile, ideGlobalSymbols}): + fileIndex = fileInfoIdx(conf, file) + msgs.setDirtyFile( + conf, + fileIndex, + if dirtyfile.isEmpty: AbsoluteFile"" else: dirtyfile) + + if not dirtyfile.isEmpty: + graph.markDirtyIfNeeded(dirtyFile.string, fileInfoIdx(conf, file)) + + # these commands require fully compiled project + if cmd in {ideUse, ideDus, ideGlobalSymbols, ideChk, ideInlayHints} and graph.needsCompilation(): + graph.recompilePartially() + # when doing incremental build for the project root we should make sure that + # everything is unmarked as no longer beeing dirty in case there is no + # longer reference to a particular module. E. g. A depends on B, B is marked + # as dirty and A loses B import. + graph.unmarkAllDirty() + + # these commands require partially compiled project + elif cmd in {ideSug, ideCon, ideOutline, ideHighlight, ideDef, ideChkFile, ideType, ideDeclaration, ideExpand} and + (graph.needsCompilation(fileIndex) or cmd in {ideSug, ideCon}): + # for ideSug use v2 implementation + if cmd in {ideSug, ideCon}: + conf.m.trackPos = newLineInfo(fileIndex, line, col) + conf.m.trackPosAttached = false + else: + conf.m.trackPos = default(TLineInfo) + graph.recompilePartially(fileIndex) + + case cmd + of ideDef: + let s = graph.findSymData(file, line, col) + if not s.isNil: + graph.suggestResult(s.sym, s.sym.info) + of ideType: + let s = graph.findSymData(file, line, col) + if not s.isNil: + let typeSym = s.sym.typ.sym + if typeSym != nil: + graph.suggestResult(typeSym, typeSym.info, ideType) + elif s.sym.typ.len != 0: + let genericType = s.sym.typ[0].sym + graph.suggestResult(genericType, genericType.info, ideType) + of ideUse, ideDus: + let symbol = graph.findSymData(file, line, col) + if not symbol.isNil: + var res: seq[SymInfoPair] = @[] + for s in graph.suggestSymbolsIter: + if s.sym.symbolEqual(symbol.sym): + res.add(s) + for s in res.deduplicateSymInfoPair(): + graph.suggestResult(s.sym, s.info) + of ideHighlight: + let sym = graph.findSymData(file, line, col) + if not sym.isNil: + let fs = graph.fileSymbols(fileIndex) + var usages: seq[SymInfoPair] = @[] + for i in fs.lineInfo.low..fs.lineInfo.high: + if fs.sym[i] == sym.sym: + usages.add(fs.getSymInfoPair(i)) + myLog fmt "Found {usages.len} usages in {file.string}" + for s in usages: + graph.suggestResult(s.sym, s.info) + of ideRecompile: + graph.recompileFullProject() + of ideChanged: + graph.markDirtyIfNeeded(file.string, fileIndex) + of ideSug, ideCon: + # ideSug/ideCon performs partial build of the file, thus mark it dirty for the + # future calls. + graph.markDirtyIfNeeded(file.string, fileIndex) + graph.recompilePartially(fileIndex) + let m = graph.getModule fileIndex + incl m.flags, sfDirty + of ideOutline: + let n = parseFile(fileIndex, graph.cache, graph.config) + graph.iterateOutlineNodes(n, graph.fileSymbols(fileIndex).deduplicateSymInfoPair) + of ideChk: + myLog fmt "Reporting errors for {graph.suggestErrors.len} file(s)" + for sug in graph.suggestErrorsIter: + suggestResult(graph.config, sug) + of ideChkFile: + let errors = graph.suggestErrors.getOrDefault(fileIndex, @[]) + myLog fmt "Reporting {errors.len} error(s) for {file.string}" + for error in errors: + suggestResult(graph.config, error) + of ideGlobalSymbols: + var + counter = 0 + res: seq[SymInfoPair] = @[] + + for s in graph.suggestSymbolsIter: + if (sfGlobal in s.sym.flags or s.sym.kind in searchableSymKinds) and + s.sym.info == s.info: + if contains(s.sym.name.s, file.string): + inc counter + res = res.filterIt(not it.info.exactEquals(s.info)) + res.add s + # stop after first 1000 matches... + if counter > 1000: + break + + # ... then sort them by weight ... + res.sort() do (left, right: SymInfoPair) -> int: + let + leftString = left.sym.name.s + rightString = right.sym.name.s + leftIndex = leftString.find(file.string) + rightIndex = rightString.find(file.string) + + if leftIndex == rightIndex: + result = cmp(toLowerAscii(leftString), + toLowerAscii(rightString)) + else: + result = cmp(leftIndex, rightIndex) + + # ... and send first 100 results + if res.len > 0: + for i in 0 .. min(100, res.len - 1): + let s = res[i] + graph.suggestResult(s.sym, s.info) + + of ideDeclaration: + let s = graph.findSymData(file, line, col) + if not s.isNil: + # find first mention of the symbol in the file containing the definition. + # It is either the definition or the declaration. + var first: SymInfoPair + let db = graph.fileSymbols(s.sym.info.fileIndex).deduplicateSymInfoPair + for i in db.lineInfo.low..db.lineInfo.high: + if s.sym.symbolEqual(db.sym[i]): + first = db.getSymInfoPair(i) + break + + if s.info.exactEquals(first.info): + # we are on declaration, go to definition + graph.suggestResult(first.sym, first.sym.info, ideDeclaration) + else: + # we are on definition or usage, look for declaration + graph.suggestResult(first.sym, first.info, ideDeclaration) + of ideExpand: + var level: int = high(int) + let index = skipWhitespace(tag, 0); + let trimmed = substr(tag, index) + if not (trimmed == "" or trimmed == "all"): + discard parseInt(trimmed, level, 0) + + conf.expandPosition = newLineInfo(fileIndex, line, col) + conf.expandLevels = level + conf.expandProgress = false + conf.expandNodeResult = "" + + graph.markDirty fileIndex + graph.markClientsDirty fileIndex + graph.recompilePartially() + var suggest = Suggest() + suggest.section = ideExpand + suggest.version = 3 + suggest.line = line + suggest.column = col + suggest.doc = graph.config.expandNodeResult + if suggest.doc != "": + let + n = parseFile(fileIndex, graph.cache, graph.config) + endInfo = n.calculateExpandRange(conf.expandPosition) + + suggest.endLine = endInfo.line + suggest.endCol = endInfo.col + + suggestResult(graph.config, suggest) + + graph.markDirty fileIndex + graph.markClientsDirty fileIndex + of ideInlayHints: + myLog fmt "Executing inlayHints" + var endLine = 0 + var endCol = -1 + var i = 0 + i += skipWhile(tag, seps, i) + i += parseInt(tag, endLine, i) + i += skipWhile(tag, seps, i) + i += parseInt(tag, endCol, i) + i += skipWhile(tag, seps, i) + var typeHints = true + var exceptionHints = false + while i <= tag.high: + var token: string + i += parseUntil(tag, token, seps, i) + i += skipWhile(tag, seps, i) + case token: + of "+typeHints": + typeHints = true + of "-typeHints": + typeHints = false + of "+exceptionHints": + exceptionHints = true + of "-exceptionHints": + exceptionHints = false + else: + myLog fmt "Discarding unknown inlay hint parameter {token}" + + let s = graph.findSymDataInRange(file, line, col, endLine, endCol) + for q in s: + if typeHints and q.sym.kind in {skLet, skVar, skForVar, skConst} and q.isDecl and not q.sym.hasUserSpecifiedType: + graph.suggestInlayHintResultType(q.sym, q.info, ideInlayHints) + if exceptionHints and q.sym.kind in {skProc, skFunc, skMethod, skVar, skLet, skParam} and not q.isDecl: + graph.suggestInlayHintResultException(q.sym, q.info, ideInlayHints, caughtExceptions = q.caughtExceptions, caughtExceptionsSet = q.caughtExceptionsSet) + else: + myLog fmt "Discarding {cmd}" + +# v3 end +when isMainModule: + handleCmdLine(newIdentCache(), newConfigRef()) +else: + export Suggest + export IdeCmd + export AbsoluteFile + type NimSuggest* = ref object + graph: ModuleGraph + idle: int + cachedMsgs: CachedMsgs + + proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = + var retval: ModuleGraph + proc mockCommand(graph: ModuleGraph) = + retval = graph + let conf = graph.config + conf.setCmd cmdIdeTools + defineSymbol(conf.symbols, $conf.backend) + clearPasses(graph) + registerPass graph, verbosePass + registerPass graph, semPass + + wantMainModule(conf) + + if not fileExists(conf.projectFull): + quit "cannot find file: " & conf.projectFull.string + + add(conf.searchPaths, conf.libpath) + + conf.setErrorMaxHighMaybe + # do not print errors, but log them + conf.writelnHook = myLog + conf.structuredErrorHook = nil + + # compile the project before showing any input so that we already + # can answer questions right away: + compileProject(graph) + + + proc mockCmdLine(pass: TCmdLinePass, cmd: string; conf: ConfigRef) = + conf.suggestVersion = 0 + let a = unixToNativePath(project) + if dirExists(a) and not fileExists(a.addFileExt("nim")): + conf.projectName = findProjectNimFile(conf, a) + # don't make it worse, report the error the old way: + if conf.projectName.len == 0: conf.projectName = a + else: + conf.projectName = a + # if processArgument(pass, p, argsCount): break + let + cache = newIdentCache() + conf = newConfigRef() + self = NimProg( + suggestMode: true, + processCmdLine: mockCmdLine + ) + self.initDefinesProg(conf, "nimsuggest") + + self.processCmdLineAndProjectPath(conf) + + if gMode != mstdin: + conf.writelnHook = proc (msg: string) = discard + # Find Nim's prefix dir. + if nimPath == "": + conf.prefixDir = conf.getPrefixDir() + else: + conf.prefixDir = AbsoluteDir nimPath + + #msgs.writelnHook = proc (line: string) = log(line) + myLog("START " & conf.projectFull.string) + + var graph = newModuleGraph(cache, conf) + if self.loadConfigsAndProcessCmdLine(cache, conf, graph): + mockCommand(graph) + if gLogging: + myLog("Search paths:") + for it in conf.searchPaths: + myLog(" " & it.string) + + retval.doStopCompile = proc (): bool = false + return NimSuggest(graph: retval, idle: 0, cachedMsgs: @[]) + + proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int): seq[Suggest] = + var retval: seq[Suggest] = @[] + let conf = nimsuggest.graph.config + conf.ideCmd = cmd + conf.writelnHook = proc (line: string) = + retval.add(Suggest(section: ideMsg, doc: line)) + conf.suggestionResultHook = proc (s: Suggest) = + retval.add(s) + conf.writelnHook = proc (s: string) = + stderr.write s & "\n" + if conf.ideCmd == ideKnown: + retval.add(Suggest(section: ideKnown, quality: ord(fileInfoKnown(conf, file)))) + elif conf.ideCmd == ideProject: + retval.add(Suggest(section: ideProject, filePath: string conf.projectFull)) + else: + if conf.ideCmd == ideChk: + for cm in nimsuggest.cachedMsgs: errorHook(conf, cm.info, cm.msg, cm.sev) + if conf.ideCmd == ideChk: + conf.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo; msg: string; sev: Severity) = + retval.add(Suggest(section: ideChk, filePath: toFullPath(conf, info), + line: toLinenumber(info), column: toColumn(info), doc: msg, + forth: $sev)) + + else: + conf.structuredErrorHook = nil + executeNoHooks(conf.ideCmd, file, dirtyfile, line, col, nimsuggest.graph) + return retval diff --git a/nimsuggest/nimsuggest.nim.cfg b/nimsuggest/nimsuggest.nim.cfg new file mode 100644 index 000000000..394449740 --- /dev/null +++ b/nimsuggest/nimsuggest.nim.cfg @@ -0,0 +1,24 @@ +# Special configuration file for the Nim project + +gc:markAndSweep + +hint[XDeclaredButNotUsed]:off + +path:"$lib/packages/docutils" + +define:useStdoutAsStdmsg +define:nimsuggest +define:nimcore + +# die when nimsuggest uses more than 4GB: +@if cpu32: + define:"nimMaxHeap=2000" +@else: + define:"nimMaxHeap=4000" +@end + +#define:useNodeIds +#define:booting +#define:noDocgen +--path:"$nim" +--threads:on diff --git a/nimsuggest/nimsuggest.nimble b/nimsuggest/nimsuggest.nimble new file mode 100644 index 000000000..b3790e116 --- /dev/null +++ b/nimsuggest/nimsuggest.nimble @@ -0,0 +1,8 @@ +include "../lib/system/compilation.nim" +version = $NimMajor & "." & $NimMinor & "." & $NimPatch +author = "Andreas Rumpf" +description = "Tool for providing auto completion data for Nim source code." +license = "MIT" +bin = @["nimsuggest"] + +requires "compiler >= 1.9.0" , "checksums" diff --git a/nimsuggest/procmonitor.nim b/nimsuggest/procmonitor.nim new file mode 100644 index 000000000..0f1ba1e0d --- /dev/null +++ b/nimsuggest/procmonitor.nim @@ -0,0 +1,34 @@ +# Monitor a client process and shutdown the current process, if the client +# process is found to be dead + +import os + +when defined(posix): + import posix_utils + import posix + +when defined(windows): + import winlean + +when defined(posix): + proc monitorClientProcessIdThreadProc(pid: int) {.thread.} = + while true: + sleep(1000) + try: + sendSignal(Pid(pid), 0) + except: + discard kill(Pid(getCurrentProcessId()), cint(SIGTERM)) + +when defined(windows): + proc monitorClientProcessIdThreadProc(pid: int) {.thread.} = + var process = openProcess(SYNCHRONIZE, 0, DWORD(pid)) + if process != 0: + discard waitForSingleObject(process, INFINITE) + discard closeHandle(process) + quit(0) + +var tid: Thread[int] + +proc hookProcMonitor*(pid: int) = + when defined(posix) or defined(windows): + createThread(tid, monitorClientProcessIdThreadProc, pid) diff --git a/nimsuggest/sexp.nim b/nimsuggest/sexp.nim new file mode 100644 index 000000000..03369ccb7 --- /dev/null +++ b/nimsuggest/sexp.nim @@ -0,0 +1,657 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2015 Andreas Rumpf, Dominik Picheta +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## **Note:** Import ``nimsuggest/sexp`` to use this module + +import + hashes, strutils, lexbase, streams, unicode, macros + +import std/private/decode_helpers + +when defined(nimPreviewSlimSystem): + import std/[assertions, formatfloat] + +type + SexpEventKind* = enum ## enumeration of all events that may occur when parsing + sexpError, ## an error occurred during parsing + sexpEof, ## end of file reached + sexpString, ## a string literal + sexpSymbol, ## a symbol + sexpInt, ## an integer literal + sexpFloat, ## a float literal + sexpNil, ## the value ``nil`` + sexpDot, ## the dot to separate car/cdr + sexpListStart, ## start of a list: the ``(`` token + sexpListEnd, ## end of a list: the ``)`` token + + TTokKind = enum # must be synchronized with SexpEventKind! + tkError, + tkEof, + tkString, + tkSymbol, + tkInt, + tkFloat, + tkNil, + tkDot, + tkParensLe, + tkParensRi + tkSpace + + SexpError* = enum ## enumeration that lists all errors that can occur + errNone, ## no error + errInvalidToken, ## invalid token + errParensRiExpected, ## ``)`` expected + errQuoteExpected, ## ``"`` expected + errEofExpected, ## EOF expected + + SexpParser* = object of BaseLexer ## the parser object. + a: string + tok: TTokKind + kind: SexpEventKind + err: SexpError + +const + errorMessages: array[SexpError, string] = [ + "no error", + "invalid token", + "')' expected", + "'\"' or \"'\" expected", + "EOF expected", + ] + tokToStr: array[TTokKind, string] = [ + "invalid token", + "EOF", + "string literal", + "symbol", + "int literal", + "float literal", + "nil", + ".", + "(", ")", "space" + ] + +proc close*(my: var SexpParser) {.inline.} = + ## closes the parser `my` and its associated input stream. + lexbase.close(my) + +proc str*(my: SexpParser): string {.inline.} = + ## returns the character data for the events: ``sexpInt``, ``sexpFloat``, + ## ``sexpString`` + assert(my.kind in {sexpInt, sexpFloat, sexpString}) + result = my.a + +proc getInt*(my: SexpParser): BiggestInt {.inline.} = + ## returns the number for the event: ``sexpInt`` + assert(my.kind == sexpInt) + result = parseBiggestInt(my.a) + +proc getFloat*(my: SexpParser): float {.inline.} = + ## returns the number for the event: ``sexpFloat`` + assert(my.kind == sexpFloat) + result = parseFloat(my.a) + +proc kind*(my: SexpParser): SexpEventKind {.inline.} = + ## returns the current event type for the SEXP parser + result = my.kind + +proc getColumn*(my: SexpParser): int {.inline.} = + ## get the current column the parser has arrived at. + result = getColNumber(my, my.bufpos) + +proc getLine*(my: SexpParser): int {.inline.} = + ## get the current line the parser has arrived at. + result = my.lineNumber + +proc errorMsg*(my: SexpParser): string = + ## returns a helpful error message for the event ``sexpError`` + assert(my.kind == sexpError) + result = "($1, $2) Error: $3" % [$getLine(my), $getColumn(my), errorMessages[my.err]] + +proc errorMsgExpected*(my: SexpParser, e: string): string = + ## returns an error message "`e` expected" in the same format as the + ## other error messages + result = "($1, $2) Error: $3" % [$getLine(my), $getColumn(my), e & " expected"] + +proc parseString(my: var SexpParser): TTokKind = + result = tkString + var pos = my.bufpos + 1 + while true: + case my.buf[pos] + of '\0': + my.err = errQuoteExpected + result = tkError + break + of '"': + inc(pos) + break + of '\\': + case my.buf[pos+1] + of '\\', '"', '\'', '/': + add(my.a, my.buf[pos+1]) + inc(pos, 2) + of 'b': + add(my.a, '\b') + inc(pos, 2) + of 'f': + add(my.a, '\f') + inc(pos, 2) + of 'n': + add(my.a, '\L') + inc(pos, 2) + of 'r': + add(my.a, '\C') + inc(pos, 2) + of 't': + add(my.a, '\t') + inc(pos, 2) + of 'u': + inc(pos, 2) + var r: int + if handleHexChar(my.buf[pos], r): inc(pos) + if handleHexChar(my.buf[pos], r): inc(pos) + if handleHexChar(my.buf[pos], r): inc(pos) + if handleHexChar(my.buf[pos], r): inc(pos) + add(my.a, toUTF8(Rune(r))) + else: + # don't bother with the error + add(my.a, my.buf[pos]) + inc(pos) + of '\c': + pos = lexbase.handleCR(my, pos) + add(my.a, '\c') + of '\L': + pos = lexbase.handleLF(my, pos) + add(my.a, '\L') + else: + add(my.a, my.buf[pos]) + inc(pos) + my.bufpos = pos # store back + +proc parseNumber(my: var SexpParser) = + var pos = my.bufpos + if my.buf[pos] == '-': + add(my.a, '-') + inc(pos) + if my.buf[pos] == '.': + add(my.a, "0.") + inc(pos) + else: + while my.buf[pos] in Digits: + add(my.a, my.buf[pos]) + inc(pos) + if my.buf[pos] == '.': + add(my.a, '.') + inc(pos) + # digits after the dot: + while my.buf[pos] in Digits: + add(my.a, my.buf[pos]) + inc(pos) + if my.buf[pos] in {'E', 'e'}: + add(my.a, my.buf[pos]) + inc(pos) + if my.buf[pos] in {'+', '-'}: + add(my.a, my.buf[pos]) + inc(pos) + while my.buf[pos] in Digits: + add(my.a, my.buf[pos]) + inc(pos) + my.bufpos = pos + +proc parseSymbol(my: var SexpParser) = + var pos = my.bufpos + if my.buf[pos] in IdentStartChars: + while my.buf[pos] in IdentChars: + add(my.a, my.buf[pos]) + inc(pos) + my.bufpos = pos + +proc getTok(my: var SexpParser): TTokKind = + setLen(my.a, 0) + case my.buf[my.bufpos] + of '-', '0'..'9': # numbers that start with a . are not parsed + # correctly. + parseNumber(my) + if {'.', 'e', 'E'} in my.a: + result = tkFloat + else: + result = tkInt + of '"': #" # gotta fix nim-mode + result = parseString(my) + of '(': + inc(my.bufpos) + result = tkParensLe + of ')': + inc(my.bufpos) + result = tkParensRi + of '\0': + result = tkEof + of 'a'..'z', 'A'..'Z', '_': + parseSymbol(my) + if my.a == "nil": + result = tkNil + else: + result = tkSymbol + of ' ': + result = tkSpace + inc(my.bufpos) + of '.': + result = tkDot + inc(my.bufpos) + else: + inc(my.bufpos) + result = tkError + my.tok = result + +# ------------- higher level interface --------------------------------------- + +type + SexpNodeKind* = enum ## possible SEXP node types + SNil, + SInt, + SFloat, + SString, + SSymbol, + SList, + SCons + + SexpNode* = ref SexpNodeObj ## SEXP node + SexpNodeObj* {.acyclic.} = object + case kind*: SexpNodeKind + of SString: + str*: string + of SSymbol: + symbol*: string + of SInt: + num*: BiggestInt + of SFloat: + fnum*: float + of SList: + elems*: seq[SexpNode] + of SCons: + car: SexpNode + cdr: SexpNode + of SNil: + discard + + Cons = tuple[car: SexpNode, cdr: SexpNode] + + SexpParsingError* = object of ValueError ## is raised for a SEXP error + +proc raiseParseErr*(p: SexpParser, msg: string) {.noinline, noreturn.} = + ## raises an `ESexpParsingError` exception. + raise newException(SexpParsingError, errorMsgExpected(p, msg)) + +proc newSString*(s: string): SexpNode = + ## Creates a new `SString SexpNode`. + result = SexpNode(kind: SString, str: s) + +proc newSInt*(n: BiggestInt): SexpNode = + ## Creates a new `SInt SexpNode`. + result = SexpNode(kind: SInt, num: n) + +proc newSFloat*(n: float): SexpNode = + ## Creates a new `SFloat SexpNode`. + result = SexpNode(kind: SFloat, fnum: n) + +proc newSNil*(): SexpNode = + ## Creates a new `SNil SexpNode`. + result = SexpNode(kind: SNil) + +proc newSCons*(car, cdr: SexpNode): SexpNode = + ## Creates a new `SCons SexpNode` + result = SexpNode(kind: SCons, car: car, cdr: cdr) + +proc newSList*(): SexpNode = + ## Creates a new `SList SexpNode` + result = SexpNode(kind: SList, elems: @[]) + +proc newSSymbol*(s: string): SexpNode = + result = SexpNode(kind: SSymbol, symbol: s) + +proc getStr*(n: SexpNode, default: string = ""): string = + ## Retrieves the string value of a `SString SexpNode`. + ## + ## Returns ``default`` if ``n`` is not a ``SString``. + if n.kind != SString: return default + else: return n.str + +proc getNum*(n: SexpNode, default: BiggestInt = 0): BiggestInt = + ## Retrieves the int value of a `SInt SexpNode`. + ## + ## Returns ``default`` if ``n`` is not a ``SInt``. + if n.kind != SInt: return default + else: return n.num + +proc getFNum*(n: SexpNode, default: float = 0.0): float = + ## Retrieves the float value of a `SFloat SexpNode`. + ## + ## Returns ``default`` if ``n`` is not a ``SFloat``. + if n.kind != SFloat: return default + else: return n.fnum + +proc getSymbol*(n: SexpNode, default: string = ""): string = + ## Retrieves the int value of a `SList SexpNode`. + ## + ## Returns ``default`` if ``n`` is not a ``SList``. + if n.kind != SSymbol: return default + else: return n.symbol + +proc getElems*(n: SexpNode, default: seq[SexpNode] = @[]): seq[SexpNode] = + ## Retrieves the int value of a `SList SexpNode`. + ## + ## Returns ``default`` if ``n`` is not a ``SList``. + if n.kind == SNil: return @[] + elif n.kind != SList: return default + else: return n.elems + +proc getCons*(n: SexpNode, defaults: Cons = (newSNil(), newSNil())): Cons = + ## Retrieves the cons value of a `SList SexpNode`. + ## + ## Returns ``default`` if ``n`` is not a ``SList``. + if n.kind == SCons: return (n.car, n.cdr) + elif n.kind == SList: return (n.elems[0], n.elems[1]) + else: return defaults + +proc sexp*(s: string): SexpNode = + ## Generic constructor for SEXP data. Creates a new `SString SexpNode`. + result = SexpNode(kind: SString, str: s) + +proc sexp*(n: BiggestInt): SexpNode = + ## Generic constructor for SEXP data. Creates a new `SInt SexpNode`. + result = SexpNode(kind: SInt, num: n) + +proc sexp*(n: float): SexpNode = + ## Generic constructor for SEXP data. Creates a new `SFloat SexpNode`. + result = SexpNode(kind: SFloat, fnum: n) + +proc sexp*(b: bool): SexpNode = + ## Generic constructor for SEXP data. Creates a new `SSymbol + ## SexpNode` with value t or `SNil SexpNode`. + if b: + result = SexpNode(kind: SSymbol, symbol: "t") + else: + result = SexpNode(kind: SNil) + +proc sexp*(elements: openArray[SexpNode]): SexpNode = + ## Generic constructor for SEXP data. Creates a new `SList SexpNode` + result = SexpNode(kind: SList) + newSeq(result.elems, elements.len) + for i, p in pairs(elements): result.elems[i] = p + +proc sexp*(s: SexpNode): SexpNode = + result = s + +proc toSexp(x: NimNode): NimNode {.compileTime.} = + case x.kind + of nnkBracket: + result = newNimNode(nnkBracket) + for i in 0 ..< x.len: + result.add(toSexp(x[i])) + + else: + result = x + + result = prefix(result, "sexp") + +macro convertSexp*(x: untyped): untyped = + ## Convert an expression to a SexpNode directly, without having to specify + ## `%` for every element. + result = toSexp(x) + +func `==`* (a, b: SexpNode): bool = + ## Check two nodes for equality + if a.isNil: + if b.isNil: return true + return false + elif b.isNil or a.kind != b.kind: + return false + else: + return case a.kind + of SString: + a.str == b.str + of SInt: + a.num == b.num + of SFloat: + a.fnum == b.fnum + of SNil: + true + of SList: + a.elems == b.elems + of SSymbol: + a.symbol == b.symbol + of SCons: + a.car == b.car and a.cdr == b.cdr + +proc hash* (n:SexpNode): Hash = + ## Compute the hash for a SEXP node + case n.kind + of SList: + result = hash(n.elems) + of SInt: + result = hash(n.num) + of SFloat: + result = hash(n.fnum) + of SString: + result = hash(n.str) + of SNil: + result = hash(0) + of SSymbol: + result = hash(n.symbol) + of SCons: + result = hash(n.car) !& hash(n.cdr) + +proc len*(n: SexpNode): int = + ## If `n` is a `SList`, it returns the number of elements. + ## If `n` is a `JObject`, it returns the number of pairs. + ## Else it returns 0. + case n.kind + of SList: result = n.elems.len + else: discard + +proc `[]`*(node: SexpNode, index: int): SexpNode = + ## Gets the node at `index` in a List. Result is undefined if `index` + ## is out of bounds + assert(not isNil(node)) + assert(node.kind == SList) + return node.elems[index] + +proc add*(father, child: SexpNode) = + ## Adds `child` to a SList node `father`. + assert father.kind == SList + father.elems.add(child) + +# ------------- pretty printing ---------------------------------------------- + +proc indent(s: var string, i: int) = + s.add(spaces(i)) + +proc newIndent(curr, indent: int, ml: bool): int = + if ml: return curr + indent + else: return indent + +proc nl(s: var string, ml: bool) = + if ml: s.add("\n") + +proc escapeJson*(s: string): string = + ## Converts a string `s` to its JSON representation. + result = newStringOfCap(s.len + s.len shr 3) + result.add("\"") + for x in runes(s): + var r = int(x) + if r >= 32 and r <= 127: + var c = chr(r) + case c + of '"': result.add("\\\"") #" # gotta fix nim-mode + of '\\': result.add("\\\\") + else: result.add(c) + else: + result.add("\\u") + result.add(toHex(r, 4)) + result.add("\"") + +proc copy*(p: SexpNode): SexpNode = + ## Performs a deep copy of `a`. + case p.kind + of SString: + result = newSString(p.str) + of SInt: + result = newSInt(p.num) + of SFloat: + result = newSFloat(p.fnum) + of SNil: + result = newSNil() + of SSymbol: + result = newSSymbol(p.symbol) + of SList: + result = newSList() + for i in items(p.elems): + result.elems.add(copy(i)) + of SCons: + result = newSCons(copy(p.car), copy(p.cdr)) + +proc toPretty(result: var string, node: SexpNode, indent = 2, ml = true, + lstArr = false, currIndent = 0) = + case node.kind + of SString: + if lstArr: result.indent(currIndent) + result.add(escapeJson(node.str)) + of SInt: + if lstArr: result.indent(currIndent) + result.addInt(node.num) + of SFloat: + if lstArr: result.indent(currIndent) + result.addFloat(node.fnum) + of SNil: + if lstArr: result.indent(currIndent) + result.add("nil") + of SSymbol: + if lstArr: result.indent(currIndent) + result.add(node.symbol) + of SList: + if lstArr: result.indent(currIndent) + if len(node.elems) != 0: + result.add("(") + result.nl(ml) + for i in 0..len(node.elems)-1: + if i > 0: + result.add(" ") + result.nl(ml) # New Line + toPretty(result, node.elems[i], indent, ml, + true, newIndent(currIndent, indent, ml)) + result.nl(ml) + result.indent(currIndent) + result.add(")") + else: result.add("nil") + of SCons: + if lstArr: result.indent(currIndent) + result.add("(") + toPretty(result, node.car, indent, ml, + true, newIndent(currIndent, indent, ml)) + result.add(" . ") + toPretty(result, node.cdr, indent, ml, + true, newIndent(currIndent, indent, ml)) + result.add(")") + +proc pretty*(node: SexpNode, indent = 2): string = + ## Converts `node` to its Sexp Representation, with indentation and + ## on multiple lines. + result = "" + toPretty(result, node, indent) + +proc `$`*(node: SexpNode): string = + ## Converts `node` to its SEXP Representation on one line. + result = "" + toPretty(result, node, 0, false) + +iterator items*(node: SexpNode): SexpNode = + ## Iterator for the items of `node`. `node` has to be a SList. + assert node.kind == SList + for i in items(node.elems): + yield i + +iterator mitems*(node: var SexpNode): var SexpNode = + ## Iterator for the items of `node`. `node` has to be a SList. Items can be + ## modified. + assert node.kind == SList + for i in mitems(node.elems): + yield i + +proc eat(p: var SexpParser, tok: TTokKind) = + if p.tok == tok: discard getTok(p) + else: raiseParseErr(p, tokToStr[tok]) + +proc parseSexp(p: var SexpParser): SexpNode = + ## Parses SEXP from a SEXP Parser `p`. + case p.tok + of tkString: + # we capture 'p.a' here, so we need to give it a fresh buffer afterwards: + result = SexpNode(kind: SString, str: move p.a) + discard getTok(p) + of tkInt: + result = newSInt(parseBiggestInt(p.a)) + discard getTok(p) + of tkFloat: + result = newSFloat(parseFloat(p.a)) + discard getTok(p) + of tkNil: + result = newSNil() + discard getTok(p) + of tkSymbol: + result = SexpNode(kind: SSymbol, symbol: move p.a) + discard getTok(p) + of tkParensLe: + result = newSList() + discard getTok(p) + while p.tok notin {tkParensRi, tkDot}: + result.add(parseSexp(p)) + if p.tok != tkSpace: break + discard getTok(p) + if p.tok == tkDot: + eat(p, tkDot) + eat(p, tkSpace) + result.add(parseSexp(p)) + result = newSCons(result[0], result[1]) + eat(p, tkParensRi) + of tkSpace, tkDot, tkError, tkParensRi, tkEof: + raiseParseErr(p, "(") + +proc open*(my: var SexpParser, input: Stream) = + ## initializes the parser with an input stream. + lexbase.open(my, input) + my.kind = sexpError + my.a = "" + +proc parseSexp*(s: Stream): SexpNode = + ## Parses from a buffer `s` into a `SexpNode`. + var p: SexpParser + p.open(s) + discard getTok(p) # read first token + result = p.parseSexp() + p.close() + +proc parseSexp*(buffer: string): SexpNode = + ## Parses Sexp from `buffer`. + result = parseSexp(newStringStream(buffer)) + +when isMainModule: + let testSexp = parseSexp("""(1 (98 2) nil (2) foobar "foo" 9.234)""") + assert(testSexp[0].getNum == 1) + assert(testSexp[1][0].getNum == 98) + assert(testSexp[2].getElems == @[]) + assert(testSexp[4].getSymbol == "foobar") + assert(testSexp[5].getStr == "foo") + + let alist = parseSexp("""((1 . 2) (2 . "foo"))""") + assert(alist[0].getCons.car.getNum == 1) + assert(alist[0].getCons.cdr.getNum == 2) + assert(alist[1].getCons.cdr.getStr == "foo") + + # Generator: + var j = convertSexp([true, false, "foobar", [1, 2, "baz"]]) + assert($j == """(t nil "foobar" (1 2 "baz"))""") diff --git a/nimsuggest/tester.nim b/nimsuggest/tester.nim new file mode 100644 index 000000000..9b9488348 --- /dev/null +++ b/nimsuggest/tester.nim @@ -0,0 +1,374 @@ +# Tester for nimsuggest. +# Every test file can have a #[!]# comment that is deleted from the input +# before 'nimsuggest' is invoked to ensure this token doesn't make a +# crucial difference for Nim's parser. +# When debugging, to run a single test, use for e.g.: +# `nim r nimsuggest/tester.nim nimsuggest/tests/tsug_accquote.nim` + +import os, osproc, strutils, streams, re, sexp, net +from sequtils import toSeq + +type + Test = object + filename, cmd, dest: string + startup: seq[string] + script: seq[(string, string)] + disabled: bool + +const + DummyEof = "!EOF!" + tpath = "nimsuggest/tests" + # we could also use `stdtest/specialpaths` + +import std/compilesettings + +proc parseTest(filename: string; epcMode=false): Test = + const cursorMarker = "#[!]#" + let nimsug = "bin" / addFileExt("nimsuggest_testing", ExeExt) + doAssert nimsug.fileExists, nimsug + const libpath = querySetting(libPath) + result.filename = filename + result.dest = getTempDir() / extractFilename(filename) + result.cmd = nimsug & " --tester " & result.dest + result.script = @[] + result.startup = @[] + var tmp = open(result.dest, fmWrite) + var specSection = 0 + var markers = newSeq[string]() + var i = 1 + for x in lines(filename): + let marker = x.find(cursorMarker) + if marker >= 0: + if epcMode: + markers.add "(\"" & filename & "\" " & $i & " " & $marker & " \"" & result.dest & "\")" + else: + markers.add "\"" & filename & "\";\"" & result.dest & "\":" & $i & ":" & $marker + tmp.writeLine x.replace(cursorMarker, "") + else: + tmp.writeLine x + if x.contains("""""""""): + inc specSection + elif specSection == 1: + if x.startsWith("disabled:"): + if x.startsWith("disabled:true"): + result.disabled = true + else: + # be strict about format + doAssert x.startsWith("disabled:false") + result.disabled = false + elif x.startsWith("$nimsuggest"): + result.cmd = x % ["nimsuggest", nimsug, "file", filename, "lib", libpath] + elif x.startsWith("!"): + if result.cmd.len == 0: + result.startup.add x + else: + result.script.add((x, "")) + elif x.startsWith(">"): + # since 'markers' here are not complete yet, we do the $substitutions + # afterwards + result.script.add((x.substr(1).replaceWord("$path", tpath).replaceWord("$file", filename), "")) + elif x.len > 0: + # expected output line: + let x = x % ["file", filename, "lib", libpath] + result.script[^1][1].add x.replace(";;", "\t") & '\L' + # else: ignore empty lines for better readability of the specs + inc i + tmp.close() + # now that we know the markers, substitute them: + for a in mitems(result.script): + a[0] = a[0] % markers + +proc parseCmd(c: string): seq[string] = + # we don't support double quotes for now so that + # we can later support them properly with escapes and stuff. + result = @[] + var i = 0 + var a = "" + while i < c.len: + setLen(a, 0) + # eat all delimiting whitespace + while i < c.len and c[i] in {' ', '\t', '\l', '\r'}: inc(i) + if i >= c.len: break + case c[i] + of '"': raise newException(ValueError, "double quotes not yet supported: " & c) + of '\'': + var delim = c[i] + inc(i) # skip ' or " + while i < c.len and c[i] != delim: + add a, c[i] + inc(i) + if i < c.len: inc(i) + else: + while i < c.len and c[i] > ' ': + add(a, c[i]) + inc(i) + add(result, a) + +proc edit(tmpfile: string; x: seq[string]) = + if x.len != 3 and x.len != 4: + quit "!edit takes two or three arguments" + let f = if x.len >= 4: tpath / x[3] else: tmpfile + try: + let content = readFile(f) + let newcontent = content.replace(x[1], x[2]) + if content == newcontent: + quit "wrong test case: edit had no effect" + writeFile(f, newcontent) + except IOError: + quit "cannot edit file " & tmpfile + +proc exec(x: seq[string]) = + if x.len != 2: quit "!exec takes one argument" + if execShellCmd(x[1]) != 0: + quit "External program failed " & x[1] + +proc copy(x: seq[string]) = + if x.len != 3: quit "!copy takes two arguments" + let rel = tpath + copyFile(rel / x[1], rel / x[2]) + +proc del(x: seq[string]) = + if x.len != 2: quit "!del takes one argument" + removeFile(tpath / x[1]) + +proc runCmd(cmd, dest: string): bool = + result = cmd[0] == '!' + if not result: return + let x = cmd.parseCmd() + case x[0] + of "!edit": + edit(dest, x) + of "!exec": + exec(x) + of "!copy": + copy(x) + of "!del": + del(x) + else: + quit "unknown command: " & cmd + +proc smartCompare(pattern, x: string): bool = + if pattern.contains('*'): + result = match(x, re(escapeRe(pattern).replace("\\x2A","(.*)"), {})) + +proc sendEpcStr(socket: Socket; cmd: string) = + let s = cmd.find(' ') + doAssert s > 0 + var args = cmd.substr(s+1) + if not args.startsWith("("): args = escapeJson(args) + let c = "(call 567 " & cmd.substr(0, s) & args & ")" + socket.send toHex(c.len, 6) + socket.send c + +proc recvEpc(socket: Socket): string = + var L = newStringOfCap(6) + if socket.recv(L, 6) != 6: + raise newException(ValueError, "recv A failed #" & L & "#") + let x = parseHexInt(L) + result = newString(x) + if socket.recv(result, x) != x: + raise newException(ValueError, "recv B failed") + +proc sexpToAnswer(s: SexpNode): string = + result = "" + doAssert s.kind == SList + doAssert s.len >= 3 + let m = s[2] + if m.kind != SList: + echo s + doAssert m.kind == SList + for a in m: + doAssert a.kind == SList + #s.section, + #s.symkind, + #s.qualifiedPath.map(newSString), + #s.filePath, + #s.forth, + #s.line, + #s.column, + #s.doc + if a.len >= 9: + let section = a[0].getStr + let symk = a[1].getStr + let qp = a[2] + let file = a[3].getStr + let typ = a[4].getStr + let line = a[5].getNum + let col = a[6].getNum + let doc = a[7].getStr.escape + result.add section + result.add '\t' + result.add symk + result.add '\t' + var i = 0 + if qp.kind == SList: + for aa in qp: + if i > 0: result.add '.' + result.add aa.getStr + inc i + result.add '\t' + result.add typ + result.add '\t' + result.add file + result.add '\t' + result.addInt line + result.add '\t' + result.addInt col + result.add '\t' + result.add doc + result.add '\t' + result.addInt a[8].getNum + if a.len >= 11: + result.add '\t' + result.addInt a[9].getNum + result.add '\t' + result.addInt a[10].getNum + elif a.len >= 10: + result.add '\t' + result.add a[9].getStr + result.add '\L' + +proc doReport(filename, answer, resp: string; report: var string) = + if resp != answer and not smartCompare(resp, answer): + report.add "\nTest failed: " & filename + var hasDiff = false + for i in 0..min(resp.len-1, answer.len-1): + if resp[i] != answer[i]: + report.add "\n Expected:\n" & resp + report.add "\n But got:\n" & answer + hasDiff = true + break + if not hasDiff: + report.add "\n Expected: " & resp + report.add "\n But got: " & answer + +proc skipDisabledTest(test: Test): bool = + if test.disabled: + echo "disabled: " & test.filename + result = test.disabled + +proc runEpcTest(filename: string): int = + let s = parseTest(filename, true) + if s.skipDisabledTest: return 0 + for req, _ in items(s.script): + if req.startsWith("highlight"): + echo "disabled epc: " & s.filename + return 0 + for cmd in s.startup: + if not runCmd(cmd, s.dest): + quit "invalid command: " & cmd + let epccmd = if s.cmd.contains("--v3"): + s.cmd.replace("--tester", "--epc --log") + else: + s.cmd.replace("--tester", "--epc --v2 --log") + let cl = parseCmdLine(epccmd) + var p = startProcess(command=cl[0], args=cl[1 .. ^1], + options={poStdErrToStdOut, poUsePath, + poInteractive, poDaemon}) + let outp = p.outputStream + var report = "" + var socket = newSocket() + try: + # read the port number: + when defined(posix): + var a = newStringOfCap(120) + discard outp.readLine(a) + else: + var i = 0 + while not osproc.hasData(p) and i < 100: + os.sleep(50) + inc i + let a = outp.readAll().strip() + var port: int + try: + port = parseInt(a) + except ValueError: + echo "Error parsing port number: " & a + echo outp.readAll() + quit 1 + socket.connect("localhost", Port(port)) + + for req, resp in items(s.script): + if not runCmd(req, s.dest): + socket.sendEpcStr(req) + let sx = parseSexp(socket.recvEpc()) + if not req.startsWith("mod "): + let answer = if sx[2].kind == SNil: "" else: sexpToAnswer(sx) + doReport(filename, answer, resp, report) + + socket.sendEpcStr "return arg" + # bugfix: this was in `finally` block, causing the original error to be + # potentially masked by another one in case `socket.sendEpcStr` raises + # (e.g. if socket couldn't connect in the 1st place) + finally: + close(p) + if report.len > 0: + echo "==== EPC ========================================" + echo report + result = report.len + +proc runTest(filename: string): int = + let s = parseTest filename + if s.skipDisabledTest: return 0 + for cmd in s.startup: + if not runCmd(cmd, s.dest): + quit "invalid command: " & cmd + let cl = parseCmdLine(s.cmd) + var p = startProcess(command=cl[0], args=cl[1 .. ^1], + options={poStdErrToStdOut, poUsePath, + poInteractive, poDaemon}) + let outp = p.outputStream + let inp = p.inputStream + var report = "" + var a = newStringOfCap(120) + try: + # read and ignore anything nimsuggest says at startup: + while outp.readLine(a): + if a == DummyEof: break + for req, resp in items(s.script): + if not runCmd(req, s.dest): + inp.writeLine(req) + inp.flush() + var answer = "" + while outp.readLine(a): + if a == DummyEof: break + answer.add a + answer.add '\L' + doReport(filename, answer, resp, report) + finally: + try: + inp.writeLine("quit") + inp.flush() + except IOError, OSError: + # assume it's SIGPIPE, ie, the child already died + discard + close(p) + if report.len > 0: + echo "==== STDIN ======================================" + echo report + result = report.len + +proc main() = + var failures = 0 + if os.paramCount() > 0: + let x = os.paramStr(1) + let xx = expandFilename x + # run only stdio when running single test + failures += runTest(xx) + else: + let files = toSeq(walkFiles(tpath / "t*.nim")) + for i, x in files: + echo "$#/$# test: $#" % [$i, $files.len, x] + when defined(i386): + if x == "nimsuggest/tests/tmacro_highlight.nim": + echo "skipping" # workaround bug #17945 + continue + let xx = expandFilename x + when not defined(windows): + # XXX Windows IO redirection seems bonkers: + failures += runTest(xx) + failures += runEpcTest(xx) + if failures > 0: + quit 1 + +main() diff --git a/nimsuggest/tests/fixtures/mclass_macro.nim b/nimsuggest/tests/fixtures/mclass_macro.nim new file mode 100644 index 000000000..cfca0bf3f --- /dev/null +++ b/nimsuggest/tests/fixtures/mclass_macro.nim @@ -0,0 +1,164 @@ + +import macros + +macro class*(head, body: untyped): untyped = + # The macro is immediate, since all its parameters are untyped. + # This means, it doesn't resolve identifiers passed to it. + + var typeName, baseName: NimNode + + # flag if object should be exported + var exported: bool + + if head.kind == nnkInfix and head[0].kind == nnkIdent and $head[0] == "of": + # `head` is expression `typeName of baseClass` + # echo head.treeRepr + # -------------------- + # Infix + # Ident !"of" + # Ident !"Animal" + # Ident !"RootObj" + typeName = head[1] + baseName = head[2] + + elif head.kind == nnkInfix and head[0].kind == nnkIdent and + $head[0] == "*" and head[2].kind == nnkPrefix and + head[2][0].kind == nnkIdent and $head[2][0] == "of": + # `head` is expression `typeName* of baseClass` + # echo head.treeRepr + # -------------------- + # Infix + # Ident !"*" + # Ident !"Animal" + # Prefix + # Ident !"of" + # Ident !"RootObj" + typeName = head[1] + baseName = head[2][1] + exported = true + + else: + quit "Invalid node: " & head.lispRepr + + # The following prints out the AST structure: + # + # import macros + # dumptree: + # type X = ref object of Y + # z: int + # -------------------- + # StmtList + # TypeSection + # TypeDef + # Ident !"X" + # Empty + # RefTy + # ObjectTy + # Empty + # OfInherit + # Ident !"Y" + # RecList + # IdentDefs + # Ident !"z" + # Ident !"int" + # Empty + + # create a type section in the result + result = newNimNode(nnkStmtList) + result.add( + if exported: + # mark `typeName` with an asterisk + quote do: + type `typeName`* = ref object of `baseName` + else: + quote do: + type `typeName` = ref object of `baseName` + ) + + # echo treeRepr(body) + # -------------------- + # StmtList + # VarSection + # IdentDefs + # Ident !"name" + # Ident !"string" + # Empty + # IdentDefs + # Ident !"age" + # Ident !"int" + # Empty + # MethodDef + # Ident !"vocalize" + # Empty + # Empty + # FormalParams + # Ident !"string" + # Empty + # Empty + # StmtList + # StrLit ... + # MethodDef + # Ident !"age_human_yrs" + # Empty + # Empty + # FormalParams + # Ident !"int" + # Empty + # Empty + # StmtList + # DotExpr + # Ident !"this" + # Ident !"age" + + # var declarations will be turned into object fields + var recList = newNimNode(nnkRecList) + + # expected name of constructor + let ctorName = newIdentNode("new" & $typeName) + + # Iterate over the statements, adding `this: T` + # to the parameters of functions, unless the + # function is a constructor + for node in body.children: + case node.kind: + + of nnkMethodDef, nnkProcDef: + # check if it is the ctor proc + if node.name.kind != nnkAccQuoted and node.name.basename == ctorName: + # specify the return type of the ctor proc + node.params[0] = typeName + else: + # inject `self: T` into the arguments + node.params.insert(1, newIdentDefs(ident("self"), typeName)) + result.add(node) + + of nnkVarSection: + # variables get turned into fields of the type. + for n in node.children: + recList.add(n) + + else: + result.add(node) + + # Inspect the tree structure: + # + # echo result.treeRepr + # -------------------- + # StmtList + # TypeSection + # TypeDef + # Ident !"Animal" + # Empty + # RefTy + # ObjectTy + # Empty + # OfInherit + # Ident !"RootObj" + # Empty <= We want to replace this + # MethodDef + # ... + + result[0][0][2][0][2] = recList + + # Lets inspect the human-readable version of the output + #echo repr(result) diff --git a/nimsuggest/tests/fixtures/mdep_v1.nim b/nimsuggest/tests/fixtures/mdep_v1.nim new file mode 100644 index 000000000..eae230e85 --- /dev/null +++ b/nimsuggest/tests/fixtures/mdep_v1.nim @@ -0,0 +1,8 @@ + + + + + +type + Foo* = object + x*, y*: int diff --git a/nimsuggest/tests/fixtures/mdep_v2.nim b/nimsuggest/tests/fixtures/mdep_v2.nim new file mode 100644 index 000000000..ab39721c4 --- /dev/null +++ b/nimsuggest/tests/fixtures/mdep_v2.nim @@ -0,0 +1,9 @@ + + + + + +type + Foo* = object + x*, y*: int + z*: string diff --git a/nimsuggest/tests/fixtures/mfakeassert.nim b/nimsuggest/tests/fixtures/mfakeassert.nim new file mode 100644 index 000000000..765831ba7 --- /dev/null +++ b/nimsuggest/tests/fixtures/mfakeassert.nim @@ -0,0 +1,5 @@ +# Template for testing defs + +template fakeAssert*(cond: untyped, msg: string = "") = + ## template to allow def lookup testing + if not cond: quit(1) diff --git a/nimsuggest/tests/fixtures/minclude_import.nim b/nimsuggest/tests/fixtures/minclude_import.nim new file mode 100644 index 000000000..5fa9e5142 --- /dev/null +++ b/nimsuggest/tests/fixtures/minclude_import.nim @@ -0,0 +1,15 @@ +# Creates an awkward set of dependencies between this, import, and include. +# This pattern appears in the compiler, compiler/(sem|ast|semexprs).nim. + +import mfakeassert +import minclude_types + +proc say*(g: Greet): string = + fakeAssert(true, "always works") + g.greeting & ", " & g.subject & "!" + +include minclude_include + +proc say*(): string = + fakeAssert(1 + 1 == 2, "math works") + say(create()) diff --git a/nimsuggest/tests/fixtures/minclude_include.nim b/nimsuggest/tests/fixtures/minclude_include.nim new file mode 100644 index 000000000..23f9892cc --- /dev/null +++ b/nimsuggest/tests/fixtures/minclude_include.nim @@ -0,0 +1,4 @@ +# this file is included and relies on imports within the include + +proc create*(greeting: string = "Hello", subject: string = "World"): Greet = + Greet(greeting: greeting, subject: subject) diff --git a/nimsuggest/tests/fixtures/minclude_types.nim b/nimsuggest/tests/fixtures/minclude_types.nim new file mode 100644 index 000000000..3e85ee540 --- /dev/null +++ b/nimsuggest/tests/fixtures/minclude_types.nim @@ -0,0 +1,6 @@ +# types used by minclude_* (import or include), to find with def in include + +type + Greet* = object + greeting*: string + subject*: string \ No newline at end of file diff --git a/nimsuggest/tests/fixtures/mstrutils.nim b/nimsuggest/tests/fixtures/mstrutils.nim new file mode 100644 index 000000000..d6f25571b --- /dev/null +++ b/nimsuggest/tests/fixtures/mstrutils.nim @@ -0,0 +1,19 @@ +import mfakeassert + +func rereplace*(s, sub: string; by: string = ""): string {.used.} = + ## competes for priority in suggestion, here first, but never used in test + + fakeAssert(true, "always works") + result = by + +func replace*(s, sub: string; by: string = ""): string = + ## this is a test version of strutils.replace, it simply returns `by` + + fakeAssert("".len == 0, "empty string is empty") + result = by + +func rerereplace*(s, sub: string; by: string = ""): string {.used.} = + ## isn't used and appears last, lowest priority + + fakeAssert(false, "never works") + result = by diff --git a/nimsuggest/tests/module_20265.nim b/nimsuggest/tests/module_20265.nim new file mode 100644 index 000000000..24b7d10c9 --- /dev/null +++ b/nimsuggest/tests/module_20265.nim @@ -0,0 +1,6 @@ +type A* = tuple + a: int + b: int + +var x*: A = (a: 2, b: 10) +var y* = (a: 2, b: 10) diff --git a/nimsuggest/tests/t20265_1.nim b/nimsuggest/tests/t20265_1.nim new file mode 100644 index 000000000..553b3d545 --- /dev/null +++ b/nimsuggest/tests/t20265_1.nim @@ -0,0 +1,8 @@ +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skField;;a;;int;;*module_20265.nim;;6;;10;;"";;100;;None +sug;;skField;;b;;int;;*module_20265.nim;;6;;16;;"";;100;;None +""" +import module_20265 +y.#[!]# diff --git a/nimsuggest/tests/t20265_2.nim b/nimsuggest/tests/t20265_2.nim new file mode 100644 index 000000000..33edf2d9a --- /dev/null +++ b/nimsuggest/tests/t20265_2.nim @@ -0,0 +1,8 @@ +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skField;;a;;int;;*module_20265.nim;;2;;2;;"";;100;;None +sug;;skField;;b;;int;;*module_20265.nim;;3;;2;;"";;100;;None +""" +import module_20265 +x.#[!]# diff --git a/nimsuggest/tests/t20440.nim b/nimsuggest/tests/t20440.nim new file mode 100644 index 000000000..0456aa074 --- /dev/null +++ b/nimsuggest/tests/t20440.nim @@ -0,0 +1,7 @@ +when not defined(js): + {.fatal: "Crash".} +echo 4 + +discard """ +$nimsuggest --v3 --tester $file +""" diff --git a/nimsuggest/tests/t20440.nims b/nimsuggest/tests/t20440.nims new file mode 100644 index 000000000..1336be3d4 --- /dev/null +++ b/nimsuggest/tests/t20440.nims @@ -0,0 +1 @@ +switch("backend", "js") diff --git a/nimsuggest/tests/t21185.nim b/nimsuggest/tests/t21185.nim new file mode 100644 index 000000000..bf5a0e3cc --- /dev/null +++ b/nimsuggest/tests/t21185.nim @@ -0,0 +1,18 @@ + +# Reduced case of 21185. Issue was first parameter being static +proc foo(x: static[int]) = discard + +type + Person = object + name: string + age: int + +let p = Person() +p.#[!]# + +discard """ +$nimsuggest --tester --v3 --maxresults:2 $file +>sug $1 +sug;;skField;;age;;int;;$file;;8;;4;;"";;100;;None +sug;;skField;;name;;string;;$file;;7;;4;;"";;100;;None +""" diff --git a/nimsuggest/tests/t22448.nim b/nimsuggest/tests/t22448.nim new file mode 100644 index 000000000..8664bbbc3 --- /dev/null +++ b/nimsuggest/tests/t22448.nim @@ -0,0 +1,11 @@ +proc fn(a: static float) = discard +proc fn(a: int) = discard + +let x = 1 +fn(x) + +discard """ +$nimsuggest --tester --v3 $file +>chk $file +chk;;skUnknown;;;;Hint;;* +""" diff --git a/nimsuggest/tests/taccent_highlight.nim b/nimsuggest/tests/taccent_highlight.nim new file mode 100644 index 000000000..52ac2fc62 --- /dev/null +++ b/nimsuggest/tests/taccent_highlight.nim @@ -0,0 +1,7 @@ +proc `$$$`#[!]# + +discard """ +$nimsuggest --tester $file +>highlight $1 +highlight;;skProc;;1;;6;;3 +""" diff --git a/nimsuggest/tests/tarrowcrash.nim b/nimsuggest/tests/tarrowcrash.nim new file mode 100644 index 000000000..a303e88f5 --- /dev/null +++ b/nimsuggest/tests/tarrowcrash.nim @@ -0,0 +1,20 @@ +# issue #24179 + +import sugar + +type + Parser[T] = object + +proc eatWhile[T](p: Parser[T], predicate: T -> bool): seq[T] = + return @[] + +proc skipWs(p: Parser[char]) = + discard p.eatWhile((c: char) => c == 'a') +#[!]# + +discard """ +$nimsuggest --tester $file +>chk $1 +chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tarrowcrash.nim [Processing]";;0 +chk;;skUnknown;;;;Hint;;$file;;11;;5;;"\'skipWs\' is declared but not used [XDeclaredButNotUsed]";;0 +""" diff --git a/nimsuggest/tests/tcallstrlit_highlight.nim b/nimsuggest/tests/tcallstrlit_highlight.nim new file mode 100644 index 000000000..6f5b0f792 --- /dev/null +++ b/nimsuggest/tests/tcallstrlit_highlight.nim @@ -0,0 +1,11 @@ +func foo(s: string) = discard + +foo"string"#[!]# + +discard """ +$nimsuggest --tester $file +>highlight $1 +highlight;;skFunc;;1;;5;;3 +highlight;;skType;;1;;12;;6 +highlight;;skFunc;;3;;0;;3 +""" diff --git a/nimsuggest/tests/tcase.nim b/nimsuggest/tests/tcase.nim new file mode 100644 index 000000000..8e3fc5548 --- /dev/null +++ b/nimsuggest/tests/tcase.nim @@ -0,0 +1,17 @@ + +type + MyEnum = enum + nkIf, nkElse, nkElif + +proc test(a: MyEnum) = + case a + of nkElse: discard + of #[!]# + +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skEnumField;;nkElse;;MyEnum;;$file;;4;;10;;"";;100;;None +sug;;skEnumField;;nkElif;;MyEnum;;$file;;4;;18;;"";;100;;None +sug;;skEnumField;;nkIf;;MyEnum;;$file;;4;;4;;"";;100;;None +""" diff --git a/nimsuggest/tests/tchk1.nim b/nimsuggest/tests/tchk1.nim new file mode 100644 index 000000000..be6115c1c --- /dev/null +++ b/nimsuggest/tests/tchk1.nim @@ -0,0 +1,27 @@ +# test we get some suggestion at the end of the file + + + + + + + +type + + +template foo() = + +proc main = + +#[!]# +discard """ +$nimsuggest --tester $file +>chk $1 +chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tchk1.nim [Processing]";;0 +chk;;skUnknown;;;;Error;;$file;;12;;0;;"identifier expected, but got \'keyword template\'";;0 +chk;;skUnknown;;;;Error;;$file;;14;;0;;"nestable statement requires indentation";;0 +chk;;skUnknown;;;;Error;;$file;;17;;0;;"invalid indentation";;0 +chk;;skUnknown;;;;Error;;$file;;12;;0;;"implementation of \'foo\' expected";;0 +chk;;skUnknown;;;;Hint;;$file;;12;;9;;"\'foo\' is declared but not used [XDeclaredButNotUsed]";;0 +chk;;skUnknown;;;;Hint;;$file;;14;;5;;"\'main\' is declared but not used [XDeclaredButNotUsed]";;0 +""" diff --git a/nimsuggest/tests/tchk2.nim b/nimsuggest/tests/tchk2.nim new file mode 100644 index 000000000..f5404368d --- /dev/null +++ b/nimsuggest/tests/tchk2.nim @@ -0,0 +1,35 @@ +# bug #22794 +type O = object + +proc `=destroy`(x: O) = discard +proc `=trace`(x: var O; env: pointer) = discard +proc `=copy`(a: var O; b: O) = discard +proc `=dup`(a: O): O {.nodestroy.} = a +proc `=sink`(a: var O; b: O) = discard + + +# bug #23316 +type SomeSturct = object + +proc `=destroy`(x: SomeSturct) = + echo "SomeSturct destroyed" + +# bug #23867 +type ObjStr = object + s: string + +let ostr = ObjStr() # <-- nimsuggest crashes +discard ostr + +type ObjSeq = object + s: seq[int] + +let oseq = ObjSeq() # <-- nimsuggest crashes +discard oseq + +#[!]# +discard """ +$nimsuggest --tester $file +>chk $1 +chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tchk2.nim [Processing]";;0 +""" diff --git a/nimsuggest/tests/tchk_compiles.nim b/nimsuggest/tests/tchk_compiles.nim new file mode 100644 index 000000000..c8a3daac4 --- /dev/null +++ b/nimsuggest/tests/tchk_compiles.nim @@ -0,0 +1,8 @@ +discard compiles(2 + "hello") + +#[!]# +discard """ +$nimsuggest --tester $file +>chk $1 +chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tchk_compiles.nim [Processing]";;0 +""" diff --git a/nimsuggest/tests/tcon1.nim b/nimsuggest/tests/tcon1.nim new file mode 100644 index 000000000..627e7f400 --- /dev/null +++ b/nimsuggest/tests/tcon1.nim @@ -0,0 +1,43 @@ +## Test Invocation `con`text in various situations + +## various of this proc are used as the basis for these tests +proc test(s: string; a: int) = discard + +## This overload should be used to ensure the lower airity `test` doesn't match +proc test(s: string; a: string, b: int) = discard + +## similar signature but different name to ensure `con` doesn't get greedy +proc testB(a, b: string) = discard + +# with a param already specified +test("hello here", #[!]#) + +# as first param +testB(#[!]# + +# dot expressions +"from behind".test(#[!]# + +# two params matched, so disqualify the lower airity `test` +# TODO: this doesn't work, because dot exprs, overloads, etc aren't currently +# handled by suggest.suggestCall. sigmatch.partialMatch by way of +# sigmatch.matchesAux. Doesn't use the operand before the dot as part of +# the formal parameters. Changing this is tricky because it's used by +# the proper compilation sem pass and that's a big change all in one go. +"and again".test("more", #[!]# + + +discard """ +$nimsuggest --tester $file +>con $1 +con;;skProc;;tcon1.test;;proc (s: string, a: int);;$file;;4;;5;;"";;100 +con;;skProc;;tcon1.test;;proc (s: string, a: string, b: int);;$file;;7;;5;;"";;100 +>con $2 +con;;skProc;;tcon1.testB;;proc (a: string, b: string);;$file;;10;;5;;"";;100 +>con $3 +con;;skProc;;tcon1.test;;proc (s: string, a: string, b: int);;$file;;7;;5;;"";;100 +con;;skProc;;tcon1.test;;proc (s: string, a: int);;$file;;4;;5;;"";;100 +>con $4 +con;;skProc;;tcon1.test;;proc (s: string, a: int);;$file;;4;;5;;"";;100 +con;;skProc;;tcon1.test;;proc (s: string, a: string, b: int);;$file;;7;;5;;"";;100 +""" diff --git a/nimsuggest/tests/tcon_variable.nim b/nimsuggest/tests/tcon_variable.nim new file mode 100644 index 000000000..cfe93604f --- /dev/null +++ b/nimsuggest/tests/tcon_variable.nim @@ -0,0 +1,12 @@ +let foo = "string" +var bar = "string" +bar#[!]#.add foo +bar.add foo#[!]# + +discard """ +$nimsuggest --tester $file +>con $1 +con;;skVar;;tcon_variable.bar;;string;;$file;;2;;4;;"";;100 +>con $2 +con;;skLet;;tcon_variable.foo;;string;;$file;;1;;4;;"";;100 +""" diff --git a/nimsuggest/tests/tconcept1.nim b/nimsuggest/tests/tconcept1.nim new file mode 100644 index 000000000..d81cd8120 --- /dev/null +++ b/nimsuggest/tests/tconcept1.nim @@ -0,0 +1,12 @@ +SomeNumber = concept + +#[!]# +discard """ +$nimsuggest --tester $file +>chk $1 +chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tconcept1.nim [Processing]";;0 +chk;;skUnknown;;;;Error;;$file;;1;;13;;"the \'concept\' keyword is only valid in \'type\' sections";;0 +chk;;skUnknown;;;;Error;;$file;;1;;13;;"invalid indentation";;0 +chk;;skUnknown;;;;Error;;$file;;1;;13;;"expression expected, but found \'keyword concept\'";;0 +chk;;skUnknown;;;;Error;;$file;;1;;0;;"\'SomeNumber\' cannot be assigned to";;0 +""" diff --git a/nimsuggest/tests/tconcept2.nim b/nimsuggest/tests/tconcept2.nim new file mode 100644 index 000000000..7f7d147f5 --- /dev/null +++ b/nimsuggest/tests/tconcept2.nim @@ -0,0 +1,15 @@ + SomeNumber = concept a, type T + a.int is int + int.to(T) is type(a) + +#[!]# +discard """ +$nimsuggest --tester $file +>chk $1 +chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tconcept2.nim [Processing]";;0 +chk;;skUnknown;;;;Error;;$file;;1;;2;;"invalid indentation";;0 +chk;;skUnknown;;;;Error;;$file;;1;;15;;"the \'concept\' keyword is only valid in \'type\' sections";;0 +chk;;skUnknown;;;;Error;;$file;;1;;15;;"invalid indentation";;0 +chk;;skUnknown;;;;Error;;$file;;1;;15;;"expression expected, but found \'keyword concept\'";;0 +chk;;skUnknown;;;;Error;;$file;;1;;2;;"\'SomeNumber\' cannot be assigned to";;0 +""" diff --git a/nimsuggest/tests/tcursor_at_end.nim b/nimsuggest/tests/tcursor_at_end.nim new file mode 100644 index 000000000..b3a0d1133 --- /dev/null +++ b/nimsuggest/tests/tcursor_at_end.nim @@ -0,0 +1,12 @@ +# test we get some suggestion at the end of the file + +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skProc;;tcursor_at_end.main;;proc ();;$file;;10;;5;;"";;* +""" + + +proc main = discard + +#[!]# diff --git a/nimsuggest/tests/tdef1.nim b/nimsuggest/tests/tdef1.nim new file mode 100644 index 000000000..49265bbc1 --- /dev/null +++ b/nimsuggest/tests/tdef1.nim @@ -0,0 +1,18 @@ +discard """ +$nimsuggest --tester $file +>def $1 +def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;11;;5;;"Return hello";;100 +>def $2 +def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;11;;5;;"Return hello";;100 +>def $2 +def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;11;;5;;"Return hello";;100 +""" + +proc hel#[!]#lo(): string = + ## Return hello + "Hello" + +hel#[!]#lo() + +# v uncompleted id for sug (13,2) +he diff --git a/nimsuggest/tests/tdef2.nim b/nimsuggest/tests/tdef2.nim new file mode 100644 index 000000000..299b83a3d --- /dev/null +++ b/nimsuggest/tests/tdef2.nim @@ -0,0 +1,13 @@ +# Test def with template and boundaries for the cursor + +import fixtures/mstrutils + +discard """ +$nimsuggest --tester $file +>def $path/fixtures/mstrutils.nim:6:4 +def;;skTemplate;;mfakeassert.fakeAssert;;template (cond: untyped, msg: string);;*fixtures/mfakeassert.nim;;3;;9;;"template to allow def lookup testing";;100 +>def $path/fixtures/mstrutils.nim:12:3 +def;;skTemplate;;mfakeassert.fakeAssert;;template (cond: untyped, msg: string);;*fixtures/mfakeassert.nim;;3;;9;;"template to allow def lookup testing";;100 +>def $path/fixtures/mstrutils.nim:18:11 +def;;skTemplate;;mfakeassert.fakeAssert;;template (cond: untyped, msg: string);;*fixtures/mfakeassert.nim;;3;;9;;"template to allow def lookup testing";;100 +""" diff --git a/nimsuggest/tests/tdef_forward.nim b/nimsuggest/tests/tdef_forward.nim new file mode 100644 index 000000000..9bdd8b21d --- /dev/null +++ b/nimsuggest/tests/tdef_forward.nim @@ -0,0 +1,13 @@ +discard """ +$nimsuggest --tester $file +>def $1 +def;;skProc;;tdef_forward.hello;;proc (): string;;$file;;8;;5;;"";;100 +def;;skProc;;tdef_forward.hello;;proc (): string;;$file;;12;;5;;"";;100 +""" + +proc hello(): string + +hel#[!]#lo() + +proc hello(): string = + "Hello" diff --git a/nimsuggest/tests/tdef_let.nim b/nimsuggest/tests/tdef_let.nim new file mode 100644 index 000000000..3e9456d2f --- /dev/null +++ b/nimsuggest/tests/tdef_let.nim @@ -0,0 +1,7 @@ +discard """ +$nimsuggest --tester $file +>def $1 +def;;skLet;;tdef_let.intVar;;int;;$file;;7;;4;;"";;100 +""" + +let int#[!]#Var = 10 diff --git a/nimsuggest/tests/tdot1.nim b/nimsuggest/tests/tdot1.nim new file mode 100644 index 000000000..c64e1138c --- /dev/null +++ b/nimsuggest/tests/tdot1.nim @@ -0,0 +1,14 @@ +discard """ +$nimsuggest --tester --maxresults:3 $file +>sug $1 +sug;;skField;;x;;int;;$file;;11;;4;;"";;100;;None +sug;;skField;;y;;int;;$file;;11;;7;;"";;100;;None +sug;;skProc;;tdot1.main;;proc (f: Foo);;$file;;13;;5;;"";;100;;None +""" + +type + Foo = object + x, y: int + +proc main(f: Foo) = + if f.#[!]#: diff --git a/nimsuggest/tests/tdot2.nim b/nimsuggest/tests/tdot2.nim new file mode 100644 index 000000000..1a2df9ba2 --- /dev/null +++ b/nimsuggest/tests/tdot2.nim @@ -0,0 +1,28 @@ +# Test basic editing. We replace the 'false' by 'true' to +# see whether then the z field is suggested. + +const zField = 0i32 + +type + Foo = object + x, y: int + when zField == 1i32: + z: string + +proc main(f: Foo) = + f.#[!]# + +# the tester supports the spec section at the bottom of the file and +# this way, the line numbers more often stay the same +discard """ +$nimsuggest --tester --maxresults:3 $file +>sug $1 +sug;;skField;;x;;int;;$file;;8;;4;;"";;100;;None +sug;;skField;;y;;int;;$file;;8;;7;;"";;100;;None +sug;;skProc;;tdot2.main;;proc (f: Foo);;$file;;12;;5;;"";;100;;None +!edit 0i32 1i32 +>sug $1 +sug;;skField;;x;;int;;$file;;8;;4;;"";;100;;None +sug;;skField;;y;;int;;$file;;8;;7;;"";;100;;None +sug;;skField;;z;;string;;$file;;10;;6;;"";;100;;None +""" diff --git a/nimsuggest/tests/tdot3.nim b/nimsuggest/tests/tdot3.nim new file mode 100644 index 000000000..30dd60591 --- /dev/null +++ b/nimsuggest/tests/tdot3.nim @@ -0,0 +1,27 @@ +# Test basic module dependency recompilations. + +import dep + +proc main(f: Foo) = + f.#[!]# + +# the tester supports the spec section at the bottom of the file and +# this way, the line numbers more often stay the same + +discard """ +!copy fixtures/mdep_v1.nim dep.nim +$nimsuggest --tester $file +>sug $1 +sug;;skField;;x;;int;;*dep.nim;;8;;4;;"";;100;;None +sug;;skField;;y;;int;;*dep.nim;;8;;8;;"";;100;;None +sug;;skProc;;tdot3.main;;proc (f: Foo);;$file;;5;;5;;"";;100;;None + +!copy fixtures/mdep_v2.nim dep.nim +>mod $path/dep.nim +>sug $1 +sug;;skField;;x;;int;;*dep.nim;;8;;4;;"";;100;;None +sug;;skField;;y;;int;;*dep.nim;;8;;8;;"";;100;;None +sug;;skField;;z;;string;;*dep.nim;;9;;4;;"";;100;;None +sug;;skProc;;tdot3.main;;proc (f: Foo);;$file;;5;;5;;"";;100;;None +!del dep.nim +""" diff --git a/nimsuggest/tests/tdot4.nim b/nimsuggest/tests/tdot4.nim new file mode 100644 index 000000000..f2c6c765f --- /dev/null +++ b/nimsuggest/tests/tdot4.nim @@ -0,0 +1,21 @@ +# Test that already used suggestions are prioritized + +from system import string, echo +import fixtures/mstrutils + +proc main(inp: string): string = + # use replace here and see if it occurs in the result, it should gain + # priority: + result = inp.replace(" ", "a").replace("b", "c") + +echo "string literal here".#[!]# + +# priority still tested, but limit results to avoid failures from other output +discard """ +$nimsuggest --tester --maxresults:2 $file +>sug $1 +sug;;skProc;;tdot4.main;;proc (inp: string): string;;$file;;6;;5;;"";;100;;None +sug;;skFunc;;mstrutils.replace;;proc (s: string, sub: string, by: string): string{.noSideEffect, gcsafe, raises: <inferred> [].};;*fixtures/mstrutils.nim;;9;;5;;"this is a test version of strutils.replace, it simply returns `by`";;100;;None +""" + +# TODO - determine appropriate behaviour for further suggest output and test it diff --git a/nimsuggest/tests/tenum_field.nim b/nimsuggest/tests/tenum_field.nim new file mode 100644 index 000000000..4ceb3e021 --- /dev/null +++ b/nimsuggest/tests/tenum_field.nim @@ -0,0 +1,17 @@ +discard """ +$nimsuggest --tester $file +>sug $1 +>sug $2 +sug;;skConst;;tenum_field.BarFoo;;int literal(1);;$file;;10;;6;;"";;100;;Prefix +""" + +proc something() = discard + +const BarFoo = 1 + +type + Foo = enum + # Test that typing the name doesn't give suggestions + somethi#[!]# + # Test that the right hand side still gets suggestions + another = BarFo#[!]# diff --git a/nimsuggest/tests/tfatal1.nim b/nimsuggest/tests/tfatal1.nim new file mode 100644 index 000000000..19778f22e --- /dev/null +++ b/nimsuggest/tests/tfatal1.nim @@ -0,0 +1,15 @@ +{.warning: "I'm a warning!".} +{.error: "I'm an error!".} +{.fatal: "I'm a fatal error!".} +{.error: "I'm an error after fatal error!".} + +#[!]# +discard """ +$nimsuggest --tester $file +>chk $1 +chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tfatal1.nim [Processing]";;0 +chk;;skUnknown;;;;Warning;;$file;;1;;9;;"I\'m a warning! [User]";;0 +chk;;skUnknown;;;;Error;;$file;;2;;7;;"I\'m an error!";;0 +chk;;skUnknown;;;;Error;;$file;;3;;7;;"fatal error: I\'m a fatal error!";;0 +chk;;skUnknown;;;;Error;;$file;;4;;7;;"I\'m an error after fatal error!";;0 +""" diff --git a/nimsuggest/tests/tgeneric_highlight.nim b/nimsuggest/tests/tgeneric_highlight.nim new file mode 100644 index 000000000..c7291d08b --- /dev/null +++ b/nimsuggest/tests/tgeneric_highlight.nim @@ -0,0 +1,13 @@ +newSeq[int]() +system.newSeq[int]()#[!]# +offsetOf[int]() + +discard """ +$nimsuggest --tester $file +>highlight $1 +highlight;;skType;;1;;7;;3 +highlight;;skProc;;1;;0;;6 +highlight;;skType;;2;;14;;3 +highlight;;skProc;;2;;7;;6 +highlight;;skType;;3;;9;;3 +""" diff --git a/nimsuggest/tests/tgenerics.nim b/nimsuggest/tests/tgenerics.nim new file mode 100644 index 000000000..7f490321c --- /dev/null +++ b/nimsuggest/tests/tgenerics.nim @@ -0,0 +1,18 @@ +type + Hello[T] = object + value: T + +proc printHelloValue[T](hello: Hello[T]) = + echo hello.value + +proc main() = + let a = Hello[float]() + p#[!]#rintHelloValue(a) + +main() + +discard """ +$nimsuggest --tester $file +>def $1 +def;;skProc;;tgenerics.printHelloValue;;proc (hello: Hello[printHelloValue.T]);;$file;;5;;5;;"";;100 +""" diff --git a/nimsuggest/tests/tic.nim b/nimsuggest/tests/tic.nim new file mode 100644 index 000000000..26e644f83 --- /dev/null +++ b/nimsuggest/tests/tic.nim @@ -0,0 +1,20 @@ +import std/[appdirs, assertions, cmdline, compilesettings, decls, + dirs, editdistance, effecttraits, enumerate, enumutils, envvars, + exitprocs, files, formatfloat, genasts, importutils, + isolation, jsonutils, logic, monotimes, objectdollar, + oserrors, outparams, packedsets, paths, private, setutils, sha1, + socketstreams, stackframes, staticos, strbasics, symlinks, syncio, + sysatomics, sysrand, tasks, tempfiles, time_t, typedthreads, varints, + vmutils, widestrs, with, wordwrap, wrapnils] + +proc test(a: string, b:string) = discard +proc test(a: int) = discard + +test(#[!]# + +discard """ +$nimsuggest --v3 --ic:off --tester $file +>con $1 +con;;skProc;;tic.test;;proc (a: string, b: string);;$file;;10;;5;;"";;100 +con;;skProc;;tic.test;;proc (a: int);;$file;;11;;5;;"";;100 +""" \ No newline at end of file diff --git a/nimsuggest/tests/timport_highlight.nim b/nimsuggest/tests/timport_highlight.nim new file mode 100644 index 000000000..043f87d98 --- /dev/null +++ b/nimsuggest/tests/timport_highlight.nim @@ -0,0 +1,12 @@ +import std/paths +import json as J +import std/[os,streams]#[!]# + +discard """ +$nimsuggest --tester $file +>highlight $1 +highlight;;skModule;;1;;11;;5 +highlight;;skModule;;2;;7;;4 +highlight;;skModule;;3;;12;;2 +highlight;;skModule;;3;;15;;7 +""" diff --git a/nimsuggest/tests/tinclude.nim b/nimsuggest/tests/tinclude.nim new file mode 100644 index 000000000..f5cbabf05 --- /dev/null +++ b/nimsuggest/tests/tinclude.nim @@ -0,0 +1,25 @@ +# import that has an include: +# * def calls must work into and out of includes +# * outline calls on the import must show included members +import fixtures/minclude_import + +proc go() = + discard create().say() + +go() + +discard """ +$nimsuggest --tester $file +>def $path/tinclude.nim:7:14 +def;;skProc;;minclude_import.create;;proc (greeting: string, subject: string): Greet{.noSideEffect, gcsafe, raises: <inferred> [].};;*fixtures/minclude_include.nim;;3;;5;;"";;100 +>def $path/fixtures/minclude_include.nim:3:71 +def;;skType;;minclude_types.Greet;;Greet;;*fixtures/minclude_types.nim;;4;;2;;"";;100 +>def $path/fixtures/minclude_include.nim:3:71 +def;;skType;;minclude_types.Greet;;Greet;;*fixtures/minclude_types.nim;;4;;2;;"";;100 +>outline $path/fixtures/minclude_import.nim +outline;;skProc;;minclude_import.say;;*fixtures/minclude_import.nim;;7;;5;;"";;100 +outline;;skProc;;minclude_import.create;;*fixtures/minclude_include.nim;;3;;5;;"";;100 +outline;;skProc;;minclude_import.say;;*fixtures/minclude_import.nim;;13;;5;;"";;100 +""" + +# TODO test/fix if the first `def` is not first or repeated we get no results diff --git a/nimsuggest/tests/tmacro_highlight.nim b/nimsuggest/tests/tmacro_highlight.nim new file mode 100644 index 000000000..6f5b5e8a7 --- /dev/null +++ b/nimsuggest/tests/tmacro_highlight.nim @@ -0,0 +1,13 @@ +macro a(b: string): untyped = discard + +a "string"#[!]# + +discard """ +$nimsuggest --tester $file +>highlight $1 +highlight;;skMacro;;1;;6;;1 +highlight;;skType;;1;;11;;6 +highlight;;skType;;1;;20;;7 +highlight;;skMacro;;3;;0;;1 +highlight;;skMacro;;3;;0;;1 +""" diff --git a/nimsuggest/tests/tno_deref.nim b/nimsuggest/tests/tno_deref.nim new file mode 100644 index 000000000..05cffa507 --- /dev/null +++ b/nimsuggest/tests/tno_deref.nim @@ -0,0 +1,14 @@ + +var x: ptr int + +proc foo(y: ptr int) = + discard + +x.#[!]# + +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skProc;;tno_deref.foo;;proc (y: ptr int)*;;$file;;4;;5;;"";;100;;None +* +""" diff --git a/nimsuggest/tests/tobj_highlight.nim b/nimsuggest/tests/tobj_highlight.nim new file mode 100644 index 000000000..c37bab183 --- /dev/null +++ b/nimsuggest/tests/tobj_highlight.nim @@ -0,0 +1,11 @@ +type + O = object + a*: int#[!]# + +discard """ +$nimsuggest --tester $file +>highlight $1 +highlight;;skType;;2;;2;;1 +highlight;;skType;;3;;8;;3 +highlight;;skField;;3;;4;;1 +""" diff --git a/nimsuggest/tests/top_highlight.nim b/nimsuggest/tests/top_highlight.nim new file mode 100644 index 000000000..c3f6bb61d --- /dev/null +++ b/nimsuggest/tests/top_highlight.nim @@ -0,0 +1,11 @@ +import json + +%*{}#[!]# + +discard """ +$nimsuggest --tester $file +>highlight $1 +highlight;;skModule;;1;;7;;4 +highlight;;skMacro;;3;;0;;2 +highlight;;skMacro;;3;;0;;2 +""" diff --git a/nimsuggest/tests/tqualified_highlight.nim b/nimsuggest/tests/tqualified_highlight.nim new file mode 100644 index 000000000..b83669e72 --- /dev/null +++ b/nimsuggest/tests/tqualified_highlight.nim @@ -0,0 +1,14 @@ +system.echo#[!]# +system.once +system.`$` 1 + +discard """ +$nimsuggest --tester $file +>highlight $1 +highlight;;skProc;;1;;7;;4 +highlight;;skProc;;1;;7;;4 +highlight;;skTemplate;;2;;7;;4 +highlight;;skTemplate;;2;;7;;4 +highlight;;skTemplate;;2;;7;;4 +highlight;;skFunc;;3;;8;;1 +""" diff --git a/nimsuggest/tests/tsetter_highlight.nim b/nimsuggest/tests/tsetter_highlight.nim new file mode 100644 index 000000000..e7388c798 --- /dev/null +++ b/nimsuggest/tests/tsetter_highlight.nim @@ -0,0 +1,10 @@ +proc `a=`(a, b: int) = discard +10.a = 1000#[!]# + +discard """ +$nimsuggest --tester $file +>highlight $1 +highlight;;skProc;;1;;6;;2 +highlight;;skType;;1;;16;;3 +highlight;;skProc;;2;;5;;1 +""" diff --git a/nimsuggest/tests/tsi_highlight.nim b/nimsuggest/tests/tsi_highlight.nim new file mode 100644 index 000000000..2c19582cc --- /dev/null +++ b/nimsuggest/tests/tsi_highlight.nim @@ -0,0 +1,11 @@ +proc a: int = 0 +e_c_h_o#[!]# + +discard """ +$nimsuggest --tester $file +>highlight $1 +highlight;;skProc;;1;;5;;1 +highlight;;skType;;1;;8;;3 +highlight;;skResult;;1;;0;;0 +highlight;;skProc;;2;;0;;7 +""" diff --git a/nimsuggest/tests/tsug_accquote.nim b/nimsuggest/tests/tsug_accquote.nim new file mode 100644 index 000000000..5b98feac4 --- /dev/null +++ b/nimsuggest/tests/tsug_accquote.nim @@ -0,0 +1,10 @@ +proc `%%%`(a: int) = discard +proc `cast`() = discard +tsug_accquote.#[!]# + +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skProc;;tsug_accquote.`%%%`;;proc (a: int);;$file;;1;;5;;"";;100;;None +sug;;skProc;;tsug_accquote.`cast`;;proc ();;$file;;2;;5;;"";;100;;None +""" diff --git a/nimsuggest/tests/tsug_enum.nim b/nimsuggest/tests/tsug_enum.nim new file mode 100644 index 000000000..97a225f16 --- /dev/null +++ b/nimsuggest/tests/tsug_enum.nim @@ -0,0 +1,18 @@ +## suggestions for enums + +type + LogLevel {.pure.} = enum + debug, log, warn, error + + FooBar = enum + fbFoo, fbBar + +echo fbFoo, fbBar + +echo LogLevel.deb#[!]# + +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skEnumField;;debug;;LogLevel;;*nimsuggest/tests/tsug_enum.nim;;5;;4;;"";;100;;Prefix +""" \ No newline at end of file diff --git a/nimsuggest/tests/tsug_pragmas.nim b/nimsuggest/tests/tsug_pragmas.nim new file mode 100644 index 000000000..ce9c4e8f8 --- /dev/null +++ b/nimsuggest/tests/tsug_pragmas.nim @@ -0,0 +1,40 @@ +template fooBar1() {.pragma.} +proc fooBar2() = discard +macro fooBar3(x: untyped) = discard +{.pragma: fooBar4 fooBar3.} + +proc test1() {.fooBar#[!]#.} = discard + +var test2 {.fooBar#[!]#.} = 9 + +type + Person {.fooBar#[!]#.} = object + hello {.fooBar#[!]#.}: string + Callback = proc () {.fooBar#[!]#.} + +# Check only macros/templates/pragmas are suggested +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix +sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix +sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix +>sug $2 +sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix +sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix +sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix +>sug $3 +sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix +sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix +sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix +>sug $4 +sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix +sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix +sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix +>sug $5 +sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix +sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix +sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix +""" + + diff --git a/nimsuggest/tests/tsug_recursive.nim b/nimsuggest/tests/tsug_recursive.nim new file mode 100644 index 000000000..97ee5ca01 --- /dev/null +++ b/nimsuggest/tests/tsug_recursive.nim @@ -0,0 +1,8 @@ +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skProc;;tsug_recursive.fooBar;;proc ();;$file;;7;;5;;"";;100;;Prefix +""" + +proc fooBar() = + fooBa#[!]# diff --git a/nimsuggest/tests/tsug_regression.nim b/nimsuggest/tests/tsug_regression.nim new file mode 100644 index 000000000..2aff3fe94 --- /dev/null +++ b/nimsuggest/tests/tsug_regression.nim @@ -0,0 +1,34 @@ +# test we only get suggestions, not error messages: + +import tables, sets, parsecfg + +type X = object + +proc main = + # bug #52 + var + set0 = initHashSet[int]() + set1 = initHashSet[X]() + set2 = initHashSet[ref int]() + + map0 = initTable[int, int]() + map1 = initOrderedTable[string, int]() + cfg = loadConfig("file") + map0.#[!]# + +# the maxresults are limited as it seems there is sort or some other +# instability that causes the suggestions to slightly differ between 32 bit +# and 64 bit versions of nimsuggest + +discard """ +disabled:true +$nimsuggest --tester --maxresults:4 $file +>sug $1 +sug;;skProc;;tables.hasKey;;proc (t: Table[hasKey.A, hasKey.B], key: A): bool;;*/lib/pure/collections/tables.nim;;374;;5;;"Returns true *";;100;;None +sug;;skProc;;tables.clear;;proc (t: var Table[clear.A, clear.B]);;*/lib/pure/collections/tables.nim;;567;;5;;"Resets the table so that it is empty*";;100;;None +sug;;skProc;;tables.contains;;proc (t: Table[contains.A, contains.B], key: A): bool;;*/lib/pure/collections/tables.nim;;*;;5;;"Alias of *";;100;;None +sug;;skProc;;tables.del;;proc (t: var Table[del.A, del.B], key: A);;*/lib/pure/collections/tables.nim;;*;;5;;"*";;100;;None +""" + +# TODO enable the tests +# TODO: test/fix suggestion sorting - deprecated suggestions should rank lower diff --git a/nimsuggest/tests/tsug_template.nim b/nimsuggest/tests/tsug_template.nim new file mode 100644 index 000000000..da494d279 --- /dev/null +++ b/nimsuggest/tests/tsug_template.nim @@ -0,0 +1,12 @@ +template tmpa() = discard +macro tmpb() = discard +converter tmpc() = discard +tmp#[!]# + +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skMacro;;tsug_template.tmpb;;macro (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;2;;6;;"";;100;;Prefix +sug;;skConverter;;tsug_template.tmpc;;converter ();;$file;;3;;10;;"";;100;;Prefix +sug;;skTemplate;;tsug_template.tmpa;;template ();;$file;;1;;9;;"";;100;;Prefix +""" diff --git a/nimsuggest/tests/tsug_typedecl.nim b/nimsuggest/tests/tsug_typedecl.nim new file mode 100644 index 000000000..2a510929d --- /dev/null +++ b/nimsuggest/tests/tsug_typedecl.nim @@ -0,0 +1,26 @@ +# suggestions for type declarations + +from system import string, int, bool + +type + super = int + someType = bool + +let str = "hello" + +proc main() = + let a: s#[!]# + +# This output show seq, even though that's not imported. This is due to the +# entire symbol table, regardless of import visibility is currently being +# scanned. This is hardly ideal, but changing it with the current level of test +# coverage is unwise as it might break more than it fixes. + +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skType;;tsug_typedecl.someType;;someType;;*nimsuggest/tests/tsug_typedecl.nim;;7;;2;;"";;100;;Prefix +sug;;skType;;tsug_typedecl.super;;super;;*nimsuggest/tests/tsug_typedecl.nim;;6;;2;;"";;100;;Prefix +sug;;skType;;system.string;;string;;*lib/system/basic_types.nim;;*;;*;;*;;100;;Prefix +sug;;skType;;system.seq;;seq;;*lib/system.nim;;*;;*;;*;;100;;Prefix +""" diff --git a/nimsuggest/tests/ttempl_inst.nim b/nimsuggest/tests/ttempl_inst.nim new file mode 100644 index 000000000..5f5b10fe9 --- /dev/null +++ b/nimsuggest/tests/ttempl_inst.nim @@ -0,0 +1,13 @@ +template foo() = + {.warning: "foo".} + +foo() + +#[!]# +discard """ +$nimsuggest --tester $file +>chk $1 +chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/ttempl_inst.nim [Processing]";;0 +chk;;skUnknown;;;;Hint;;$file;;4;;3;;"template/generic instantiation from here";;0 +chk;;skUnknown;;;;Warning;;$file;;2;;11;;"foo [User]";;0 +""" diff --git a/nimsuggest/tests/ttemplate_highlight.nim b/nimsuggest/tests/ttemplate_highlight.nim new file mode 100644 index 000000000..2cbac3be5 --- /dev/null +++ b/nimsuggest/tests/ttemplate_highlight.nim @@ -0,0 +1,9 @@ +doAssert true#[!]# + +discard """ +$nimsuggest --tester $1 +>highlight $1 +highlight;;skTemplate;;1;;0;;8 +highlight;;skTemplate;;1;;0;;8 +highlight;;skEnumField;;1;;9;;4 +""" diff --git a/nimsuggest/tests/ttype_decl.nim b/nimsuggest/tests/ttype_decl.nim new file mode 100644 index 000000000..61d8c26cd --- /dev/null +++ b/nimsuggest/tests/ttype_decl.nim @@ -0,0 +1,17 @@ +discard """ +$nimsuggest --tester --maxresults:3 $file +>sug $1 +sug;;skType;;ttype_decl.Other;;Other;;$file;;10;;2;;"";;100;;None +sug;;skType;;system.int;;int;;*lib/system/basic_types.nim;;2;;2;;"";;100;;None +sug;;skType;;system.string;;string;;*lib/system/basic_types.nim;;23;;2;;"";;100;;None +""" +import strutils +type + Other = object ## My other object. + Foo = #[!]# + OldOne {.deprecated.} = object + x: int + +proc main(f: Foo) = + +# XXX why no doc comments? diff --git a/nimsuggest/tests/ttype_highlight.nim b/nimsuggest/tests/ttype_highlight.nim new file mode 100644 index 000000000..a324215fe --- /dev/null +++ b/nimsuggest/tests/ttype_highlight.nim @@ -0,0 +1,27 @@ +type + TypeA = int + TypeB* = int + TypeC {.unchecked.} = array[1, int] + TypeD[T] = T + TypeE* {.unchecked.} = array[0, int]#[!]# + +discard """ +$nimsuggest --tester $file +>highlight $1 +highlight;;skType;;2;;2;;5 +highlight;;skType;;3;;2;;5 +highlight;;skType;;4;;2;;5 +highlight;;skType;;5;;2;;5 +highlight;;skType;;6;;2;;5 +highlight;;skType;;2;;10;;3 +highlight;;skType;;3;;11;;3 +highlight;;skType;;4;;24;;5 +highlight;;skType;;4;;33;;3 +highlight;;skType;;5;;13;;1 +highlight;;skType;;6;;25;;5 +highlight;;skType;;6;;34;;3 +highlight;;skType;;2;;10;;3 +highlight;;skType;;3;;11;;3 +highlight;;skType;;4;;33;;3 +highlight;;skType;;6;;34;;3 +""" diff --git a/nimsuggest/tests/tuse.nim b/nimsuggest/tests/tuse.nim new file mode 100644 index 000000000..7c1d1ad0c --- /dev/null +++ b/nimsuggest/tests/tuse.nim @@ -0,0 +1,22 @@ +# basic tests for use + +# bug #58 +proc someOtherProc() = + discard + +someOtherProc() + +proc #[!]#someProc*() = + discard + +#[!]#someProc() + +discard """ +$nimsuggest --tester $file +>use $1 +def;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;9;;5;;"";;100 +use;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;12;;0;;"";;100 +>use $2 +def;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;9;;5;;"";;100 +use;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;12;;0;;"";;100 +""" diff --git a/nimsuggest/tests/tuse_enum.nim b/nimsuggest/tests/tuse_enum.nim new file mode 100644 index 000000000..8a40a8348 --- /dev/null +++ b/nimsuggest/tests/tuse_enum.nim @@ -0,0 +1,15 @@ +discard """ +$nimsuggest --tester $file +>use $1 +def;;skEnumField;;tuse_enum.Colour.Red;;Colour;;$file;;10;;4;;"";;100 +use;;skEnumField;;tuse_enum.Colour.Red;;Colour;;$file;;14;;8;;"";;100 +""" + +type + Colour = enum + Red + Green + Blue + +discard #[!]#Red + diff --git a/nimsuggest/tests/tuse_structure.nim b/nimsuggest/tests/tuse_structure.nim new file mode 100644 index 000000000..f65ab9060 --- /dev/null +++ b/nimsuggest/tests/tuse_structure.nim @@ -0,0 +1,15 @@ +# tests for use and structures + +type + Foo* = ref object of RootObj + bar*: string + +proc test(f: Foo) = + echo f.#[!]#bar + +discard """ +$nimsuggest --tester $file +>use $1 +def skField tuse_structure.Foo.bar string $file 5 4 "" 100 +use skField tuse_structure.Foo.bar string $file 8 9 "" 100 +""" diff --git a/nimsuggest/tests/tv3.nim b/nimsuggest/tests/tv3.nim new file mode 100644 index 000000000..80e51e364 --- /dev/null +++ b/nimsuggest/tests/tv3.nim @@ -0,0 +1,27 @@ +# tests v3 + +type + Foo* = ref object of RootObj + bar*: string + +proc test(f: Foo) = + echo f.ba#[!]#r + +#[!]# + +discard """ +$nimsuggest --v3 --tester $file +>use $1 +def skField tv3.Foo.bar string $file 5 4 "" 100 +use skField tv3.Foo.bar string $file 8 9 "" 100 +>def $1 +def skField tv3.Foo.bar string $file 5 4 "" 100 +>sug $1 +sug skField bar string $file 5 4 "" 100 Prefix +>globalSymbols test +def skProc tv3.test proc (f: Foo){.gcsafe, raises: <inferred> [].} $file 7 5 "" 100 +>globalSymbols Foo +def skType tv3.Foo Foo $file 4 2 "" 100 +>def $2 +>use $2 +""" diff --git a/nimsuggest/tests/tv3_con.nim b/nimsuggest/tests/tv3_con.nim new file mode 100644 index 000000000..4714c366b --- /dev/null +++ b/nimsuggest/tests/tv3_con.nim @@ -0,0 +1,13 @@ +# tests v3 + +proc test(a: string, b:string) = discard +proc test(a: int) = discard + +test(#[!]# + +discard """ +$nimsuggest --v3 --tester $file +>con $1 +con;;skProc;;tv3_con.test;;proc (a: string, b: string);;$file;;3;;5;;"";;100 +con;;skProc;;tv3_con.test;;proc (a: int);;$file;;4;;5;;"";;100 +""" diff --git a/nimsuggest/tests/tv3_definition.nim b/nimsuggest/tests/tv3_definition.nim new file mode 100644 index 000000000..03684b7cd --- /dev/null +++ b/nimsuggest/tests/tv3_definition.nim @@ -0,0 +1,9 @@ + +let foo = 30 +let bar = foo + fo#[!]#o + foo + +discard """ +$nimsuggest --v3 --tester $file +>def $1 +def skLet tv3_definition.foo int $file 2 4 "" 100 +""" diff --git a/nimsuggest/tests/tv3_forward_definition.nim b/nimsuggest/tests/tv3_forward_definition.nim new file mode 100644 index 000000000..7a16ea331 --- /dev/null +++ b/nimsuggest/tests/tv3_forward_definition.nim @@ -0,0 +1,23 @@ +proc de#[!]#mo(): int + +proc de#[!]#mo(): int = 5 + +let a = de#[!]#mo() + +discard """ +$nimsuggest --v3 --tester $file +>use $1 +use skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 1 5 "" 100 +def skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 3 5 "" 100 +use skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 5 8 "" 100 +>use $2 +use skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 1 5 "" 100 +def skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 3 5 "" 100 +use skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 5 8 "" 100 +>declaration $1 +declaration skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 3 5 "" 100 +>declaration $2 +declaration skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 1 5 "" 100 +>declaration $3 +declaration skProc tv3_forward_definition.demo proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 1 5 "" 100 +""" diff --git a/nimsuggest/tests/tv3_generics.nim b/nimsuggest/tests/tv3_generics.nim new file mode 100644 index 000000000..2bfb2ca1d --- /dev/null +++ b/nimsuggest/tests/tv3_generics.nim @@ -0,0 +1,18 @@ +type + Hello[T] = object + value: T + +proc printHelloValue[T](hello: Hello[T]) = + echo hello.value + +proc main() = + let a = Hello[float]() + p#[!]#rintHelloValue(a) + +main() + +discard """ +$nimsuggest --v3 --tester $file +>def $1 +def;;skProc;;tv3_generics.printHelloValue;;proc (hello: Hello[printHelloValue.T]);;$file;;5;;5;;"";;100 +""" diff --git a/nimsuggest/tests/tv3_globalSymbols.nim b/nimsuggest/tests/tv3_globalSymbols.nim new file mode 100644 index 000000000..c3bb9933b --- /dev/null +++ b/nimsuggest/tests/tv3_globalSymbols.nim @@ -0,0 +1,14 @@ +# Tests the order of the matches +proc Btoken(): int = 5 +proc tokenA(): int = 5 +proc token(): int = 5 +proc BBtokenA(): int = 5 + +discard """ +$nimsuggest --v3 --tester $file +>globalSymbols token +def skProc tv3_globalSymbols.token proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 4 5 "" 100 +def skProc tv3_globalSymbols.tokenA proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 3 5 "" 100 +def skProc tv3_globalSymbols.Btoken proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 2 5 "" 100 +def skProc tv3_globalSymbols.BBtokenA proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 5 5 "" 100 +""" diff --git a/nimsuggest/tests/tv3_import.nim b/nimsuggest/tests/tv3_import.nim new file mode 100644 index 000000000..3c128f85b --- /dev/null +++ b/nimsuggest/tests/tv3_import.nim @@ -0,0 +1,7 @@ +import tv#[!]#3 + +discard """ +$nimsuggest --v3 --tester $file +>def $1 +def skModule tv3 */tv3.nim 1 0 "" 100 +""" diff --git a/nimsuggest/tests/tv3_outline.nim b/nimsuggest/tests/tv3_outline.nim new file mode 100644 index 000000000..518620c87 --- /dev/null +++ b/nimsuggest/tests/tv3_outline.nim @@ -0,0 +1,45 @@ +# tests v3 outline + +type + Foo* = ref object of RootObj + bar*: string + FooEnum = enum value1, value2 + FooPrivate = ref object of RootObj + barPrivate: string + +macro m(arg: untyped): untyped = discard +template t(arg: untyped): untyped = discard +proc p(): void = discard +iterator i(): int = discard +converter c(s: string): int = discard +method m(f: Foo): void = discard +func f(): void = discard + +let a = 1 +var b = 2 +const con = 2 + +proc outer(): void = + proc inner() = discard + +proc procWithLocal(): void = + let local = 10 + +discard """ +$nimsuggest --v3 --tester $file +>outline $file +outline skType tv3_outline.Foo Foo $file 4 2 "" 100 5 16 +outline skType tv3_outline.FooEnum FooEnum $file 6 2 "" 100 6 31 +outline skEnumField tv3_outline.FooEnum.value1 FooEnum $file 6 17 "" 100 6 23 +outline skEnumField tv3_outline.FooEnum.value2 FooEnum $file 6 25 "" 100 6 31 +outline skType tv3_outline.FooPrivate FooPrivate $file 7 2 "" 100 8 22 +outline skMacro tv3_outline.m macro (arg: untyped): untyped{.noSideEffect, gcsafe, raises: <inferred> [].} $file 10 6 "" 100 10 40 +outline skTemplate tv3_outline.t template (arg: untyped): untyped $file 11 9 "" 100 11 43 +outline skProc tv3_outline.p proc (){.noSideEffect, gcsafe, raises: <inferred> [].} $file 12 5 "" 100 12 24 +outline skConverter tv3_outline.c converter (s: string): int{.noSideEffect, gcsafe, raises: <inferred> [].} $file 14 10 "" 100 14 37 +outline skFunc tv3_outline.f proc (){.noSideEffect, gcsafe, raises: <inferred> [].} $file 16 5 "" 100 16 24 +outline skConst tv3_outline.con int literal(2) $file 20 6 "" 100 20 13 +outline skProc tv3_outline.outer proc (){.noSideEffect, gcsafe, raises: <inferred> [].} $file 22 5 "" 100 23 24 +outline skProc tv3_outline.outer.inner proc (){.noSideEffect, gcsafe, raises: <inferred> [].} $file 23 7 "" 100 23 24 +outline skProc tv3_outline.procWithLocal proc (){.noSideEffect, gcsafe, raises: <inferred> [].} $file 25 5 "" 100 26 16 +""" diff --git a/nimsuggest/tests/tv3_typeDefinition.nim b/nimsuggest/tests/tv3_typeDefinition.nim new file mode 100644 index 000000000..f86d12cc6 --- /dev/null +++ b/nimsuggest/tests/tv3_typeDefinition.nim @@ -0,0 +1,32 @@ +# tests v3 + +type + Foo* = ref object of RootObj + bar*: string + +proc test(ff: Foo) = + echo f#[!]#f.bar + +type + Fo#[!]#o2* = ref object of RootObj + +type + FooGeneric[T] = ref object of RootObj + bar*: T + +let fooGeneric = FooGeneric[string]() +echo fo#[!]#oGeneric.bar + +# bad type +echo unde#[!]#fined + +discard """ +$nimsuggest --v3 --tester $file +>type $1 +type skType tv3_typeDefinition.Foo Foo $file 4 2 "" 100 +>type $2 +type skType tv3_typeDefinition.Foo2 Foo2 $file 11 2 "" 100 +>type $3 +type skType tv3_typeDefinition.FooGeneric FooGeneric $file 14 2 "" 100 +>type $4 +""" diff --git a/nimsuggest/tests/twithin_macro.nim b/nimsuggest/tests/twithin_macro.nim new file mode 100644 index 000000000..98d58381f --- /dev/null +++ b/nimsuggest/tests/twithin_macro.nim @@ -0,0 +1,51 @@ +from system import string, int, seq, `&`, `$`, `*`, `@`, echo, add, items, RootObj +import fixtures/mclass_macro + +class Animal of RootObj: + var name: string + var age: int + method vocalize: string {.base.} = "..." # use `base` pragma to annonate base methods + method age_human_yrs: int {.base.} = self.age # `this` is injected + proc `$`: string = "animal:" & self.name & ":" & $self.age + +class Dog of Animal: + method vocalize: string = "woof" + method age_human_yrs: int = self.age * 7 + proc `$`: string = "dog:" & self.name & ":" & $self.age + +class Cat of Animal: + method vocalize: string = "meow" + proc `$`: string = "cat:" & self.name & ":" & $self.age + +class Rabbit of Animal: + proc newRabbit(name: string, age: int) = # the constructor doesn't need a return type + result = Rabbit(name: name, age: age) + method vocalize: string = "meep" + proc `$`: string = + self.#[!]# + result = "rabbit:" & self.name & ":" & $self.age + +# --- + +var animals: seq[Animal] = @[] +animals.add(Dog(name: "Sparky", age: 10)) +animals.add(Cat(name: "Mitten", age: 10)) + +for a in animals: + echo a.vocalize() + echo a.age_human_yrs() + +let r = newRabbit("Fluffy", 3) +echo r.vocalize() +echo r.age_human_yrs() +echo r + +discard """ +$nimsuggest --tester --maxresults:5 $file +>sug $1 +sug;;skField;;age;;int;;$file;;6;;6;;"";;100;;None +sug;;skField;;name;;string;;$file;;5;;6;;"";;100;;None +sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int{.raises: <inferred> [].};;$file;;8;;9;;"";;100;;None +sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string{.raises: <inferred> [].};;$file;;7;;9;;"";;100;;None +sug;;skMethod;;twithin_macro.vocalize;;proc (self: Rabbit): string;;$file;;23;;9;;"";;100;;None +""" diff --git a/nimsuggest/tests/twithin_macro_prefix.nim b/nimsuggest/tests/twithin_macro_prefix.nim new file mode 100644 index 000000000..e89c8b942 --- /dev/null +++ b/nimsuggest/tests/twithin_macro_prefix.nim @@ -0,0 +1,48 @@ +from system import string, int, seq, `&`, `$`, `*`, `@`, echo, add, RootObj +import fixtures/mclass_macro + +class Animal of RootObj: + var name: string + var age: int + method vocalize: string {.base.} = "..." # use `base` pragma to annonate base methods + method age_human_yrs: int {.base.} = self.age # `this` is injected + proc `$`: string = "animal:" & self.name & ":" & $self.age + +class Dog of Animal: + method vocalize: string = "woof" + method age_human_yrs: int = self.age * 7 + proc `$`: string = "dog:" & self.name & ":" & $self.age + +class Cat of Animal: + method vocalize: string = "meow" + proc `$`: string = "cat:" & self.name & ":" & $self.age + +class Rabbit of Animal: + proc newRabbit(name: string, age: int) = # the constructor doesn't need a return type + result = Rabbit(name: name, age: age) + method vocalize: string = "meep" + proc `$`: string = + self.ag#[!]# + result = "rabbit:" & self.name & ":" & $self.age + +# --- + +var animals: seq[Animal] = @[] +animals.add(Dog(name: "Sparky", age: 10)) +animals.add(Cat(name: "Mitten", age: 10)) + +for a in animals: + echo a.vocalize() + echo a.age_human_yrs() + +let r = newRabbit("Fluffy", 3) +echo r.vocalize() +echo r.age_human_yrs() +echo r + +discard """ +$nimsuggest --tester $file +>sug $1 +sug;;skField;;age;;int;;$file;;6;;6;;"";;100;;Prefix +sug;;skMethod;;twithin_macro_prefix.age_human_yrs;;proc (self: Animal): int{.raises: <inferred> [].};;$file;;8;;9;;"";;100;;Prefix +""" |