1 /*
2  * RoxygenHelper.java
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 package org.rstudio.studio.client.common.r.roxygen;
16 
17 import com.google.gwt.core.client.JavaScriptObject;
18 import com.google.gwt.core.client.JsArray;
19 import com.google.gwt.core.client.JsArrayInteger;
20 import com.google.gwt.core.client.JsArrayString;
21 import com.google.inject.Inject;
22 
23 import org.rstudio.core.client.Debug;
24 import org.rstudio.core.client.StringUtil;
25 import org.rstudio.core.client.regex.Match;
26 import org.rstudio.core.client.regex.Pattern;
27 import org.rstudio.studio.client.RStudioGinjector;
28 import org.rstudio.studio.client.common.GlobalDisplay;
29 import org.rstudio.studio.client.common.filetypes.DocumentMode;
30 import org.rstudio.studio.client.server.ServerError;
31 import org.rstudio.studio.client.server.ServerRequestCallback;
32 import org.rstudio.studio.client.workbench.views.source.editors.text.AceEditor;
33 import org.rstudio.studio.client.workbench.views.source.editors.text.DocDisplay;
34 import org.rstudio.studio.client.workbench.views.source.editors.text.Scope;
35 import org.rstudio.studio.client.workbench.views.source.editors.text.WarningBarDisplay;
36 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.AceEditorNative;
37 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
38 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Range;
39 import org.rstudio.studio.client.workbench.views.source.editors.text.ace.TokenCursor;
40 
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 
44 public class RoxygenHelper
45 {
RoxygenHelper(DocDisplay docDisplay, WarningBarDisplay view)46    public RoxygenHelper(DocDisplay docDisplay,
47                         WarningBarDisplay view)
48    {
49       editor_ = (AceEditor) docDisplay;
50       view_ = view;
51       RStudioGinjector.INSTANCE.injectMembers(this);
52    }
53 
54    @Inject
initialize(GlobalDisplay globalDisplay, RoxygenServerOperations server)55    void initialize(GlobalDisplay globalDisplay,
56                    RoxygenServerOperations server)
57    {
58       server_ = server;
59       globalDisplay_ = globalDisplay;
60    }
61 
getFunctionName(Scope scope)62    private static native final String getFunctionName(Scope scope)
63    /*-{
64       return scope.attributes.name;
65    }-*/;
66 
getFunctionArgs(Scope scope)67    private static native final JsArrayString getFunctionArgs(Scope scope)
68    /*-{
69       return scope.attributes.args;
70    }-*/;
71 
insertRoxygenSkeleton()72    public void insertRoxygenSkeleton()
73    {
74       if (!DocumentMode.isCursorInRMode(editor_))
75          return;
76 
77       // We check these first because we might lie within an
78       // anonymous function scope, whereas what we first want
79       // to check is for an enclosing `setGeneric` etc.
80       TokenCursor cursor = getTokenCursor();
81       if (cursor.moveToPosition(editor_.getCursorPosition(), true))
82       {
83          String enclosingScope = findEnclosingScope(cursor);
84 
85          if (enclosingScope == "setClass")
86             insertRoxygenSkeletonS4Class(cursor);
87          else if (enclosingScope == "setGeneric")
88             insertRoxygenSkeletonSetGeneric(cursor);
89          else if (enclosingScope == "setMethod")
90             insertRoxygenSkeletonSetMethod(cursor);
91          else if (enclosingScope == "setRefClass")
92             insertRoxygenSkeletonSetRefClass(cursor);
93 
94          if (enclosingScope != null)
95             return;
96       }
97 
98       // If the above checks failed, we'll want to insert a
99       // roxygen skeleton for a 'regular' function call.
100       Scope scope = editor_.getCurrentScope();
101       if (scope != null && scope.isFunction())
102       {
103          insertRoxygenSkeletonFunction(scope);
104       }
105       else
106       {
107          globalDisplay_.showErrorMessage(
108              "Insert Roxygen Skeleton",
109              "Unable to insert skeleton (the cursor is not currently " +
110              "inside an R function definition).");
111       }
112    }
113 
getTokenCursor()114    private TokenCursor getTokenCursor()
115    {
116       return editor_.getSession().getMode().getCodeModel().getTokenCursor();
117    }
118 
extractCall(TokenCursor cursor)119    private String extractCall(TokenCursor cursor)
120    {
121       // Force document tokenization
122       editor_.getSession().getMode().getCodeModel().tokenizeUpToRow(
123             editor_.getSession().getDocument().getLength());
124 
125       TokenCursor clone = cursor.cloneCursor();
126       final Position startPos = clone.currentPosition();
127 
128       if (!clone.moveToNextToken())
129          return null;
130 
131       if (clone.currentValue() != "(")
132          return null;
133 
134       if (!clone.fwdToMatchingToken())
135          return null;
136 
137       Position endPos = clone.currentPosition();
138       endPos.setColumn(endPos.getColumn() + 1);
139 
140       return editor_.getSession().getTextRange(
141             Range.fromPoints(startPos, endPos));
142    }
143 
insertRoxygenSkeletonSetRefClass(TokenCursor cursor)144    private void insertRoxygenSkeletonSetRefClass(TokenCursor cursor)
145    {
146       final Position startPos = cursor.currentPosition();
147       String call = extractCall(cursor);
148       if (call == null)
149          return;
150 
151       server_.getSetRefClassCall(
152             call,
153             new ServerRequestCallback<SetRefClassCall>()
154             {
155                @Override
156                public void onResponseReceived(SetRefClassCall response)
157                {
158                   if (hasRoxygenBlock(startPos))
159                   {
160                      amendExistingRoxygenBlock(
161                            startPos.getRow() - 1,
162                            response.getClassName(),
163                            response.getFieldNames(),
164                            response.getFieldTypes(),
165                            "field",
166                            RE_ROXYGEN_FIELD);
167                   }
168                   else
169                   {
170                      insertRoxygenTemplate(
171                            response.getClassName(),
172                            response.getFieldNames(),
173                            response.getFieldTypes(),
174                            "field",
175                            "reference class",
176                            startPos);
177                   }
178                }
179 
180                @Override
181                public void onError(ServerError error)
182                {
183                   Debug.logError(error);
184                }
185 
186             });
187    }
188 
insertRoxygenSkeletonSetGeneric(TokenCursor cursor)189    private void insertRoxygenSkeletonSetGeneric(TokenCursor cursor)
190    {
191       final Position startPos = cursor.currentPosition();
192       String call = extractCall(cursor);
193       if (call == null)
194          return;
195 
196       server_.getSetGenericCall(
197             call,
198             new ServerRequestCallback<SetGenericCall>()
199             {
200                @Override
201                public void onResponseReceived(SetGenericCall response)
202                {
203                   if (hasRoxygenBlock(startPos))
204                   {
205                      amendExistingRoxygenBlock(
206                            startPos.getRow() - 1,
207                            response.getGeneric(),
208                            response.getParameters(),
209                            null,
210                            "param",
211                            RE_ROXYGEN_PARAM);
212                   }
213                   else
214                   {
215                      insertRoxygenTemplate(
216                            response.getGeneric(),
217                            response.getParameters(),
218                            null,
219                            "param",
220                            "generic function",
221                            startPos);
222                   }
223                }
224 
225                @Override
226                public void onError(ServerError error)
227                {
228                   Debug.logError(error);
229                }
230 
231             });
232    }
233 
insertRoxygenSkeletonSetMethod(TokenCursor cursor)234    private void insertRoxygenSkeletonSetMethod(TokenCursor cursor)
235    {
236       final Position startPos = cursor.currentPosition();
237       String call = extractCall(cursor);
238       if (call == null)
239          return;
240 
241       server_.getSetMethodCall(
242             call,
243             new ServerRequestCallback<SetMethodCall>()
244             {
245                @Override
246                public void onResponseReceived(SetMethodCall response)
247                {
248                   if (hasRoxygenBlock(startPos))
249                   {
250                      amendExistingRoxygenBlock(
251                            startPos.getRow() - 1,
252                            response.getGeneric(),
253                            response.getParameterNames(),
254                            response.getParameterTypes(),
255                            "param",
256                            RE_ROXYGEN_PARAM);
257                   }
258                   else
259                   {
260                      insertRoxygenTemplate(
261                            response.getGeneric(),
262                            response.getParameterNames(),
263                            response.getParameterTypes(),
264                            "param",
265                            "method",
266                            startPos);
267                   }
268                }
269 
270                @Override
271                public void onError(ServerError error)
272                {
273                   Debug.logError(error);
274                }
275 
276             });
277    }
278 
279 
insertRoxygenSkeletonS4Class(TokenCursor cursor)280    private void insertRoxygenSkeletonS4Class(TokenCursor cursor)
281    {
282       final Position startPos = cursor.currentPosition();
283       String setClassCall = extractCall(cursor);
284       if (setClassCall == null)
285          return;
286 
287       server_.getSetClassCall(
288             setClassCall,
289             new ServerRequestCallback<SetClassCall>()
290             {
291                @Override
292                public void onResponseReceived(SetClassCall response)
293                {
294                   if (hasRoxygenBlock(startPos))
295                   {
296                      amendExistingRoxygenBlock(
297                            startPos.getRow() - 1,
298                            response.getClassName(),
299                            response.getSlots(),
300                            null,
301                            "slot",
302                            RE_ROXYGEN_SLOT);
303                   }
304                   else
305                   {
306                      insertRoxygenTemplate(
307                            response.getClassName(),
308                            response.getSlots(),
309                            response.getTypes(),
310                            "slot",
311                            "S4 class",
312                            startPos);
313                   }
314                }
315 
316                @Override
317                public void onError(ServerError error)
318                {
319                   Debug.logError(error);
320                }
321             });
322    }
323 
findEnclosingScope(TokenCursor cursor)324    private String findEnclosingScope(TokenCursor cursor)
325    {
326       if (ROXYGEN_ANNOTATABLE_CALLS.contains(cursor.currentValue()))
327          return cursor.currentValue();
328 
329       // Check to see if we're on e.g. `x <- setRefClass(...)`.
330       if (cursor.isLeftAssign() &&
331           ROXYGEN_ANNOTATABLE_CALLS.contains(cursor.nextValue()))
332       {
333          cursor.moveToNextToken();
334          return cursor.currentValue();
335       }
336 
337       if (ROXYGEN_ANNOTATABLE_CALLS.contains(cursor.nextValue(2)))
338       {
339          cursor.moveToNextToken();
340          cursor.moveToNextToken();
341          return cursor.currentValue();
342       }
343 
344       while (cursor.currentValue() ==")")
345          if (!cursor.moveToPreviousToken())
346             return null;
347 
348       while (cursor.findOpeningBracket("(", false))
349       {
350          if (!cursor.moveToPreviousToken())
351             return null;
352 
353          if (ROXYGEN_ANNOTATABLE_CALLS.contains(cursor.currentValue()))
354             return cursor.currentValue();
355       }
356 
357       return null;
358    }
359 
insertRoxygenSkeletonFunction(Scope scope)360    public void insertRoxygenSkeletonFunction(Scope scope)
361    {
362       // Attempt to find the bounds for the roxygen block
363       // associated with this function, if it exists
364       if (hasRoxygenBlock(scope.getPreamble()))
365       {
366          amendExistingRoxygenBlock(
367                scope.getPreamble().getRow() - 1,
368                getFunctionName(scope),
369                getFunctionArgs(scope),
370                null,
371                "param",
372                RE_ROXYGEN_PARAM);
373       }
374       else
375       {
376          insertRoxygenTemplate(
377                getFunctionName(scope),
378                getFunctionArgs(scope),
379                null,
380                "param",
381                "function",
382                scope.getPreamble());
383       }
384    }
385 
amendExistingRoxygenBlock( int row, String objectName, JsArrayString argNames, JsArrayString argTypes, String tagName, Pattern pattern)386    private void amendExistingRoxygenBlock(
387          int row,
388          String objectName,
389          JsArrayString argNames,
390          JsArrayString argTypes,
391          String tagName,
392          Pattern pattern)
393    {
394       // Get the range encompassing this Roxygen block.
395       Range range = getRoxygenBlockRange(row);
396 
397       // Extract that block (as an array of strings)
398       JsArrayString block = extractRoxygenBlock(
399             editor_.getWidget().getEditor(),
400             range);
401 
402       // If the block contains roxygen parameters that require
403       // non-local information (e.g. @inheritParams), then
404       // bail.
405       for (int i = 0; i < block.length(); i++)
406       {
407          if (RE_ROXYGEN_NONLOCAL.test(block.get(i)))
408          {
409             view_.showWarningBar(
410                   "Cannot automatically update roxygen blocks " +
411                   "that are not self-contained.");
412             return;
413          }
414       }
415 
416       String roxygenDelim = RE_ROXYGEN.match(block.get(0), 0).getGroup(1);
417 
418       // The replacement block (we build by munging parts of
419       // the old block
420       JsArrayString replacement = JsArray.createArray().cast();
421 
422       // Scan through the block to get the names of
423       // pre-existing parameters.
424       JsArrayString params = listParametersInRoxygenBlock(
425             block,
426             pattern);
427 
428       // Figure out what parameters need to be removed, and remove them.
429       // Any parameter not mentioned in the current function's argument list
430       // should be stripped out.
431       JsArrayString paramsToRemove = setdiff(params, argNames);
432 
433       int blockLength = block.length();
434       for (int i = 0; i < blockLength; i++)
435       {
436          // If we encounter a param we don't want to extract, then
437          // move over it.
438          Match match = pattern.match(block.get(i), 0);
439          if (match != null && contains(paramsToRemove, match.getGroup(1)))
440          {
441             i++;
442             while (i < blockLength && !RE_ROXYGEN_WITH_TAG.test(block.get(i)))
443                i++;
444 
445             i--;
446             continue;
447          }
448 
449          replacement.push(block.get(i));
450       }
451 
452       // Now, add example roxygen for any parameters that are
453       // present in the function prototype, but not present
454       // within the roxygen block.
455       int insertionPosition = findParamsInsertionPosition(replacement, pattern);
456       JsArrayInteger indices = setdiffIndices(argNames, params);
457 
458       // NOTE: modifies replacement
459       insertNewTags(
460             replacement,
461             argNames,
462             argTypes,
463             indices,
464             roxygenDelim,
465             tagName,
466             insertionPosition);
467 
468       // Ensure space between final param and next tag
469       ensureSpaceBetweenFirstParamAndPreviousEntry(replacement, roxygenDelim, pattern);
470       ensureSpaceBetweenFinalParamAndNextTag(replacement, roxygenDelim, pattern);
471 
472       // Apply the replacement.
473       editor_.getSession().replace(range, replacement.join("\n") + "\n");
474    }
475 
ensureSpaceBetweenFirstParamAndPreviousEntry( JsArrayString replacement, String roxygenDelim, Pattern pattern)476    private void ensureSpaceBetweenFirstParamAndPreviousEntry(
477          JsArrayString replacement,
478          String roxygenDelim,
479          Pattern pattern)
480    {
481       int n = replacement.length();
482       for (int i = 1; i < n; i++)
483       {
484          if (pattern.test(replacement.get(i)))
485          {
486             if (!RE_ROXYGEN_EMPTY.test(replacement.get(i - 1)))
487                spliceIntoArray(replacement, roxygenDelim, i);
488             return;
489          }
490       }
491    }
492 
ensureSpaceBetweenFinalParamAndNextTag( JsArrayString replacement, String roxygenDelim, Pattern pattern)493    private void ensureSpaceBetweenFinalParamAndNextTag(
494          JsArrayString replacement,
495          String roxygenDelim,
496          Pattern pattern)
497    {
498       int n = replacement.length();
499       for (int i = n - 1; i >= 0; i--)
500       {
501          if (pattern.test(replacement.get(i)))
502          {
503             i++;
504             if (i < n && RE_ROXYGEN_WITH_TAG.test(replacement.get(i)))
505             {
506                spliceIntoArray(replacement, roxygenDelim, i);
507             }
508             return;
509          }
510       }
511    }
512 
spliceIntoArray( JsArrayString array, String string, int pos)513    private static final native void spliceIntoArray(
514          JsArrayString array,
515          String string,
516          int pos)
517    /*-{
518       array.splice(pos, 0, string);
519    }-*/;
520 
insertNewTags( JsArrayString array, JsArrayString argNames, JsArrayString argTypes, JsArrayInteger indices, String roxygenDelim, String tagName, int position)521    private static final native void insertNewTags(
522          JsArrayString array,
523          JsArrayString argNames,
524          JsArrayString argTypes,
525          JsArrayInteger indices,
526          String roxygenDelim,
527          String tagName,
528          int position)
529    /*-{
530 
531       var newRoxygenEntries = [];
532       for (var i = 0; i < indices.length; i++) {
533 
534          var idx = indices[i];
535          var arg = argNames[idx];
536          var type = argTypes == null ? null : argTypes[idx];
537 
538          var entry = roxygenDelim + " @" + tagName + " " + arg + " ";
539 
540          if (type != null)
541             entry += type = ". ";
542 
543          newRoxygenEntries.push(entry);
544       }
545 
546       Array.prototype.splice.apply(
547          array,
548          [position, 0].concat(newRoxygenEntries)
549       );
550 
551    }-*/;
552 
findParamsInsertionPosition( JsArrayString block, Pattern pattern)553    private int findParamsInsertionPosition(
554          JsArrayString block,
555          Pattern pattern)
556    {
557       // Try to find the last '@param' block, and insert after that.
558       int n = block.length();
559       for (int i = n - 1; i >= 0; i--)
560       {
561          if (pattern.test(block.get(i)))
562          {
563             i++;
564 
565             // Move up to the next tag (or end)
566             while (i < n && !RE_ROXYGEN_WITH_TAG.test(block.get(i)))
567                i++;
568 
569             return i - 1;
570          }
571       }
572 
573       // Try to find the first tag, and insert before that.
574       for (int i = 0; i < n; i++)
575          if (RE_ROXYGEN_WITH_TAG.test(block.get(i)))
576             return i;
577 
578       // Just insert at the end
579       return block.length();
580    }
581 
setdiff( JsArrayString self, JsArrayString other)582    private static JsArrayString setdiff(
583          JsArrayString self,
584          JsArrayString other)
585    {
586       JsArrayString result = JsArray.createArray().cast();
587       for (int i = 0; i < self.length(); i++)
588          if (!contains(other, self.get(i)))
589             result.push(self.get(i));
590       return result;
591    }
592 
setdiffIndices( JsArrayString self, JsArrayString other)593    private static JsArrayInteger setdiffIndices(
594          JsArrayString self,
595          JsArrayString other)
596    {
597       JsArrayInteger result = JsArray.createArray().cast();
598       for (int i = 0; i < self.length(); i++)
599          if (!contains(other, self.get(i)))
600             result.push(i);
601       return result;
602    }
603 
contains( JsArrayString array, String object)604    private static native final boolean contains(
605          JsArrayString array,
606          String object)
607    /*-{
608       for (var i = 0, n = array.length; i < n; i++)
609          if (array[i] === object)
610             return true;
611       return false;
612    }-*/;
613 
614 
extractRoxygenBlock( AceEditorNative editor, Range range)615    private static native final JsArrayString extractRoxygenBlock(
616          AceEditorNative editor,
617          Range range)
618    /*-{
619       var lines = editor.getSession().getDocument().$lines;
620       return lines.slice(range.start.row, range.end.row);
621    }-*/;
622 
listParametersInRoxygenBlock( JsArrayString block, Pattern pattern)623    private JsArrayString listParametersInRoxygenBlock(
624          JsArrayString block,
625          Pattern pattern)
626    {
627       JsArrayString roxygenParams = JsArrayString.createArray().cast();
628       for (int i = 0; i < block.length(); i++)
629       {
630          String line = block.get(i);
631          Match match = pattern.match(line, 0);
632          if (match != null)
633             roxygenParams.push(match.getGroup(1));
634       }
635 
636       return roxygenParams;
637    }
638 
getRoxygenBlockRange(int row)639    private Range getRoxygenBlockRange(int row)
640    {
641       while (row >= 0 && StringUtil.isWhitespace(editor_.getLine(row)))
642          row--;
643 
644       int blockEnd = row + 1;
645 
646       while (row >= 0 && RE_ROXYGEN.test(editor_.getLine(row)))
647          row--;
648 
649       if (row == 0 && !RE_ROXYGEN.test(editor_.getLine(row)))
650          row++;
651 
652       int blockStart = row + 1;
653 
654       return Range.fromPoints(
655             Position.create(blockStart, 0),
656             Position.create(blockEnd, 0));
657    }
658 
hasRoxygenBlock(Position position)659    private boolean hasRoxygenBlock(Position position)
660    {
661       int row = position.getRow() - 1;
662       if (row < 0) return false;
663 
664       // Skip whitespace.
665       while (row >= 0 && StringUtil.isWhitespace(editor_.getLine(row)))
666          row--;
667 
668       // Check if we landed on an Roxygen block.
669       return RE_ROXYGEN.test(editor_.getLine(row));
670    }
671 
insertRoxygenTemplate( String name, JsArrayString argNames, JsArrayString argTypes, String argTagName, String type, Position position)672    private void insertRoxygenTemplate(
673          String name,
674          JsArrayString argNames,
675          JsArrayString argTypes,
676          String argTagName,
677          String type,
678          Position position)
679    {
680       String roxygenParams = argsToExampleRoxygen(
681             argNames,
682             argTypes,
683             argTagName);
684 
685       // Add some spacing between params and the next tags,
686       // if there were one or more arguments.
687       if (argNames.length() != 0)
688          roxygenParams += "\n#'\n";
689 
690       String block =
691                   "#' Title\n" +
692                   "#'\n" +
693                   roxygenParams +
694                   "#' @return\n" +
695                   "#' @export\n" +
696                   "#'\n" +
697                   "#' @examples\n";
698 
699       Position insertionPosition = Position.create(
700             position.getRow(),
701             0);
702 
703       editor_.insertCode(insertionPosition, block);
704    }
705 
argsToExampleRoxygen( JsArrayString argNames, JsArrayString argTypes, String tagName)706    private String argsToExampleRoxygen(
707          JsArrayString argNames,
708          JsArrayString argTypes,
709          String tagName)
710    {
711       String roxygen = "";
712       if (argNames.length() == 0) return "";
713 
714       if (argTypes == null)
715       {
716          roxygen += argToExampleRoxygen(argNames.get(0), null, tagName);
717          for (int i = 1; i < argNames.length(); i++)
718             roxygen += "\n" + argToExampleRoxygen(argNames.get(i), null, tagName);
719       }
720       else
721       {
722          roxygen += argToExampleRoxygen(argNames.get(0), argTypes.get(0), tagName);
723          for (int i = 1; i < argNames.length(); i++)
724             roxygen += "\n" + argToExampleRoxygen(argNames.get(i), argTypes.get(i), tagName);
725       }
726 
727       return roxygen;
728    }
729 
argToExampleRoxygen(String argName, String argType, String tagName)730    private String argToExampleRoxygen(String argName, String argType, String tagName)
731    {
732       String output = "#' @" + tagName + " " + argName + " ";
733       if (argType != null)
734          output += argType + ". ";
735       return output;
736    }
737 
738    public static class SetClassCall extends JavaScriptObject
739    {
SetClassCall()740       protected SetClassCall() {}
741 
getClassName()742       public final native String getClassName() /*-{ return this["Class"]; }-*/;
getSlots()743       public final native JsArrayString getSlots() /*-{ return this["slots"]; }-*/;
getTypes()744       public final native JsArrayString getTypes() /*-{ return this["types"]; }-*/;
getNumSlots()745       public final native int getNumSlots() /*-{ return this["slots"].length; }-*/;
746    }
747 
748    public static class SetGenericCall extends JavaScriptObject
749    {
SetGenericCall()750       protected SetGenericCall() {}
751 
getGeneric()752       public final native String getGeneric() /*-{ return this["generic"]; }-*/;
getParameters()753       public final native JsArrayString getParameters() /*-{ return this["parameters"]; }-*/;
754    }
755 
756    public static class SetMethodCall extends JavaScriptObject
757    {
SetMethodCall()758       protected SetMethodCall() {}
759 
getGeneric()760       public final native String getGeneric() /*-{ return this["generic"]; }-*/;
getParameterNames()761       public final native JsArrayString getParameterNames() /*-{ return this["parameter.names"]; }-*/;
getParameterTypes()762       public final native JsArrayString getParameterTypes() /*-{ return this["parameter.types"]; }-*/;
763    }
764 
765    public static class SetRefClassCall extends JavaScriptObject
766    {
SetRefClassCall()767       protected SetRefClassCall() {}
768 
getClassName()769       public final native String getClassName() /*-{ return this["Class"]; }-*/;
getFieldNames()770       public final native JsArrayString getFieldNames() /*-{ return this["field.names"]; }-*/;
getFieldTypes()771       public final native JsArrayString getFieldTypes() /*-{ return this["field.types"]; }-*/;
772    }
773 
774    private final AceEditor editor_;
775    private final WarningBarDisplay view_;
776    private GlobalDisplay globalDisplay_;
777 
778    private RoxygenServerOperations server_;
779 
780    private static final Pattern RE_ROXYGEN =
781          Pattern.create("^(\\s*#+')", "");
782 
783    private static final Pattern RE_ROXYGEN_EMPTY =
784          Pattern.create("^\\s*#+'\\s*$", "");
785 
786    private static final Pattern RE_ROXYGEN_PARAM =
787          Pattern.create("^\\s*#+'\\s*@param\\s+([^\\s]+)", "");
788 
789    private static final Pattern RE_ROXYGEN_FIELD =
790          Pattern.create("^\\s*#+'\\s*@field\\s+([^\\s]+)", "");
791 
792    private static final Pattern RE_ROXYGEN_SLOT =
793          Pattern.create("^\\s*#+'\\s*@slot\\s+([^\\s]+)", "");
794 
795    private static final Pattern RE_ROXYGEN_WITH_TAG =
796          Pattern.create("^\\s*#+'\\s*@[^@]", "");
797 
798    private static final ArrayList<String> ROXYGEN_ANNOTATABLE_CALLS =
799       new ArrayList<>(
800             Arrays.asList(new String[] {
801             "setClass",
802             "setRefClass",
803             "setMethod",
804             "setGeneric"
805       }));
806 
807    private static final Pattern RE_ROXYGEN_NONLOCAL =
808          Pattern.create("^\\s*#+'\\s*@(?:inheritParams|template)", "");
809 }
810