Changeset 288473 in webkit for trunk/Source/JavaScriptCore
- Timestamp:
- Jan 24, 2022, 2:51:13 PM (3 years ago)
- Location:
- trunk/Source/JavaScriptCore
- Files:
-
- 10 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/JavaScriptCore/ChangeLog
r288458 r288473 1 2022-01-24 Yusuke Suzuki <[email protected]> 2 3 [JSC] Support import assertion syntax 4 https://bugs.webkit.org/show_bug.cgi?id=235312 5 6 Reviewed by Ross Kirsling. 7 8 This patch adds syntax support for import assertion[1]. 9 This does not add the actual feature propagating import assertion 10 to the module request yet. 11 12 [1]: https://github.com/tc39/proposal-import-assertions 13 14 * bytecompiler/NodesCodegen.cpp: 15 (JSC::ImportNode::emitBytecode): 16 * parser/ASTBuilder.h: 17 (JSC::ASTBuilder::createImportExpr): 18 (JSC::ASTBuilder::createImportAssertionList): 19 (JSC::ASTBuilder::appendImportAssertion): 20 (JSC::ASTBuilder::createImportDeclaration): 21 (JSC::ASTBuilder::createExportAllDeclaration): 22 (JSC::ASTBuilder::createExportNamedDeclaration): 23 * parser/NodeConstructors.h: 24 (JSC::ImportNode::ImportNode): 25 (JSC::ImportDeclarationNode::ImportDeclarationNode): 26 (JSC::ExportAllDeclarationNode::ExportAllDeclarationNode): 27 (JSC::ExportNamedDeclarationNode::ExportNamedDeclarationNode): 28 * parser/Nodes.h: 29 * parser/Parser.cpp: 30 (JSC::Parser<LexerType>::parseImportAssertions): 31 (JSC::Parser<LexerType>::parseImportDeclaration): 32 (JSC::Parser<LexerType>::parseExportDeclaration): 33 (JSC::Parser<LexerType>::parseMemberExpression): 34 * parser/Parser.h: 35 * parser/SyntaxChecker.h: 36 (JSC::SyntaxChecker::createImportExpr): 37 (JSC::SyntaxChecker::createImportAssertionList): 38 (JSC::SyntaxChecker::appendImportAssertion): 39 (JSC::SyntaxChecker::createImportDeclaration): 40 (JSC::SyntaxChecker::createExportAllDeclaration): 41 (JSC::SyntaxChecker::createExportNamedDeclaration): 42 * runtime/JSGlobalObjectFunctions.cpp: 43 (JSC::JSC_DEFINE_HOST_FUNCTION): 44 * runtime/OptionsList.h: 45 1 46 2022-01-24 Mark Lam <[email protected]> 2 47 -
trunk/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
r287544 r288473 225 225 { 226 226 RefPtr<RegisterID> importModule = generator.moveLinkTimeConstant(nullptr, LinkTimeConstant::importModule); 227 CallArguments arguments(generator, nullptr, 1);227 CallArguments arguments(generator, nullptr, m_option ? 2 : 1); 228 228 generator.emitLoad(arguments.thisRegister(), jsUndefined()); 229 229 generator.emitNode(arguments.argumentRegister(0), m_expr); 230 if (m_option) 231 generator.emitNode(arguments.argumentRegister(1), m_option); 230 232 return generator.emitCall(generator.finalDestination(dst, importModule.get()), importModule.get(), NoExpectedFunction, arguments, divot(), divotStart(), divotEnd(), DebuggableCall::No); 231 233 } -
trunk/Source/JavaScriptCore/parser/ASTBuilder.h
r287531 r288473 112 112 typedef ImportSpecifierNode* ImportSpecifier; 113 113 typedef ImportSpecifierListNode* ImportSpecifierList; 114 typedef ImportAssertionListNode* ImportAssertionList; 114 115 typedef ExportSpecifierNode* ExportSpecifier; 115 116 typedef ExportSpecifierListNode* ExportSpecifierList; … … 180 181 return new (m_parserArena) SuperNode(location); 181 182 } 182 ExpressionNode* createImportExpr(const JSTokenLocation& location, ExpressionNode* expr, const JSTextPosition& start, const JSTextPosition& divot, const JSTextPosition& end)183 { 184 auto* node = new (m_parserArena) ImportNode(location, expr );183 ExpressionNode* createImportExpr(const JSTokenLocation& location, ExpressionNode* expr, ExpressionNode* option, const JSTextPosition& start, const JSTextPosition& divot, const JSTextPosition& end) 184 { 185 auto* node = new (m_parserArena) ImportNode(location, expr, option); 185 186 setExceptionLocation(node, start, divot, end); 186 187 return node; … … 812 813 } 813 814 814 StatementNode* createImportDeclaration(const JSTokenLocation& location, ImportSpecifierListNode* importSpecifierList, ModuleNameNode* moduleName) 815 { 816 return new (m_parserArena) ImportDeclarationNode(location, importSpecifierList, moduleName); 817 } 818 819 StatementNode* createExportAllDeclaration(const JSTokenLocation& location, ModuleNameNode* moduleName) 820 { 821 return new (m_parserArena) ExportAllDeclarationNode(location, moduleName); 815 ImportAssertionListNode* createImportAssertionList() 816 { 817 return new (m_parserArena) ImportAssertionListNode(); 818 } 819 820 void appendImportAssertion(ImportAssertionListNode* assertionList, const Identifier& key, const Identifier& value) 821 { 822 assertionList->append(key, value); 823 } 824 825 StatementNode* createImportDeclaration(const JSTokenLocation& location, ImportSpecifierListNode* importSpecifierList, ModuleNameNode* moduleName, ImportAssertionListNode* importAssertionList) 826 { 827 return new (m_parserArena) ImportDeclarationNode(location, importSpecifierList, moduleName, importAssertionList); 828 } 829 830 StatementNode* createExportAllDeclaration(const JSTokenLocation& location, ModuleNameNode* moduleName, ImportAssertionListNode* importAssertionList) 831 { 832 return new (m_parserArena) ExportAllDeclarationNode(location, moduleName, importAssertionList); 822 833 } 823 834 … … 832 843 } 833 844 834 StatementNode* createExportNamedDeclaration(const JSTokenLocation& location, ExportSpecifierListNode* exportSpecifierList, ModuleNameNode* moduleName )835 { 836 return new (m_parserArena) ExportNamedDeclarationNode(location, exportSpecifierList, moduleName );845 StatementNode* createExportNamedDeclaration(const JSTokenLocation& location, ExportSpecifierListNode* exportSpecifierList, ModuleNameNode* moduleName, ImportAssertionListNode* importAssertionList) 846 { 847 return new (m_parserArena) ExportNamedDeclarationNode(location, exportSpecifierList, moduleName, importAssertionList); 837 848 } 838 849 -
trunk/Source/JavaScriptCore/parser/NodeConstructors.h
r287531 r288473 180 180 } 181 181 182 inline ImportNode::ImportNode(const JSTokenLocation& location, ExpressionNode* expr) 183 : ExpressionNode(location) 184 , m_expr(expr) 182 inline ImportNode::ImportNode(const JSTokenLocation& location, ExpressionNode* expr, ExpressionNode* option) 183 : ExpressionNode(location) 184 , m_expr(expr) 185 , m_option(option) 185 186 { 186 187 } … … 866 867 } 867 868 868 inline ImportDeclarationNode::ImportDeclarationNode(const JSTokenLocation& location, ImportSpecifierListNode* importSpecifierList, ModuleNameNode* moduleName )869 inline ImportDeclarationNode::ImportDeclarationNode(const JSTokenLocation& location, ImportSpecifierListNode* importSpecifierList, ModuleNameNode* moduleName, ImportAssertionListNode* importAssertionList) 869 870 : ModuleDeclarationNode(location) 870 871 , m_specifierList(importSpecifierList) 871 872 , m_moduleName(moduleName) 872 { 873 } 874 875 inline ExportAllDeclarationNode::ExportAllDeclarationNode(const JSTokenLocation& location, ModuleNameNode* moduleName) 873 , m_assertionList(importAssertionList) 874 { 875 } 876 877 inline ExportAllDeclarationNode::ExportAllDeclarationNode(const JSTokenLocation& location, ModuleNameNode* moduleName, ImportAssertionListNode* importAssertionList) 876 878 : ModuleDeclarationNode(location) 877 879 , m_moduleName(moduleName) 880 , m_assertionList(importAssertionList) 878 881 { 879 882 } … … 892 895 } 893 896 894 inline ExportNamedDeclarationNode::ExportNamedDeclarationNode(const JSTokenLocation& location, ExportSpecifierListNode* exportSpecifierList, ModuleNameNode* moduleName )897 inline ExportNamedDeclarationNode::ExportNamedDeclarationNode(const JSTokenLocation& location, ExportSpecifierListNode* exportSpecifierList, ModuleNameNode* moduleName, ImportAssertionListNode* importAssertionList) 895 898 : ModuleDeclarationNode(location) 896 899 , m_specifierList(exportSpecifierList) 897 900 , m_moduleName(moduleName) 901 , m_assertionList(importAssertionList) 898 902 { 899 903 } -
trunk/Source/JavaScriptCore/parser/Nodes.h
r287531 r288473 628 628 class ImportNode final : public ExpressionNode, public ThrowableExpressionData { 629 629 public: 630 ImportNode(const JSTokenLocation&, ExpressionNode* );630 ImportNode(const JSTokenLocation&, ExpressionNode*, ExpressionNode*); 631 631 632 632 private: … … 635 635 636 636 ExpressionNode* m_expr; 637 ExpressionNode* m_option; 637 638 }; 638 639 … … 2058 2059 }; 2059 2060 2061 class ImportAssertionListNode final : public ParserArenaDeletable { 2062 JSC_MAKE_PARSER_ARENA_DELETABLE_ALLOCATED(ImportAssertionListNode); 2063 public: 2064 using Assertions = Vector<std::tuple<const Identifier*, const Identifier*>, 3>; 2065 2066 const Assertions& assertions() const { return m_assertions; } 2067 void append(const Identifier& key, const Identifier& value) 2068 { 2069 m_assertions.append(std::tuple { &key, &value }); 2070 } 2071 2072 private: 2073 Assertions m_assertions; 2074 }; 2075 2060 2076 class ModuleDeclarationNode : public StatementNode { 2061 2077 public: … … 2070 2086 class ImportDeclarationNode final : public ModuleDeclarationNode { 2071 2087 public: 2072 ImportDeclarationNode(const JSTokenLocation&, ImportSpecifierListNode*, ModuleNameNode* );2088 ImportDeclarationNode(const JSTokenLocation&, ImportSpecifierListNode*, ModuleNameNode*, ImportAssertionListNode*); 2073 2089 2074 2090 ImportSpecifierListNode* specifierList() const { return m_specifierList; } 2075 2091 ModuleNameNode* moduleName() const { return m_moduleName; } 2092 ImportAssertionListNode* assertionList() const { return m_assertionList; } 2076 2093 2077 2094 private: … … 2081 2098 ImportSpecifierListNode* m_specifierList; 2082 2099 ModuleNameNode* m_moduleName; 2100 ImportAssertionListNode* m_assertionList; 2083 2101 }; 2084 2102 2085 2103 class ExportAllDeclarationNode final : public ModuleDeclarationNode { 2086 2104 public: 2087 ExportAllDeclarationNode(const JSTokenLocation&, ModuleNameNode* );2105 ExportAllDeclarationNode(const JSTokenLocation&, ModuleNameNode*, ImportAssertionListNode*); 2088 2106 2089 2107 ModuleNameNode* moduleName() const { return m_moduleName; } 2108 ImportAssertionListNode* assertionList() const { return m_assertionList; } 2090 2109 2091 2110 private: … … 2094 2113 2095 2114 ModuleNameNode* m_moduleName; 2115 ImportAssertionListNode* m_assertionList; 2096 2116 }; 2097 2117 … … 2151 2171 class ExportNamedDeclarationNode final : public ModuleDeclarationNode { 2152 2172 public: 2153 ExportNamedDeclarationNode(const JSTokenLocation&, ExportSpecifierListNode*, ModuleNameNode* );2173 ExportNamedDeclarationNode(const JSTokenLocation&, ExportSpecifierListNode*, ModuleNameNode*, ImportAssertionListNode*); 2154 2174 2155 2175 ExportSpecifierListNode* specifierList() const { return m_specifierList; } 2156 2176 ModuleNameNode* moduleName() const { return m_moduleName; } 2177 ImportAssertionListNode* assertionList() const { return m_assertionList; } 2157 2178 2158 2179 private: … … 2161 2182 ExportSpecifierListNode* m_specifierList; 2162 2183 ModuleNameNode* m_moduleName { nullptr }; 2184 ImportAssertionListNode* m_assertionList { nullptr }; 2163 2185 }; 2164 2186 -
trunk/Source/JavaScriptCore/parser/Parser.cpp
r284435 r288473 3577 3577 3578 3578 template <typename LexerType> 3579 template <class TreeBuilder> typename TreeBuilder::ImportAssertionList Parser<LexerType>::parseImportAssertions(TreeBuilder& context) 3580 { 3581 auto assertionList = context.createImportAssertionList(); 3582 consumeOrFail(OPENBRACE, "Expected opening '{' at the start of import assertion"); 3583 while (!match(CLOSEBRACE)) { 3584 failIfFalse(matchIdentifierOrKeyword() || match(STRING), "Expected an assertion key"); 3585 auto key = m_token.m_data.ident; 3586 next(); 3587 consumeOrFail(COLON, "Expected ':' after assertion key"); 3588 failIfFalse(match(STRING), "Expected an assertion value"); 3589 auto value = m_token.m_data.ident; 3590 next(); 3591 context.appendImportAssertion(assertionList, *key, *value); 3592 if (!consume(COMMA)) 3593 break; 3594 } 3595 handleProductionOrFail2(CLOSEBRACE, "}", "end", "import assertion"); 3596 return assertionList; 3597 } 3598 3599 template <typename LexerType> 3579 3600 template <class TreeBuilder> TreeStatement Parser<LexerType>::parseImportDeclaration(TreeBuilder& context) 3580 3601 { … … 3588 3609 if (match(STRING)) { 3589 3610 // import ModuleSpecifier ; 3611 // import ModuleSpecifier [no LineTerminator here] AssertClause ; 3590 3612 auto moduleName = parseModuleName(context); 3591 3613 failIfFalse(moduleName, "Cannot parse the module name"); 3614 3615 typename TreeBuilder::ImportAssertionList assertionList = 0; 3616 if (Options::useImportAssertion() && !m_lexer->hasLineTerminatorBeforeToken() && matchContextualKeyword(m_vm.propertyNames->builtinNames().assertPublicName())) { 3617 next(); 3618 assertionList = parseImportAssertions(context); 3619 failIfFalse(assertionList, "Unable to parse import assertion"); 3620 } 3621 3592 3622 failIfFalse(autoSemiColon(), "Expected a ';' following a targeted import declaration"); 3593 return context.createImportDeclaration(importLocation, specifierList, moduleName );3623 return context.createImportDeclaration(importLocation, specifierList, moduleName, assertionList); 3594 3624 } 3595 3625 … … 3641 3671 auto moduleName = parseModuleName(context); 3642 3672 failIfFalse(moduleName, "Cannot parse the module name"); 3673 3674 // [no LineTerminator here] AssertClause ; 3675 typename TreeBuilder::ImportAssertionList assertionList = 0; 3676 if (Options::useImportAssertion() && !m_lexer->hasLineTerminatorBeforeToken() && matchContextualKeyword(m_vm.propertyNames->builtinNames().assertPublicName())) { 3677 next(); 3678 assertionList = parseImportAssertions(context); 3679 failIfFalse(assertionList, "Unable to parse import assertion"); 3680 } 3681 3643 3682 failIfFalse(autoSemiColon(), "Expected a ';' following a targeted import declaration"); 3644 3683 3645 return context.createImportDeclaration(importLocation, specifierList, moduleName );3684 return context.createImportDeclaration(importLocation, specifierList, moduleName, assertionList); 3646 3685 } 3647 3686 … … 3715 3754 auto moduleName = parseModuleName(context); 3716 3755 failIfFalse(moduleName, "Cannot parse the 'from' clause"); 3756 3757 // [no LineTerminator here] AssertClause ; 3758 typename TreeBuilder::ImportAssertionList assertionList = 0; 3759 if (Options::useImportAssertion() && !m_lexer->hasLineTerminatorBeforeToken() && matchContextualKeyword(m_vm.propertyNames->builtinNames().assertPublicName())) { 3760 next(); 3761 assertionList = parseImportAssertions(context); 3762 failIfFalse(assertionList, "Unable to parse import assertion"); 3763 } 3764 3717 3765 failIfFalse(autoSemiColon(), "Expected a ';' following a targeted export declaration"); 3718 3766 … … 3723 3771 auto specifier = context.createExportSpecifier(specifierLocation, *localName, *exportedName); 3724 3772 context.appendExportSpecifier(specifierList, specifier); 3725 return context.createExportNamedDeclaration(exportLocation, specifierList, moduleName );3726 } 3727 3728 return context.createExportAllDeclaration(exportLocation, moduleName );3773 return context.createExportNamedDeclaration(exportLocation, specifierList, moduleName, assertionList); 3774 } 3775 3776 return context.createExportAllDeclaration(exportLocation, moduleName, assertionList); 3729 3777 } 3730 3778 … … 3848 3896 3849 3897 typename TreeBuilder::ModuleName moduleName = 0; 3898 typename TreeBuilder::ImportAssertionList assertionList = 0; 3850 3899 if (matchContextualKeyword(m_vm.propertyNames->from)) { 3851 3900 next(); 3852 3901 moduleName = parseModuleName(context); 3853 3902 failIfFalse(moduleName, "Cannot parse the 'from' clause"); 3903 3904 // [no LineTerminator here] AssertClause ; 3905 if (Options::useImportAssertion() && !m_lexer->hasLineTerminatorBeforeToken() && matchContextualKeyword(m_vm.propertyNames->builtinNames().assertPublicName())) { 3906 next(); 3907 assertionList = parseImportAssertions(context); 3908 failIfFalse(assertionList, "Unable to parse import assertion"); 3909 } 3854 3910 } else 3855 3911 semanticFailIfTrue(hasReferencedModuleExportNames, "Cannot use module export names if they reference variable names in the current module"); … … 3873 3929 } 3874 3930 3875 return context.createExportNamedDeclaration(exportLocation, specifierList, moduleName );3931 return context.createExportNamedDeclaration(exportLocation, specifierList, moduleName, assertionList); 3876 3932 } 3877 3933 … … 5137 5193 } else { 5138 5194 semanticFailIfTrue(newCount, "Cannot use new with import"); 5139 consumeOrFail(OPENPAREN, "import call expects exactly one argument");5195 consumeOrFail(OPENPAREN, "import call expects one or two arguments"); 5140 5196 TreeExpression expr = parseAssignmentExpression(context); 5141 5197 failIfFalse(expr, "Cannot parse expression"); 5142 consumeOrFail(CLOSEPAREN, "import call expects exactly one argument"); 5143 base = context.createImportExpr(location, expr, expressionStart, expressionEnd, lastTokenEndPosition()); 5198 TreeExpression optionExpression = 0; 5199 if (consume(COMMA)) { 5200 if (!match(CLOSEPAREN)) { 5201 optionExpression = parseAssignmentExpression(context); 5202 failIfFalse(optionExpression, "Cannot parse expression"); 5203 consume(COMMA); 5204 } 5205 } 5206 consumeOrFail(CLOSEPAREN, "import call expects one or two arguments"); 5207 base = context.createImportExpr(location, expr, optionExpression, expressionStart, expressionEnd, lastTokenEndPosition()); 5144 5208 } 5145 5209 } else if (!baseIsNewTarget) { -
trunk/Source/JavaScriptCore/parser/Parser.h
r288261 r288473 1785 1785 template <class TreeBuilder> typename TreeBuilder::ImportSpecifier parseImportClauseItem(TreeBuilder&, ImportSpecifierType); 1786 1786 template <class TreeBuilder> typename TreeBuilder::ModuleName parseModuleName(TreeBuilder&); 1787 template <class TreeBuilder> typename TreeBuilder::ImportAssertionList parseImportAssertions(TreeBuilder&); 1787 1788 template <class TreeBuilder> TreeStatement parseImportDeclaration(TreeBuilder&); 1788 1789 template <class TreeBuilder> typename TreeBuilder::ExportSpecifier parseExportSpecifier(TreeBuilder& context, Vector<std::pair<const Identifier*, const Identifier*>>& maybeExportedLocalNames, bool& hasKeywordForLocalBindings, bool& hasReferencedModuleExportNames); -
trunk/Source/JavaScriptCore/parser/SyntaxChecker.h
r279447 r288473 87 87 TaggedTemplateExpr, YieldExpr, AwaitExpr, 88 88 ModuleNameResult, PrivateIdentifier, 89 ImportSpecifierResult, ImportSpecifierListResult, 89 ImportSpecifierResult, ImportSpecifierListResult, ImportAssertionListResult, 90 90 ExportSpecifierResult, ExportSpecifierListResult, 91 91 … … 129 129 typedef int ImportSpecifier; 130 130 typedef int ImportSpecifierList; 131 typedef int ImportAssertionList; 131 132 typedef int ExportSpecifier; 132 133 typedef int ExportSpecifierList; … … 160 161 ExpressionType createUnaryPlus(const JSTokenLocation&, ExpressionType) { return UnaryExpr; } 161 162 ExpressionType createVoid(const JSTokenLocation&, ExpressionType) { return UnaryExpr; } 162 ExpressionType createImportExpr(const JSTokenLocation&, ExpressionType, int, int, int) { return ImportExpr; }163 ExpressionType createImportExpr(const JSTokenLocation&, ExpressionType, ExpressionType, int, int, int) { return ImportExpr; } 163 164 ExpressionType createThisExpr(const JSTokenLocation&) { return ThisExpr; } 164 165 ExpressionType createSuperExpr(const JSTokenLocation&) { return SuperExpr; } … … 279 280 ImportSpecifierList createImportSpecifierList() { return ImportSpecifierListResult; } 280 281 void appendImportSpecifier(ImportSpecifierList, ImportSpecifier) { } 281 int createImportDeclaration(const JSTokenLocation&, ImportSpecifierList, ModuleName) { return StatementResult; } 282 int createExportAllDeclaration(const JSTokenLocation&, ModuleName) { return StatementResult; } 282 ImportAssertionList createImportAssertionList() { return ImportAssertionListResult; } 283 void appendImportAssertion(ImportAssertionList, const Identifier&, const Identifier&) { } 284 int createImportDeclaration(const JSTokenLocation&, ImportSpecifierList, ModuleName, ImportAssertionList) { return StatementResult; } 285 int createExportAllDeclaration(const JSTokenLocation&, ModuleName, ImportAssertionList) { return StatementResult; } 283 286 int createExportDefaultDeclaration(const JSTokenLocation&, int, const Identifier&) { return StatementResult; } 284 287 int createExportLocalDeclaration(const JSTokenLocation&, int) { return StatementResult; } 285 int createExportNamedDeclaration(const JSTokenLocation&, ExportSpecifierList, ModuleName ) { return StatementResult; }288 int createExportNamedDeclaration(const JSTokenLocation&, ExportSpecifierList, ModuleName, ImportAssertionList) { return StatementResult; } 286 289 ExportSpecifier createExportSpecifier(const JSTokenLocation&, const Identifier&, const Identifier&) { return ExportSpecifierResult; } 287 290 ExportSpecifierList createExportSpecifierList() { return ExportSpecifierListResult; } -
trunk/Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp
r287303 r288473 823 823 824 824 auto sourceOrigin = callFrame->callerSourceOrigin(vm); 825 RELEASE_ASSERT(callFrame->argumentCount() == 1);825 RELEASE_ASSERT(callFrame->argumentCount() >= 1); 826 826 auto* specifier = callFrame->uncheckedArgument(0).toString(globalObject); 827 827 RETURN_IF_EXCEPTION(scope, JSValue::encode(promise->rejectWithCaughtException(globalObject, scope))); -
trunk/Source/JavaScriptCore/runtime/OptionsList.h
r287136 r288473 542 542 v(Bool, useAtMethod, true, Normal, "Expose the at() method on Array, %TypedArray%, and String.") \ 543 543 v(Bool, useHasOwn, true, Normal, "Expose the Object.hasOwn method") \ 544 v(Bool, useImportAssertion, false, Normal, "Enable import assertion.") \ 544 545 v(Bool, useIntlEnumeration, true, Normal, "Expose the Intl enumeration APIs.") \ 545 546 v(Bool, useSharedArrayBuffer, false, Normal, nullptr) \
Note:
See TracChangeset
for help on using the changeset viewer.