/******************************************************************************* * Copyright (c) 2014 Bruno Medeiros and other Contributors. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Bruno Medeiros - initial API and implementation *******************************************************************************/ package dtool.engine.analysis; import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull; import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue; import java.nio.file.Path; import melnorme.lang.tooling.BundlePath; import melnorme.lang.tooling.ast.ASTNodeFinder; import melnorme.lang.tooling.ast.ASTVisitor; import melnorme.lang.tooling.ast.ILanguageElement; import melnorme.lang.tooling.ast.util.NodeElementUtil; import melnorme.lang.tooling.ast_actual.ASTNode; import melnorme.lang.tooling.context.ISemanticContext; import melnorme.lang.tooling.engine.ErrorElement.NotAValueErrorElement; import melnorme.lang.tooling.engine.ErrorElement.NotFoundErrorElement; import melnorme.lang.tooling.engine.PickedElement; import melnorme.lang.tooling.engine.resolver.ConcreteElementResult; import melnorme.lang.tooling.engine.scoping.CommonScopeLookup; import melnorme.lang.tooling.symbols.INamedElement; import melnorme.utilbox.core.CommonException; import java.util.function.Predicate; import melnorme.utilbox.misc.Location; import dtool.ast.references.Reference; import dtool.engine.CommonSemanticsTest; import dtool.engine.ResolvedModule; import dtool.engine.StandardLibraryResolution; import dtool.engine.tests.DefUnitResultsChecker; import dtool.parser.SourceEquivalenceChecker; public class CommonNodeSemanticsTest extends CommonSemanticsTest { public static final BundlePath DEFAULT_TestsBundle = bundlePath(SEMANTICS_TEST_BUNDLES, "defaultBundle"); protected static final String DEFAULT_ModuleName = "_tests"; public static final Location DEFAULT_TestsBundle_Source = loc(DEFAULT_TestsBundle, "source"); public static final BundlePath TESTER_TestsBundle = bundlePath(SEMANTICS_TEST_BUNDLES, "tester"); public static final Location DEFAULT_TestsModule = DEFAULT_TestsBundle_Source.resolve_fromValid(DEFAULT_ModuleName + ".d"); protected static ISemanticContext getDefaultTestsModuleContext() throws CommonException { return getDefaultTestsModule().getSemanticContext(); } protected static ResolvedModule getUpdatedModule(Location filePath) throws CommonException { return defaultSemMgr.getUpdatedResolvedModule(filePath, DEFAULT_TestsCompilerInstall, testsDubPath()); } protected static ResolvedModule getDefaultTestsModule() throws CommonException { return getUpdatedModule(DEFAULT_TestsModule); } protected static ResolvedModule getTesterModule_(String sourcePath) throws CommonException { Location filePath = loc(TESTER_TestsBundle, "source").resolve_fromValid(sourcePath); return getUpdatedModule(filePath); } protected static ResolvedModule getTesterModule(String sourcePath) { try { return getTesterModule_(sourcePath); } catch (CommonException e) { throw melnorme.utilbox.core.ExceptionAdapter.unchecked(e); } } /* ----------------- ----------------- */ protected static ResolvedModule parseModule(String source) throws CommonException { return parseModule(source, DEFAULT_TestsModule.path); } protected static ResolvedModule parseModule(String source, Path filePath) throws CommonException { // make sure we reparse, even if source is the same. defaultSemMgr.getParseCache().discardEntry(filePath); defaultSemMgr.getParseCache().setSourceAndParseModule(filePath, source); ResolvedModule updatedModule = getUpdatedModule(Location.create_fromValid(filePath)); assertTrue(updatedModule.getSource().equals(source)); return updatedModule; } protected static ResolvedModule parseModule_(String source) { try { return parseModule(source); } catch (CommonException e) { throw melnorme.utilbox.core.ExceptionAdapter.unchecked(e); } } protected static ResolvedModule parseModule_(String source, Path filePath) { try { return parseModule(source, filePath); } catch (CommonException e) { throw melnorme.utilbox.core.ExceptionAdapter.unchecked(e); } } protected static <T extends ILanguageElement> T findNode(ResolvedModule moduleRes, int offset, Class<T> klass) { ASTNode node = ASTNodeFinder.findElement(moduleRes.getModuleNode(), offset); return NodeElementUtil.getMatchingParent(node, klass); } /* ----------------- ----------------- */ public static <E extends ILanguageElement> PickedElement<E> parseElement(String source, Class<E> klass) { return parseElement(source, "/*M*/", klass); } public static <E extends ILanguageElement> PickedElement<E> parseElement(String source, String offsetSource, Class<E> klass) { ResolvedModule resolvedModule = parseModule_(source); return pickElement(resolvedModule, offsetSource, klass); } public static <E extends ILanguageElement> PickedElement<E> parseElement(String source, int offset, Class<E> klass) { ResolvedModule resolvedModule = parseModule_(source); return pickElement(resolvedModule, offset, klass); } public static <E extends ILanguageElement> PickedElement<E> pickElement(ResolvedModule resolvedModule, String offsetSource, Class<E> klass) { String source = resolvedModule.getParsedModule().source; int indexOf = offsetSource == null ? source.length() : source.indexOf(offsetSource); assertTrue(indexOf >= 0); return pickElement(resolvedModule, indexOf, klass); } public static <E extends ILanguageElement> PickedElement<E> pickElement(ResolvedModule resolvedModule, int index, Class<E> klass) { E node = findNode(resolvedModule, index, klass); assertNotNull(node); ISemanticContext context = resolvedModule.getSemanticContext(); return picked(node, context); } /* ----------------- ----------------- */ public static <E extends ILanguageElement> PickedElement<E> picked(E node, ISemanticContext context) { return new PickedElement<>(node, context); } public static <E extends ILanguageElement> PickedElement<E> pickedNative(E node) { return new PickedElement<>(node, getDefaultStdLibContext()); } protected static StandardLibraryResolution getDefaultStdLibContext() { return defaultSemMgr.getUpdatedStdLibResolution(DEFAULT_TestsCompilerInstall); } /* ----------------- more complex pickers ----------------- */ public static PickedElement<INamedElement> parseSourceAndPickFromRefResolving(String source) { return parseSourceAndPickFromRefResolving(source, "/*M*/"); } public static PickedElement<INamedElement> parseSourceAndPickFromRefResolving(String source, String refMarker) { ResolvedModule resolvedModule = parseModule_(source); Reference ref = findNode(resolvedModule, resolvedModule.getSource().indexOf(refMarker), Reference.class); ISemanticContext context = resolvedModule.getSemanticContext(); INamedElement derivedElement = ref.resolveTargetElement(context); return new PickedElement<>(derivedElement, context); } /* ----------------- Helper to test caching ----------------- */ protected static void checkIsSameResolution(ConcreteElementResult resA, ConcreteElementResult resOther) { assertTrue(resA == resOther); } /* ----------------- result checkers ----------------- */ public static DefUnitResultsChecker resultsChecker(CommonScopeLookup lookup) { return resultsChecker(lookup, true, true, true); } public static DefUnitResultsChecker resultsChecker(CommonScopeLookup lookup, boolean ignoreDummy, boolean ignorePrimitives, boolean ignoreObjectModule) { DefUnitResultsChecker checker = new DefUnitResultsChecker(lookup.getMatchedElements()); checker.removeIgnoredDefUnits(ignoreDummy, ignorePrimitives); if(ignoreObjectModule) { checker.removeStdLibObjectDefUnits(); } return checker; } public static Predicate<INamedElement> namedElementChecker(final String expectedLabel) { return new Predicate<INamedElement>() { @Override public boolean test(INamedElement matchedElement) { assertTrue(DefUnitResultsChecker.matchesLabel(matchedElement, expectedLabel)); return true; } }; } public static void checkElementLabel(INamedElement namedElement, String expectedLabel) { assertTrue(DefUnitResultsChecker.matchesLabel(namedElement, expectedLabel)); } public static Predicate<INamedElement> notfoundChecker(final String name) { return namedElementChecker(expectNotFound(name)); } public static String expectNotFound(String name) { return NotFoundErrorElement.NOT_FOUND__Name + ":" + name; } public static String expectNotAValue(String name) { return NotAValueErrorElement.ERROR_IS_NOT_A_VALUE + ":" + name; } /* ----------------- ----------------- */ public static class NodeFinderByString extends ASTVisitor { @SuppressWarnings("unchecked") public static <T extends ASTNode> T find(ASTNode node, Class<T> klass, String toStringAsCode) { NodeFinderByString nodeFinder = new NodeFinderByString(klass, toStringAsCode); node.accept(nodeFinder); return (T) nodeFinder.result; } protected final Class<? extends ASTNode> klass; protected final String toStringAsCode; public ASTNode result; public NodeFinderByString(Class<? extends ASTNode> klass, String toStringAsCode) { this.klass = klass; this.toStringAsCode = toStringAsCode; } @Override public boolean preVisit(ASTNode node) { if(klass.isInstance(node)) { if(toStringAsCode == null || SourceEquivalenceChecker.check(node.toStringAsCode(), toStringAsCode)) { result = node; } } return result == null; } } }