[nobug] Allow registering task scanners by filename extensions
        Correct the task text extraction in single-line JS block comments
diff --git a/core/bundles/org.eclipse.wst.sse.core/schema/taskscanner.exsd b/core/bundles/org.eclipse.wst.sse.core/schema/taskscanner.exsd
index 5b561aa..1d049dc 100644
--- a/core/bundles/org.eclipse.wst.sse.core/schema/taskscanner.exsd
+++ b/core/bundles/org.eclipse.wst.sse.core/schema/taskscanner.exsd
@@ -56,6 +56,13 @@
                </documentation>
             </annotation>
          </attribute>
+         <attribute name="fileExtensions" type="string" use="optional">
+            <annotation>
+               <documentation>
+                  Case-insensitive filename extensions for which this scanner applies, separated by commas.
+               </documentation>
+            </annotation>
+         </attribute>
          <attribute name="class" type="string" use="required">
             <annotation>
                <documentation>
diff --git a/core/bundles/org.eclipse.wst.sse.core/src-tasktags/org/eclipse/wst/sse/core/internal/tasks/FileTaskScannerRegistryReader.java b/core/bundles/org.eclipse.wst.sse.core/src-tasktags/org/eclipse/wst/sse/core/internal/tasks/FileTaskScannerRegistryReader.java
index 3bd9579..bae6f5b 100644
--- a/core/bundles/org.eclipse.wst.sse.core/src-tasktags/org/eclipse/wst/sse/core/internal/tasks/FileTaskScannerRegistryReader.java
+++ b/core/bundles/org.eclipse.wst.sse.core/src-tasktags/org/eclipse/wst/sse/core/internal/tasks/FileTaskScannerRegistryReader.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2001, 2018 IBM Corporation and others.
+ * Copyright (c) 2001, 2020 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
  * which accompanies this distribution, and is available at
@@ -10,18 +10,24 @@
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *     Jens Lukowski/Innoopract - initial renaming/restructuring
- *     David Carver (Intalio) - bug 300434 - Make inner classes static where possibl *     
+ *     David Carver (Intalio) - bug 300434 - Make inner classes static where
+ *                                           possible
+ *
  *******************************************************************************/
 package org.eclipse.wst.sse.core.internal.tasks;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.content.IContentType;
 import org.eclipse.core.runtime.content.IContentTypeManager;
@@ -31,6 +37,15 @@
 import org.eclipse.wst.sse.core.utils.StringUtils;
 
 public class FileTaskScannerRegistryReader {
+	static class EnabledDisabledScanners {
+		public IFileTaskScanner[] enabled;
+		public IFileTaskScanner[] disabled;
+		EnabledDisabledScanners(IFileTaskScanner[] enabled, IFileTaskScanner[] disabled) {
+			this.enabled = enabled;
+			this.disabled = disabled;
+		}
+	}
+
 	private static class ScannerInfo {
 		String fId;
 		IFileTaskScanner fScanner;
@@ -60,15 +75,18 @@
 	}
 
 	private String ATT_CLASS = "class"; //$NON-NLS-1$
+	private String ATT_ID = "id"; //$NON-NLS-1$
 
 	private String ATT_CONTENT_TYPES = "contentTypeIds"; //$NON-NLS-1$
-
-	private String ATT_ID = "id"; //$NON-NLS-1$
+	private String ATT_FILENAME_EXTENSIONS = "fileExtensions"; //$NON-NLS-1$
 
 	private IConfigurationElement[] fScannerElements;
 
 	// a mapping from content types to ScannerInfo instances
-	private Map<String, ScannerInfo[]> fScannerInfos = null;
+	private Map<String, ScannerInfo[]> fScannerInfoByContentTypes = null;
+
+	// a mapping from filename extensions to scanner configuration elements
+	private Map<String, IConfigurationElement[]> fConfigurationElementsByFilenameExtensions = null;
 
 	private String NAME_SCANNER = "scanner"; //$NON-NLS-1$
 
@@ -86,7 +104,7 @@
 		List<ScannerInfo> scannerInfos = new ArrayList<>(1);
 
 		for (int i = 0; i < contentTypes.length; i++) {
-			ScannerInfo[] scannerInfosForContentType = fScannerInfos.get(contentTypes[i].getId());
+			ScannerInfo[] scannerInfosForContentType = fScannerInfoByContentTypes.get(contentTypes[i].getId());
 			if (scannerInfosForContentType == null) {
 				scannerInfosForContentType = loadScanners(contentTypes[i]);
 			}
@@ -104,6 +122,66 @@
 		return scanners;
 	}
 
+	EnabledDisabledScanners getFileTaskScanners(IPath filePath, IContentType[] exceptForContentTypeIds) {
+		if (fScannerElements == null) {
+			readRegistry();
+		}
+		String fileExtension = filePath.getFileExtension();
+		// what if there is no filename extension?
+		if (fileExtension == null) {
+			return new EnabledDisabledScanners(new IFileTaskScanner[0], new IFileTaskScanner[0]);
+		}
+
+		String loweredExtension = fileExtension.toLowerCase(Locale.US);
+		IConfigurationElement[] matchingElements = fConfigurationElementsByFilenameExtensions.get(loweredExtension);
+		if (matchingElements == null) {
+			Set<String> classnames = new HashSet<>();
+			List<IConfigurationElement> collectedMatchingElements = new ArrayList<>(0);
+			for (int i = 0; i < fScannerElements.length; i++) {
+				if (!fScannerElements[i].getName().equals(NAME_SCANNER))
+					continue;
+				String[] supportedFilenameExtensions = StringUtils.unpack(fScannerElements[i].getAttribute(ATT_FILENAME_EXTENSIONS));
+				for (int k = 0; k < supportedFilenameExtensions.length; k++) {
+					String scannerClassName = fScannerElements[i].getAttribute(ATT_CLASS);
+					if (scannerClassName != null && loweredExtension.equals(supportedFilenameExtensions[k].toLowerCase(Locale.US)) && !classnames.contains(scannerClassName)) {
+						classnames.add(scannerClassName);
+						collectedMatchingElements.add(fScannerElements[i]);
+					}
+				}
+			}
+			matchingElements = collectedMatchingElements.toArray(new IConfigurationElement[collectedMatchingElements.size()]);
+			fConfigurationElementsByFilenameExtensions.put(loweredExtension, matchingElements);
+		}
+		// instantiate and return the scanners
+		List<IFileTaskScanner> enabled = new ArrayList<>();
+		List<IFileTaskScanner> disabled = new ArrayList<>();
+		for (int i = 0; i < matchingElements.length; i++) {
+			boolean disallowed = false;
+			if (exceptForContentTypeIds.length > 0) {
+				String[] supportedContentTypeIds = StringUtils.unpack(matchingElements[i].getAttribute(ATT_CONTENT_TYPES));
+				for (int j = 0; !disallowed && j < supportedContentTypeIds.length; j++) {
+					for (int k = 0; !disallowed && k < exceptForContentTypeIds.length; k++) {
+						if (supportedContentTypeIds[j].equals(exceptForContentTypeIds[k].getId()))
+							disallowed = true;
+					}
+				}
+			}
+			try {
+				IFileTaskScanner scanner = (IFileTaskScanner) matchingElements[i].createExecutableExtension(ATT_CLASS);
+				if (disallowed) {
+					disabled.add(scanner);
+				}
+				else {
+					enabled.add(scanner);
+				}
+			}
+			catch (CoreException e) {
+				Logger.logException("Non-fatal exception creating file task scanner for " + filePath + " from " + matchingElements[i].getContributor().getName(), e); //$NON-NLS-1$
+			}
+		}
+		return new EnabledDisabledScanners(enabled.toArray(new IFileTaskScanner[enabled.size()]), disabled.toArray(new IFileTaskScanner[disabled.size()]));
+	}
+
 	public String[] getSupportedContentTypeIds() {
 		if (fScannerElements == null) {
 			readRegistry();
@@ -161,7 +239,7 @@
 				}
 			}
 			scannerInfos = scannerInfoList.toArray(new ScannerInfo[scannerInfoList.size()]);
-			fScannerInfos.put(contentType.getId(), scannerInfos);
+			fScannerInfoByContentTypes.put(contentType.getId(), scannerInfos);
 			if (Logger.DEBUG_TASKSREGISTRY) {
 				System.out.println("Created " + scannerInfos.length + " task scanner for " + contentType.getId()); //$NON-NLS-1$ //$NON-NLS-2$
 			}
@@ -170,7 +248,8 @@
 	}
 
 	private void readRegistry() {
-		fScannerInfos = new HashMap<>();
+		fScannerInfoByContentTypes = new HashMap<>();
+		fConfigurationElementsByFilenameExtensions = new HashMap<>();
 		// Just remember the elements, so plug-ins don't have to be activated,
 		// unless extension attributes match those of interest
 		IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(SCANNER_EXTENSION_POINT_ID);
diff --git a/core/bundles/org.eclipse.wst.sse.core/src-tasktags/org/eclipse/wst/sse/core/internal/tasks/WorkspaceTaskScanner.java b/core/bundles/org.eclipse.wst.sse.core/src-tasktags/org/eclipse/wst/sse/core/internal/tasks/WorkspaceTaskScanner.java
index 1de98b7..483561e 100644
--- a/core/bundles/org.eclipse.wst.sse.core/src-tasktags/org/eclipse/wst/sse/core/internal/tasks/WorkspaceTaskScanner.java
+++ b/core/bundles/org.eclipse.wst.sse.core/src-tasktags/org/eclipse/wst/sse/core/internal/tasks/WorkspaceTaskScanner.java
@@ -27,6 +27,7 @@
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IResourceDelta;
+import org.eclipse.core.resources.IWorkspace;
 import org.eclipse.core.resources.IWorkspaceRunnable;
 import org.eclipse.core.resources.ProjectScope;
 import org.eclipse.core.resources.ResourcesPlugin;
@@ -82,6 +83,19 @@
 		fCurrentIgnoreContentTypes = new IContentType[0];
 	}
 
+	private IFileTaskScanner[] combined(IFileTaskScanner[] a, IFileTaskScanner[] b) {
+		if (a.length == 0) {
+			return b;
+		}
+		if (b.length == 0) {
+			return a;
+		}
+		IFileTaskScanner[] both = new  IFileTaskScanner[a.length + b.length];
+		System.arraycopy(a, 0, both, 0, a.length);
+		System.arraycopy(b, 0, both, a.length, b.length);
+		return both;
+	}
+
 	private IContentType[] detectContentTypes(IResource resource) {
 		IContentType[] types = null;
 		if (resource.getType() == IResource.FILE && resource.isAccessible()) {
@@ -302,7 +316,7 @@
 					}
 				};
 				if (file.isAccessible()) {
-					finalFile.getWorkspace().run(r, ResourcesPlugin.getWorkspace().getRuleFactory().markerRule(file), 0, monitor);
+					finalFile.getWorkspace().run(r, ResourcesPlugin.getWorkspace().getRuleFactory().markerRule(file), IWorkspace.AVOID_UPDATE, monitor);
 				}
 			}
 			catch (CoreException e1) {
@@ -394,10 +408,16 @@
 				}
 				fileScanners = registry.getFileTaskScanners(validTypes.toArray(new IContentType[validTypes.size()]));
 			}
+			FileTaskScannerRegistryReader.EnabledDisabledScanners filenameExtensionScanners = registry.getFileTaskScanners(file.getFullPath(), fCurrentIgnoreContentTypes);
+			fileScanners = combined(fileScanners, filenameExtensionScanners.enabled);
 			monitor.worked(1);
 			if (ignoredFileScanners != null && ignoredFileScanners.length > 0) {
-				for (int i = 0; i < ignoredFileScanners.length; i++)
+				for (int i = 0; i < ignoredFileScanners.length; i++) {
 					markerTypes.add(ignoredFileScanners[i].getMarkerType());
+				}
+			}
+			for (int i = 0; i < filenameExtensionScanners.disabled.length; i++) {
+				markerTypes.add(filenameExtensionScanners.disabled[i].getMarkerType());
 			}
 
 			if (fileScanners.length > 0) {
@@ -440,7 +460,8 @@
 			return;
 		// only update markers if we ran a scanner on this file
 		if (fileScanners != null && fileScanners.length > 0 ||
-				ignoredFileScanners != null && ignoredFileScanners.length > 0) {
+				ignoredFileScanners != null && ignoredFileScanners.length > 0 ||
+				markerTypes.size() > 0) {
 			IProgressMonitor markerUpdateMonitor = new SubProgressMonitor(monitor, 3, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK);
 			if (markerAttributes != null) {
 				replaceTaskMarkers(file, markerTypes.toArray(new String[markerTypes.size()]), markerAttributes.toArray(new Map[markerAttributes.size()]), markerUpdateMonitor);
diff --git a/web/bundles/org.eclipse.wst.jsdt.web.ui/META-INF/MANIFEST.MF b/web/bundles/org.eclipse.wst.jsdt.web.ui/META-INF/MANIFEST.MF
index 9d3b334..1e1bbdf 100644
--- a/web/bundles/org.eclipse.wst.jsdt.web.ui/META-INF/MANIFEST.MF
+++ b/web/bundles/org.eclipse.wst.jsdt.web.ui/META-INF/MANIFEST.MF
@@ -40,7 +40,7 @@
  org.eclipse.wst.jsdt.web.core;bundle-version="[1.1.0,2.0.0)",
  org.eclipse.wst.css.core;bundle-version="[1.2.0,2.0.0)",
  org.eclipse.wst.xml.core;bundle-version="[1.2.0,2.0.0)",
- org.eclipse.wst.sse.core;bundle-version="[1.2.0,2.0.0)",
+ org.eclipse.wst.sse.core;bundle-version="[1.2.500,2.0.0)",
  org.eclipse.debug.core;bundle-version="[3.11.0,4.0.0)",
  org.eclipse.core.runtime;bundle-version="[3.14.0,4.0.0)",
  org.eclipse.core.resources;bundle-version="[3.13.0,4.0.0)",
diff --git a/web/bundles/org.eclipse.wst.jsdt.web.ui/plugin.xml b/web/bundles/org.eclipse.wst.jsdt.web.ui/plugin.xml
index 1a6528a..815a1a9 100644
--- a/web/bundles/org.eclipse.wst.jsdt.web.ui/plugin.xml
+++ b/web/bundles/org.eclipse.wst.jsdt.web.ui/plugin.xml
@@ -815,7 +815,8 @@
 		<scanner
 			id="org.eclipse.wst.sse.core.internal.tasks.JSFileTaskScanner"
 			class="org.eclipse.wst.jsdt.web.ui.internal.tasks.JSFileTaskScanner"
-			contentTypeIds="org.eclipse.wst.jsdt.core.jsSource" />
+			contentTypeIds="org.eclipse.wst.jsdt.core.jsSource"
+			fileExtensions="ts, jsx" />
 	</extension>
     
 </plugin>
diff --git a/web/bundles/org.eclipse.wst.jsdt.web.ui/src/org/eclipse/wst/jsdt/web/ui/internal/tasks/JSFileTaskScanner.java b/web/bundles/org.eclipse.wst.jsdt.web.ui/src/org/eclipse/wst/jsdt/web/ui/internal/tasks/JSFileTaskScanner.java
index 18cbb1b..bd39155 100644
--- a/web/bundles/org.eclipse.wst.jsdt.web.ui/src/org/eclipse/wst/jsdt/web/ui/internal/tasks/JSFileTaskScanner.java
+++ b/web/bundles/org.eclipse.wst.jsdt.web.ui/src/org/eclipse/wst/jsdt/web/ui/internal/tasks/JSFileTaskScanner.java
@@ -121,6 +121,8 @@
 								int start = tagMatch.getOffset();

 								IRegion lineInfo = document.getLineInformationOfOffset(tagMatch.getOffset());

 								int lengthToEndOfLine = lineInfo.getLength() - (tagMatch.getOffset() - lineInfo.getOffset());

+								// shorter of end of line or all but the close of the comment

+								lengthToEndOfLine = Math.min(lengthToEndOfLine, partitions[i].getOffset() + partitions[i].getLength() - 2 - start);

 								String text = document.get(start, lengthToEndOfLine).trim();

 								int lineNumber = document.getLineOfOffset(tagMatch.getOffset());

 								Map<String, Object> attributesForNewTaskMarker = createInitialMarkerAttributes(text, lineNumber, start, text.length(), taskTags[j].getPriority());