xref: /Universal-ctags/parsers/rspec.c (revision e8daccd09e1c611624d995b73e529eca85ebef7f)
1 /*
2 *
3 * Copyright (c) 2017, Red Hat, Inc.
4 * Copyright (c) 2017, Masatake YAMATO
5 *
6 * Author: Masatake YAMATO <yamato@redhat.com>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
21 * USA.
22 *
23 * Inspired by the following commit but written from scratch:
24 * ==========================================================
25 *
26 * commit 76dbed4de88875d8c8409dfd65da4f94f901c94a
27 * Author: Ram Singla <ram.singla@gmail.com>
28 * Date:   Tue Jan 18 13:24:46 2011 +0800
29 *
30 *    RSpec Code added. Courtesy: mortice
31 *
32 * ==========================================================
33 *
34 * Reference:
35 * - https://rubydoc.info/gems/rspec-core/frames
36 */
37 
38 /*
39 *   INCLUDE FILES
40 */
41 #include "general.h"  /* must always come first */
42 
43 #include "entry.h"
44 #include "kind.h"
45 #include "numarray.h"
46 #include "parse.h"
47 #include "subparser.h"
48 
49 #include "ruby.h"
50 
51 #include <string.h>
52 
53 /*
54 * DATA STRUCTURES
55 */
56 struct sRSpecSubparser {
57 	rubySubparser ruby;
58 	int scope;
59 };
60 
61 typedef enum {
62 	K_DESCRIBE,
63 	K_CONTEXT,
64 	K_IT,
65 } rspecKind;
66 
67 static kindDefinition RSpecKinds [] = {
68 	{ true, 'd', "describe", "describes" },
69 	{ true, 'c', "context", "contexts" },
70 	{ true, 'i', "it", "things described with \"it\"" },
71 };
72 
73 /*
74 * FUNCTIONS
75 */
makeSimpleRSpecTag(vString * vstr,int kindIndex,rubySubparser * subparser)76 static int makeSimpleRSpecTag (vString *vstr, int kindIndex, rubySubparser *subparser)
77 {
78 	int r = makeSimpleTag (vstr, kindIndex);
79 	tagEntryInfo *e = getEntryInCorkQueue (r);
80 	if (e)
81 	{
82 		struct sRSpecSubparser *rspec = (struct sRSpecSubparser *)subparser;
83 		e->extensionFields.scopeIndex = rspec->scope;
84 	}
85 	return r;
86 }
87 
readRest(const unsigned char ** cp)88 static vString *readRest (const unsigned char **cp)
89 {
90 	vString *vstr = NULL;
91 	unsigned char b;
92 
93 	switch (**cp)
94 	{
95 	case '\'':
96 	case '"':
97 		b = **cp;
98 		++*cp;
99 		vstr = vStringNew ();
100 		if (!rubyParseString (cp, b, vstr))
101 		{
102 			vStringDelete (vstr);
103 			vstr = NULL;
104 		}
105 		break;
106 	case ':':
107 		/* symbol, Should this be part of the Ruby parser side? */
108 		break;
109 	default:
110 		vstr = vStringNew ();
111 		if (!rubyParseModuleName (cp, vstr))
112 		{
113 			vStringDelete (vstr);
114 			vstr = NULL;
115 		}
116 		else if (strcmp (vStringValue (vstr), "do") == 0)
117 		{
118 			vStringDelete (vstr);
119 			vstr = NULL;
120 			*cp += -2;			/* push back "do" */
121 		}
122 
123 		break;
124 	}
125 
126 	if (vstr)
127 	{
128 		rubySkipWhitespace (cp);
129 		if (**cp == ',')
130 		{
131 			++*cp;
132 			rubySkipWhitespace (cp);
133 			vString *vstr_rest = readRest (cp);
134 			if (vstr_rest)
135 			{
136 				vStringPut (vstr, ' ');
137 				vStringCat (vstr, vstr_rest);
138 				vStringDelete (vstr_rest);
139 			}
140 		}
141 	}
142 
143 	return vstr;
144 }
145 
146 struct caseType {
147 	const char *keyword;
148 	rspecKind    kind;
149 };
150 
lineNotify(rubySubparser * s,const unsigned char ** cp)151 static int lineNotify (rubySubparser *s, const unsigned char **cp)
152 {
153 	struct caseType caseTypes [] = {
154 		{ "describe", K_DESCRIBE },
155 		{ "RSpec.describe", K_DESCRIBE },
156 		{ "context", K_CONTEXT },
157 		{ "it", K_IT },
158 	};
159 
160 	for (int i = 0; i < ARRAY_SIZE(caseTypes); i++)
161 	{
162 		if (rubyCanMatchKeywordWithAssign(cp, caseTypes[i].keyword))
163 		{
164 			vString *vstr = NULL;
165 			rubySkipWhitespace (cp);
166 			vstr = readRest (cp);
167 			if (vstr)
168 			{
169 				int r = makeSimpleRSpecTag (vstr, caseTypes[i].kind, s);
170 				vStringDelete (vstr);
171 				return r;
172 			}
173 		}
174 	}
175 
176 	return CORK_NIL;
177 }
178 
enterBlockNotify(rubySubparser * s,int corkIndex)179 static void enterBlockNotify (rubySubparser *s, int corkIndex)
180 {
181 	struct sRSpecSubparser *rspec = (struct sRSpecSubparser *)s;
182 	rspec->scope = corkIndex;
183 }
184 
leaveBlockNotify(rubySubparser * s,int corkIndex)185 static void leaveBlockNotify (rubySubparser *s, int corkIndex)
186 {
187 	tagEntryInfo *e = getEntryInCorkQueue (corkIndex);
188 	if (e)
189 	{
190 		struct sRSpecSubparser *rspec = (struct sRSpecSubparser *)s;
191 		rspec->scope = e->extensionFields.scopeIndex;
192 	}
193 }
194 
findRSpecTags(void)195 static void findRSpecTags (void)
196 {
197 	scheduleRunningBaseparser (RUN_DEFAULT_SUBPARSERS);
198 }
199 
inputStart(subparser * s)200 static void inputStart (subparser *s)
201 {
202 	struct sRSpecSubparser *rspec = (struct sRSpecSubparser *)s;
203 	rspec->scope = CORK_NIL;
204 }
205 
RSpecParser(void)206 extern parserDefinition* RSpecParser (void)
207 {
208 	static struct sRSpecSubparser rspecSubparser = {
209 		.ruby = {
210 			.subparser = {
211 				.direction = SUBPARSER_BASE_RUNS_SUB,
212 				.inputStart = inputStart,
213 			},
214 			.lineNotify = lineNotify,
215 			.enterBlockNotify = enterBlockNotify,
216 			.leaveBlockNotify = leaveBlockNotify,
217 		},
218 		.scope = CORK_NIL,
219 	};
220 
221 	static parserDependency dependencies [] = {
222 		[0] = { DEPTYPE_SUBPARSER, "Ruby", &rspecSubparser },
223 	};
224 
225 	parserDefinition* const def = parserNew ("RSpec");
226 
227 	def->dependencies = dependencies;
228 	def->dependencyCount = ARRAY_SIZE(dependencies);
229 	def->kindTable  = RSpecKinds;
230 	def->kindCount  = ARRAY_SIZE (RSpecKinds);
231 	def->parser     = findRSpecTags;
232 	def->useCork    = CORK_QUEUE;
233 
234 	return def;
235 }
236