package org.rubypeople.rdt.internal.core.parser.warnings; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.jruby.ast.ClassNode; import org.jruby.ast.DefnNode; import org.jruby.ast.ModuleNode; import org.jruby.ast.Node; import org.jruby.ast.RootNode; import org.rubypeople.rdt.core.IRubyElement; import org.rubypeople.rdt.core.IRubyScript; import org.rubypeople.rdt.core.ISourceFolderRoot; import org.rubypeople.rdt.core.RubyCore; import org.rubypeople.rdt.core.RubyModelException; import org.rubypeople.rdt.core.parser.warnings.RubyLintVisitor; import org.rubypeople.rdt.core.search.CollectingSearchRequestor; import org.rubypeople.rdt.core.search.IRubySearchConstants; import org.rubypeople.rdt.core.search.IRubySearchScope; import org.rubypeople.rdt.core.search.SearchEngine; import org.rubypeople.rdt.core.search.SearchMatch; import org.rubypeople.rdt.core.search.SearchParticipant; import org.rubypeople.rdt.core.search.SearchPattern; import org.rubypeople.rdt.internal.core.util.ASTUtil; public class CoreClassReOpening extends RubyLintVisitor { private List<Node> typeStack; private RootNode rootNode; private IRubyScript script; private static Set<String> coreTypes = new HashSet<String>(); static { coreTypes.add("Array"); coreTypes.add("Bignum"); coreTypes.add("Class"); coreTypes.add("Complex"); coreTypes.add("Date"); coreTypes.add("DateTime"); coreTypes.add("Enumerable"); coreTypes.add("FalseClass"); coreTypes.add("Fixnum"); coreTypes.add("Float"); coreTypes.add("NilClass"); coreTypes.add("Numeric"); coreTypes.add("Rational"); coreTypes.add("Regexp"); coreTypes.add("Set"); coreTypes.add("String"); coreTypes.add("Time"); coreTypes.add("TrueClass"); } public CoreClassReOpening(IRubyScript script, String contents) { super(contents); this.script = script; typeStack = new ArrayList<Node>(); } @Override protected String getOptionKey() { return RubyCore.COMPILER_PB_REDEFINITION_CORE_CLASS_METHOD; } @Override public Object visitDefnNode(DefnNode iVisited) { String typeName = getCurrentTypeName(); if (isCoreClass(typeName)) { String methodName = iVisited.getName(); if (methodExistsOnType(typeName, methodName)) createProblem(iVisited.getPosition(), "Redefinition of a Ruby Core class method is dangerous"); } return super.visitDefnNode(iVisited); } protected boolean methodExistsOnType(String typeName, String methodName) { try { ISourceFolderRoot[] roots = script.getRubyProject().getAllSourceFolderRoots(); ISourceFolderRoot stubs = findCoreStubsRoot(roots); if (stubs == null) return false; SearchParticipant[] participants = new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }; SearchEngine engine = new SearchEngine(); IRubySearchScope scope = SearchEngine.createRubySearchScope(new IRubyElement[] { stubs }); CollectingSearchRequestor requestor = new CollectingSearchRequestor(); SearchPattern pattern = SearchPattern.createPattern(IRubyElement.METHOD, typeName + '.' + methodName, IRubySearchConstants.DECLARATIONS, SearchPattern.R_EXACT_MATCH); engine.search(pattern, participants, scope, requestor, null); List<SearchMatch> matches = requestor.getResults(); return matches != null && !matches.isEmpty(); } catch (RubyModelException e) { RubyCore.log(e); } catch (CoreException e) { RubyCore.log(e); } return false; } private ISourceFolderRoot findCoreStubsRoot(ISourceFolderRoot[] roots) { for (int i = 0; i < roots.length; i++) { ISourceFolderRoot root = roots[i]; IPath path = root.getPath(); if (path.segmentCount() < 3) continue; String segment = path.segment(path.segmentCount() - 3); if (segment.equals("org.rubypeople.rdt.launching")) { return root; } } return null; } private boolean isCoreClass(String typeName) { return coreTypes.contains(typeName); } @Override public Object visitRootNode(RootNode iVisited) { this.rootNode = iVisited; return super.visitRootNode(iVisited); } private String getCurrentTypeName() { Node typeNode = typeStack.get(typeStack.size() - 1); return ASTUtil.getFullyQualifiedTypeName(rootNode, typeNode); } @Override public Object visitClassNode(ClassNode iVisited) { push(iVisited); return super.visitClassNode(iVisited); } @Override public void exitClassNode(ClassNode iVisited) { pop(); super.exitClassNode(iVisited); } @Override public Object visitModuleNode(ModuleNode iVisited) { push(iVisited); return super.visitModuleNode(iVisited); } @Override public void exitModuleNode(ModuleNode iVisited) { pop(); super.exitModuleNode(iVisited); } private void pop() { typeStack.remove(typeStack.size() - 1); } private void push(Node visited) { typeStack.add(visited); } }