From a84cc678b049023775443225cbb1429059142170 Mon Sep 17 00:00:00 2001 From: Christian Lenz Date: Mon, 21 Aug 2023 21:55:01 +0200 Subject: [PATCH 1/7] Add AltAttributeVisitor to find the correct tag and attribute to show the hint --- .../html/editor/hints/Bundle.properties | 3 + .../other/AddMissingAltAttributeHint.java | 90 +++++++++++++++ .../other/AddMissingAltAttributeRule.java | 75 +++++++++++++ .../hints/other/AltAttributeVisitor.java | 105 ++++++++++++++++++ .../editor/utils/HtmlTagContextUtils.java | 82 ++++++++++++++ 5 files changed, 355 insertions(+) create mode 100644 ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java create mode 100644 ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRule.java create mode 100644 ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AltAttributeVisitor.java create mode 100644 ide/html.editor/src/org/netbeans/modules/html/editor/utils/HtmlTagContextUtils.java diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/Bundle.properties b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/Bundle.properties index ea391606a5dc..1a4e9e1d2d9a 100644 --- a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/Bundle.properties +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/Bundle.properties @@ -106,4 +106,7 @@ MSG_SET_DEFAULT_HTML_VERSION=Change project default to {0} MSG_FatalHtmlRuleName=Fatal Errors MSG_FatalHtmlErrorAddendum=Further processing of the file may be significantly affected by this fatal error. Please fix the problem before continuing editing of this file! +AddMissingAltAttributeRule=Provide Text Alternatives +AddMissingAltAttributeRule_Desc=Find img/applet/area elements where no alt attribute is provided. + #MSG_HINT_GENERATE_REQUIRED_ATTRIBUTES=Generate required attributes \ No newline at end of file diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java new file mode 100644 index 000000000000..9d80d4a889a6 --- /dev/null +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.html.editor.hints.other; + +import java.awt.EventQueue; +import java.util.Collections; +import javax.swing.text.BadLocationException; +import org.netbeans.modules.csl.api.Hint; +import org.netbeans.modules.csl.api.HintFix; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.api.Rule; +import org.netbeans.modules.csl.api.RuleContext; +import org.netbeans.modules.html.editor.hints.HtmlRuleContext; +import org.netbeans.modules.html.editor.utils.HtmlTagContextUtils; +import org.netbeans.modules.parsing.api.Source; +import org.openide.util.Exceptions; + +/** + * + * @author Christian Lenz + */ +public class AddMissingAltAttributeHint extends Hint { + + public AddMissingAltAttributeHint(HtmlRuleContext context, OffsetRange range) { + super(AddMissingAltAttributeRule.SINGLETON, + AddMissingAltAttributeRule.SINGLETON.getDescription(), + context.getFile(), + range, + Collections.singletonList(new AddMissingAltAttributeHintFix(context, range)), + 10); + } + + private static class AddMissingAltAttributeHintFix implements HintFix { + + HtmlRuleContext context; + OffsetRange range; + + public AddMissingAltAttributeHintFix(HtmlRuleContext context, OffsetRange range) { + this.context = context; + this.range = range; + } + + @Override + public String getDescription() { + return AddMissingAltAttributeRule.SINGLETON.getDisplayName(); + } + + @Override + public void implement() throws Exception { + EventQueue.invokeLater(() -> { + try { + Source source = Source.create(context.getFile()); + OffsetRange adjustContextRange = HtmlTagContextUtils.adjustContextRange(source.getDocument(false), range.getStart(), range.getEnd(), true); + + source.getDocument(false).insertString(adjustContextRange.getEnd(), " alt=\"\"", null); + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); + } + }); + + } + + @Override + public boolean isSafe() { + return true; + } + + @Override + public boolean isInteractive() { + return false; + } + } + +} diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRule.java b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRule.java new file mode 100644 index 000000000000..425b8ec2a4c5 --- /dev/null +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRule.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.html.editor.hints.other; + +import java.io.IOException; +import java.util.List; +import org.netbeans.modules.csl.api.Hint; +import org.netbeans.modules.csl.api.HintSeverity; +import org.netbeans.modules.csl.api.RuleContext; +import org.netbeans.modules.html.editor.api.gsf.HtmlParserResult; +import org.netbeans.modules.html.editor.hints.HtmlRule; +import org.netbeans.modules.html.editor.hints.HtmlRuleContext; +import org.netbeans.modules.html.editor.lib.api.elements.ElementType; +import org.netbeans.modules.html.editor.lib.api.elements.ElementUtils; +import org.openide.filesystems.FileObject; +import org.openide.util.Exceptions; + +/** + * + * @author Christian Lenz + */ +public class AddMissingAltAttributeRule extends HtmlRule { + + public static AddMissingAltAttributeRule SINGLETON = new AddMissingAltAttributeRule(); + + @Override + public boolean appliesTo(RuleContext context) { + HtmlParserResult result = (HtmlParserResult) context.parserResult; + FileObject file = result.getSnapshot().getSource().getFileObject(); + + if (file == null) { + return false; + } + + return true; + } + + @Override + public boolean showInTasklist() { + return true; + } + + @Override + public HintSeverity getDefaultSeverity() { + return HintSeverity.CURRENT_LINE_WARNING; + } + + @Override + protected void run(HtmlRuleContext context, List result) { + try { + HtmlParserResult parserResult = context.getHtmlParserResult(); + AltAttributeVisitor visitor = new AltAttributeVisitor(this, context, result, "img|applet|area"); + + ElementUtils.visitChildren(parserResult.root(), visitor, ElementType.OPEN_TAG); + } catch (IOException ioe) { + Exceptions.printStackTrace(ioe); + } + } +} diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AltAttributeVisitor.java b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AltAttributeVisitor.java new file mode 100644 index 000000000000..9599cad34bec --- /dev/null +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AltAttributeVisitor.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.html.editor.hints.other; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import javax.swing.text.Document; +import org.netbeans.api.html.lexer.HTMLTokenId; +import org.netbeans.api.lexer.Token; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.modules.csl.api.Hint; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.api.Rule; +import org.netbeans.modules.html.editor.api.Utils; +import org.netbeans.modules.html.editor.hints.HtmlRuleContext; +import org.netbeans.modules.html.editor.lib.api.elements.*; +import org.netbeans.modules.html.editor.refactoring.InlinedStyleInfo; +import org.netbeans.modules.parsing.api.Source; + +/** + * + * @author Christian Lenz + */ +public class AltAttributeVisitor implements ElementVisitor { + private static final String ALT_ATTR = "alt"; + + private final HtmlRuleContext context; + private final List hints; + private final String tagToFindRegEx; + + public AltAttributeVisitor(Rule rule, HtmlRuleContext context, List hints, String tagToFindRegEx) throws IOException { + this.context = context; + this.hints = hints; + this.tagToFindRegEx = tagToFindRegEx; + } + + @Override + public void visit(Element node) { + Source source = Source.create(context.getFile()); + Document doc = source.getDocument(false); + final AtomicReference> result = new AtomicReference<>(); + + doc.render(() -> { + List found = new LinkedList<>(); + result.set(found); + + TokenHierarchy th = TokenHierarchy.get(doc); + TokenSequence ts = Utils.getJoinedHtmlSequence(th, node.from()); + + if (ts == null) { + return; + } + + OffsetRange range; + String tag = null; + String attr = null; + int startTagOffset = 0; + int endTagOffset = 0; + + do { + Token t = ts.token(); + if (t.id() == HTMLTokenId.TAG_OPEN) { + tag = t.text().toString(); + attr = null; + + startTagOffset = ts.offset(); + } else if (t.id() == HTMLTokenId.TAG_CLOSE_SYMBOL) { + endTagOffset = ts.offset(); + + range = new OffsetRange(startTagOffset, endTagOffset); + //closing tag, produce the info + if (tag != null && tag.matches(tagToFindRegEx) && attr == null) { + //alt attribute found + hints.add(new AddMissingAltAttributeHint(context, range)); + + tag = attr = null; + } + } else if (t.id() == HTMLTokenId.ARGUMENT) { + if (t.text().toString().equals(ALT_ATTR)) { + attr = t.text().toString(); + range = null; + } + } + } while (ts.moveNext() && ts.offset() <= node.to()); + }); + } +} diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/utils/HtmlTagContextUtils.java b/ide/html.editor/src/org/netbeans/modules/html/editor/utils/HtmlTagContextUtils.java new file mode 100644 index 000000000000..454e990eaf3c --- /dev/null +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/utils/HtmlTagContextUtils.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.html.editor.utils; + +import java.util.concurrent.atomic.AtomicReference; +import javax.swing.text.Document; +import org.netbeans.api.html.lexer.HTMLTokenId; +import org.netbeans.api.lexer.Token; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.html.editor.api.Utils; + +/** + * + * @author Christian Lenz + */ +public class HtmlTagContextUtils { + private HtmlTagContextUtils() { + } + + public static OffsetRange adjustContextRange(final Document doc, final int from, final int to, final boolean beforeClosingToken) { + final AtomicReference ret = new AtomicReference<>(); + ret.set(new OffsetRange(from, to)); //return the same pair by default + doc.render(() -> { + TokenSequence ts = Utils.getJoinedHtmlSequence(doc, from); + + if (ts == null) { + //no html token sequence at the offset, try to + //TODO possibly try to travese the top level sequence backward + //and try to find an html embedding. + return; + } + + Token openTag = Utils.findTagOpenToken(ts); + + if (openTag == null) { + return; + } + + int adjustedFrom = ts.offset(); + + //now try to find the tag's end + ts.move(to); + int adjustedTo = -1; + while (ts.moveNext()) { + Token t = ts.token(); + if (t.id() == HTMLTokenId.TAG_CLOSE_SYMBOL) { + adjustedTo = beforeClosingToken ? ts.offset() : ts.offset() + t.length(); + + break; + } else if (t.id() == HTMLTokenId.TEXT) { + //do not go too far - out of the tag + break; + } + } + + if (adjustedTo == -1) { + return; + } + + //we found the adjusted range + ret.set(new OffsetRange(adjustedFrom, adjustedTo)); + }); + return ret.get(); + } +} From c7fb0719f6357eb055fe6c8219c15ce8e5bb7393 Mon Sep 17 00:00:00 2001 From: Christian Lenz Date: Thu, 24 Aug 2023 21:56:50 +0200 Subject: [PATCH 2/7] Add NOI18N, null checks and clean up as requested --- .../other/AddMissingAltAttributeHint.java | 11 +++++----- .../hints/other/AltAttributeVisitor.java | 22 +++++++++++++------ .../editor/utils/HtmlTagContextUtils.java | 8 ++++++- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java index 9d80d4a889a6..a960c6f86fc2 100644 --- a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java @@ -20,16 +20,15 @@ import java.awt.EventQueue; import java.util.Collections; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.text.BadLocationException; import org.netbeans.modules.csl.api.Hint; import org.netbeans.modules.csl.api.HintFix; import org.netbeans.modules.csl.api.OffsetRange; -import org.netbeans.modules.csl.api.Rule; -import org.netbeans.modules.csl.api.RuleContext; import org.netbeans.modules.html.editor.hints.HtmlRuleContext; import org.netbeans.modules.html.editor.utils.HtmlTagContextUtils; import org.netbeans.modules.parsing.api.Source; -import org.openide.util.Exceptions; /** * @@ -48,6 +47,8 @@ public AddMissingAltAttributeHint(HtmlRuleContext context, OffsetRange range) { private static class AddMissingAltAttributeHintFix implements HintFix { + private static final Logger LOGGER = Logger.getLogger(AddMissingAltAttributeHintFix.class.getSimpleName()); + HtmlRuleContext context; OffsetRange range; @@ -68,9 +69,9 @@ public void implement() throws Exception { Source source = Source.create(context.getFile()); OffsetRange adjustContextRange = HtmlTagContextUtils.adjustContextRange(source.getDocument(false), range.getStart(), range.getEnd(), true); - source.getDocument(false).insertString(adjustContextRange.getEnd(), " alt=\"\"", null); + source.getDocument(false).insertString(adjustContextRange.getEnd(), " alt=\"\"", null); // NOI18N } catch (BadLocationException ex) { - Exceptions.printStackTrace(ex); + LOGGER.log(Level.SEVERE, ex.getMessage()); } }); diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AltAttributeVisitor.java b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AltAttributeVisitor.java index 9599cad34bec..75f606d567b6 100644 --- a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AltAttributeVisitor.java +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AltAttributeVisitor.java @@ -21,11 +21,13 @@ import java.io.IOException; import java.util.*; import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Pattern; import javax.swing.text.Document; import org.netbeans.api.html.lexer.HTMLTokenId; import org.netbeans.api.lexer.Token; import org.netbeans.api.lexer.TokenHierarchy; import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.api.lexer.TokenUtilities; import org.netbeans.modules.csl.api.Hint; import org.netbeans.modules.csl.api.OffsetRange; import org.netbeans.modules.csl.api.Rule; @@ -40,7 +42,8 @@ * @author Christian Lenz */ public class AltAttributeVisitor implements ElementVisitor { - private static final String ALT_ATTR = "alt"; + + private static final CharSequence ALT_ATTR = "alt"; // NOI18N private final HtmlRuleContext context; private final List hints; @@ -70,15 +73,20 @@ public void visit(Element node) { } OffsetRange range; - String tag = null; - String attr = null; + CharSequence tag = null; + CharSequence attr = null; int startTagOffset = 0; int endTagOffset = 0; do { Token t = ts.token(); + + if (t == null) { + return; + } + if (t.id() == HTMLTokenId.TAG_OPEN) { - tag = t.text().toString(); + tag = t.text(); attr = null; startTagOffset = ts.offset(); @@ -87,15 +95,15 @@ public void visit(Element node) { range = new OffsetRange(startTagOffset, endTagOffset); //closing tag, produce the info - if (tag != null && tag.matches(tagToFindRegEx) && attr == null) { + if (tag != null && Pattern.matches(tagToFindRegEx, tag) && attr == null) { //alt attribute found hints.add(new AddMissingAltAttributeHint(context, range)); tag = attr = null; } } else if (t.id() == HTMLTokenId.ARGUMENT) { - if (t.text().toString().equals(ALT_ATTR)) { - attr = t.text().toString(); + if (TokenUtilities.textEquals(t.text(), ALT_ATTR)) { + attr = t.text(); range = null; } } diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/utils/HtmlTagContextUtils.java b/ide/html.editor/src/org/netbeans/modules/html/editor/utils/HtmlTagContextUtils.java index 454e990eaf3c..a5dcd63ef2a4 100644 --- a/ide/html.editor/src/org/netbeans/modules/html/editor/utils/HtmlTagContextUtils.java +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/utils/HtmlTagContextUtils.java @@ -30,7 +30,8 @@ * * @author Christian Lenz */ -public class HtmlTagContextUtils { +public final class HtmlTagContextUtils { + private HtmlTagContextUtils() { } @@ -60,6 +61,11 @@ public static OffsetRange adjustContextRange(final Document doc, final int from, int adjustedTo = -1; while (ts.moveNext()) { Token t = ts.token(); + + if (t == null) { + return; + } + if (t.id() == HTMLTokenId.TAG_CLOSE_SYMBOL) { adjustedTo = beforeClosingToken ? ts.offset() : ts.offset() + t.length(); From ff1fd4e55ae776130d0b2951b42a224c62276350 Mon Sep 17 00:00:00 2001 From: Christian Lenz Date: Mon, 20 Nov 2023 20:00:43 +0100 Subject: [PATCH 3/7] Add test for the new hint as requested --- .../other/AddMissingAltAttributeHint.java | 8 +- .../other/AddMissingAltAttributeRule.java | 11 ++- .../hints/other/AltAttributeVisitor.java | 84 ++++--------------- .../modules/html/editor/resources/layer.xml | 3 + .../hints/addMissingAltAttribute1.html | 32 +++++++ ....html.testProvideTextAlternativeHint.hints | 4 + .../other/AddMissingAltAttributeRuleTest.java | 41 +++++++++ 7 files changed, 110 insertions(+), 73 deletions(-) create mode 100644 ide/html.editor/test/unit/data/testfiles/hints/addMissingAltAttribute1.html create mode 100644 ide/html.editor/test/unit/data/testfiles/hints/addMissingAltAttribute1.html.testProvideTextAlternativeHint.hints create mode 100644 ide/html.editor/test/unit/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRuleTest.java diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java index a960c6f86fc2..3f5a938f7f33 100644 --- a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java @@ -37,8 +37,8 @@ public class AddMissingAltAttributeHint extends Hint { public AddMissingAltAttributeHint(HtmlRuleContext context, OffsetRange range) { - super(AddMissingAltAttributeRule.SINGLETON, - AddMissingAltAttributeRule.SINGLETON.getDescription(), + super(AddMissingAltAttributeRule.getInstance(), + AddMissingAltAttributeRule.getInstance().getDescription(), context.getFile(), range, Collections.singletonList(new AddMissingAltAttributeHintFix(context, range)), @@ -59,7 +59,7 @@ public AddMissingAltAttributeHintFix(HtmlRuleContext context, OffsetRange range) @Override public String getDescription() { - return AddMissingAltAttributeRule.SINGLETON.getDisplayName(); + return AddMissingAltAttributeRule.getInstance().getDisplayName(); } @Override @@ -71,7 +71,7 @@ public void implement() throws Exception { source.getDocument(false).insertString(adjustContextRange.getEnd(), " alt=\"\"", null); // NOI18N } catch (BadLocationException ex) { - LOGGER.log(Level.SEVERE, ex.getMessage()); + LOGGER.log(Level.WARNING, "Invalid offset: {0}", ex.offsetRequested()); } }); diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRule.java b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRule.java index 425b8ec2a4c5..98782df0a172 100644 --- a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRule.java +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRule.java @@ -37,7 +37,14 @@ */ public class AddMissingAltAttributeRule extends HtmlRule { - public static AddMissingAltAttributeRule SINGLETON = new AddMissingAltAttributeRule(); + private final static AddMissingAltAttributeRule INSTANCE = new AddMissingAltAttributeRule(); + + private AddMissingAltAttributeRule() { + } + + public static AddMissingAltAttributeRule getInstance() { + return INSTANCE; + } @Override public boolean appliesTo(RuleContext context) { @@ -65,7 +72,7 @@ public HintSeverity getDefaultSeverity() { protected void run(HtmlRuleContext context, List result) { try { HtmlParserResult parserResult = context.getHtmlParserResult(); - AltAttributeVisitor visitor = new AltAttributeVisitor(this, context, result, "img|applet|area"); + AltAttributeVisitor visitor = new AltAttributeVisitor(this, context, result); // NOI18N ElementUtils.visitChildren(parserResult.root(), visitor, ElementType.OPEN_TAG); } catch (IOException ioe) { diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AltAttributeVisitor.java b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AltAttributeVisitor.java index 75f606d567b6..a457ad0664ac 100644 --- a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AltAttributeVisitor.java +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AltAttributeVisitor.java @@ -20,22 +20,11 @@ import java.io.IOException; import java.util.*; -import java.util.concurrent.atomic.AtomicReference; -import java.util.regex.Pattern; -import javax.swing.text.Document; -import org.netbeans.api.html.lexer.HTMLTokenId; -import org.netbeans.api.lexer.Token; -import org.netbeans.api.lexer.TokenHierarchy; -import org.netbeans.api.lexer.TokenSequence; -import org.netbeans.api.lexer.TokenUtilities; import org.netbeans.modules.csl.api.Hint; import org.netbeans.modules.csl.api.OffsetRange; import org.netbeans.modules.csl.api.Rule; -import org.netbeans.modules.html.editor.api.Utils; import org.netbeans.modules.html.editor.hints.HtmlRuleContext; import org.netbeans.modules.html.editor.lib.api.elements.*; -import org.netbeans.modules.html.editor.refactoring.InlinedStyleInfo; -import org.netbeans.modules.parsing.api.Source; /** * @@ -43,71 +32,32 @@ */ public class AltAttributeVisitor implements ElementVisitor { - private static final CharSequence ALT_ATTR = "alt"; // NOI18N + private static final String ALT_ATTR = "alt"; // NOI18N private final HtmlRuleContext context; private final List hints; - private final String tagToFindRegEx; - public AltAttributeVisitor(Rule rule, HtmlRuleContext context, List hints, String tagToFindRegEx) throws IOException { + public AltAttributeVisitor(Rule rule, HtmlRuleContext context, List hints) throws IOException { this.context = context; this.hints = hints; - this.tagToFindRegEx = tagToFindRegEx; } @Override public void visit(Element node) { - Source source = Source.create(context.getFile()); - Document doc = source.getDocument(false); - final AtomicReference> result = new AtomicReference<>(); - - doc.render(() -> { - List found = new LinkedList<>(); - result.set(found); - - TokenHierarchy th = TokenHierarchy.get(doc); - TokenSequence ts = Utils.getJoinedHtmlSequence(th, node.from()); - - if (ts == null) { - return; - } - - OffsetRange range; - CharSequence tag = null; - CharSequence attr = null; - int startTagOffset = 0; - int endTagOffset = 0; - - do { - Token t = ts.token(); - - if (t == null) { - return; - } - - if (t.id() == HTMLTokenId.TAG_OPEN) { - tag = t.text(); - attr = null; - - startTagOffset = ts.offset(); - } else if (t.id() == HTMLTokenId.TAG_CLOSE_SYMBOL) { - endTagOffset = ts.offset(); - - range = new OffsetRange(startTagOffset, endTagOffset); - //closing tag, produce the info - if (tag != null && Pattern.matches(tagToFindRegEx, tag) && attr == null) { - //alt attribute found - hints.add(new AddMissingAltAttributeHint(context, range)); - - tag = attr = null; - } - } else if (t.id() == HTMLTokenId.ARGUMENT) { - if (TokenUtilities.textEquals(t.text(), ALT_ATTR)) { - attr = t.text(); - range = null; - } - } - } while (ts.moveNext() && ts.offset() <= node.to()); - }); + // We should only be invoked for opening tags + if (!(node instanceof OpenTag)) { + return; + } + + // We are only interested in img, area, applet elements + String lowerCaseTag = node.id().toString().toLowerCase(); + if (!(lowerCaseTag.equals("img") || lowerCaseTag.equals("area") || lowerCaseTag.equals("applet"))) { + return; + } + + OpenTag ot = (OpenTag) node; + if (ot.getAttribute(ALT_ATTR) == null) { + hints.add(new AddMissingAltAttributeHint(context, new OffsetRange(node.from(), node.to()))); + } } } diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/resources/layer.xml b/ide/html.editor/src/org/netbeans/modules/html/editor/resources/layer.xml index 3895708ee15e..c4c33ded68f0 100644 --- a/ide/html.editor/src/org/netbeans/modules/html/editor/resources/layer.xml +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/resources/layer.xml @@ -534,6 +534,9 @@ + + + diff --git a/ide/html.editor/test/unit/data/testfiles/hints/addMissingAltAttribute1.html b/ide/html.editor/test/unit/data/testfiles/hints/addMissingAltAttribute1.html new file mode 100644 index 000000000000..9f8d465e76ae --- /dev/null +++ b/ide/html.editor/test/unit/data/testfiles/hints/addMissingAltAttribute1.html @@ -0,0 +1,32 @@ + + + + + TODO supply a title + + + + +
TODO write content
+ + + diff --git a/ide/html.editor/test/unit/data/testfiles/hints/addMissingAltAttribute1.html.testProvideTextAlternativeHint.hints b/ide/html.editor/test/unit/data/testfiles/hints/addMissingAltAttribute1.html.testProvideTextAlternativeHint.hints new file mode 100644 index 000000000000..6a51d0a5a6ec --- /dev/null +++ b/ide/html.editor/test/unit/data/testfiles/hints/addMissingAltAttribute1.html.testProvideTextAlternativeHint.hints @@ -0,0 +1,4 @@ + + +HINT:Provide Text Alternatives +FIX:Provide Text Alternatives diff --git a/ide/html.editor/test/unit/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRuleTest.java b/ide/html.editor/test/unit/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRuleTest.java new file mode 100644 index 000000000000..ff62813a0942 --- /dev/null +++ b/ide/html.editor/test/unit/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRuleTest.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.html.editor.hints.other; + +import org.netbeans.modules.csl.api.Rule; +import org.netbeans.modules.html.editor.test.TestBase; + +/** + * + * @author Christian Lenz + */ +public class AddMissingAltAttributeRuleTest extends TestBase { + + public AddMissingAltAttributeRuleTest(String testName) { + super(testName); + } + + private Rule createRule() { + return AddMissingAltAttributeRule.getInstance(); + } + + public void testProvideTextAlternativeHint() throws Exception { + checkHints(this, createRule(), "testfiles/hints/addMissingAltAttribute1.html", " Date: Fri, 26 Jan 2024 21:20:15 +0100 Subject: [PATCH 4/7] Add test for hintfix too and change test code to figure out why the tests area failing --- .../modules/csl/api/test/CslTestBase.java | 7 ++-- .../other/AddMissingAltAttributeHint.java | 24 +++++++++++--- .../other/AddMissingAltAttributeRule.java | 10 +----- .../hints/other/AltAttributeVisitor.java | 2 +- ....html.testProvideTextAlternativeHint.hints | 4 +-- ...testProvideTextAlternativeHintFix_01.fixed | 32 +++++++++++++++++++ .../other/AddMissingAltAttributeRuleTest.java | 11 +++++++ 7 files changed, 72 insertions(+), 18 deletions(-) create mode 100644 ide/html.editor/test/unit/data/testfiles/hints/addMissingAltAttribute1.html.testProvideTextAlternativeHintFix_01.fixed diff --git a/ide/csl.api/test/unit/src/org/netbeans/modules/csl/api/test/CslTestBase.java b/ide/csl.api/test/unit/src/org/netbeans/modules/csl/api/test/CslTestBase.java index 2e523dbfae46..c414283301a8 100644 --- a/ide/csl.api/test/unit/src/org/netbeans/modules/csl/api/test/CslTestBase.java +++ b/ide/csl.api/test/unit/src/org/netbeans/modules/csl/api/test/CslTestBase.java @@ -4384,8 +4384,11 @@ protected ComputedHints computeHints(final NbTestCase test, final Rule hint, Str } } if (HintsSettings.getSeverity(manager, ucr) == HintSeverity.CURRENT_LINE_WARNING) { - manager.setTestingRules(null, Collections.EMPTY_MAP, testHints, null); + manager.setTestingRules(null, testHints, testHints, null); provider.computeSuggestions(manager, context, hints, caretOffset); + } else if(HintsSettings.getSeverity(manager, ucr) == HintSeverity.ERROR || HintsSettings.getSeverity(manager, ucr) == HintSeverity.WARNING) { + manager.setTestingRules(null, testHints, testHints, null); + provider.computeErrors(manager, context, hints, new ArrayList()); } else { manager.setTestingRules(null, testHints, null, null); context.caretOffset = -1; @@ -4618,7 +4621,7 @@ private HintFix findApplicableFix(ComputedHints r, String text) { List list = desc.getFixes(); assertNotNull(list); for (HintFix fix : list) { - if (text == null || + if (text != null || (substringMatch && fix.getDescription().indexOf(text) != -1) || (!substringMatch && fix.getDescription().equals(text))) { return fix; diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java index 3f5a938f7f33..a66382ee53b4 100644 --- a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java @@ -22,7 +22,10 @@ import java.util.Collections; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.swing.text.BadLocationException; +import javax.swing.text.Document; import org.netbeans.modules.csl.api.Hint; import org.netbeans.modules.csl.api.HintFix; import org.netbeans.modules.csl.api.OffsetRange; @@ -67,14 +70,28 @@ public void implement() throws Exception { EventQueue.invokeLater(() -> { try { Source source = Source.create(context.getFile()); - OffsetRange adjustContextRange = HtmlTagContextUtils.adjustContextRange(source.getDocument(false), range.getStart(), range.getEnd(), true); + OffsetRange adjustedRange = HtmlTagContextUtils.adjustContextRange(source.getDocument(false), range.getStart(), range.getEnd(), true); + Document document = source.getDocument(false); + String tagContent = document.getText(adjustedRange.getStart(), adjustedRange.getLength()); - source.getDocument(false).insertString(adjustContextRange.getEnd(), " alt=\"\"", null); // NOI18N + // Find last self-closing or non self-closing tag + Pattern closingTagPattern = Pattern.compile("(/?>)"); + Matcher closingTagMatcher = closingTagPattern.matcher(tagContent); + + if (closingTagMatcher.find()) { + int altInsertPosition = adjustedRange.getStart() + closingTagMatcher.start(1); + + // Check whether a space before alt is needed or not + boolean needsSpaceBefore = altInsertPosition == 0 || tagContent.charAt(closingTagMatcher.start(1) - 1) != ' '; + boolean isSelfClosing = closingTagMatcher.group(0).endsWith("/>"); + String altAttribute = (needsSpaceBefore ? " " : "") + "alt=\"\"" + (isSelfClosing ? " " : ""); // NOI18N + + document.insertString(altInsertPosition, altAttribute, null); + } } catch (BadLocationException ex) { LOGGER.log(Level.WARNING, "Invalid offset: {0}", ex.offsetRequested()); } }); - } @Override @@ -87,5 +104,4 @@ public boolean isInteractive() { return false; } } - } diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRule.java b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRule.java index 98782df0a172..40ea51b6ab77 100644 --- a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRule.java +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRule.java @@ -28,7 +28,6 @@ import org.netbeans.modules.html.editor.hints.HtmlRuleContext; import org.netbeans.modules.html.editor.lib.api.elements.ElementType; import org.netbeans.modules.html.editor.lib.api.elements.ElementUtils; -import org.openide.filesystems.FileObject; import org.openide.util.Exceptions; /** @@ -48,13 +47,6 @@ public static AddMissingAltAttributeRule getInstance() { @Override public boolean appliesTo(RuleContext context) { - HtmlParserResult result = (HtmlParserResult) context.parserResult; - FileObject file = result.getSnapshot().getSource().getFileObject(); - - if (file == null) { - return false; - } - return true; } @@ -72,7 +64,7 @@ public HintSeverity getDefaultSeverity() { protected void run(HtmlRuleContext context, List result) { try { HtmlParserResult parserResult = context.getHtmlParserResult(); - AltAttributeVisitor visitor = new AltAttributeVisitor(this, context, result); // NOI18N + AltAttributeVisitor visitor = new AltAttributeVisitor(this, context, result); ElementUtils.visitChildren(parserResult.root(), visitor, ElementType.OPEN_TAG); } catch (IOException ioe) { diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AltAttributeVisitor.java b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AltAttributeVisitor.java index a457ad0664ac..7abd532fcf4b 100644 --- a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AltAttributeVisitor.java +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AltAttributeVisitor.java @@ -51,7 +51,7 @@ public void visit(Element node) { // We are only interested in img, area, applet elements String lowerCaseTag = node.id().toString().toLowerCase(); - if (!(lowerCaseTag.equals("img") || lowerCaseTag.equals("area") || lowerCaseTag.equals("applet"))) { + if (!(lowerCaseTag.equals("img") || lowerCaseTag.equals("area") || lowerCaseTag.equals("applet"))) { // NOI18N return; } diff --git a/ide/html.editor/test/unit/data/testfiles/hints/addMissingAltAttribute1.html.testProvideTextAlternativeHint.hints b/ide/html.editor/test/unit/data/testfiles/hints/addMissingAltAttribute1.html.testProvideTextAlternativeHint.hints index 6a51d0a5a6ec..0ab0c5fd1cf4 100644 --- a/ide/html.editor/test/unit/data/testfiles/hints/addMissingAltAttribute1.html.testProvideTextAlternativeHint.hints +++ b/ide/html.editor/test/unit/data/testfiles/hints/addMissingAltAttribute1.html.testProvideTextAlternativeHint.hints @@ -1,4 +1,4 @@ - -HINT:Provide Text Alternatives + ------- +HINT:Find img/applet/area elements where no alt attribute is provided. FIX:Provide Text Alternatives diff --git a/ide/html.editor/test/unit/data/testfiles/hints/addMissingAltAttribute1.html.testProvideTextAlternativeHintFix_01.fixed b/ide/html.editor/test/unit/data/testfiles/hints/addMissingAltAttribute1.html.testProvideTextAlternativeHintFix_01.fixed new file mode 100644 index 000000000000..58ebb3c874f0 --- /dev/null +++ b/ide/html.editor/test/unit/data/testfiles/hints/addMissingAltAttribute1.html.testProvideTextAlternativeHintFix_01.fixed @@ -0,0 +1,32 @@ + + + + + TODO supply a title + + + + +
TODO write content
+ + + diff --git a/ide/html.editor/test/unit/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRuleTest.java b/ide/html.editor/test/unit/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRuleTest.java index ff62813a0942..16cf9dda0359 100644 --- a/ide/html.editor/test/unit/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRuleTest.java +++ b/ide/html.editor/test/unit/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRuleTest.java @@ -38,4 +38,15 @@ private Rule createRule() { public void testProvideTextAlternativeHint() throws Exception { checkHints(this, createRule(), "testfiles/hints/addMissingAltAttribute1.html", " Date: Tue, 20 Feb 2024 22:28:29 +0100 Subject: [PATCH 5/7] Fix missing test for applying the hint due to removing EDT logic. Revert change inside the CslTestBase for findApplicableFix --- .../org/netbeans/modules/csl/api/test/CslTestBase.java | 2 +- .../editor/hints/other/AddMissingAltAttributeHint.java | 8 ++++---- .../hints/other/AddMissingAltAttributeRuleTest.java | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ide/csl.api/test/unit/src/org/netbeans/modules/csl/api/test/CslTestBase.java b/ide/csl.api/test/unit/src/org/netbeans/modules/csl/api/test/CslTestBase.java index c414283301a8..45d8d96e13c5 100644 --- a/ide/csl.api/test/unit/src/org/netbeans/modules/csl/api/test/CslTestBase.java +++ b/ide/csl.api/test/unit/src/org/netbeans/modules/csl/api/test/CslTestBase.java @@ -4621,7 +4621,7 @@ private HintFix findApplicableFix(ComputedHints r, String text) { List list = desc.getFixes(); assertNotNull(list); for (HintFix fix : list) { - if (text != null || + if (text == null || (substringMatch && fix.getDescription().indexOf(text) != -1) || (!substringMatch && fix.getDescription().equals(text))) { return fix; diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java index a66382ee53b4..22878d831cee 100644 --- a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java @@ -26,6 +26,7 @@ import java.util.regex.Pattern; import javax.swing.text.BadLocationException; import javax.swing.text.Document; +import org.netbeans.editor.BaseDocument; import org.netbeans.modules.csl.api.Hint; import org.netbeans.modules.csl.api.HintFix; import org.netbeans.modules.csl.api.OffsetRange; @@ -67,11 +68,10 @@ public String getDescription() { @Override public void implement() throws Exception { - EventQueue.invokeLater(() -> { + BaseDocument document = (BaseDocument) context.getSnapshot().getSource().getDocument(true); + document.runAtomic(() -> { try { - Source source = Source.create(context.getFile()); - OffsetRange adjustedRange = HtmlTagContextUtils.adjustContextRange(source.getDocument(false), range.getStart(), range.getEnd(), true); - Document document = source.getDocument(false); + OffsetRange adjustedRange = HtmlTagContextUtils.adjustContextRange(document, range.getStart(), range.getEnd(), true); String tagContent = document.getText(adjustedRange.getStart(), adjustedRange.getLength()); // Find last self-closing or non self-closing tag diff --git a/ide/html.editor/test/unit/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRuleTest.java b/ide/html.editor/test/unit/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRuleTest.java index 16cf9dda0359..e90c89163473 100644 --- a/ide/html.editor/test/unit/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRuleTest.java +++ b/ide/html.editor/test/unit/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeRuleTest.java @@ -46,7 +46,7 @@ public void testProvideTextAlternativeHintFix_01() throws Exception { createRule(), "testfiles/hints/addMissingAltAttribute1.html", " Date: Tue, 30 Dec 2025 21:06:35 +0100 Subject: [PATCH 6/7] Revert CslTestBase changes. Add cancel and resume functionality in HtmlHintsProvider as for PhpHintsProvider. --- .../modules/csl/api/test/CslTestBase.java | 7 +- .../html/editor/hints/HtmlHintsProvider.java | 208 +++++++++++++----- .../html/editor/hints/HtmlRuleContext.java | 52 +++-- 3 files changed, 188 insertions(+), 79 deletions(-) diff --git a/ide/csl.api/test/unit/src/org/netbeans/modules/csl/api/test/CslTestBase.java b/ide/csl.api/test/unit/src/org/netbeans/modules/csl/api/test/CslTestBase.java index 77a27e1da6b4..4fdec0cea511 100644 --- a/ide/csl.api/test/unit/src/org/netbeans/modules/csl/api/test/CslTestBase.java +++ b/ide/csl.api/test/unit/src/org/netbeans/modules/csl/api/test/CslTestBase.java @@ -4358,11 +4358,8 @@ protected ComputedHints computeHints(final NbTestCase test, final Rule hint, Str } } if (HintsSettings.getSeverity(manager, ucr) == HintSeverity.CURRENT_LINE_WARNING) { - manager.setTestingRules(null, testHints, testHints, null); + manager.setTestingRules(null, Collections.EMPTY_MAP, testHints, null); provider.computeSuggestions(manager, context, hints, caretOffset); - } else if(HintsSettings.getSeverity(manager, ucr) == HintSeverity.ERROR || HintsSettings.getSeverity(manager, ucr) == HintSeverity.WARNING) { - manager.setTestingRules(null, testHints, testHints, null); - provider.computeErrors(manager, context, hints, new ArrayList()); } else { manager.setTestingRules(null, testHints, null, null); context.caretOffset = -1; @@ -4816,4 +4813,4 @@ public CaretLineOffset(int offset, String caretLine) { } } -} +} \ No newline at end of file diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/HtmlHintsProvider.java b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/HtmlHintsProvider.java index 4eb17a75f55e..6edd51d9f42d 100644 --- a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/HtmlHintsProvider.java +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/HtmlHintsProvider.java @@ -67,12 +67,16 @@ public class HtmlHintsProvider implements HintsProvider { private static RequestProcessor RP = new RequestProcessor(HtmlHintsProvider.class); private static final Logger LOG = Logger.getLogger(HtmlHintsProvider.class.getName()); + + private volatile boolean cancel = false; + /** * Compute hints applicable to the given compilation info and add to the * given result list. */ @Override public void computeHints(HintsManager manager, RuleContext context, List hints) { + resume(); HtmlParserResult result = (HtmlParserResult) context.parserResult; HtmlVersion version = result.getDetectedHtmlVersion(); FileObject file = result.getSnapshot().getSource().getFileObject(); @@ -80,6 +84,11 @@ public void computeHints(HintsManager manager, RuleContext context, List h //the Hint doesn't allow the fileObject argument to be null return; } + + if (cancel) { + return; + } + Project project = FileOwnerQuery.getOwner(file); boolean xhtml = result.getSyntaxAnalyzerResult().mayBeXhtml(); if (version == null) { @@ -89,32 +98,35 @@ public void computeHints(HintsManager manager, RuleContext context, List h //we cannot set the default anywhere, just show a warning message hints.add(new Hint(getRule(Severity.INFO), - NbBundle.getMessage(HtmlHintsProvider.class, "MSG_CANNOT_DETERMINE_HTML_VERSION_NO_PROJECT"), - file, - new OffsetRange(0, 0), - Collections.emptyList(), - 100) { + NbBundle.getMessage(HtmlHintsProvider.class, "MSG_CANNOT_DETERMINE_HTML_VERSION_NO_PROJECT"), + file, + new OffsetRange(0, 0), + Collections.emptyList(), + 100) { }); } else { + if (cancel) { + return; + } + //no doctype declaration found, generate the set default project html version hint HtmlVersion defaulted = ProjectDefaultHtmlSourceVersionController.getDefaultHtmlVersion(project, xhtml); String msg = defaulted == null - ? NbBundle.getMessage(HtmlHintsProvider.class, xhtml ? "MSG_CANNOT_DETERMINE_XHTML_VERSION" : "MSG_CANNOT_DETERMINE_HTML_VERSION") - : NbBundle.getMessage(HtmlHintsProvider.class, xhtml ? "MSG_CANNOT_DETERMINE_XHTML_VERSION_DEFAULTED_ALREADY" : "MSG_CANNOT_DETERMINE_HTML_VERSION_DEFAULTED_ALREADY", defaulted.getDisplayName()); + ? NbBundle.getMessage(HtmlHintsProvider.class, xhtml ? "MSG_CANNOT_DETERMINE_XHTML_VERSION" : "MSG_CANNOT_DETERMINE_HTML_VERSION") + : NbBundle.getMessage(HtmlHintsProvider.class, xhtml ? "MSG_CANNOT_DETERMINE_XHTML_VERSION_DEFAULTED_ALREADY" : "MSG_CANNOT_DETERMINE_HTML_VERSION_DEFAULTED_ALREADY", defaulted.getDisplayName()); hints.add(new Hint(getRule(Severity.INFO), - msg, - file, - new OffsetRange(0, 0), - generateSetDefaultHtmlVersionHints(project, result.getSnapshot().getSource().getDocument(false), xhtml), - 100) { + msg, + file, + new OffsetRange(0, 0), + generateSetDefaultHtmlVersionHints(project, result.getSnapshot().getSource().getDocument(false), xhtml), + 100) { }); } } //add html-css related hints // HtmlCssHints.computeHints(manager, context, hints); - } private static List generateSetDefaultHtmlVersionHints(Project project, Document doc, boolean xhtml) { @@ -136,46 +148,69 @@ private static List generateSetDefaultHtmlVersionHints(Project project, */ @Override public void computeSuggestions(HintsManager manager, RuleContext context, List suggestions, int caretOffset) { + resume(); int errorType = 0; if (context instanceof HtmlErrorFilterContext) { errorType = ((HtmlErrorFilterContext) context).isOnlyBadging() ? 2 : 1; } HtmlParserResult result = (HtmlParserResult) context.parserResult; SyntaxAnalyzerResult saresult = result.getSyntaxAnalyzerResult(); - + + if (cancel) { + return; + } + if (isErrorCheckingEnabled(result)) { - HtmlRuleContext htmlRuleContext = new HtmlRuleContext(result, saresult, Collections.emptyList()); + HtmlRuleContext htmlRuleContext = initializeContext(context, saresult, Collections.emptyList()); + if (cancel) { + return; + } for (org.netbeans.modules.html.editor.hints.HtmlRule rule : getSortedRules(manager, context, true)) { //line hints + if (cancel) { + return; + } + //skip the rule if we are called from the tasklist, //the rule is not supposed to show in tasklist and is not badging - if(errorType > 0 && !rule.showInTasklist() && !(rule instanceof ErrorBadgingRule)) { + if (errorType > 0 && !rule.showInTasklist() && !(rule instanceof ErrorBadgingRule)) { continue; } // do not run regular rules when only error badging, or vice versa if ((errorType == 2) != (rule instanceof ErrorBadgingRule)) { continue; } - if(manager.isEnabled(rule)) { + if (manager.isEnabled(rule)) { rule.run(htmlRuleContext, suggestions); } } } - + + if (cancel) { + return; + } + //TODO remove the hardcoding - put the rules to the layer - if(ExtractInlinedStyleRule.SINGLETON.appliesTo(context)) { + if (ExtractInlinedStyleRule.SINGLETON.appliesTo(context)) { suggestions.add(new ExtractInlinedStyleHint(context, new OffsetRange(context.caretOffset, context.caretOffset))); } - if(RemoveSurroundingTag.RULE.appliesTo(context)) { + if (RemoveSurroundingTag.RULE.appliesTo(context)) { suggestions.add(new RemoveSurroundingTag(context, new OffsetRange(context.caretOffset, context.caretOffset))); } - + + if (cancel) { + return; + } + //html extensions String sourceMimetype = WebPageMetadata.getContentMimeType(result, true); for (HtmlExtension ext : HtmlExtensions.getRegisteredExtensions(sourceMimetype)) { + if (cancel) { + return; + } ext.computeSuggestions(manager, context, suggestions, caretOffset); } - + } /** @@ -184,13 +219,21 @@ public void computeSuggestions(HintsManager manager, RuleContext context, List suggestions, int start, int end) { + resume(); //html extensions - HtmlParserResult result = (HtmlParserResult)context.parserResult; + HtmlParserResult result = (HtmlParserResult) context.parserResult; String sourceMimetype = WebPageMetadata.getContentMimeType(result, true); for (HtmlExtension ext : HtmlExtensions.getRegisteredExtensions(sourceMimetype)) { + if (cancel) { + return; + } ext.computeSelectionHints(manager, context, suggestions, start, end); } + if (cancel) { + return; + } + suggestions.add(new SurroundWithTag(context, new OffsetRange(start, end))); suggestions.add(new RemoveSurroundingTag(context, new OffsetRange(start, end))); } @@ -202,6 +245,7 @@ public void computeSelectionHints(HintsManager manager, RuleContext context, Lis */ @Override public void computeErrors(HintsManager manager, RuleContext context, List hints, List unhandled) { + resume(); int errorType = 0; // in the case the context is a regular one, not for indexing, all enabled hints should run. if (context instanceof HtmlErrorFilterContext) { @@ -209,7 +253,11 @@ public void computeErrors(HintsManager manager, RuleContext context, List } LOG.log(Level.FINE, "computing errors (errorType:{0}) for source {1}", new Object[]{errorType, context.parserResult.getSnapshot().getSource()}); LOG.log(Level.FINER, null, new Exception()); - + + if (cancel) { + return; + } + HtmlParserResult result = (HtmlParserResult) context.parserResult; SyntaxAnalyzerResult saresult = result.getSyntaxAnalyzerResult(); Snapshot snapshot = result.getSnapshot(); @@ -220,6 +268,10 @@ public void computeErrors(HintsManager manager, RuleContext context, List return; } + if (cancel) { + return; + } + //add default fixes List defaultFixes = new ArrayList<>(3); if (!isErrorCheckingDisabledForFile(result)) { @@ -229,7 +281,11 @@ public void computeErrors(HintsManager manager, RuleContext context, List defaultFixes.add(new DisableErrorChecksForMimetypeFix(saresult)); } - HtmlRuleContext htmlRuleContext = new HtmlRuleContext(result, saresult, defaultFixes); + HtmlRuleContext htmlRuleContext = initializeContext(context, saresult, defaultFixes); + + if (cancel) { + return; + } //filter out fatal errors and remove them from the html validator hints processing Collection fatalErrors = new ArrayList<>(); @@ -238,18 +294,21 @@ public void computeErrors(HintsManager manager, RuleContext context, List fatalErrors.add(e); } } - //To resolve following + //To resolve following //Bug 200801 - Fatal error hint for mixed php/html code - //but keep the behavior described in - //Bug 199104 - No error for unmatched
tag + //but keep the behavior described in + //Bug 199104 - No error for unmatched
tag //I need to keep the fatal errors enabled only for something which is xml-like // //Really proper solution would be to introduce a facility which would filter - //out the error messages selectively and keep just those whose cannot be + //out the error messages selectively and keep just those whose cannot be //false errors caused by a templating language. The tags pairing in facelets //is nice example as described in the issue above. if (isXmlBasedMimetype(saresult)) { for (Error e : fatalErrors) { + if (cancel) { + return; + } //remove the fatal error from the list of errors for further processing htmlRuleContext.getLeftDiagnostics().remove(e); @@ -260,26 +319,33 @@ public void computeErrors(HintsManager manager, RuleContext context, List String message = new StringBuilder().append(e.getDescription()).append('\n').append(NbBundle.getMessage(HtmlValidatorRule.class, "MSG_FatalHtmlErrorAddendum")).toString(); //add a special hint for the fatal error - // TODO - should FatalHtmlRule implement ErrorBadginRule + // TODO - should FatalHtmlRule implement ErrorBadginRule Hint fatalErrorHint = new Hint(new FatalHtmlRule(), - message, - fo, - EmbeddingUtil.getErrorOffsetRange(e, snapshot), - Collections.emptyList(), - 5);//looks like lower number o the priority means higher priority + message, + fo, + EmbeddingUtil.getErrorOffsetRange(e, snapshot), + Collections.emptyList(), + 5);//looks like lower number o the priority means higher priority hints.add(fatalErrorHint); } } + if (cancel) { + return; + } + //now process the non-fatal errors if (isErrorCheckingEnabled(result)) { for (org.netbeans.modules.html.editor.hints.HtmlRule rule : getSortedRules(manager, context, false)) { + if (cancel) { + return; + } LOG.log(Level.FINE, "checking rule {0}", rule.getDisplayName()); //skip the rule if we are called from the tasklist, //the rule is not supposed to show in tasklist and is not badging - if(errorType > 0 && !rule.showInTasklist() && !(rule instanceof ErrorBadgingRule)) { + if (errorType > 0 && !rule.showInTasklist() && !(rule instanceof ErrorBadgingRule)) { continue; } // do not run regular rules when only error badging, or vice versa @@ -287,7 +353,7 @@ public void computeErrors(HintsManager manager, RuleContext context, List continue; } boolean enabled = manager.isEnabled(rule); - //html validator error categories workaround. + //html validator error categories workaround. //See the HtmlValidatorRule.isSpecialHtmlValidatorRule() documentation LOG.log(Level.FINE, "rule runs"); if (rule.isSpecialHtmlValidatorRule()) { @@ -310,18 +376,25 @@ public void computeErrors(HintsManager manager, RuleContext context, List } Hint h = new Hint(new HtmlRule(HintSeverity.INFO, false), - NbBundle.getMessage(HtmlHintsProvider.class, "MSG_HINT_ENABLE_ERROR_CHECKS_FILE_DESCR"), //NOI18N - fo, - new OffsetRange(0, 0), - fixes, - 50); + NbBundle.getMessage(HtmlHintsProvider.class, "MSG_HINT_ENABLE_ERROR_CHECKS_FILE_DESCR"), //NOI18N + fo, + new OffsetRange(0, 0), + fixes, + 50); hints.add(h); } + if (cancel) { + return; + } + //html extensions String sourceMimetype = WebPageMetadata.getContentMimeType(result, true); for (HtmlExtension ext : HtmlExtensions.getRegisteredExtensions(sourceMimetype)) { + if (cancel) { + return; + } ext.computeErrors(manager, context, hints, unhandled); } @@ -330,7 +403,7 @@ public void computeErrors(HintsManager manager, RuleContext context, List /* test */ static List getSortedRules(HintsManager manager, RuleContext context, boolean lineContext) { Map> allHints = manager.getHints(lineContext, context); List ids - = (List) allHints.get(org.netbeans.modules.html.editor.hints.HtmlRule.Kinds.DEFAULT); + = (List) allHints.get(org.netbeans.modules.html.editor.hints.HtmlRule.Kinds.DEFAULT); if (ids == null) { return Collections.emptyList(); } @@ -408,21 +481,46 @@ public void computeErrors(HintsManager manager, RuleContext context, List */ @Override public void cancel() { + cancel = true; + } + + private void resume() { + cancel = false; + } + + private HtmlRuleContext initializeContext(RuleContext context, SyntaxAnalyzerResult saresult, List defaultFixes) { + HtmlRuleContext htmlRuleContext; + if (context instanceof HtmlRuleContext) { + htmlRuleContext = (HtmlRuleContext) context; + } else { + // Fallback: create a new HtmlRuleContext and copy base fields + htmlRuleContext = new HtmlRuleContext(); + htmlRuleContext.parserResult = context.parserResult; + htmlRuleContext.doc = context.doc; + htmlRuleContext.caretOffset = context.caretOffset; + htmlRuleContext.selectionStart = context.selectionStart; + htmlRuleContext.selectionEnd = context.selectionEnd; + htmlRuleContext.manager = context.manager; + } + htmlRuleContext.initialize(saresult, defaultFixes); + return htmlRuleContext; } /** - *

Optional builtin Rules. Typically you don't use this; you register - * your rules in your filesystem layer in the gsf-hints/mimetype1/mimetype2 + *

+ * Optional builtin Rules. Typically you don't use this; you register your + * rules in your filesystem layer in the gsf-hints/mimetype1/mimetype2 * folder, for example gsf-hints/text/x-ruby/. Error hints should go in the * "errors" folder, selection hints should go in the "selection" folder, and * all other hints should go in the "hints" folder (but note that you can * create localized folders and organize them under hints; these categories * are shown in the hints options panel. Hints returned from this method - * will be placed in the "general" folder.

This method is primarily - * intended for rules that should be added dynamically, for example for - * Rules that have a many different flavors yet a single implementation - * class (such as JavaScript's StrictWarning rule which wraps a number of - * builtin parser warnings.) + * will be placed in the "general" folder.

+ *

+ * This method is primarily intended for rules that should be added + * dynamically, for example for Rules that have a many different flavors yet + * a single implementation class (such as JavaScript's StrictWarning rule + * which wraps a number of builtin parser warnings.) * * @return A list of rules that are builtin, or null or an empty list when * there are no builtins @@ -437,11 +535,11 @@ public List getBuiltinRules() { * implementations of this interface created subclasses of the RuleContext * that can be passed around to all the executed rules. * - * @return A new instance of a RuleContext object + * @return A new instance of a HtmlRuleContext object */ @Override public RuleContext createRuleContext() { - return new RuleContext(); + return new HtmlRuleContext(); } private static final HtmlRule ERROR_RULE = new HtmlRule(HintSeverity.ERROR, true); private static final HtmlRule WARNING_RULE = new HtmlRule(HintSeverity.WARNING, true); @@ -567,7 +665,7 @@ public void implement() throws Exception { //refresh Action Items for this file reindexFile(fo); - + refreshDocument(fo); } @@ -588,7 +686,7 @@ private static void reindexFile(final FileObject fo) { public void run() { //refresh Action Items for this file IndexingManager.getDefault().refreshIndexAndWait(fo.getParent().toURL(), - Collections.singleton(fo.toURL()), true, false); + Collections.singleton(fo.toURL()), true, false); } }); } @@ -613,7 +711,7 @@ public void run() { EditorCookie editorCookie = dobj.getLookup().lookup(EditorCookie.class); StyledDocument document = editorCookie.openDocument(); forceReparse(document); - } catch (IOException ex) { + } catch (IOException ex) { Exceptions.printStackTrace(ex); } } diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/HtmlRuleContext.java b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/HtmlRuleContext.java index d518ac726c54..d36b55ca0ef5 100644 --- a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/HtmlRuleContext.java +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/HtmlRuleContext.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.List; @@ -29,6 +30,7 @@ import org.netbeans.api.project.Project; import org.netbeans.modules.csl.api.Error; import org.netbeans.modules.csl.api.HintFix; +import org.netbeans.modules.csl.api.RuleContext; import org.netbeans.modules.csl.api.Severity; import org.netbeans.modules.css.indexing.api.CssIndex; import org.netbeans.modules.html.editor.api.gsf.HtmlParserResult; @@ -43,44 +45,56 @@ * * @author marekfukala */ -public class HtmlRuleContext { +public class HtmlRuleContext extends RuleContext { - private final SyntaxAnalyzerResult syntaxAnalyzerResult; - private final HtmlParserResult parserResult; - private final List defaultFixes; - private final List leftDiagnostics; + private SyntaxAnalyzerResult syntaxAnalyzerResult; + private List defaultFixes; + private List leftDiagnostics; private CssIndex cssIndex; private DependenciesGraph cssDependencies; - private final Lines lines; - private final Collection linesWithHints; + private Lines lines; + private Collection linesWithHints; - public HtmlRuleContext(HtmlParserResult parserResult, SyntaxAnalyzerResult syntaxAnalyzerResult, List defaultFixes) { - this.parserResult = parserResult; + public void initialize(SyntaxAnalyzerResult syntaxAnalyzerResult, List defaultFixes) { this.syntaxAnalyzerResult = syntaxAnalyzerResult; this.defaultFixes = defaultFixes; - this.leftDiagnostics = new ArrayList<>(parserResult.getDiagnostics(EnumSet.allOf(Severity.class))); - this.lines = new Lines(parserResult.getSnapshot().getText()); + + HtmlParserResult htmlResult = getHtmlParserResult(); + if (htmlResult != null) { + this.leftDiagnostics = new ArrayList<>(htmlResult.getDiagnostics(EnumSet.allOf(Severity.class))); + this.lines = new Lines(htmlResult.getSnapshot().getText()); + } else { + this.leftDiagnostics = Collections.emptyList(); + this.lines = null; + } + this.linesWithHints = new HashSet<>(); } public boolean isFirstHintForPosition(int offset) { + if (lines == null) { + return true; + } + try { int lineIndex = lines.getLineIndex(offset); - if(linesWithHints.contains(lineIndex)) { + if (linesWithHints.contains(lineIndex)) { return false; } else { linesWithHints.add(lineIndex); return true; } - } catch (BadLocationException ex) { Exceptions.printStackTrace(ex); } return true; } - + public HtmlParserResult getHtmlParserResult() { - return parserResult; + if (parserResult instanceof HtmlParserResult) { + return (HtmlParserResult) parserResult; + } + return null; } public SyntaxAnalyzerResult getSyntaxAnalyzerResult() { @@ -112,15 +126,15 @@ public synchronized CssIndex getCssIndex() throws IOException { } return cssIndex; } - + public synchronized DependenciesGraph getCssDependenciesGraph() throws IOException { - if(cssDependencies == null) { + if (cssDependencies == null) { CssIndex index = getCssIndex(); - if(index != null) { + if (index != null) { cssDependencies = index.getDependencies(getFile()); } } return cssDependencies; } - + } From 9cdbbd3f93b43d2e73dd431aabd338e04e60bd49 Mon Sep 17 00:00:00 2001 From: Christian Lenz Date: Tue, 30 Dec 2025 21:19:35 +0100 Subject: [PATCH 7/7] Code cleanup as requestsd --- .../editor/hints/other/AddMissingAltAttributeHint.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java index 22878d831cee..2657c66a3460 100644 --- a/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java +++ b/ide/html.editor/src/org/netbeans/modules/html/editor/hints/other/AddMissingAltAttributeHint.java @@ -18,21 +18,18 @@ */ package org.netbeans.modules.html.editor.hints.other; -import java.awt.EventQueue; import java.util.Collections; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.text.BadLocationException; -import javax.swing.text.Document; import org.netbeans.editor.BaseDocument; import org.netbeans.modules.csl.api.Hint; import org.netbeans.modules.csl.api.HintFix; import org.netbeans.modules.csl.api.OffsetRange; import org.netbeans.modules.html.editor.hints.HtmlRuleContext; import org.netbeans.modules.html.editor.utils.HtmlTagContextUtils; -import org.netbeans.modules.parsing.api.Source; /** * @@ -53,8 +50,8 @@ private static class AddMissingAltAttributeHintFix implements HintFix { private static final Logger LOGGER = Logger.getLogger(AddMissingAltAttributeHintFix.class.getSimpleName()); - HtmlRuleContext context; - OffsetRange range; + private final HtmlRuleContext context; + private final OffsetRange range; public AddMissingAltAttributeHintFix(HtmlRuleContext context, OffsetRange range) { this.context = context;