@@ -1,7 +1,298 @@
1+import type { Patp } from '@urbit/api';
2+import { listStrings } from './utils';
3+
4+type Problem = string;
5+
6+class SearchAnalysis {
7+ queryProblems: Problem[] = [];
8+ private _search: string = '';
9+ patpProblems: Problem[] = [];
10+
11+ constructor(search: string) {
12+ this.search = search;
13+ }
14+
15+ get searchIsSigged() {
16+ return sanitizeSearch(this._search)[0] === '~';
17+ }
18+
19+ get query(): RegExp {
20+ let search = this._search;
21+ search = expandAbbreviatedId(search);
22+ if (this.searchIsSigged) search = sig(search);
23+ return convertSearchTextToRegex(search);
24+ }
25+
26+ get patp(): Patp {
27+ return normalizeId(this._search);
28+ }
29+
30+ get queryIsValid(): boolean {
31+ return this.queryProblems.length === 0;
32+ }
33+
34+ get patpIsValid() {
35+ return this.patpProblems.length === 0;
36+ }
37+
38+ get queryAndPatpAreInvalid() {
39+ return !this.queryIsValid && !this.patpIsValid;
40+ }
41+
42+ get search() {
43+ return this._search;
44+ }
45+
46+ set search(search: string) {
47+ this._search = search;
48+ this.analyze();
49+ }
50+
51+ pushQueryProblem(...problems: Problem[]): void {
52+ if (!Object.isFrozen(this.queryProblems)) {
53+ this.queryProblems.push(...problems);
54+ }
55+ }
56+
57+ pushPatpProblem(...problems: Problem[]): void {
58+ if (!Object.isFrozen(this.patpProblems)) {
59+ this.patpProblems.push(...problems);
60+ }
61+ }
62+
63+ pushProblem(...problems: Problem[]): void {
64+ this.pushQueryProblem(...problems);
65+ this.pushPatpProblem(...problems);
66+ }
67+
68+ checkpointQueryValidation(): void {
69+ if (!this.queryIsValid) {
70+ Object.freeze(this.queryProblems);
71+ }
72+ }
73+
74+ checkpointPatpValidation(): void {
75+ if (!this.patpIsValid) {
76+ Object.freeze(this.patpProblems);
77+ }
78+ }
79+
80+ checkpointValidation(): void {
81+ this.checkpointQueryValidation();
82+ this.checkpointPatpValidation();
83+ }
84+
85+ resetAnalysis() {
86+ this.queryProblems = [];
87+ this.patpProblems = [];
88+ }
89+
90+ // mergeAnalysis(analysis: SearchAnalysis): void {
91+ // this.pushQueryProblem(...analysis.queryProblems);
92+ // this.pushPatpProblem(...analysis.patpProblems);
93+ // if (analysis.search) this._search = analysis.search;
94+ // if (analysis.patp) this.patp = analysis.patp;
95+ // }
96+
97+ analyze(): void {
98+ this.resetAnalysis();
99+ let search = normalizeIdAndDesig(this._search);
100+
101+ // Validate patp is not empty
102+ if (search.length === 0) {
103+ this.pushProblem(emptyPatpMessage);
104+ return;
105+ }
106+
107+ // Validate proper use of sig
108+ if (/~/.test(search)) {
109+ this.pushProblem(improperSigMessage);
110+ }
111+
112+ // Validate all characters are legal
113+ let illegalQueryChars = search.match(illegalQueryCharsRegex);
114+ if (illegalQueryChars) {
115+ this.pushQueryProblem(illegalCharsMessage(illegalQueryChars));
116+ }
117+ let illegalPatpChars = search.match(illegalPatpCharsRegex);
118+ if (illegalPatpChars) {
119+ this.pushPatpProblem(illegalCharsMessage(illegalPatpChars));
120+ }
121+ if (/\^/.test(search)) {
122+ this.pushPatpProblem(cannotInferMoonPatpMessage);
123+ }
124+ if (/_/.test(search)) {
125+ this.pushPatpProblem(cannotInferCometPatpMessage);
126+ }
127+
128+ // Validate separators don't appear on boundaries
129+ // This is fine in an unsigged query, but not a patp
130+ // TODO: check for if a query ends in something dumb like ^-
131+ let patp = search;
132+ if (/^[-^_]/.test(patp)) {
133+ this.pushPatpProblem(outsideSeparatorsMessage);
134+ patp = patp.replace(/^[-^_]*/, '');
135+ if (this.searchIsSigged) {
136+ this.pushQueryProblem(outsideSeparatorsMessage);
137+ search = patp;
138+ }
139+ }
140+ if (/[-^_]$/.test(patp)) {
141+ this.pushPatpProblem(outsideSeparatorsMessage);
142+ patp = patp.replace(/[-^_]*$/, '');
143+ }
144+
145+ // Validate word lengths
146+ // TODO: check words for query and patp separately 😥
147+ // because ~min- is min- for query but min for patp
148+ // (b/c already stripped of extra chars)
149+ let words = splitIdIntoWords(patp);
150+ let queryWords = splitIdIntoWords(search);
151+ if (words.length > 1) {
152+ let wrongLengthPatpWords = [];
153+ let wrongLengthQueryWords = [];
154+ let emptyWords = false;
155+ words.forEach((word, i) => {
156+ if (word.length !== 6) {
157+ if (word.length > 0) {
158+ wrongLengthPatpWords.push(word);
159+ if ((i > 0 || this.searchIsSigged) && i < words.length - 1) {
160+ wrongLengthQueryWords.push(word);
161+ }
162+ } else {
163+ emptyWords = true;
164+ }
165+ }
166+ });
167+ if (wrongLengthPatpWords.length > 0) {
168+ this.pushPatpProblem(multipleWordsLengthMessage(wrongLengthPatpWords));
169+ }
170+ if (wrongLengthQueryWords.length > 0) {
171+ this.pushQueryProblem(multipleWordsLengthMessage(wrongLengthQueryWords));
172+ }
173+ if (emptyWords) {
174+ this.pushProblem(emptyWordsMessage);
175+ }
176+ } else {
177+ let word = words[0];
178+ if (word.length !== 3 && word.length !== 6) {
179+ this.pushPatpProblem(singleWordLengthMessage(word));
180+ if (word.length > 6) {
181+ this.pushQueryProblem(singleWordTooLongMessage(word));
182+ }
183+ }
184+ }
185+
186+ this.checkpointValidation();
187+
188+ // Validate remaining structural edge cases
189+ if (words.length >= 5) {
190+ let cometIdRegex = /^([\w*]+-)+-([\w*]+-){3}[\w*]+$/;
191+ if (!cometIdRegex.test(patp)) {
192+ this.pushPatpProblem(invalidCometIdMessage);
193+ }
194+ } else if (words.length > 2) {
195+ let manyPartIdRegex = /^([\w*]+-)+[\w*]+$/;
196+ if (!manyPartIdRegex.test(patp)) {
197+ this.pushPatpProblem(tooLongAbbreviationMessage);
198+ }
199+ } else if (words.length == 2) {
200+ let twoPartIdRegex = /^[\w*]+[-^_][\w*]+$/;
201+ if (!twoPartIdRegex.test(patp)) {
202+ this.pushPatpProblem(tooLongAbbreviationMessage);
203+ }
204+ }
205+
206+ this.checkpointValidation();
207+
208+ // Validate valid prefixes and suffixes
209+ let wrongPatpPrefixes = [];
210+ let wrongPatpSuffixes = [];
211+ let wrongQueryPrefixes = [];
212+ let wrongQuerySuffixes = [];
213+ let wrongQueryFragments = [];
214+ queryWords.forEach((word, i) => {
215+ if (!word) return;
216+ let patpPrefix = word.substring(0, 3);
217+ let patpSuffix = word.substring(3, 6);
218+
219+ // if galaxy
220+ if (!patpSuffix) {
221+ patpSuffix = patpPrefix;
222+ patpPrefix = null;
223+ }
224+ const patpPrefixInvalid = patpPrefix && !prefixSearchValid(patpPrefix);
225+ if (patpPrefixInvalid) {
226+ wrongPatpPrefixes.push(patpPrefix);
227+ }
228+ const patpSuffixInvalid = patpSuffix && !suffixSearchValid(patpSuffix);
229+ if (patpSuffixInvalid) {
230+ wrongPatpSuffixes.push(patpSuffix);
231+ }
232+ const hasLeftNeighboringWord = i > 0 || this.searchIsSigged;
233+ const hasRightNeighboringWord = i < queryWords.length -1;
234+ let minLeftSplit = hasLeftNeighboringWord ? Math.min(3, word.length) : 0;
235+ // if maybe galaxy
236+ if (queryWords.length === 1 && word.length <= 3) minLeftSplit = 0;
237+ const maxRightSplit = hasRightNeighboringWord ? Math.max(word.length - 3, 0) : word.length;
238+
239+ // Please forgive this black magic
240+ const diff = 7 - word.length;
241+ const splitIndexes = [];
242+ for (let j = 0; j < diff; j++) {
243+ let splitIndex = 3 - j;
244+ if (splitIndex >= minLeftSplit && splitIndex <= maxRightSplit) {
245+ splitIndexes.push(splitIndex);
246+ }
247+ }
248+ let wordInvalid = true;
249+ let queryPrefix;
250+ let queryPrefixInvalid;
251+ let querySuffix;
252+ let querySuffixInvalid;
253+ for (let splitIndex of splitIndexes) {
254+ queryPrefix = word.substring(0, splitIndex);
255+ querySuffix = word.substring(splitIndex, word.length);
256+
257+ queryPrefixInvalid = queryPrefix && !prefixSearchValid(queryPrefix, { checkEnd: word.length >= 3 });
258+ querySuffixInvalid = querySuffix && !suffixSearchValid(querySuffix, { checkStart: word.length >= 3 });
259+ let wordValid = (!queryPrefixInvalid && !querySuffixInvalid);
260+ if (wordValid) {
261+ wordInvalid = false;
262+ }
263+ }
264+ if(wordInvalid && !wrongQueryFragments.includes(word)) {
265+ if (splitIndexes.length == 1) {
266+ if (queryPrefixInvalid) wrongQueryPrefixes.push(queryPrefix);
267+ if (querySuffixInvalid) wrongQuerySuffixes.push(querySuffix);
268+ } else {
269+ wrongQueryFragments.push(word);
270+ }
271+ }
272+ });
273+ if (wrongPatpPrefixes.length > 0) {
274+ this.pushPatpProblem(invalidPrefixesMessage(wrongPatpPrefixes));
275+ }
276+ if (wrongPatpSuffixes.length > 0) {
277+ this.pushPatpProblem(invalidSuffixesMessage(wrongPatpSuffixes));
278+ }
279+ if (wrongQueryPrefixes.length > 0) {
280+ this.pushQueryProblem(invalidPrefixesMessage(wrongQueryPrefixes));
281+ }
282+ if (wrongQuerySuffixes.length > 0) {
283+ this.pushQueryProblem(invalidSuffixesMessage(wrongQuerySuffixes));
284+ }
285+ if (wrongQueryFragments.length > 0) {
286+ this.pushQueryProblem(invalidFragmentsMessage(wrongQueryFragments));
287+ }
288+ }
289+}
290+
291+
1292const prefixes = ['doz', 'mar', 'bin', 'wan', 'sam', 'lit', 'sig', 'hid', 'fid', 'lis', 'sog', 'dir', 'wac', 'sab', 'wis', 'sib', 'rig', 'sol', 'dop', 'mod', 'fog', 'lid', 'hop', 'dar', 'dor', 'lor', 'hod', 'fol', 'rin', 'tog', 'sil', 'mir', 'hol', 'pas', 'lac', 'rov', 'liv', 'dal', 'sat', 'lib', 'tab', 'han', 'tic', 'pid', 'tor', 'bol', 'fos', 'dot', 'los', 'dil', 'for', 'pil', 'ram', 'tir', 'win', 'tad', 'bic', 'dif', 'roc', 'wid', 'bis', 'das', 'mid', 'lop', 'ril', 'nar', 'dap', 'mol', 'san', 'loc', 'nov', 'sit', 'nid', 'tip', 'sic', 'rop', 'wit', 'nat', 'pan', 'min', 'rit', 'pod', 'mot', 'tam', 'tol', 'sav', 'pos', 'nap', 'nop', 'som', 'fin', 'fon', 'ban', 'mor', 'wor', 'sip', 'ron', 'nor', 'bot', 'wic', 'soc', 'wat', 'dol', 'mag', 'pic', 'dav', 'bid', 'bal', 'tim', 'tas', 'mal', 'lig', 'siv', 'tag', 'pad', 'sal', 'div', 'dac', 'tan', 'sid', 'fab', 'tar', 'mon', 'ran', 'nis', 'wol', 'mis', 'pal', 'las', 'dis', 'map', 'rab', 'tob', 'rol', 'lat', 'lon', 'nod', 'nav', 'fig', 'nom', 'nib', 'pag', 'sop', 'ral', 'bil', 'had', 'doc', 'rid', 'moc', 'pac', 'rav', 'rip', 'fal', 'tod', 'til', 'tin', 'hap', 'mic', 'fan', 'pat', 'tac', 'lab', 'mog', 'sim', 'son', 'pin', 'lom', 'ric', 'tap', 'fir', 'has', 'bos', 'bat', 'poc', 'hac', 'tid', 'hav', 'sap', 'lin', 'dib', 'hos', 'dab', 'bit', 'bar', 'rac', 'par', 'lod', 'dos', 'bor', 'toc', 'hil', 'mac', 'tom', 'dig', 'fil', 'fas', 'mit', 'hob', 'har', 'mig', 'hin', 'rad', 'mas', 'hal', 'rag', 'lag', 'fad', 'top', 'mop', 'hab', 'nil', 'nos', 'mil', 'fop', 'fam', 'dat', 'nol', 'din', 'hat', 'nac', 'ris', 'fot', 'rib', 'hoc', 'nim', 'lar', 'fit', 'wal', 'rap', 'sar', 'nal', 'mos', 'lan', 'don', 'dan', 'lad', 'dov', 'riv', 'bac', 'pol', 'lap', 'tal', 'pit', 'nam', 'bon', 'ros', 'ton', 'fod', 'pon', 'sov', 'noc', 'sor', 'lav', 'mat', 'mip', 'fip'];
2293const suffixes = ['zod', 'nec', 'bud', 'wes', 'sev', 'per', 'sut', 'let', 'ful', 'pen', 'syt', 'dur', 'wep', 'ser', 'wyl', 'sun', 'ryp', 'syx', 'dyr', 'nup', 'heb', 'peg', 'lup', 'dep', 'dys', 'put', 'lug', 'hec', 'ryt', 'tyv', 'syd', 'nex', 'lun', 'mep', 'lut', 'sep', 'pes', 'del', 'sul', 'ped', 'tem', 'led', 'tul', 'met', 'wen', 'byn', 'hex', 'feb', 'pyl', 'dul', 'het', 'mev', 'rut', 'tyl', 'wyd', 'tep', 'bes', 'dex', 'sef', 'wyc', 'bur', 'der', 'nep', 'pur', 'rys', 'reb', 'den', 'nut', 'sub', 'pet', 'rul', 'syn', 'reg', 'tyd', 'sup', 'sem', 'wyn', 'rec', 'meg', 'net', 'sec', 'mul', 'nym', 'tev', 'web', 'sum', 'mut', 'nyx', 'rex', 'teb', 'fus', 'hep', 'ben', 'mus', 'wyx', 'sym', 'sel', 'ruc', 'dec', 'wex', 'syr', 'wet', 'dyl', 'myn', 'mes', 'det', 'bet', 'bel', 'tux', 'tug', 'myr', 'pel', 'syp', 'ter', 'meb', 'set', 'dut', 'deg', 'tex', 'sur', 'fel', 'tud', 'nux', 'rux', 'ren', 'wyt', 'nub', 'med', 'lyt', 'dus', 'neb', 'rum', 'tyn', 'seg', 'lyx', 'pun', 'res', 'red', 'fun', 'rev', 'ref', 'mec', 'ted', 'rus', 'bex', 'leb', 'dux', 'ryn', 'num', 'pyx', 'ryg', 'ryx', 'fep', 'tyr', 'tus', 'tyc', 'leg', 'nem', 'fer', 'mer', 'ten', 'lus', 'nus', 'syl', 'tec', 'mex', 'pub', 'rym', 'tuc', 'fyl', 'lep', 'deb', 'ber', 'mug', 'hut', 'tun', 'byl', 'sud', 'pem', 'dev', 'lur', 'def', 'bus', 'bep', 'run', 'mel', 'pex', 'dyt', 'byt', 'typ', 'lev', 'myl', 'wed', 'duc', 'fur', 'fex', 'nul', 'luc', 'len', 'ner', 'lex', 'rup', 'ned', 'lec', 'ryd', 'lyd', 'fen', 'wel', 'nyd', 'hus', 'rel', 'rud', 'nes', 'hes', 'fet', 'des', 'ret', 'dun', 'ler', 'nyr', 'seb', 'hul', 'ryl', 'lud', 'rem', 'lys', 'fyn', 'wer', 'ryc', 'sug', 'nys', 'nyl', 'lyn', 'dyn', 'dem', 'lux', 'fed', 'sed', 'bec', 'mun', 'lyr', 'tes', 'mud', 'nyt', 'byr', 'sen', 'weg', 'fyr', 'mur', 'tel', 'rep', 'teg', 'pec', 'nel', 'nev', 'fes'];
3−const illegalIdCharsRegex = /[^-_^~abcdefghilmnoprstuvwxyz]/g;
4−const illegalSearchCharsRegex = /[^-_*^~abcdefghilmnoprstuvwxyz]/g;
294+const illegalPatpCharsRegex = /[^-_^~abcdefghilmnoprstuvwxyz]/g;
295+const illegalQueryCharsRegex = /[^-_*^~abcdefghilmnoprstuvwxyz]/g;
5296
6297const wildWord = '******';
7298const moonFiller = `${wildWord}-${wildWord}`;
@@ -10,6 +301,8 @@ const cometFiller = `-${wildWord}-${wildWord}-${wildWord}--${wildWord}-${wildWor
10301// Error Messages
11302const emptyPatpMessage = "ID cannot be empty.";
12303const improperSigMessage = "There should only be one ~ in an ID, at the beginning.";
304+const cannotInferMoonPatpMessage = "Please enter the full moon ID, rather than using the '^' abbreviation.";
305+const cannotInferCometPatpMessage = "Please enter the full comet ID, rather than using the '_' abbreviation.";
13306const emptyWordsMessage = "Separators (- ^ _) cannot appear next to each other, except for a '--' before the last four parts of a comet ID.";
14307const separatorsNeighboringMessage = "Separators (- ^ _) cannot appear next to each other, except for a '--' before the last four parts of a comet ID.";
15308const outsideSeparatorsMessage = "Separators (- ^ _) cannot appear at the beginning or end of an ID.";
@@ -35,6 +328,10 @@ function singleWordLengthMessage(word: string) {
35328 return `'${word}' must be either 3 or 6 letters long (either a galaxy or a star).`;
36329}
37330
331+function singleWordTooLongMessage(word: string) {
332+ return `'${word}' will not match any IDs. It should not be longer than 6 letters.`;
333+}
334+
38335function invalidSyllablesMessage(syllables: string[], syllableSet: string) {
39336 let wordListString = listStrings(syllables, true);
40337 return `${wordListString} did not match any ${syllableSet}.`;
@@ -48,6 +345,11 @@ function invalidSuffixesMessage(syllables: string[]) {
48345 return invalidSyllablesMessage(syllables, 'suffixes');
49346}
50347
348+function invalidFragmentsMessage(fragments: string[]) {
349+ let wordListString = listStrings(fragments, true);
350+ return `${wordListString} cannot match any prefix/suffix combinations.`;
351+}
352+
51353function sig(str: string): string {
52354 if (str[0] !== '~') {
53355 return '~' + str;
@@ -62,37 +364,56 @@ function desig(str: string): string {
62364 return str;
63365}
64366
65−function normalizeId(patp: string): string {
66− patp = patp.toLowerCase();
67− patp = sig(patp);
68− return patp;
367+function sanitizeSearch(search: string): string {
368+ return search.toLowerCase().trim();
69369}
70370
71−function normalizeIdAndDesig(patp: string): string {
72− patp = patp.toLowerCase();
73− patp = desig(patp);
74− return patp;
371+function normalizeId(patp: string): string {
372+ return sig(sanitizeSearch(patp));
75373}
76374
77−function listStrings(list: string[], includeAnd: boolean = false): string {
78− if (includeAnd && list.length > 1) {
79− return listStrings(list.slice(0, -1)) + ' and ' + listStrings(list.slice(-1));
80− }
81− return "'" + list.join("', '") + "'";
375+function normalizeIdAndDesig(patp: string): string {
376+ return desig(sanitizeSearch(patp));
82377}
83378
84379function splitIdIntoWords(str: string): string[] {
85380 return str.split(/(?:--|[-_^])/);
86381}
87382
88−function convertSearchTextToRegex(str: string): RegExp {
89− str = '^' + str.replace(/\*/g, '.') + '$';
383+function padWithWild(search: string) {
384+ let words = search.split(/[_^]/);
385+ words[0] = '*'.repeat(6 - words[0].length) + words[0];
386+ words[1] += '*'.repeat(6 - words[1].length);
387+ return words.join(search.match(/([_^])/)[1]);
388+}
389+
390+function convertSearchTextToRegex(str: string, { checkStart = false, checkEnd = false } = {}): RegExp {
391+ str = str.replace(/\*/g, '.');
392+ if (checkStart) {
393+ str = '^' + str;
394+ }
395+ if (checkEnd) {
396+ str = str + '$';
397+ }
90398 return new RegExp(str);
91399}
92400
93−function syllableSearchValid(search: string, syllables: string[]) {
401+function expandAbbreviatedId(patp: string): string {
402+ patp = normalizeIdAndDesig(patp);
403+ if (/\^/.test(patp)) {
404+ patp = padWithWild(patp);
405+ patp = '~' + moonFiller + '-' + patp.replace(/\^/, '-') + '$';
406+ } else if (/_/.test(patp)) {
407+ patp = padWithWild(patp);
408+ patp = '~' + patp.replace('_', cometFiller);
409+ }
410+ return patp;
411+}
412+
413+
414+function syllableSearchValid(search: string, syllables: string[], options = {}) {
94415 for (let syllable of syllables) {
95− let regex = convertSearchTextToRegex(search);
416+ let regex = convertSearchTextToRegex(search, options);
96417 if (regex.test(syllable)) {
97418 return true;
98419 }
@@ -100,149 +421,24 @@ function syllableSearchValid(search: string, syllables: string[]) {
100421 return false;
101422}
102423
103−function prefixSearchValid(prefixSearch: string) {
104− return syllableSearchValid(prefixSearch, prefixes);
424+function prefixSearchValid(prefixSearch: string, options = {}) {
425+ return syllableSearchValid(prefixSearch, prefixes, options);
105426}
106427
107−function suffixSearchValid(search: string) {
108− return syllableSearchValid(search, suffixes);
428+function suffixSearchValid(search: string, options = {}) {
429+ return syllableSearchValid(search, suffixes, options);
109430}
110431
111−function validateIdSearchText(patp: string): string[] {
112− return validateId(patp, true);
113−}
114−
115−
116−function validateId(patp: string, isSearchQuery: boolean = false): string[] {
117− let problems = [];
118− patp = normalizeIdAndDesig(patp);
119−
120− // Validate patp is not empty
121− if (patp.length === 0) {
122− problems.push(emptyPatpMessage);
123− return problems;
124− }
125−
126− // Validate proper use of sig
127− if (/~/.test(patp)) {
128− problems.push(improperSigMessage);
129− }
130−
131− // Validate all characters are legal
132− const illegalCharsRegex = isSearchQuery ? illegalSearchCharsRegex : illegalIdCharsRegex;
133− let illegalChars = patp.match(illegalCharsRegex);
134− if (illegalChars) {
135− problems.push(illegalCharsMessage(illegalChars));
136− }
137−
138− // Validate proper use of separators
139− if (/^([-^_].*)?(.*[-^_])?$/.test(patp)) {
140− problems.push(outsideSeparatorsMessage);
141− patp = patp.match(/^[-^_]*(.*?)[-^_]*$/)[1];
142− }
143−
144− // Validate word lengths
145− let words = splitIdIntoWords(patp);
146− if (words.length > 1) {
147− let wrongLengthWords = [];
148− let emptyWords = false;
149− for (let word of words) {
150− if (word.length !== 6) {
151− if (word.length > 0) {
152− wrongLengthWords.push(word);
153− } else {
154− emptyWords = true;
155− }
156− }
157− }
158− if (wrongLengthWords.length > 0) {
159− problems.push(multipleWordsLengthMessage(wrongLengthWords));
160− }
161− if (emptyWords) {
162− problems.push(emptyWordsMessage);
163− }
164− } else {
165− let word = words[0];
166− if (word.length !== 3 && word.length !== 6) {
167− problems.push(singleWordLengthMessage(word));
168− }
169− }
170−
171− // Don't bother checking syllables if there are already problems
172− if (problems.length > 0) {
173− return problems;
174− }
175−
176− // Validate remaining structural edge cases
177− if (words.length >= 5) {
178− let cometIdRegex = /^([\w*]+-)+-([\w*]+-){3}[\w*]+$/;
179− if (!cometIdRegex.test(patp)) {
180− problems.push(invalidCometIdMessage);
181− }
182− } else if (words.length > 2) {
183− let manyPartIdRegex = /^([\w*]+-)+[\w*]+$/;
184− if (!manyPartIdRegex.test(patp)) {
185− problems.push(tooLongAbbreviationMessage);
186− }
187− } else if (words.length == 2) {
188− let twoPartIdRegex = /^[\w*]+[-^_][\w*]+$/;
189− if (!twoPartIdRegex.test(patp)) {
190− problems.push(tooLongAbbreviationMessage);
191− }
192− }
193−
194− // Don't bother checking syllables if there are already problems
195− if (problems.length > 0) {
196− return problems;
197− }
198−
199− // Validate valid prefixes and suffixes
200− let wrongPrefixes = [];
201− let wrongSuffixes = [];
202− for (let word of words) {
203− let prefix = word.substring(0, 3);
204− let suffix = word.substring(3, 6);
205− // if galaxy
206− if (!suffix) {
207− suffix = prefix;
208− prefix = null;
209− }
210− if (prefix && !prefixSearchValid(prefix)) {
211− wrongPrefixes.push(prefix);
212− }
213− if (suffix && !suffixSearchValid(suffix)) {
214− wrongSuffixes.push(suffix);
215− }
216− }
217− if (wrongPrefixes.length > 0) {
218− problems.push(invalidPrefixesMessage(wrongPrefixes));
219− }
220− if (wrongSuffixes.length > 0) {
221− problems.push(invalidSuffixesMessage(wrongSuffixes));
222− }
223432
224− return problems;
225−}
226−
227−function expandAbbreviatedId(patp: string): string {
228− patp = normalizeIdAndDesig(patp);
229− if (/\^/.test(patp)) {
230− patp = moonFiller + '-' + patp.replace(/\^/, '-');
231− } else if (/_/.test(patp)) {
232− patp = patp.replace('_', cometFiller);
233− }
234− return patp;
235−}
236433
237−function convertIdSearchTextToQuery(patp: string): RegExp {
238− return convertSearchTextToRegex(sig(expandAbbreviatedId(patp)));
434+function analyzeSearch(search: string): SearchAnalysis {
435+ return new SearchAnalysis(search);
239436}
240437
241438export {
439+ SearchAnalysis,
242440 normalizeId,
243441 normalizeIdAndDesig,
244− validateIdSearchText,
245− validateId,
246− convertIdSearchTextToQuery,
247442 convertSearchTextToRegex,
443+ analyzeSearch,
248444};