mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-04-29 19:45:01 +02:00
Patch for Andrew Niefer
core : - add parameter references to index - modify CharOperation.match to allow escaping wildcards (bug43063) - modify AbstractIndexer.bestPrefix to handle wildcard escaping in name (bug43063) - modify CSearchPattern to handle escaping wildcards (bug43063) - modify enterFunctionBody and enterMethodBody to fix bug42979 - search for Parameter References - added setThrowExceptionOnBadCharacterRead to IScanner to help with wildcard bug43063 tests: - modified resources/search/classDecl.cpp & include.h to include some operators - added testOperators_bug43063_bug42979() to MethodDeclarationPatternTests - added testParameterREferences to OtherPatternTests
This commit is contained in:
parent
d32b3bf161
commit
51a73af86d
18 changed files with 214 additions and 52 deletions
|
@ -1,4 +1,9 @@
|
|||
2003-09-17 Bogdan Gheorghe
|
||||
2003-09-16 Andrew Niefer
|
||||
- modified resources/search/classDecl.cpp & include.h to include some operators
|
||||
- added testOperators_bug43063_bug42979() to MethodDeclarationPatternTests
|
||||
- added testParameterREferences to OtherPatternTests
|
||||
|
||||
2003-09-16 Bogdan Gheorghe
|
||||
Added asserts to all index lookups in IndexManagerTests
|
||||
Fixed testAddNewFileToIndex
|
||||
|
||||
|
|
|
@ -53,4 +53,12 @@ class AClassForFoo {};
|
|||
AClassForFoo foo( AClassForFoo ){
|
||||
AClassForFoo b;
|
||||
return b;
|
||||
}
|
||||
|
||||
Head * Head::operator *= ( int index ){
|
||||
return array[ index ];
|
||||
}
|
||||
|
||||
Head * Head::operator += ( int index ){
|
||||
return array[ index ];
|
||||
}
|
|
@ -2,6 +2,11 @@
|
|||
#define INCLUDE_H
|
||||
|
||||
class Head {
|
||||
Head * operator *= ( int index );
|
||||
Head * operator * ( int index ){ return array[ index ]; }
|
||||
Head * operator += ( int index );
|
||||
|
||||
Head ** array;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -16,6 +16,7 @@ package org.eclipse.cdt.core.search.tests;
|
|||
import java.util.Set;
|
||||
|
||||
import org.eclipse.cdt.core.search.ICSearchPattern;
|
||||
import org.eclipse.cdt.core.search.IMatch;
|
||||
import org.eclipse.cdt.core.search.SearchEngine;
|
||||
import org.eclipse.cdt.internal.core.CharOperation;
|
||||
import org.eclipse.cdt.internal.core.search.matching.MethodDeclarationPattern;
|
||||
|
@ -103,4 +104,40 @@ public class FunctionMethodPatternTests extends BaseSearchTest {
|
|||
matches = resultCollector.getSearchResults();
|
||||
assertEquals( matches.size(), 1 );
|
||||
}
|
||||
|
||||
public void testOperators_bug43063_bug42979(){
|
||||
ICSearchPattern pattern = SearchEngine.createSearchPattern( "operator \\*", METHOD, DECLARATIONS, true );
|
||||
|
||||
search( workspace, pattern, scope, resultCollector );
|
||||
Set matches = resultCollector.getSearchResults();
|
||||
assertEquals( matches.size(), 1 );
|
||||
IMatch match1 = (IMatch) matches.iterator().next();
|
||||
|
||||
pattern = SearchEngine.createSearchPattern( "operator \\*", METHOD, DEFINITIONS, true );
|
||||
search( workspace, pattern, scope, resultCollector );
|
||||
matches = resultCollector.getSearchResults();
|
||||
assertEquals( matches.size(), 1 );
|
||||
IMatch match2 = (IMatch) matches.iterator().next();
|
||||
|
||||
assertTrue( match1.getStartOffset() == match2.getStartOffset() );
|
||||
|
||||
pattern = SearchEngine.createSearchPattern( "operator \\*=", METHOD, DECLARATIONS, true );
|
||||
search( workspace, pattern, scope, resultCollector );
|
||||
matches = resultCollector.getSearchResults();
|
||||
assertEquals( matches.size(), 1 );
|
||||
match1 = (IMatch) matches.iterator().next();
|
||||
|
||||
pattern = SearchEngine.createSearchPattern( "operator \\*=", METHOD, DEFINITIONS, true );
|
||||
search( workspace, pattern, scope, resultCollector );
|
||||
matches = resultCollector.getSearchResults();
|
||||
assertEquals( matches.size(), 1 );
|
||||
match2 = (IMatch) matches.iterator().next();
|
||||
|
||||
assertTrue( match1.getStartOffset() != match2.getStartOffset() );
|
||||
|
||||
pattern = SearchEngine.createSearchPattern( "operator *", METHOD, DECLARATIONS, true );
|
||||
search( workspace, pattern, scope, resultCollector );
|
||||
matches = resultCollector.getSearchResults();
|
||||
assertEquals( matches.size(), 5 ); //3 in classDecl.cpp, 2 in mail.cpp
|
||||
}
|
||||
}
|
||||
|
|
|
@ -228,4 +228,13 @@ public class OtherPatternTests extends BaseSearchTest {
|
|||
assertTrue( match.getName().equals( "eE" ) );
|
||||
assertTrue( match.getParentName().equals( "NS3::C" ));
|
||||
}
|
||||
|
||||
public void testParameterReferences(){
|
||||
ICSearchPattern pattern = SearchEngine.createSearchPattern( "index", VAR, REFERENCES, true );
|
||||
|
||||
search( workspace, pattern, scope, resultCollector );
|
||||
|
||||
Set matches = resultCollector.getSearchResults();
|
||||
assertEquals( matches.size(), 3 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
2003-09-16 Andrew Niefer
|
||||
- add parameter references to index
|
||||
- modify CharOperation.match to allow escaping wildcards (bug43063)
|
||||
- modify AbstractIndexer.bestPrefix to handle wildcard escaping in name (bug43063)
|
||||
|
||||
2003-09-13 Andrew Niefer
|
||||
- add Typedefs to index as Types with suffix T (bug42902)
|
||||
- added addTypedefReference to AbstractIndexer
|
||||
|
|
|
@ -1530,7 +1530,7 @@ public final class CharOperation {
|
|||
name,
|
||||
0,
|
||||
name.length,
|
||||
isCaseSensitive);
|
||||
isCaseSensitive, true);
|
||||
}
|
||||
/**
|
||||
* Answers true if the a sub-pattern matches the subpart of the given name, false otherwise.
|
||||
|
@ -1583,7 +1583,21 @@ public final class CharOperation {
|
|||
char[] name,
|
||||
int nameStart,
|
||||
int nameEnd,
|
||||
boolean isCaseSensitive) {
|
||||
boolean isCaseSensitive){
|
||||
|
||||
return match( pattern, patternStart, patternEnd, name, nameStart, nameEnd, isCaseSensitive, false );
|
||||
}
|
||||
|
||||
|
||||
public static final boolean match(
|
||||
char[] pattern,
|
||||
int patternStart,
|
||||
int patternEnd,
|
||||
char[] name,
|
||||
int nameStart,
|
||||
int nameEnd,
|
||||
boolean isCaseSensitive,
|
||||
boolean allowEscaping) {
|
||||
|
||||
if (name == null)
|
||||
return false; // null name cannot match
|
||||
|
@ -1599,8 +1613,17 @@ public final class CharOperation {
|
|||
|
||||
/* check first segment */
|
||||
char patternChar = 0;
|
||||
while ((iPattern < patternEnd)
|
||||
&& (patternChar = pattern[iPattern]) != '*') {
|
||||
boolean isEscaped = false;
|
||||
while ((iPattern < patternEnd) &&
|
||||
( (patternChar = pattern[iPattern]) != '*' ||
|
||||
(patternChar == '*' && isEscaped) ) ) {
|
||||
|
||||
if( allowEscaping && pattern[iPattern] == '\\' && !isEscaped ){
|
||||
iPattern++;
|
||||
isEscaped = true;
|
||||
continue;
|
||||
} else isEscaped = false;
|
||||
|
||||
if (iName == nameEnd)
|
||||
return false;
|
||||
if (patternChar
|
||||
|
@ -1612,6 +1635,7 @@ public final class CharOperation {
|
|||
}
|
||||
iName++;
|
||||
iPattern++;
|
||||
patternChar = 0;
|
||||
}
|
||||
/* check sequence of star+segment */
|
||||
int segmentStart;
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.eclipse.cdt.core.parser.ast.IASTFunction;
|
|||
import org.eclipse.cdt.core.parser.ast.IASTMacro;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTMethod;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTNamespaceDefinition;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTParameterDeclaration;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTTypeSpecifier;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTTypedefDeclaration;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTVariable;
|
||||
|
@ -118,6 +119,10 @@ public abstract class AbstractIndexer implements IIndexer, IIndexConstants, ICSe
|
|||
this.output.addRef(encodeTypeEntry(variable.getFullyQualifiedName(), VAR, ICSearchConstants.REFERENCES));
|
||||
}
|
||||
|
||||
public void addParameterReference( IASTParameterDeclaration parameter ){
|
||||
this.output.addRef( encodeTypeEntry( new String [] { parameter.getName() }, VAR, ICSearchConstants.REFERENCES));
|
||||
}
|
||||
|
||||
public void addTypedefDeclaration(IASTTypedefDeclaration typedef) {
|
||||
this.output.addRef(encodeEntry(typedef.getFullyQualifiedName(), TYPEDEF_DECL, TYPEDEF_DECL_LENGTH));
|
||||
}
|
||||
|
@ -463,7 +468,7 @@ public abstract class AbstractIndexer implements IIndexer, IIndexConstants, ICSe
|
|||
char[] result = null;
|
||||
int pos = 0;
|
||||
|
||||
int wildPos, starPos, questionPos;
|
||||
int wildPos, starPos = -1, questionPos;
|
||||
|
||||
//length of prefix + separator
|
||||
int length = prefix.length;
|
||||
|
@ -477,7 +482,29 @@ public abstract class AbstractIndexer implements IIndexer, IIndexConstants, ICSe
|
|||
//type name.
|
||||
name = null;
|
||||
} else if( matchMode == PATTERN_MATCH && name != null ){
|
||||
starPos = CharOperation.indexOf( '*', name );
|
||||
int start = 0;
|
||||
|
||||
char [] temp = new char [ name.length ];
|
||||
boolean isEscaped = false;
|
||||
int tmpIdx = 0;
|
||||
for( int i = 0; i < name.length; i++ ){
|
||||
if( name[i] == '\\' ){
|
||||
if( !isEscaped ){
|
||||
isEscaped = true;
|
||||
continue;
|
||||
}
|
||||
isEscaped = false;
|
||||
} else if( name[i] == '*' && !isEscaped ){
|
||||
starPos = i;
|
||||
break;
|
||||
}
|
||||
temp[ tmpIdx++ ] = name[i];
|
||||
}
|
||||
|
||||
name = new char [ tmpIdx ];
|
||||
System.arraycopy( temp, 0, name, 0, tmpIdx );
|
||||
|
||||
//starPos = CharOperation.indexOf( '*', name );
|
||||
questionPos = CharOperation.indexOf( '?', name );
|
||||
|
||||
if( starPos >= 0 ){
|
||||
|
|
|
@ -39,6 +39,7 @@ import org.eclipse.cdt.core.parser.ast.IASTMethod;
|
|||
import org.eclipse.cdt.core.parser.ast.IASTMethodReference;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTNamespaceDefinition;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTNamespaceReference;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTParameterDeclaration;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTParameterReference;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTTemplateDeclaration;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTTemplateInstantiation;
|
||||
|
@ -450,7 +451,8 @@ public class SourceIndexerRequestor implements ISourceElementRequestor, IIndexCo
|
|||
*/
|
||||
public void acceptParameterReference(IASTParameterReference reference)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
if( reference.getReferencedElement() instanceof IASTParameterDeclaration )
|
||||
indexer.addParameterReference( (IASTParameterDeclaration) reference.getReferencedElement() );
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
2003-09-16 Andrew Niefer
|
||||
- added setThrowExceptionOnBadCharacterRead to IScanner to help with wildcard bug43063
|
||||
|
||||
2003-09-16 John Camelon
|
||||
Implement CompleteParse IASTFunction::previouslyDeclared().
|
||||
|
||||
|
|
|
@ -30,7 +30,8 @@ public interface IScanner {
|
|||
|
||||
public IToken nextTokenForStringizing() throws ScannerException, EndOfFile;
|
||||
public void setTokenizingMacroReplacementList(boolean b);
|
||||
|
||||
public void setThrowExceptionOnBadCharacterRead( boolean throwOnBad );
|
||||
|
||||
public void onParseEnd();
|
||||
/**
|
||||
* @param i
|
||||
|
|
|
@ -2398,6 +2398,10 @@ public class Scanner implements IScanner {
|
|||
language = value;
|
||||
}
|
||||
|
||||
public void setThrowExceptionOnBadCharacterRead( boolean throwOnBad ){
|
||||
throwExceptionOnBadCharacterRead = throwOnBad;
|
||||
}
|
||||
|
||||
private final ISourceElementRequestor requestor;
|
||||
private IASTFactory astFactory = null;
|
||||
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
2003-09-15 Andrew Niefer
|
||||
- modify CSearchPattern to handle escaping wildcards (bug43063)
|
||||
- modify enterFunctionBody and enterMethodBody to fix bug42979
|
||||
- search for Parameter References
|
||||
|
||||
2003-09-13 Andrew Niefer
|
||||
-Searching for Typedefs: (bug42902)
|
||||
- modified setElementInfo in BasicSearchResultCollector
|
||||
|
|
|
@ -415,6 +415,7 @@ public abstract class CSearchPattern implements ICSearchConstants, ICSearchPatte
|
|||
|
||||
try {
|
||||
IToken token = ( unusedToken != null ) ? unusedToken : scanner.nextToken();
|
||||
scanner.setThrowExceptionOnBadCharacterRead( true );
|
||||
|
||||
boolean lastTokenWasWild = false;
|
||||
|
||||
|
@ -432,13 +433,26 @@ public abstract class CSearchPattern implements ICSearchConstants, ICSearchPatte
|
|||
lastTokenWasWild = true;
|
||||
} else if( !lastTokenWasWild && name.length() > 0 ) {
|
||||
name += " ";
|
||||
} else {
|
||||
lastTokenWasWild = false;
|
||||
}
|
||||
|
||||
name += token.getImage();
|
||||
break;
|
||||
}
|
||||
token = null;
|
||||
while( token == null ){
|
||||
try{
|
||||
token = scanner.nextToken();
|
||||
} catch ( ScannerException e ){
|
||||
if( e.getErrorCode() == ScannerException.ErrorCode.INVALID_ESCAPE_CHARACTER_SEQUENCE ){
|
||||
if( !lastTokenWasWild ) name += " ";
|
||||
name += "\\";
|
||||
lastTokenWasWild = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
token = scanner.nextToken();
|
||||
}
|
||||
} catch (EndOfFile e) {
|
||||
list.addLast( name.toCharArray() );
|
||||
|
|
|
@ -105,15 +105,17 @@ public class ClassDeclarationPattern extends CSearchPattern {
|
|||
return IMPOSSIBLE_MATCH;
|
||||
}
|
||||
|
||||
//create char[][] out of full name,
|
||||
String [] fullName = ((IASTQualifiedNameElement) node).getFullyQualifiedName();
|
||||
char [][] qualName = new char [ fullName.length - 1 ][];
|
||||
for( int i = 0; i < fullName.length - 1; i++ ){
|
||||
qualName[i] = fullName[i].toCharArray();
|
||||
}
|
||||
//check containing scopes
|
||||
if( !matchQualifications( qualifications, qualName ) ){
|
||||
return IMPOSSIBLE_MATCH;
|
||||
if( node instanceof IASTQualifiedNameElement ){
|
||||
//create char[][] out of full name,
|
||||
String [] fullName = ((IASTQualifiedNameElement) node).getFullyQualifiedName();
|
||||
char [][] qualName = new char [ fullName.length - 1 ][];
|
||||
for( int i = 0; i < fullName.length - 1; i++ ){
|
||||
qualName[i] = fullName[i].toCharArray();
|
||||
}
|
||||
//check containing scopes
|
||||
if( !matchQualifications( qualifications, qualName ) ){
|
||||
return IMPOSSIBLE_MATCH;
|
||||
}
|
||||
}
|
||||
|
||||
//check type
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.eclipse.cdt.core.parser.ast.IASTEnumerationSpecifier;
|
|||
import org.eclipse.cdt.core.parser.ast.IASTEnumerator;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTField;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTOffsetableNamedElement;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTParameterDeclaration;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTQualifiedNameElement;
|
||||
import org.eclipse.cdt.core.parser.ast.IASTVariable;
|
||||
import org.eclipse.cdt.core.search.ICSearchScope;
|
||||
|
@ -64,7 +65,10 @@ public class FieldDeclarationPattern extends CSearchPattern {
|
|||
} else if ( node instanceof IASTEnumerator ){
|
||||
if( searchFor != FIELD || !canAccept( limit ) )
|
||||
return IMPOSSIBLE_MATCH;
|
||||
} else return IMPOSSIBLE_MATCH;
|
||||
} else if( node instanceof IASTParameterDeclaration ){
|
||||
if( searchFor != VAR || !canAccept( limit ) )
|
||||
return IMPOSSIBLE_MATCH;
|
||||
} else return IMPOSSIBLE_MATCH;
|
||||
|
||||
String nodeName = ((IASTOffsetableNamedElement)node).getName();
|
||||
|
||||
|
@ -91,17 +95,19 @@ public class FieldDeclarationPattern extends CSearchPattern {
|
|||
enumeratorFullName[ fullName.length - 1 ] = nodeName;
|
||||
|
||||
fullName = enumeratorFullName;
|
||||
} else {
|
||||
} else if( node instanceof IASTQualifiedNameElement ){
|
||||
fullName = ((IASTQualifiedNameElement) node).getFullyQualifiedName();
|
||||
}
|
||||
}
|
||||
|
||||
char [][] qualName = new char [ fullName.length - 1 ][];
|
||||
for( int i = 0; i < fullName.length - 1; i++ ){
|
||||
qualName[i] = fullName[i].toCharArray();
|
||||
}
|
||||
//check containing scopes
|
||||
if( !matchQualifications( qualifications, qualName ) ){
|
||||
return IMPOSSIBLE_MATCH;
|
||||
if( fullName != null ){
|
||||
char [][] qualName = new char [ fullName.length - 1 ][];
|
||||
for( int i = 0; i < fullName.length - 1; i++ ){
|
||||
qualName[i] = fullName[i].toCharArray();
|
||||
}
|
||||
//check containing scopes
|
||||
if( !matchQualifications( qualifications, qualName ) ){
|
||||
return IMPOSSIBLE_MATCH;
|
||||
}
|
||||
}
|
||||
|
||||
return ACCURATE_MATCH;
|
||||
|
|
|
@ -129,6 +129,14 @@ public class MatchLocator implements ISourceElementRequestor, ICSearchConstants
|
|||
public void enterCodeBlock(IASTCodeScope scope) { }
|
||||
public void exitCodeBlock(IASTCodeScope scope) { }
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.cdt.core.parser.ISourceElementRequestor#acceptParameterReference(org.eclipse.cdt.internal.core.parser.ast.complete.ASTParameterReference)
|
||||
*/
|
||||
public void acceptParameterReference(IASTParameterReference reference)
|
||||
{
|
||||
check( REFERENCES, reference );
|
||||
}
|
||||
|
||||
public void acceptTypedefDeclaration(IASTTypedefDeclaration typedef){
|
||||
lastDeclaration = typedef;
|
||||
|
@ -208,13 +216,19 @@ public class MatchLocator implements ISourceElementRequestor, ICSearchConstants
|
|||
|
||||
public void enterFunctionBody(IASTFunction function){
|
||||
lastDeclaration = function;
|
||||
check( DECLARATIONS, function );
|
||||
|
||||
if( !function.previouslyDeclared() )
|
||||
check( DECLARATIONS, function );
|
||||
|
||||
check( DEFINITIONS, function );
|
||||
pushScope( function );
|
||||
}
|
||||
|
||||
public void enterMethodBody(IASTMethod method) {
|
||||
lastDeclaration = method;
|
||||
if( !method.previouslyDeclared() )
|
||||
check( DECLARATIONS, method );
|
||||
|
||||
check( DEFINITIONS, method );
|
||||
pushScope( method );
|
||||
}
|
||||
|
@ -471,7 +485,7 @@ public class MatchLocator implements ISourceElementRequestor, ICSearchConstants
|
|||
if( level != ICSearchPattern.IMPOSSIBLE_MATCH )
|
||||
{
|
||||
report( node, level );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void pushScope( IASTScope scope ){
|
||||
|
@ -509,15 +523,4 @@ public class MatchLocator implements ISourceElementRequestor, ICSearchConstants
|
|||
public static void verbose(String log) {
|
||||
System.out.println("(" + Thread.currentThread() + ") " + log);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.cdt.core.parser.ISourceElementRequestor#acceptParameterReference(org.eclipse.cdt.internal.core.parser.ast.complete.ASTParameterReference)
|
||||
*/
|
||||
public void acceptParameterReference(IASTParameterReference reference)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -79,16 +79,18 @@ public class MethodDeclarationPattern extends CSearchPattern {
|
|||
return IMPOSSIBLE_MATCH;
|
||||
}
|
||||
|
||||
//create char[][] out of full name,
|
||||
String [] fullName = ((IASTQualifiedNameElement) node).getFullyQualifiedName();
|
||||
char [][] qualName = new char [ fullName.length - 1 ][];
|
||||
for( int i = 0; i < fullName.length - 1; i++ ){
|
||||
qualName[i] = fullName[i].toCharArray();
|
||||
}
|
||||
|
||||
//check containing scopes
|
||||
if( !matchQualifications( qualifications, qualName ) ){
|
||||
return IMPOSSIBLE_MATCH;
|
||||
if( node instanceof IASTQualifiedNameElement ){
|
||||
//create char[][] out of full name,
|
||||
String [] fullName = ((IASTQualifiedNameElement) node).getFullyQualifiedName();
|
||||
char [][] qualName = new char [ fullName.length - 1 ][];
|
||||
for( int i = 0; i < fullName.length - 1; i++ ){
|
||||
qualName[i] = fullName[i].toCharArray();
|
||||
}
|
||||
|
||||
//check containing scopes
|
||||
if( !matchQualifications( qualifications, qualName ) ){
|
||||
return IMPOSSIBLE_MATCH;
|
||||
}
|
||||
}
|
||||
|
||||
//parameters
|
||||
|
|
Loading…
Add table
Reference in a new issue