.
702 | *
703 | * Implementation notes: This test checks whether some named macros are wrapped in other elements
704 | * than
s.
705 | */
706 |
707 | const ERROR = require('./doctests.js').ERROR;
708 |
709 | exports.incorrectlyWrappedSidebarMacros = {
710 | name: "incorrectly_wrapped_sidebar_macros",
711 | desc: "incorrectly_wrapped_sidebar_macros_desc",
712 |
713 | check: function checkIncorrectlyWrappedSidebarMacros(rootElement) {
714 | const allowedMacros = /^(?:apiref|cssref|htmlref|jsref|makesimplequicklinks|mathmlref|svgrefelem)$|sidebar$/i;
715 |
716 | let treeWalker = document.createTreeWalker(
717 | rootElement,
718 | NodeFilter.SHOW_TEXT,
719 | // eslint-disable-next-line
720 | {acceptNode: node => node.textContent.match(/\{\{.*?\}\}/) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT}
721 | );
722 | let matches = [];
723 |
724 | while (treeWalker.nextNode()) {
725 | let reMacroName = /\{\{\s*([^(}\s]+).*?\}\}/g;
726 | let macroNameMatch = reMacroName.exec(treeWalker.currentNode.textContent);
727 | while (macroNameMatch) {
728 | if (macroNameMatch[1].match(allowedMacros) !== null &&
729 | treeWalker.currentNode.parentElement.localName !== "div") {
730 | matches.push({
731 | node: treeWalker.currentNode.parentElement,
732 | msg: "wrong_element_wrapping_sidebar_macro",
733 | msgParams: [macroNameMatch[0], treeWalker.currentNode.parentElement.localName],
734 | type: ERROR
735 | });
736 | }
737 | macroNameMatch = reMacroName.exec(treeWalker.currentNode.textContent);
738 | }
739 | }
740 |
741 | return matches;
742 | },
743 |
744 | fix: function fixIncorrectlyWrappedSidebarMacros(matches) {
745 | matches.forEach(match => {
746 | let divElement = document.createElement("div");
747 | let childNodes = match.node.childNodes;
748 | for (let i = 0; i < childNodes.length; i++) {
749 | divElement.appendChild(childNodes[i].cloneNode(true));
750 | }
751 |
752 | match.node.parentNode.replaceChild(divElement, match.node);
753 | });
754 | }
755 | };
756 |
757 | },{"./doctests.js":9}],17:[function(require,module,exports){
758 | const docTests = {
759 | absoluteURLsForInternalLinks : require('./absolute-urls-for-internal-links.js').absoluteURLsForInternalLinks,
760 | alertPrintInCode : require('./alert-print-in-code.js').alertPrintInCode,
761 | anchorExists : require('./anchor-exists.js').anchorExists,
762 | apiSyntaxHeadlines : require('./api-syntax-headlines.js').apiSyntaxHeadlines,
763 | articleLength : require('./article-length.js').articleLength,
764 | codeInPre : require('./code-in-pre.js').codeInPre,
765 | dataMacroNote : require('./data-macro-note.js').dataMacroNote,
766 | differentLocaleLinks : require('./different-locale-links.js').differentLocaleLinks,
767 | emptyBrackets : require('./empty-brackets.js').emptyBrackets,
768 | emptyElements : require('./empty-elements.js').emptyElements,
769 | exampleColonHeading : require('./example-colon-heading.js').exampleColonHeading,
770 | fontElements : require('./font-elements.js').fontElements,
771 | htmlComments : require('./html-comments.js').htmlComments,
772 | httpLinks : require('./http-links.js').httpLinks,
773 | incorrectlyWrappedSidebarMacros : require('./incorrectly-wrapped-sidebar-macros.js').incorrectlyWrappedSidebarMacros,
774 | invalidMacros : require('./invalid-macros.js').invalidMacros,
775 | lineLengthInPre : require('./line-length-in-pre.js').lineLengthInPre,
776 | linkCount : require('./link-count.js').linkCount,
777 | macroSyntaxError : require('./macro-syntax-error.js').macroSyntaxError,
778 | mixedContent : require('./mixed-content.js').mixedContent,
779 | nameAttribute : require('./name-attribute.js').nameAttribute,
780 | oldURLs : require('./old-urls.js').oldURLs,
781 | preWithoutClass : require('./pre-without-class.js').preWithoutClass,
782 | shellPrompts : require('./shell-prompts.js').shellPrompts,
783 | spanCount : require('./span-count.js').spanCount,
784 | styleAttribute : require('./style-attribute.js').styleAttribute,
785 | summaryHeading : require('./summary-heading.js').summaryHeading,
786 | unnecessaryMacroParams : require('./unnecessary-macro-params.js').unnecessaryMacroParams,
787 | urlInLinkTitle : require('./url-in-link-title.js').urlInLinkTitle,
788 | wrongHighlightedLine : require('./wrong-highlighted-line.js').wrongHighlightedLine,
789 | wrongSyntaxClass : require('./wrong-syntax-class.js').wrongSyntaxClass
790 | };
791 |
792 | module.exports = docTests;
793 |
794 | },{"./absolute-urls-for-internal-links.js":1,"./alert-print-in-code.js":2,"./anchor-exists.js":3,"./api-syntax-headlines.js":4,"./article-length.js":5,"./code-in-pre.js":6,"./data-macro-note.js":7,"./different-locale-links.js":8,"./empty-brackets.js":10,"./empty-elements.js":11,"./example-colon-heading.js":12,"./font-elements.js":13,"./html-comments.js":14,"./http-links.js":15,"./incorrectly-wrapped-sidebar-macros.js":16,"./invalid-macros.js":18,"./line-length-in-pre.js":19,"./link-count.js":20,"./macro-syntax-error.js":21,"./mixed-content.js":22,"./name-attribute.js":23,"./old-urls.js":24,"./pre-without-class.js":25,"./shell-prompts.js":26,"./span-count.js":27,"./style-attribute.js":28,"./summary-heading.js":29,"./unnecessary-macro-params.js":30,"./url-in-link-title.js":31,"./wrong-highlighted-line.js":32,"./wrong-syntax-class.js":33}],18:[function(require,module,exports){
795 | /*
796 | * Title: Test for the usage of invalid macros.
797 | *
798 | * Example 1: The usage of
{{SomeMacro}}
should rather be removed, replaced by a valid
799 | * macro or by static text and {{SomeMacro}} should be deleted.
800 | *
801 | * Implementation notes: This test uses an (incomprehensive) whitelist of allowed macros and a
802 | * list of obsolete macros. Obsolete macros are marked as errors, all others, which are not
803 | * whitelisted are marked as warnings.
804 | */
805 |
806 | const ERROR = require('./doctests.js').ERROR;
807 | const WARNING = require('./doctests.js').WARNING;
808 |
809 | const obsoleteMacros = [
810 | "languages"
811 | ];
812 |
813 | exports.invalidMacros = {
814 | name: "invalid_macros",
815 | desc: "invalid_macros_desc",
816 | check: function checkInvalidMacros(rootElement) {
817 | const allowedMacros = [
818 | "addonsidebar",
819 | "apiref",
820 | "anch",
821 | "availableinworkers",
822 | "bug",
823 | "canvassidebar",
824 | "chromebug",
825 | "communitybox",
826 | "compat",
827 | "cssdata",
828 | "cssinfo",
829 | "cssref",
830 | "csssyntax",
831 | "cssxref",
832 | "defaultapisidebar",
833 | "deprecated_header",
834 | "deprecated_inline",
835 | "discussionlist",
836 | "docstatus",
837 | "domxref",
838 | "draft",
839 | "edgebug",
840 | "embedghlivesample",
841 | "embedlivesample",
842 | "embedyoutube",
843 | "event",
844 | "eventref",
845 | "experimental_inline",
846 | "firefox_for_developers",
847 | "fx_minversion_inline",
848 | "fxos_maxversion_inline",
849 | "fxos_minversion_inline",
850 | "gecko",
851 | "gecko_minversion_inline",
852 | "geckorelease",
853 | "glossary",
854 | "groupdata",
855 | "htmlattrdef",
856 | "htmlattrxref",
857 | "htmlelement",
858 | "htmlref",
859 | "httpheader",
860 | "httpmethod",
861 | "httpsidebar",
862 | "httpstatus",
863 | "includesubnav",
864 | "inheritancediagram",
865 | "interface",
866 | "interfacedata",
867 | "jsfiddlelink",
868 | "jsref",
869 | "jssidebar",
870 | "jsxref",
871 | "js_property_attributes",
872 | "l10n:common",
873 | "l10n:compattable",
874 | "l10n:css",
875 | "l10n:javascript",
876 | "l10n:svg",
877 | "localizationstatusinsection",
878 | "mathmlelement",
879 | "mathmlref",
880 | "next",
881 | "non-standard_header",
882 | "non-standard_inline",
883 | "noscript_inline",
884 | "obsolete_header",
885 | "obsolete_inline",
886 | "optional_inline",
887 | "page",
888 | "previous",
889 | "previousmenunext",
890 | "previousnext",
891 | "promote-mdn",
892 | "property_prefix",
893 | "readonlyinline",
894 | "releasegecko",
895 | "rfc",
896 | "seecompattable",
897 | "sidebarutilities",
898 | "sm_minversion_inline",
899 | "spec2",
900 | "specname",
901 | "svgattr",
902 | "svgdata",
903 | "svgelement",
904 | "svginfo",
905 | "svgref",
906 | "tb_minversion_inline",
907 | "webextapiembedtype",
908 | "webextapiref",
909 | "webextapisidebar",
910 | "webextchromecompat",
911 | "webextexamplesdata",
912 | "webextexamples",
913 | "webglsidebar",
914 | "webkitbug",
915 | "webrtcsidebar",
916 | "xref",
917 | "xulattr",
918 | "xulelem"
919 | ];
920 |
921 | let treeWalker = document.createTreeWalker(
922 | rootElement,
923 | NodeFilter.SHOW_TEXT,
924 | // eslint-disable-next-line
925 | {acceptNode: node => node.textContent.match(/\{\{.*?\}\}/) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT}
926 | );
927 | let matches = [];
928 |
929 | while (treeWalker.nextNode()) {
930 | let reMacroName = /\{\{\s*([^(}\s]+).*?\}\}/g;
931 | let macroNameMatch = reMacroName.exec(treeWalker.currentNode.textContent);
932 | while (macroNameMatch) {
933 | if (obsoleteMacros.includes(macroNameMatch[1].toLowerCase())) {
934 | matches.push({
935 | node: treeWalker.currentNode,
936 | msg: "obsolete_macro",
937 | msgParams: [macroNameMatch[0]],
938 | type: ERROR
939 | });
940 | } else if (!allowedMacros.includes(macroNameMatch[1].toLowerCase())) {
941 | matches.push({
942 | msg: macroNameMatch[0],
943 | type: WARNING
944 | });
945 | }
946 | macroNameMatch = reMacroName.exec(treeWalker.currentNode.textContent);
947 | }
948 | }
949 |
950 | return matches;
951 | },
952 |
953 | fix: function fixInvalidMacros(matches) {
954 | let reObsoleteMacros =
955 | new RegExp(`\\{\\{\\s*(?:${obsoleteMacros.join("|")}).*?\\}\\}`, "gi");
956 |
957 | matches.forEach(match => {
958 | if (!match.node) {
959 | return;
960 | }
961 |
962 | match.node.textContent = match.node.textContent.replace(reObsoleteMacros, "");
963 | if (match.node.parentNode.textContent.match(/^(\s| )*$/)) {
964 | match.node.parentNode.remove();
965 | }
966 | });
967 | }
968 | };
969 |
970 | },{"./doctests.js":9}],19:[function(require,module,exports){
971 | /*
972 | * Title: Test for the line length in code blocks.
973 | *
974 | * Example 1: Code blocks with very long lines like
975 | *
This is some code block with a long line exceeding the maximum of 78 characters.
976 | * should either be shortened or split into several lines to avoid the display of horizontal
977 | * scrollbars.
978 | *
979 | * Implementation notes: This test uses a threshold of 78 characters for the maximum length of
980 | * a line.
tags added while editing are replaced by line breaks and all other HTML tags
981 | * are removed.
982 | */
983 |
984 |
985 | const WARNING = require('./doctests.js').WARNING;
986 | const mapMatches = require('./doctests.js').mapMatches;
987 |
988 | exports.lineLengthInPre = {
989 | name: "pre_line_too_long",
990 | desc: "pre_line_too_long_desc",
991 | check: function checkLineLengthInPre(rootElement) {
992 | let pres = rootElement.getElementsByTagName("pre");
993 | let matches = [];
994 |
995 | for (let i = 0; i < pres.length; i++) {
996 | // While editing it happens that there are
s added instead of line break characters
997 | // Those need to be replaced by line breaks to correctly recognize long lines
998 | let codeBlock = pres[i].innerHTML.replace(/
/g, "\n");
999 |
1000 | // Remove all other HTML tags and only display the plain text
1001 | codeBlock = codeBlock.replace(/<.+?>/g, "");
1002 |
1003 | let longLines = codeBlock.match(/^(?:[^\r\n]|\r(?!\n)){78,}$/gm);
1004 | if (longLines) {
1005 | matches = matches.concat(longLines);
1006 | }
1007 | }
1008 |
1009 | return mapMatches(matches, WARNING);
1010 | }
1011 | };
1012 |
1013 | },{"./doctests.js":9}],20:[function(require,module,exports){
1014 | /*
1015 | * Title : Test whether there are too many links in a page
1016 | * This is done for SEO reasons, based on the assumption that in the case of a lot of links not all of them would be followed and indexed.
1017 | *
1018 | * A Warning is emitted between 100 and 250 links
1019 | * An Error is emitted for more than 250 links
1020 | * An Info is emitted with the amount of links
1021 | */
1022 |
1023 | const WARNING = require('./doctests.js').WARNING;
1024 | const ERROR = require('./doctests.js').ERROR;
1025 | const INFO = require('./doctests.js').INFO;
1026 |
1027 | exports.linkCount = {
1028 | name: "link_count",
1029 | desc: "link_count_desc",
1030 | check: function checkLinkCost(rootElement) {
1031 | let links = rootElement.getElementsByTagName("a");
1032 |
1033 | if(links.length >= 100 && links.length < 250) {
1034 | return [{
1035 | msg: "count_link_warning",
1036 | msgParams: [links.length],
1037 | type: WARNING
1038 | }];
1039 | }
1040 |
1041 | if(links.length >= 250) {
1042 | return [{
1043 | msg: "count_link_error",
1044 | msgParams: [links.length],
1045 | type: ERROR
1046 | }];
1047 | }
1048 |
1049 | return [{
1050 | msg: "count_link_info",
1051 | msgParams: [links.length],
1052 | type: INFO
1053 | }];
1054 | },
1055 |
1056 | fix: function fixLinkCost(matches) {
1057 | return;
1058 | }
1059 | };
1060 |
1061 | },{"./doctests.js":9}],21:[function(require,module,exports){
1062 | /*
1063 | * Title: Test for syntax errors in macro calls.
1064 | *
1065 | * Example 1: {{macro} misses a closing curly brace, so it will be recognized as error.
1066 | *
1067 | * Example 2: {{macro('param'}} misses a closing bracket, so it will be recognized as error.
1068 | *
1069 | * Example 3: {{macro("param"))}} has an additional closing bracket, so it will be recognized as error.
1070 | *
1071 | * Example 4: {{macro('param)}} and {{macro(param")}} have incorrectly quoted string parameters,
1072 | * so they will be recognized as errors.
1073 | *
1074 | * Implementation notes: This test uses regular expressions to recognize invalid macros.
1075 | * It currently fails to properly validate macros containing JSON parameters (see issue #139).
1076 | */
1077 |
1078 | const ERROR = require('./doctests.js').ERROR;
1079 |
1080 | exports.macroSyntaxError = {
1081 | name: "macro_syntax_error",
1082 | desc: "macro_syntax_error_desc",
1083 | check: function checkMacroSyntaxError(rootElement) {
1084 | function validateStringParams(macro) {
1085 | let paramListStartIndex = macro.indexOf("(") + 1;
1086 | let paramListEndMatch = macro.match(/\)*\s*\}{1,2}$/);
1087 | let paramListEndIndex = macro.length - paramListEndMatch[0].length;
1088 | let stringParamQuote = "";
1089 | for (let i = paramListStartIndex; i < paramListEndIndex; i++) {
1090 | if (macro[i] === "\"") {
1091 | if (stringParamQuote === "") {
1092 | stringParamQuote = "\"";
1093 | } else if (stringParamQuote === "\"" && macro[i - 1] !== "\\") {
1094 | stringParamQuote = "";
1095 | }
1096 | } else if (macro[i] === "'") {
1097 | if (stringParamQuote === "") {
1098 | stringParamQuote = "'";
1099 | } else if (stringParamQuote === "'" && macro[i - 1] !== "\\") {
1100 | stringParamQuote = "";
1101 | }
1102 | } else if (stringParamQuote === "" && macro[i].match(/[^\s,\d\-.]/)) {
1103 | return false;
1104 | }
1105 | }
1106 | return stringParamQuote === "";
1107 | }
1108 |
1109 | let treeWalker = document.createTreeWalker(
1110 | rootElement,
1111 | NodeFilter.SHOW_TEXT,
1112 | // eslint-disable-next-line
1113 | {acceptNode: node => node.textContent.match(/\{\{[^\(\}]*\([^\}]*\}\}|\{\{[^\}]*?\}(?:(?=[^\}])|$)/) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT}
1114 | );
1115 | let matches = [];
1116 |
1117 | while (treeWalker.nextNode()) {
1118 | let textNodeMatches = treeWalker.currentNode.textContent.match(/\{\{[^(}]*\([^}]*\}\}|\{\{[^}]*?\}(?:(?=[^}])|$)/gi) || [];
1119 | textNodeMatches.forEach(macro => {
1120 | if (macro.match(/[^}]\}$/)) {
1121 | matches.push({
1122 | msg: "missing_closing_curly_brace",
1123 | msgParams: [macro],
1124 | type: ERROR
1125 | });
1126 | }
1127 | if (macro.match(/^\{\{[^(]+\(.+?[^)\s]\s*\}\}$/)) {
1128 | matches.push({
1129 | msg: "missing_closing_bracket",
1130 | msgParams: [macro],
1131 | type: ERROR
1132 | });
1133 | }
1134 | if (!validateStringParams(macro)) {
1135 | matches.push({
1136 | msg: "string_parameter_incorrectly_quoted",
1137 | msgParams: [macro],
1138 | type: ERROR
1139 | });
1140 | }
1141 | if (macro.match(/\){2,}\}{1,2}$/)) {
1142 | matches.push({
1143 | msg: "additional_closing_bracket",
1144 | msgParams: [macro],
1145 | type: ERROR
1146 | });
1147 | }
1148 | });
1149 | }
1150 |
1151 | return matches;
1152 | }
1153 | };
1154 |
1155 | },{"./doctests.js":9}],22:[function(require,module,exports){
1156 | /*
1157 | * Title : Prevent non https content inclusion
1158 | *
1159 | * Implementation notes : We check that every
![]()
is using https://
1160 | */
1161 |
1162 | const ERROR = require('./doctests.js').ERROR;
1163 |
1164 | // The string must start by https://
1165 | const HTTPS_URL = /^https:\/\//
1166 |
1167 | exports.mixedContent = {
1168 | name: "mixed_content",
1169 | desc: "mixed_content_desc",
1170 | check: function checkMixedContent(rootElement) {
1171 | let images = rootElement.getElementsByTagName("img");
1172 | let matches = [];
1173 |
1174 | for(let index = 0; index < images.length; index++){
1175 | if(!images[index].getAttribute("src").match(HTTPS_URL)) {
1176 | matches.push({node: images[index], msg: images[index].outerHTML, type: ERROR});
1177 | }
1178 | }
1179 |
1180 | return matches;
1181 | }
1182 | };
1183 |
1184 | },{"./doctests.js":9}],23:[function(require,module,exports){
1185 | /*
1186 | * Title: Test for elements with 'name' attributes.
1187 | *
1188 | * Example 1:
Syntax
should rather be
.
1189 | *
1190 | * Example 2: The name="" attribute in
paragraph should rather be
1191 | * removed.
1192 | *
1193 | * Implementation notes: This test checks all elements containing 'name' attributes.
1194 | */
1195 |
1196 | const ERROR = require('./doctests.js').ERROR;
1197 |
1198 | exports.nameAttribute = {
1199 | name: "name_attributes",
1200 | desc: "name_attributes_desc",
1201 | check: function checkNameAttribute(rootElement) {
1202 | let elementsWithNameAttribute = rootElement.querySelectorAll("[name]");
1203 | let matches = [];
1204 |
1205 | for (let i = 0; i < elementsWithNameAttribute.length; i++) {
1206 | matches.push({
1207 | node: elementsWithNameAttribute[i],
1208 | msg: `name="${elementsWithNameAttribute[i].getAttribute("name")}"`,
1209 | type: ERROR
1210 | });
1211 | }
1212 |
1213 | return matches;
1214 | },
1215 | fix: function fixNameAttribute(matches) {
1216 | matches.forEach(match => {
1217 | match.node.removeAttribute("name");
1218 | });
1219 | }
1220 | };
1221 |
1222 | },{"./doctests.js":9}],24:[function(require,module,exports){
1223 | /*
1224 | * Title: Test for old /en/ MDN URLs.
1225 | *
1226 | * Example 1: All URLs using MDN links, which contain "/en/" as locale should be replaced by
1227 | * "/en-US/" URLs. E.g. CSS should rather be
1228 | * CSS.
1229 | *
1230 | * Implementation notes: This test checks whether a link's 'href' attribute starts with "/en/".
1231 | * It does not check whether the link is an internal MDN link, nor does it check different
1232 | * locales than the English one.
1233 | */
1234 |
1235 | const ERROR = require('./doctests.js').ERROR;
1236 |
1237 | exports.oldURLs = {
1238 | name: "old_en_urls",
1239 | desc: "old_en_urls_desc",
1240 | check: function checkOldURLs(rootElement) {
1241 | let links = rootElement.querySelectorAll("a[href^='/en/' i]");
1242 | let matches = [];
1243 |
1244 | for (let i = 0; i < links.length; i++) {
1245 | matches.push({
1246 | msg: links[i].outerHTML,
1247 | type: ERROR
1248 | });
1249 | }
1250 |
1251 | return matches;
1252 | }
1253 | };
1254 |
1255 | },{"./doctests.js":9}],25:[function(require,module,exports){
1256 | /*
1257 | * Title: Test for code blocks without 'class' attribute specifying the syntax highlighting.
1258 | *
1259 | * Example 1:
var x = 1
should rather be replaced by
1260 | *
var x = 1
.
1261 | *
1262 | * Implementation notes: This test checks all
elements that have either an empty 'class'
1263 | * attribute or none at all. It also checks for elements that have the class 'eval'
1264 | */
1265 |
1266 | const WARNING = require('./doctests.js').WARNING;
1267 | const ERROR = require('./doctests.js').ERROR;
1268 |
1269 | exports.preWithoutClass = {
1270 | name: "pre_without_class",
1271 | desc: "pre_without_class_desc",
1272 | check: function checkPreWithoutClass(rootElement) {
1273 | let presWithoutClass = rootElement.querySelectorAll("pre:not([class]), pre[class='']");
1274 | let presWithEvalClass = rootElement.querySelectorAll("pre[class='eval']");
1275 | let matches = [];
1276 |
1277 | for (let i = 0; i < presWithoutClass.length; i++) {
1278 | // If the content is recognized as folder structure, don't add a warning for empty
1279 | if (presWithoutClass[i].textContent.match(/^\S[^\n*]*\/\n/)) {
1280 | continue;
1281 | }
1282 |
1283 | let type = WARNING;
1284 |
1285 | // If the content is recognized as code or {{csssyntax}} macro, mark it as error
1286 | if (presWithoutClass[i].textContent.match(/^\s*(?:\/\*.+?\*\/|<.+?>|@[^\s\n]+[^\n]*\{\n|\{\{\s*csssyntax(?:\(\))?\s*\}\})/)) {
1287 | type = ERROR;
1288 | }
1289 |
1290 | matches.push({
1291 | msg: presWithoutClass[i].outerHTML,
1292 | type
1293 | });
1294 | }
1295 |
1296 | for(let i = 0; i < presWithEvalClass.length; i++) {
1297 | matches.push({
1298 | msg: presWithEvalClass[i].outerHTML,
1299 | type: ERROR
1300 | });
1301 | }
1302 |
1303 | return matches;
1304 | }
1305 | };
1306 |
1307 | },{"./doctests.js":9}],26:[function(require,module,exports){
1308 | /*
1309 | * Title: Test for shell prompts, i.e. lines starting with '>' or '$' in code blocks.
1310 | *
1311 | * Example 1: $user:
should rather be replaced by
1312 | * .
1313 | *
1314 | * Implementation notes: This test checks whether lines within elements start with '$' or
1315 | * '>'.
1316 | */
1317 |
1318 | const ERROR = require('./doctests.js').ERROR;
1319 |
1320 | exports.shellPrompts = {
1321 | name: "shell_prompts",
1322 | desc: "shell_prompts_desc",
1323 | check: function checkShellPrompts(rootElement) {
1324 | let pres = rootElement.querySelectorAll("pre");
1325 | let matches = [];
1326 |
1327 | for (let i = 0; i < pres.length; i++) {
1328 | let code = pres[i].innerHTML.replace(/
/g, "\n").replace(" ", " ");
1329 | let shellPrompts = code.match(/^(?:\$|>).*/gm);
1330 | if (shellPrompts) {
1331 | shellPrompts.forEach(function addMatch(shellPrompt) {
1332 | matches.push({
1333 | msg: shellPrompt.replace(/<.+?>/g, ""),
1334 | type: ERROR
1335 | });
1336 | });
1337 | }
1338 | }
1339 |
1340 | return matches;
1341 | }
1342 | };
1343 |
1344 | },{"./doctests.js":9}],27:[function(require,module,exports){
1345 | /*
1346 | * Title: Test for incorrectly used elements.
1347 | *
1348 | * Example 1: Emphasized text/ should rather be replaced
1349 | * by Emphasized text.
1350 | *
1351 | * Implementation notes: This test searches for all elements, which don't hold the SEO
1352 | * summary and are not part of CKEditor's new paragraph helper.
1353 | */
1354 |
1355 | const ERROR = require('./doctests.js').ERROR;
1356 |
1357 | const isNewParagraphHelper = require('./doctests.js').isNewParagraphHelper;
1358 |
1359 | exports.spanCount = {
1360 | name: "span_elements",
1361 | desc: "span_elements_desc",
1362 |
1363 | check: function checkSpanCount(rootElement) {
1364 | let spanElements = rootElement.querySelectorAll("span:not(.seoSummary)");
1365 | let matches = [];
1366 |
1367 | for (let i = 0; i < spanElements.length; i++) {
1368 | let node = spanElements[i];
1369 |
1370 | // Exclude new paragraph helper
1371 | if (isNewParagraphHelper(node) || isNewParagraphHelper(node.firstElementChild)) {
1372 | continue;
1373 | }
1374 |
1375 | matches.push({
1376 | node,
1377 | msg: node.outerHTML,
1378 | type: ERROR
1379 | });
1380 | }
1381 |
1382 | return matches;
1383 | },
1384 |
1385 | fix: function fixSpanCount(matches) {
1386 | matches.forEach(match => {
1387 | // Remove element in case it is unstyled
1388 | if (!match.node.getAttribute("id") && !match.node.getAttribute("class") && !match.node.getAttribute("style")) {
1389 | match.node.remove();
1390 | }
1391 | });
1392 | }
1393 | };
1394 |
1395 | },{"./doctests.js":9}],28:[function(require,module,exports){
1396 | /*
1397 | * Title: Test for incorrectly 'style' attributes.
1398 | *
1399 | * Example 1: Emphasized text/
should rather be replaced
1400 | * by
Emphasized text
.
1401 | *
1402 | * Implementation notes: This test searches for all 'style' attributes, which are not part of
1403 | * CKEditor's new paragraph helper.
1404 | */
1405 |
1406 | const ERROR = require('./doctests.js').ERROR;
1407 |
1408 | const isNewParagraphHelper = require('./doctests.js').isNewParagraphHelper;
1409 |
1410 | exports.styleAttribute = {
1411 | name: "style_attributes",
1412 | desc: "style_attributes_desc",
1413 | check: function checkStyleAttribute(rootElement) {
1414 | let elementsWithStyleAttribute = rootElement.querySelectorAll("[style]");
1415 | let matches = [];
1416 |
1417 | for (let i = 0; i < elementsWithStyleAttribute.length; i++) {
1418 | let node = elementsWithStyleAttribute[i];
1419 |
1420 | // Exclude new paragraph helper
1421 | if (isNewParagraphHelper(node) || isNewParagraphHelper(node.firstElementChild)) {
1422 | continue;
1423 | }
1424 |
1425 | matches.push({
1426 | msg: `style="${node.getAttribute("style")}"`,
1427 | type: ERROR
1428 | });
1429 | }
1430 |
1431 | return matches;
1432 | }
1433 | };
1434 |
1435 | },{"./doctests.js":9}],29:[function(require,module,exports){
1436 | /*
1437 | * Title: Test for obsolete 'Summary' heading.
1438 | *
1439 | * Example 1: Summary
is redundant, because the page title is shown above the article,
1440 | * so it should be removed.
1441 | *
1442 | */
1443 |
1444 |
1445 | const ERROR = require('./doctests.js').ERROR;
1446 |
1447 | exports.summaryHeading = {
1448 | name: "summary_heading",
1449 | desc: "summary_heading_desc",
1450 |
1451 | check: function checkSummaryHeading(rootElement) {
1452 | let headlines = rootElement.querySelectorAll("h1, h2, h3, h4, h5, h6");
1453 | let matches = [];
1454 |
1455 | if (headlines[0].textContent.match(/^\s*Summary\s*$/)) {
1456 | matches.push({
1457 | node: headlines[0],
1458 | msg: headlines[0].outerHTML,
1459 | type: ERROR
1460 | });
1461 | }
1462 |
1463 | return matches;
1464 | },
1465 |
1466 | fix: function fixSummaryHeading(matches) {
1467 | matches.forEach(match => match.node.remove());
1468 | }
1469 | };
1470 |
1471 | },{"./doctests.js":9}],30:[function(require,module,exports){
1472 | /*
1473 | * Title: Test for obsolete macro parameters.
1474 | *
1475 | * Example 1: Some macros like {{JSRef}} don't require parameters anymore, so they should be
1476 | * removed.
1477 | *
1478 | * Example 2: Some macros like {{cssinfo}} don't require parameters when the related information
1479 | * can be read from the page's slug, so they should be removed in those cases.
1480 | *
1481 | * Implementation notes: This test checks for a specific list of macros, which either have no
1482 | * parameters at all or their parameters are redundant. It uses the page title for comparison, so
1483 | * the unit test doesn't break while working on about:blank.
1484 | */
1485 |
1486 |
1487 | const ERROR = require('./doctests.js').ERROR;
1488 |
1489 | const reMacrosNotRequiringParams = /\{\{\s*(?:JSRef|csssyntax|cssinfo|svginfo)\([^)]+?\)\s*\}\}/i;
1490 | const reMacrosNotRequiringParamsGlobal = new RegExp(reMacrosNotRequiringParams.source, "gi");
1491 |
1492 | exports.unnecessaryMacroParams = {
1493 | name: "unnecessary_macro_params",
1494 | desc: "unnecessary_macro_params_desc",
1495 | check: function checkUnnecessaryMacroParams(rootElement) {
1496 | let treeWalker = document.createTreeWalker(
1497 | rootElement,
1498 | NodeFilter.SHOW_TEXT,
1499 | {
1500 | // eslint-disable-next-line
1501 | acceptNode: node => node.textContent.match(reMacrosNotRequiringParams) ?
1502 | NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
1503 | }
1504 | );
1505 | let matches = [];
1506 |
1507 | while (treeWalker.nextNode()) {
1508 | let textNodeMatches = treeWalker.currentNode.textContent.match(
1509 | reMacrosNotRequiringParamsGlobal) || [];
1510 | textNodeMatches.forEach(match => {
1511 | let paramMatch = match.match(/(?:csssyntax|cssinfo|svginfo)\((["'])(.+?)\1/i);
1512 | if (paramMatch) {
1513 | let param = paramMatch[2];
1514 | if (param === document.title.replace(/^(.+?) \| Edit.*$/, "$1")) {
1515 | matches.push({
1516 | msg: "macro_with_unnecessary_params_equalling_slug",
1517 | msgParams: [match],
1518 | type: ERROR
1519 | });
1520 | }
1521 | } else {
1522 | matches.push({
1523 | msg: "macro_with_unused_params",
1524 | msgParams: [match],
1525 | type: ERROR
1526 | });
1527 | }
1528 | });
1529 | }
1530 |
1531 | return matches;
1532 | }
1533 | };
1534 |
1535 | },{"./doctests.js":9}],31:[function(require,module,exports){
1536 | /*
1537 | * Title: Test for incorrectly used URLs in link titles.
1538 | *
1539 | * Example 1: The 'title' attribute on
1540 | * CSS
1541 | * should be removed, because it's redundant.
1542 | *
1543 | * Example 2: The 'title' attribute on
1544 | * CSS
1545 | * should be removed, because it's redundant and misleading.
1546 | *
1547 | * Implementation notes: This test checks whether the 'title' attribute of an element
1548 | * contains the same URL or a part of it as within its 'href' attribute. It also handles URLs
1549 | * using two-character locales vs. four character locales, e.g. "/en-US/" and "/en/".
1550 | */
1551 |
1552 | const ERROR = require('./doctests.js').ERROR;
1553 |
1554 | exports.urlInLinkTitle = {
1555 | name: "url_in_link_title",
1556 | desc: "url_in_link_title_desc",
1557 | check: function checkURLsInTitleAttributes(rootElement) {
1558 | let linkElements = rootElement.getElementsByTagName("a");
1559 | let matches = [];
1560 |
1561 | for (let i = 0; i < linkElements.length; i++) {
1562 | let href = (linkElements[i].getAttribute("href") || "").toLowerCase();
1563 | let title = (linkElements[i].getAttribute("title") || "").toLowerCase();
1564 | if (title !== "" && (href.indexOf(title) !== -1 ||
1565 | (title.match(/[a-z]{2}(?:-[A-Z]{2})?\/docs\/.*?\//) ||
1566 | title === href.replace(/([a-z]{2})(?:-[a-z]{2})?\/docs\/(.*)/, "$1/$2")))) {
1567 | matches.push({
1568 | node: linkElements[i],
1569 | msg: linkElements[i].outerHTML,
1570 | type: ERROR
1571 | });
1572 | }
1573 | }
1574 |
1575 | return matches;
1576 | },
1577 | fix: function fixURLsInTitleAttributes(matches) {
1578 | matches.forEach(match => {
1579 | match.node.removeAttribute("title");
1580 | });
1581 | }
1582 | };
1583 |
1584 | },{"./doctests.js":9}],32:[function(require,module,exports){
1585 | /*
1586 | * Title: Test for incorrect line highlights in code examples.
1587 | *
1588 | * Example 1: Negative highlights like in
1589 | * var x = 1;
1590 | * are invalid.
1591 | *
1592 | * Example 2: Highlights exceeding the line count like in
1593 | * var x = 1;
1594 | * are invalid.
1595 | *
1596 | * Example 3: Highlighted ranges of lines exceeding the line count like in
1597 | * var x = 1;
1598 | * are invalid.
1599 | *
1600 | * Example 4: Highlighted ranges where the start line is bigger than the end line line in
1601 | * var x = 1;\nvar y = 2;
1602 | * are invalid.
1603 | *
1604 | * Implementation notes: This test searches for all elements containing a 'highlight'
1605 | * class, then splits the numbers and ranges wrapped by square brackets following the 'highlight'
1606 | * class and finally checks each item whether its valid.
1607 | */
1608 |
1609 | const ERROR = require('./doctests.js').ERROR;
1610 |
1611 | const reHighlighting = /highlight:?\s*\[(.+?)\]/i;
1612 |
1613 | exports.wrongHighlightedLine = {
1614 | name: "wrong_highlighted_line",
1615 | desc: "wrong_highlighted_line_desc",
1616 |
1617 | check: function checkWrongHighlightedLine(rootElement) {
1618 | let presWithHighlighting = rootElement.querySelectorAll("pre[class*='highlight']");
1619 | let matches = [];
1620 |
1621 | for (let i = 0; i < presWithHighlighting.length; i++) {
1622 | let match = presWithHighlighting[i].getAttribute("class").match(reHighlighting);
1623 | if (match) {
1624 | let numbersAndRanges = match[1].split(",");
1625 | let lineCount = presWithHighlighting[i].innerHTML.split(/
|\n/gi).length;
1626 |
1627 | numbersAndRanges.forEach(numberOrRange => {
1628 | let start;
1629 | let end;
1630 | [, start, end] = numberOrRange.match(/^\s*(-?\d+)(?:\s*-\s*(-?\d+))?\s*$/);
1631 |
1632 | if (start === undefined) {
1633 | return;
1634 | }
1635 |
1636 | start = Number(start);
1637 | end = Number(end);
1638 |
1639 | if (start <= 0) {
1640 | matches.push({
1641 | node: presWithHighlighting[i],
1642 | msg: "highlighted_line_number_not_positive",
1643 | msgParams: [String(start), match[1]],
1644 | type: ERROR
1645 | });
1646 | }
1647 | if (start > lineCount) {
1648 | matches.push({
1649 | node: presWithHighlighting[i],
1650 | msg: "highlighted_line_number_too_big",
1651 | msgParams: [String(start), String(lineCount), match[1]],
1652 | type: ERROR
1653 | });
1654 | }
1655 | if (!Number.isNaN(end)) {
1656 | if (end > lineCount) {
1657 | matches.push({
1658 | node: presWithHighlighting[i],
1659 | msg: "highlighted_line_number_too_big",
1660 | msgParams: [String(end), String(lineCount), match[1]],
1661 | type: ERROR
1662 | });
1663 | }
1664 | if (end <= 0) {
1665 | matches.push({
1666 | node: presWithHighlighting[i],
1667 | msg: "highlighted_line_number_not_positive",
1668 | msgParams: [String(end), match[1]],
1669 | type: ERROR
1670 | });
1671 | }
1672 | if (start > end) {
1673 | matches.push({
1674 | node: presWithHighlighting[i],
1675 | msg: "invalid_highlighted_range",
1676 | msgParams: [String(start), String(end), match[1]],
1677 | type: ERROR
1678 | });
1679 | }
1680 | }
1681 | });
1682 | }
1683 | }
1684 |
1685 | return matches;
1686 | },
1687 |
1688 | fix: function fixWrongHighlightedLine(matches) {
1689 | matches.forEach(match => {
1690 | match.node.className = match.node.className.replace(reHighlighting, "").replace(/;\s*$/, "");
1691 | });
1692 | }
1693 | };
1694 |
1695 | },{"./doctests.js":9}],33:[function(require,module,exports){
1696 | /*
1697 | * Title: Test for whether the 'syntax' class is properly used on a syntax block.
1698 | *
1699 | * Example 1: elements following a 'Formal syntax' heading are expected to contain a syntax
1700 | * definition, which needs to be styled using class="syntaxbox".
1701 | *
1702 | * Example 2: elements following a 'Syntax' heading where there is no 'Formal syntax'
1703 | * section are expected to contain a syntax definition, which needs to be styled using
1704 | * class="syntaxbox".
1705 | *
1706 | * Implementation notes: This test first searches for an Formal syntax
heading. If none
1707 | * is found, it searches for a Syntax
heading. If one of those is found, the following
1708 | * element is expected to hold a syntax definition, which needs to be styled using
1709 | * class="syntaxbox".
1710 | */
1711 |
1712 | const ERROR = require('./doctests.js').ERROR;
1713 |
1714 | exports.wrongSyntaxClass = {
1715 | name: "wrong_syntax_class",
1716 | desc: "wrong_syntax_class_desc",
1717 | check: function checkWrongSyntaxClass(rootElement) {
1718 | function checkPre(heading) {
1719 | let element = heading.nextSibling;
1720 | while (element && element.localName !== "h2") {
1721 | if (element.localName === "pre" && element.className !== "syntaxbox") {
1722 | return {
1723 | node: element,
1724 | msg: "wrong_syntax_class_used",
1725 | msgParams: [element.className],
1726 | type: ERROR
1727 | };
1728 | }
1729 | element = element.nextElementSibling;
1730 | }
1731 | return undefined;
1732 | }
1733 |
1734 | let subHeadings = rootElement.getElementsByTagName("h3");
1735 | let formalSyntaxSection = null;
1736 | for (let i = 0; !formalSyntaxSection && i < subHeadings.length; i++) {
1737 | if (subHeadings[i].textContent.match(/Formal syntax/i)) {
1738 | formalSyntaxSection = subHeadings[i];
1739 | }
1740 | }
1741 |
1742 | let matches = [];
1743 | if (formalSyntaxSection) {
1744 | let match = checkPre(formalSyntaxSection);
1745 | if (match) {
1746 | matches.push(match);
1747 | }
1748 | } else {
1749 | let headings = rootElement.getElementsByTagName("h2");
1750 | let syntaxSection = null;
1751 | for (let i = 0; !syntaxSection && i < headings.length; i++) {
1752 | if (headings[i].textContent.toLowerCase() === "syntax") {
1753 | syntaxSection = headings[i];
1754 | }
1755 | }
1756 |
1757 | if (syntaxSection) {
1758 | let match = checkPre(syntaxSection);
1759 | if (match) {
1760 | matches.push(match);
1761 | }
1762 | }
1763 | }
1764 |
1765 | return matches;
1766 | },
1767 | fix: function fixWrongSyntaxClass(matches) {
1768 | matches.forEach(match => {
1769 | match.node.className = "syntaxbox";
1770 | });
1771 | }
1772 | };
1773 |
1774 | },{"./doctests.js":9}]},{},[17])(17)
1775 | });
1776 |
--------------------------------------------------------------------------------