Skip to content
Draft

Ruby #67

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bin/clone-java-repositories.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ git -C "$1" clone https://github.com/zulip/zulip.git

# TypeScript Repos
git -C "$1" clone https://github.com/microsoft/vscode.git

# Ruby Repos
git -C "$1" clone https://github.com/fastlane/fastlane.git
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@
<artifactId>${j2v8.artifactId}</artifactId>
<version>4.8.0</version>
</dependency>
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jrubyparser</artifactId>
<version>0.5.3</version>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ private Yparameter getParameter(PythonParser.Def_parameterContext param) {

private Yparameter getParameter(PythonParser.Named_parameterContext param) {
String argumentName = "";
// TODO should * and ** be part of the metadata?
if (param.getParent().getChild(0).getText().contains("*")) {
// OK this looks a little shaky but is meant to include *args and **kwargs
argumentName += param.getParent().getChild(0).getText();
Expand Down
104 changes: 104 additions & 0 deletions src/main/java/com/felixgrund/codeshovel/parser/impl/RubyFunction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.felixgrund.codeshovel.parser.impl;

import com.felixgrund.codeshovel.entities.Yexceptions;
import com.felixgrund.codeshovel.entities.Ymodifiers;
import com.felixgrund.codeshovel.entities.Yparameter;
import com.felixgrund.codeshovel.parser.AbstractFunction;
import com.felixgrund.codeshovel.parser.Yfunction;
import com.felixgrund.codeshovel.wrappers.Commit;
import org.jrubyparser.ast.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class RubyFunction extends AbstractFunction<MethodDefNode> implements Yfunction {

RubyFunction(MethodDefNode function, Commit commit, String sourceFilePath, String sourceFileContent) {
super(function, commit, sourceFilePath, sourceFileContent);
}

@Override
protected String getInitialName(MethodDefNode rawMethod) {
return rawMethod.getName();
}

@Override
protected String getInitialType(MethodDefNode rawMethod) {
return null;
}

@Override
protected Ymodifiers getInitialModifiers(MethodDefNode rawMethod) {
// TODO is this the best place to put a receiver? Perhaps this should be a context?
if (rawMethod instanceof DefsNode) {
DefsNode singletonMethod = (DefsNode) rawMethod;
Node receiver = singletonMethod.getReceiver();
if (receiver instanceof INameNode) {
INameNode namedReceiver = (INameNode) receiver;
String receiverName = namedReceiver.getName();
return new Ymodifiers(Collections.singletonList(receiverName));
}
}
return Ymodifiers.NONE;
}

@Override
protected Yexceptions getInitialExceptions(MethodDefNode rawMethod) {
return Yexceptions.NONE;
}

@Override
protected List<Yparameter> getInitialParameters(MethodDefNode rawMethod) {
// TODO how does namesOnly affect this line?
List<String> parameterStrings = rawMethod.getArgs().getNormativeParameterNameList(false);
List<Yparameter> parametersList = new ArrayList<>();
for (String parameterString : parameterStrings) {
Yparameter parameter = new Yparameter(parameterString, null);
// TODO set metadata using default arguments and optional arguments
// see: https://medium.com/podiihq/ruby-parameters-c178fdcd1f4e
// parameter.setMetadata();
parametersList.add(parameter);
}
return parametersList;
}

@Override
protected String getInitialBody(MethodDefNode rawMethod) {
return rawMethod.getBody().toString();
}

@Override
protected int getInitialBeginLine(MethodDefNode rawMethod) {
return rawMethod.getNamePosition().getStartLine() + 1;
}

@Override
protected int getInitialEndLine(MethodDefNode rawMethod) {
return rawMethod.getPosition().getEndLine() + 1;
}

@Override
protected String getInitialParentName(MethodDefNode rawMethod) {
// TODO this currently only covers class and module
// node.getClosestIScope is probably more what we want, but we need custom
// name retrieval for every node that implements IScope
for(Node scope = rawMethod.getParent(); scope != null; scope = scope.getParent()) {
if (scope instanceof IScopingNode) {
return ((IScopingNode) scope).getCPath().getName();
}
}

return null;
}

@Override
protected String getInitialFunctionPath(MethodDefNode rawMethod) {
return null;
}

@Override
protected String getInitialAnnotation(MethodDefNode rawMethod) {
return null;
}
}
128 changes: 128 additions & 0 deletions src/main/java/com/felixgrund/codeshovel/parser/impl/RubyParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package com.felixgrund.codeshovel.parser.impl;

import com.felixgrund.codeshovel.changes.*;
import com.felixgrund.codeshovel.entities.Ycommit;
import com.felixgrund.codeshovel.exceptions.ParseException;
import com.felixgrund.codeshovel.parser.AbstractParser;
import com.felixgrund.codeshovel.parser.Yfunction;
import com.felixgrund.codeshovel.parser.Yparser;
import com.felixgrund.codeshovel.util.Utl;
import com.felixgrund.codeshovel.wrappers.Commit;
import com.felixgrund.codeshovel.wrappers.StartEnvironment;
import org.jrubyparser.CompatVersion;
import org.jrubyparser.Parser;
import org.jrubyparser.ast.DefnNode;
import org.jrubyparser.ast.DefsNode;
import org.jrubyparser.ast.MethodDefNode;
import org.jrubyparser.ast.Node;
import org.jrubyparser.parser.ParserConfiguration;
import org.jrubyparser.util.NoopVisitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

public class RubyParser extends AbstractParser implements Yparser {

public static final String ACCEPTED_FILE_EXTENSION = ".*\\.rb$";
private Logger log = LoggerFactory.getLogger(RubyParser.class);

public RubyParser(StartEnvironment startEnv, String filePath, String fileContent, Commit commit) throws ParseException {
super(startEnv, filePath, fileContent, commit);
}

@Override
protected List<Yfunction> parseMethods() throws ParseException {
try {
Parser rubyParser = new Parser();
StringReader in = new StringReader(this.fileContent);
CompatVersion version = CompatVersion.RUBY2_0;
ParserConfiguration config = new ParserConfiguration(0, version);
Node rootNode = rubyParser.parse(this.filePath, in, config);
RubyMethodVisitor visitor = new RubyMethodVisitor() {
@Override
public boolean methodMatches(Yfunction method) {
return method.getBody() != null;
}
};
rootNode.accept(visitor);
return visitor.getMatchedNodes();
} catch (Exception e) {
System.err.println("JavaParser::parseMethods() - parse error. path: " + this.filePath + "; content:\n" + this.fileContent);
throw new ParseException("Error during parsing of content", this.filePath, this.fileContent);
}
}

@Override
public double getScopeSimilarity(Yfunction function, Yfunction compareFunction) {
// TODO
return Utl.parentNamesMatch(function, compareFunction) ? 1.0 : 0.0;
}

@Override
public String getAcceptedFileExtension() {
return ACCEPTED_FILE_EXTENSION;
}

@Override
public List<Ychange> getMinorChanges(Ycommit commit, Yfunction compareFunction) {
List<Ychange> changes = new ArrayList<>();
Yreturntypechange yreturntypechange = getReturnTypeChange(commit, compareFunction);
Ymodifierchange ymodifierchange = getModifiersChange(commit, compareFunction);
Ybodychange ybodychange = getBodyChange(commit, compareFunction);

// TODO this currently does nothing. Parameter metadata is not collected
Yparametermetachange yparametermetachange = getParametersMetaChange(commit, compareFunction);
if (yreturntypechange != null) {
changes.add(yreturntypechange);
}
if (ymodifierchange != null) {
changes.add(ymodifierchange);
}
if (ybodychange != null) {
changes.add(ybodychange);
}
if (yparametermetachange != null) {
changes.add(yparametermetachange);
}
return changes;
}


private Yfunction transformMethod(MethodDefNode method) {
return new RubyFunction(method, this.commit, this.filePath, this.fileContent);
}

public abstract class RubyMethodVisitor extends NoopVisitor {

private List<Yfunction> matchedNodes = new ArrayList<>();

public abstract boolean methodMatches(Yfunction method);

private void addMethodIfMatches(MethodDefNode method) {
Yfunction yfunction = transformMethod(method);
if (methodMatches(yfunction)) {
matchedNodes.add(yfunction);
}
}

@Override
public Object visitDefnNode(DefnNode defnNode) {
addMethodIfMatches(defnNode);
return this.visit(defnNode);
}

@Override
public Object visitDefsNode(DefsNode defsNode) {
addMethodIfMatches(defsNode);
return this.visit(defsNode);
}

public List<Yfunction> getMatchedNodes() {
return matchedNodes;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class TypeScriptFunction extends AbstractFunction<V8Object> implements Yf
}

private Yparameter getParameter(V8Object param) {
// TODO should `?` (implying optional) or `...` (implying varargs) be in the metadata?
String stype;
V8Object v8type = param.getObject("type");
if (v8type.isUndefined()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public List<Ychange> getMinorChanges(Ycommit commit, Yfunction compareFunction)
Ymodifierchange ymodifierchange = getModifiersChange(commit, compareFunction);
Ybodychange ybodychange = getBodyChange(commit, compareFunction);
Yannotationchange yannotationchange = getAnnotationChange(commit, compareFunction);
// TODO consider parameter meta change
// TODO: handle Ydocchange
if (yreturntypechange != null) {
changes.add(yreturntypechange);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.felixgrund.codeshovel.parser.Yparser;
import com.felixgrund.codeshovel.parser.impl.JavaParser;
import com.felixgrund.codeshovel.parser.impl.PythonParser;
import com.felixgrund.codeshovel.parser.impl.RubyParser;
import com.felixgrund.codeshovel.parser.impl.TypeScriptParser;
import com.felixgrund.codeshovel.wrappers.Commit;
import com.felixgrund.codeshovel.wrappers.StartEnvironment;
Expand All @@ -27,6 +28,8 @@ public static Yparser getParser(StartEnvironment startEnv, String filePath, Stri
parser = new PythonParser(startEnv, filePath, fileContent, commit);
} else if (filePath.matches(TypeScriptParser.ACCEPTED_FILE_EXTENSION)) {
parser = new TypeScriptParser(startEnv, filePath, fileContent, commit);
} else if (filePath.matches(RubyParser.ACCEPTED_FILE_EXTENSION)) {
parser = new RubyParser(startEnv, filePath, fileContent, commit);
} else {
throw new NoParserFoundException("No parser found for filename " + filePath);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"repositoryName": "fastlane",
"filePath": "gym/lib/gym/detect_values.rb",
"functionName": "detect_selected_provisioning_profiles",
"functionStartLine": 79,
"startCommitName": "2c34e6573dcf0014670af3a554fa92a9ad2566b7",
"expectedResult": {

}
}