pi-revit 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/package.json +1 -1
- package/scripts/benchmark-search-docs.py +304 -0
- package/src/Revit/Tools/SearchApiDocs.cs +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,26 @@ Format follows [Keep a Changelog](https://keepachangelog.com/); version headers
|
|
|
7
7
|
Every published version gets an entry with **Added** / **Changed** / **Fixed** sections
|
|
8
8
|
describing what the user will notice — not internal refactors.
|
|
9
9
|
|
|
10
|
+
## [0.2.10] - 2026-07-22
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `search_api_docs`: constructor overloads can now be targeted with the natural C#
|
|
14
|
+
spelling — `FilteredElementCollector(Document` matches even though constructors are
|
|
15
|
+
rendered as `new FilteredElementCollector(Document)`. Previously only the `new `-prefixed
|
|
16
|
+
form matched (the same typography-cliff family as the 0.2.8 comma-spacing fix).
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- `scripts/benchmark-search-docs.py`: a reproducible ~630-query benchmark of
|
|
20
|
+
`search_api_docs` scored against Autodesk's own RevitAPI.xml — exact-name recall,
|
|
21
|
+
signature/spacing variants, hard syntax, namespace ambiguity, adversarial honesty
|
|
22
|
+
controls, documentation fidelity, and latency percentiles. Run it against any Revit
|
|
23
|
+
version with the bridge loaded. Measured on Revit 2025 at 0.2.9: 100% recall / 98.8%
|
|
24
|
+
top-1 on exact names, 100% spacing-variant agreement, 0 false positives across 99
|
|
25
|
+
adversarial mutations, 50/50 correct parameter docs, p50 8 ms.
|
|
26
|
+
|
|
27
|
+
Requires redeploying the Revit add-in (`scripts\deploy.ps1` with Revit closed, then
|
|
28
|
+
restart Revit).
|
|
29
|
+
|
|
10
30
|
## [0.2.9] - 2026-07-21
|
|
11
31
|
|
|
12
32
|
### Added
|
package/package.json
CHANGED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Reproducible benchmark of pi-revit search_api_docs against RevitAPI.xml ground truth.
|
|
3
|
+
|
|
4
|
+
Usage: python scripts/benchmark-search-docs.py [results.json] [path-to-RevitAPI.xml]
|
|
5
|
+
Requires: Revit running with the pi-revit bridge add-in loaded (a document need not
|
|
6
|
+
be open) and Python 3.8+. Ground truth is Autodesk's own RevitAPI.xml, so every
|
|
7
|
+
number is reproducible on any machine with the same Revit version.
|
|
8
|
+
|
|
9
|
+
Categories:
|
|
10
|
+
A exact-name recall/ranking B signature + spacing variants
|
|
11
|
+
C complex syntax (arrays, nested generics, ref/out, ctors)
|
|
12
|
+
D namespace ambiguity E honesty/adversarial controls
|
|
13
|
+
F documentation fidelity G latency stats
|
|
14
|
+
"""
|
|
15
|
+
import glob, json, os, random, re, sys, time, urllib.request, xml.etree.ElementTree as ET
|
|
16
|
+
from collections import defaultdict
|
|
17
|
+
|
|
18
|
+
random.seed(20260722) # reproducible sampling
|
|
19
|
+
|
|
20
|
+
APPDATA = os.environ.get("APPDATA") or os.path.expanduser(r"~\AppData\Roaming")
|
|
21
|
+
BRIDGE = json.load(open(os.path.join(APPDATA, "RevitBridge", "bridge.json")))
|
|
22
|
+
BASE, TOKEN = BRIDGE["baseUrl"], BRIDGE["token"]
|
|
23
|
+
if len(sys.argv) > 2:
|
|
24
|
+
XML_PATH = sys.argv[2]
|
|
25
|
+
else:
|
|
26
|
+
revit_version = BRIDGE.get("revitVersion", "")
|
|
27
|
+
candidates = glob.glob(rf"C:\Program Files\Autodesk\Revit {revit_version}*\RevitAPI.xml") \
|
|
28
|
+
or glob.glob(r"C:\Program Files\Autodesk\Revit 20*\RevitAPI.xml")
|
|
29
|
+
if not candidates:
|
|
30
|
+
sys.exit("RevitAPI.xml not found; pass its path as the second argument.")
|
|
31
|
+
XML_PATH = candidates[-1]
|
|
32
|
+
|
|
33
|
+
LAT = []
|
|
34
|
+
def search(query, max_results=10):
|
|
35
|
+
body = json.dumps({"query": query, "max_results": max_results}).encode("utf-8")
|
|
36
|
+
url = f"{BASE}/tools/search_api_docs/execute?token={TOKEN}&timeout_ms=60000"
|
|
37
|
+
req = urllib.request.Request(url, data=body, headers={"content-type": "application/json"}, method="POST")
|
|
38
|
+
t0 = time.perf_counter()
|
|
39
|
+
with urllib.request.urlopen(req, timeout=90) as resp:
|
|
40
|
+
payload = json.load(resp)
|
|
41
|
+
LAT.append((time.perf_counter() - t0) * 1000)
|
|
42
|
+
det = (payload.get("details") or {}).get("payload") or {}
|
|
43
|
+
return det.get("matches") or [], det.get("totalMatches", 0)
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------- ground truth
|
|
46
|
+
PRIM = {"System.String": "string", "System.Boolean": "bool", "System.Double": "double",
|
|
47
|
+
"System.Int32": "int", "System.Int64": "long", "System.Object": "object",
|
|
48
|
+
"System.Byte": "byte", "System.Single": "float", "System.UInt32": "uint"}
|
|
49
|
+
|
|
50
|
+
def split_top(params): # split on commas outside {}
|
|
51
|
+
out, depth, cur = [], 0, ""
|
|
52
|
+
for ch in params:
|
|
53
|
+
if ch == "{": depth += 1
|
|
54
|
+
elif ch == "}": depth -= 1
|
|
55
|
+
if ch == "," and depth == 0: out.append(cur); cur = ""
|
|
56
|
+
else: cur += ch
|
|
57
|
+
if cur: out.append(cur)
|
|
58
|
+
return out
|
|
59
|
+
|
|
60
|
+
def short_type(t):
|
|
61
|
+
t = t.strip()
|
|
62
|
+
byref = t.endswith("@")
|
|
63
|
+
if byref: t = t[:-1]
|
|
64
|
+
array = ""
|
|
65
|
+
while t.endswith("[]"): array += "[]"; t = t[:-2]
|
|
66
|
+
if t in PRIM: base = PRIM[t]
|
|
67
|
+
elif "{" in t:
|
|
68
|
+
outer, inner = t[:t.index("{")], t[t.index("{")+1:t.rindex("}")]
|
|
69
|
+
outer = outer.split(".")[-1].split("`")[0]
|
|
70
|
+
base = f"{outer}<{', '.join(short_type(x) for x in split_top(inner))}>"
|
|
71
|
+
else:
|
|
72
|
+
base = t.split(".")[-1]
|
|
73
|
+
return base + array + ("&" if byref else "")
|
|
74
|
+
|
|
75
|
+
class Member:
|
|
76
|
+
__slots__ = ("kind","full","typename","member","params","summary","pnames","since")
|
|
77
|
+
def __init__(s, kind, full, typename, member, params, summary, pnames, since):
|
|
78
|
+
s.kind, s.full, s.typename, s.member = kind, full, typename, member
|
|
79
|
+
s.params, s.summary, s.pnames, s.since = params, summary, pnames, since
|
|
80
|
+
@property
|
|
81
|
+
def short_sig_prefix(s):
|
|
82
|
+
shorts = [short_type(p) for p in s.params]
|
|
83
|
+
return f"{s.typename}.{s.member}(" + ", ".join(shorts)
|
|
84
|
+
|
|
85
|
+
def text_of(el):
|
|
86
|
+
if el is None: return None
|
|
87
|
+
return re.sub(r"\s+", " ", "".join(el.itertext())).strip() or None
|
|
88
|
+
|
|
89
|
+
print("parsing RevitAPI.xml ...", flush=True)
|
|
90
|
+
root = ET.parse(XML_PATH).getroot()
|
|
91
|
+
methods, props, types = [], [], []
|
|
92
|
+
for m in root.iter("member"):
|
|
93
|
+
name = m.get("name", "")
|
|
94
|
+
summary = text_of(m.find("summary"))
|
|
95
|
+
since = text_of(m.find("since"))
|
|
96
|
+
pnames = [p.get("name") for p in m.findall("param")]
|
|
97
|
+
if name.startswith("M:"):
|
|
98
|
+
body = name[2:]
|
|
99
|
+
params = ""
|
|
100
|
+
if "(" in body:
|
|
101
|
+
body, params = body[:body.index("(")], body[body.index("(")+1:body.rindex(")")]
|
|
102
|
+
parts = body.split(".")
|
|
103
|
+
if len(parts) < 2: continue
|
|
104
|
+
member, typename = parts[-1], parts[-2]
|
|
105
|
+
if "`" in typename or "`" in member: continue
|
|
106
|
+
full = ".".join(parts)
|
|
107
|
+
methods.append(Member("M", full, typename, member, split_top(params) if params else [], summary, pnames, since))
|
|
108
|
+
elif name.startswith("P:"):
|
|
109
|
+
parts = name[2:].split(".")
|
|
110
|
+
if len(parts) < 2 or "`" in parts[-2]: continue
|
|
111
|
+
props.append(Member("P", name[2:], parts[-2], parts[-1], [], summary, pnames, since))
|
|
112
|
+
elif name.startswith("T:"):
|
|
113
|
+
parts = name[2:].split(".")
|
|
114
|
+
if "`" in parts[-1]: continue
|
|
115
|
+
types.append(Member("T", name[2:], parts[-1], "", [], summary, pnames, since))
|
|
116
|
+
|
|
117
|
+
print(f"golden pool: {len(methods)} methods, {len(props)} properties, {len(types)} types", flush=True)
|
|
118
|
+
|
|
119
|
+
by_ns = defaultdict(list)
|
|
120
|
+
for mm in methods: by_ns[".".join(mm.full.split(".")[:-2])].append(mm)
|
|
121
|
+
real_methods = {(m.typename, m.member) for m in methods}
|
|
122
|
+
overload_count = defaultdict(int)
|
|
123
|
+
for m in methods: overload_count[(m.typename, m.member)] += 1
|
|
124
|
+
|
|
125
|
+
def stratified(pool, n):
|
|
126
|
+
groups = defaultdict(list)
|
|
127
|
+
for m in pool: groups[".".join(m.full.split(".")[:4])].append(m)
|
|
128
|
+
out, keys = [], sorted(groups)
|
|
129
|
+
while len(out) < n and keys:
|
|
130
|
+
for k in list(keys):
|
|
131
|
+
if groups[k]: out.append(groups[k].pop(random.randrange(len(groups[k]))))
|
|
132
|
+
else: keys.remove(k)
|
|
133
|
+
if len(out) >= n: break
|
|
134
|
+
return out
|
|
135
|
+
|
|
136
|
+
R = {} # results
|
|
137
|
+
|
|
138
|
+
# ---------------------------------------------------------- A: exact-name recall
|
|
139
|
+
A = {"n":0,"top1":0,"top3":0,"recall":0}
|
|
140
|
+
sampleA = stratified(methods, 90) + stratified(props, 40) + stratified(types, 30)
|
|
141
|
+
for m in sampleA:
|
|
142
|
+
q = f"{m.typename}.{m.member}" if m.kind in "MP" else m.typename
|
|
143
|
+
matches, _ = search(q)
|
|
144
|
+
target = m.full if m.kind in "MP" else m.full
|
|
145
|
+
names = [x.get("name","") for x in matches]
|
|
146
|
+
A["n"] += 1
|
|
147
|
+
A["recall"] += any(n == target for n in names)
|
|
148
|
+
A["top1"] += bool(names and names[0] == target)
|
|
149
|
+
A["top3"] += any(n == target for n in names[:3])
|
|
150
|
+
print("A done", A, flush=True)
|
|
151
|
+
R["A exact-name"] = A
|
|
152
|
+
|
|
153
|
+
# ------------------------------------------------- B: signatures + spacing variants
|
|
154
|
+
B = {"n":0,"top1_sig":0,"variants_agree":0,"variant_sets":0}
|
|
155
|
+
sigpool = [m for m in methods if 2 <= len(m.params) <= 5]
|
|
156
|
+
sampleB = stratified(sigpool, 60)
|
|
157
|
+
for m in sampleB:
|
|
158
|
+
try: prefix = m.short_sig_prefix
|
|
159
|
+
except Exception: continue
|
|
160
|
+
canonical = prefix # "Type.Member(P1, P2..."
|
|
161
|
+
matches, _ = search(canonical)
|
|
162
|
+
B["n"] += 1
|
|
163
|
+
sigs = [x.get("signature","") for x in matches]
|
|
164
|
+
ok = bool(sigs and sigs[0].startswith(f"{m.typename}.{m.member}("))
|
|
165
|
+
B["top1_sig"] += ok
|
|
166
|
+
variants = [canonical.replace(", ", ","),
|
|
167
|
+
canonical.replace(", ", " , "),
|
|
168
|
+
canonical.replace(", ", ", "),
|
|
169
|
+
canonical.replace("(", "( ", 1)]
|
|
170
|
+
counts = {len(matches)}
|
|
171
|
+
for v in variants:
|
|
172
|
+
vm, _ = search(v)
|
|
173
|
+
counts.add(len(vm))
|
|
174
|
+
B["variant_sets"] += 1
|
|
175
|
+
B["variants_agree"] += (len(counts) == 1)
|
|
176
|
+
print("B done", B, flush=True)
|
|
177
|
+
R["B signature+spacing"] = B
|
|
178
|
+
|
|
179
|
+
# --------------------------------------------------------- C: complex syntax shapes
|
|
180
|
+
C = {"cases": []}
|
|
181
|
+
def probe(label, q, expect_nonzero=True):
|
|
182
|
+
try: matches, total = search(q)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
C["cases"].append((label, q, f"ERROR {e}")); return
|
|
185
|
+
ok = (total > 0) if expect_nonzero else (total == 0)
|
|
186
|
+
top = matches[0]["signature"] if matches else "-"
|
|
187
|
+
C["cases"].append((label, q, f"{'OK' if ok else 'MISS'} total={total} top={top[:70]}"))
|
|
188
|
+
|
|
189
|
+
arr = next((m for m in methods if any(p.endswith("[]") for p in m.params)), None)
|
|
190
|
+
if arr: probe("array param", arr.short_sig_prefix)
|
|
191
|
+
nest = next((m for m in methods if any("{" in p and "{" in p[p.index("{")+1:] for p in m.params)), None)
|
|
192
|
+
if nest: probe("nested generic", nest.short_sig_prefix)
|
|
193
|
+
byref = next((m for m in methods if any(p.endswith("@") for p in m.params)), None)
|
|
194
|
+
if byref: probe("ref/out param", byref.short_sig_prefix)
|
|
195
|
+
ctor = next((m for m in methods if m.member == "#ctor" and m.typename == "FilteredElementCollector"), None)
|
|
196
|
+
probe("constructor", "FilteredElementCollector(Document")
|
|
197
|
+
probe("property", "PDFExportOptions.FileName")
|
|
198
|
+
probe("fully qualified type", "Autodesk.Revit.DB.Document")
|
|
199
|
+
probe("case-insensitive", "wall.create(document, curve")
|
|
200
|
+
probe("Double vs double", "UnitUtils.ConvertToInternalUnits(Double, ForgeTypeId")
|
|
201
|
+
probe("trailing garbage brackets", "Wall.Create(Document, Curve]]", True) # observational
|
|
202
|
+
probe("double periods", "Wall..Create", True) # observational
|
|
203
|
+
print("C done", flush=True)
|
|
204
|
+
R["C complex-syntax"] = C
|
|
205
|
+
|
|
206
|
+
# ------------------------------------------------------------ D: namespace ambiguity
|
|
207
|
+
D = {"cases": []}
|
|
208
|
+
def ns_case(q, want_substr):
|
|
209
|
+
matches, total = search(q)
|
|
210
|
+
top = matches[0]["name"] if matches else "-"
|
|
211
|
+
D["cases"].append((q, want_substr, top, "OK" if want_substr in top else "MISS"))
|
|
212
|
+
for q, want in [
|
|
213
|
+
("Autodesk.Revit.DB.Architecture.Room", ".Architecture.Room"),
|
|
214
|
+
("Autodesk.Revit.DB.Mechanical.Space", ".Mechanical.Space"),
|
|
215
|
+
("Architecture.Room", ".Architecture.Room"),
|
|
216
|
+
("Mechanical.Duct", ".Mechanical.Duct"),
|
|
217
|
+
("Plumbing.Pipe", ".Plumbing.Pipe"),
|
|
218
|
+
("Electrical.Wire", ".Electrical.Wire"),
|
|
219
|
+
("Structure.Rebar", ".Structure.Rebar"),
|
|
220
|
+
("Room", "Room"), ("Space", "Space"), ("Area", "Area"),
|
|
221
|
+
("Connector", "Connector"),
|
|
222
|
+
]: ns_case(q, want)
|
|
223
|
+
print("D done", flush=True)
|
|
224
|
+
R["D namespace"] = D
|
|
225
|
+
|
|
226
|
+
# ------------------------------------------------------- E: honesty / adversarial
|
|
227
|
+
E = {"n":0,"exact_fp":0,"zero":0,"kinds":defaultdict(lambda:[0,0,0])}
|
|
228
|
+
adv = []
|
|
229
|
+
pool = stratified([m for m in methods if 1 <= len(m.params) <= 4 and m.member != "#ctor"], 120)
|
|
230
|
+
for m in pool[:30]: # misspelled member (drop 2nd letter)
|
|
231
|
+
if len(m.member) < 5: continue
|
|
232
|
+
adv.append(("misspelled-member", f"{m.typename}.{m.member[0]+m.member[2:]}", m))
|
|
233
|
+
for m in pool[30:60]: # nonsense parameter
|
|
234
|
+
adv.append(("nonsense-param", f"{m.typename}.{m.member}(Banana", m))
|
|
235
|
+
for m in pool[60:90]: # wrong declaring class
|
|
236
|
+
other = random.choice(types).typename
|
|
237
|
+
if (other, m.member) in real_methods: continue
|
|
238
|
+
adv.append(("wrong-class", f"{other}.{m.member}", (other, m.member)))
|
|
239
|
+
for m in pool[90:120]: # swapped params (only if that order is not a real overload)
|
|
240
|
+
if len(m.params) < 2 or m.params[0] == m.params[1]: continue
|
|
241
|
+
sw = [m.params[1], m.params[0]] + m.params[2:]
|
|
242
|
+
exists = any(o.params[:len(sw)] == sw for o in methods if (o.typename,o.member)==(m.typename,m.member))
|
|
243
|
+
if exists: continue
|
|
244
|
+
shorts = [short_type(p) for p in sw]
|
|
245
|
+
adv.append(("swapped-params", f"{m.typename}.{m.member}(" + ", ".join(shorts), m))
|
|
246
|
+
for kind, q, _meta in adv:
|
|
247
|
+
matches, total = search(q)
|
|
248
|
+
E["n"] += 1
|
|
249
|
+
zero = (total == 0)
|
|
250
|
+
# exact false positive: a returned entry CLAIMS the impossible thing exists
|
|
251
|
+
fp = False
|
|
252
|
+
if kind == "wrong-class":
|
|
253
|
+
t, mem = _meta
|
|
254
|
+
fp = any(x.get("name","").endswith(f".{t}.{mem}") or f".{t}." in x.get("name","") and x.get("name","").endswith(f".{mem}") for x in matches)
|
|
255
|
+
elif kind == "swapped-params":
|
|
256
|
+
fp = any(x.get("signature","").lower() == (q.lower()+")") for x in matches)
|
|
257
|
+
elif kind == "nonsense-param":
|
|
258
|
+
fp = any("banana" in x.get("signature","").lower() for x in matches)
|
|
259
|
+
elif kind == "misspelled-member":
|
|
260
|
+
bad = q.split(".")[-1].lower()
|
|
261
|
+
fp = any(x.get("name","").lower().endswith("."+bad) for x in matches)
|
|
262
|
+
E["exact_fp"] += fp
|
|
263
|
+
E["zero"] += zero
|
|
264
|
+
k = E["kinds"][kind]; k[0] += 1; k[1] += fp; k[2] += zero
|
|
265
|
+
print("E done", {k: list(v) for k, v in E["kinds"].items()}, flush=True)
|
|
266
|
+
E["kinds"] = {k: list(v) for k, v in E["kinds"].items()}
|
|
267
|
+
R["E honesty"] = E
|
|
268
|
+
|
|
269
|
+
# ------------------------------------------------------------- F: doc fidelity
|
|
270
|
+
F = {"n":0,"param_names_ok":0,"summary_ok":0,"since_ok":0,"since_present":0,"overload_docs_ok":0,"overload_checked":0}
|
|
271
|
+
fsample = stratified([m for m in methods if m.pnames and m.summary and m.member != "#ctor"], 50)
|
|
272
|
+
for m in fsample:
|
|
273
|
+
try: q = m.short_sig_prefix
|
|
274
|
+
except Exception: continue
|
|
275
|
+
matches, _ = search(q)
|
|
276
|
+
if not matches: continue
|
|
277
|
+
top = matches[0]
|
|
278
|
+
F["n"] += 1
|
|
279
|
+
got_pnames = [p.get("name") for p in (top.get("parameters") or [])]
|
|
280
|
+
F["param_names_ok"] += (got_pnames == m.pnames)
|
|
281
|
+
gs, ws = (top.get("summary") or ""), m.summary
|
|
282
|
+
F["summary_ok"] += (re.sub(r"\W","",gs)[:80] == re.sub(r"\W","",ws)[:80])
|
|
283
|
+
if m.since:
|
|
284
|
+
F["since_present"] += 1
|
|
285
|
+
F["since_ok"] += (top.get("since") == m.since)
|
|
286
|
+
# overload-doc attribution: right param COUNT on the top signature
|
|
287
|
+
if overload_count[(m.typename, m.member)] > 1:
|
|
288
|
+
F["overload_checked"] += 1
|
|
289
|
+
sig = top.get("signature","")
|
|
290
|
+
inner = sig[sig.index("(")+1:sig.rindex(")")] if "(" in sig else ""
|
|
291
|
+
F["overload_docs_ok"] += (len(split_top(inner)) == len(m.params) if inner else len(m.params)==0)
|
|
292
|
+
print("F done", F, flush=True)
|
|
293
|
+
R["F fidelity"] = F
|
|
294
|
+
|
|
295
|
+
# ------------------------------------------------------------------ G: latency
|
|
296
|
+
LAT.sort()
|
|
297
|
+
def pct(p): return LAT[min(len(LAT)-1, int(len(LAT)*p))]
|
|
298
|
+
R["G latency"] = {"queries": len(LAT), "cold_first_ms": round(LAT[-1] if not LAT else None or 0),
|
|
299
|
+
"p50_ms": round(pct(0.50)), "p95_ms": round(pct(0.95)), "p99_ms": round(pct(0.99)),
|
|
300
|
+
"max_ms": round(LAT[-1])}
|
|
301
|
+
|
|
302
|
+
json.dump(R, open(sys.argv[1] if len(sys.argv)>1 else "benchmark-results.json","w"), indent=1, default=str)
|
|
303
|
+
print("ALL DONE", flush=True)
|
|
304
|
+
print(json.dumps(R, indent=1, default=str)[:3000])
|
|
@@ -165,7 +165,11 @@ namespace RevitBridge.Tools
|
|
|
165
165
|
int score;
|
|
166
166
|
if (member.CompositeLower == q) score = 1000;
|
|
167
167
|
else if (signatureQuery && member.SignatureLower == q) score = 980;
|
|
168
|
+
// Constructors render as "new Type(...)": accept the natural C# spelling
|
|
169
|
+
// 'FilteredElementCollector(Document' without requiring the 'new' prefix.
|
|
170
|
+
else if (signatureQuery && member.SignatureLower == "new " + q) score = 975;
|
|
168
171
|
else if (signatureQuery && member.SignatureLower.StartsWith(q, StringComparison.Ordinal)) score = 950;
|
|
172
|
+
else if (signatureQuery && member.SignatureLower.StartsWith("new " + q, StringComparison.Ordinal)) score = 945;
|
|
169
173
|
else if (member.ShortNameLower == q) score = 900;
|
|
170
174
|
else if (member.CompositeLower.StartsWith(q, StringComparison.Ordinal)) score = 700;
|
|
171
175
|
else if (member.ShortNameLower.StartsWith(q, StringComparison.Ordinal)) score = 650;
|