| /* |
| * 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.css.lib.nbparser; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import org.netbeans.modules.css.lib.ExtCss3Lexer; |
| import org.netbeans.modules.css.lib.ExtCss3Parser; |
| import org.netbeans.modules.css.lib.api.CssParserResult; |
| import javax.swing.event.ChangeListener; |
| import org.antlr.runtime.CommonTokenStream; |
| import org.antlr.runtime.RecognitionException; |
| import org.antlr.runtime.TokenStream; |
| import org.netbeans.api.editor.mimelookup.MimePath; |
| import org.netbeans.modules.css.lib.AbstractParseTreeNode; |
| import org.netbeans.modules.css.lib.NbParseTreeBuilder; |
| import org.netbeans.modules.css.lib.api.ProblemDescription; |
| import org.netbeans.modules.parsing.api.Snapshot; |
| import org.netbeans.modules.parsing.api.Task; |
| import org.netbeans.modules.parsing.spi.ParseException; |
| import org.netbeans.modules.parsing.spi.Parser; |
| import org.netbeans.modules.parsing.spi.SourceModificationEvent; |
| import org.netbeans.modules.web.common.api.Constants; |
| import org.openide.filesystems.FileObject; |
| import org.openide.util.CharSequences; |
| import org.openide.util.NbBundle; |
| |
| /** |
| * |
| * @author mfukala@netbeans.org |
| */ |
| @NbBundle.Messages({ |
| "too.large.snapshot=The source code is too large for CSS parsing.", |
| }) |
| public class CssParser extends Parser { |
| |
| private static final Logger LOG = Logger.getLogger(CssParser.class.getSimpleName()); |
| |
| private final AtomicBoolean cancelled = new AtomicBoolean(); |
| private final String topLevelSnapshotMimetype; |
| |
| private static final int MAX_SNAPSHOT_SIZE = 2 * 1024 * 1014; //2MB |
| |
| //cache |
| private Snapshot snapshot; |
| private AbstractParseTreeNode tree; |
| private List<ProblemDescription> problems; |
| |
| public CssParser() { |
| topLevelSnapshotMimetype = null; |
| } |
| |
| /* test */ public CssParser(String topLevelSnapshotMimetype) { |
| this.topLevelSnapshotMimetype = topLevelSnapshotMimetype; |
| } |
| |
| @Override |
| public void parse(Snapshot snapshot, Task task, SourceModificationEvent event) throws ParseException { |
| cancelled.set(false); |
| if (snapshot == null) { |
| return; |
| } |
| |
| this.snapshot = snapshot; |
| FileObject fo = snapshot.getSource().getFileObject(); |
| String fileName = fo == null ? "no file" : fo.getPath(); //NOI18N |
| String mimeType = topLevelSnapshotMimetype != null ? topLevelSnapshotMimetype : (fo == null ? null : fo.getMIMEType()); |
| LOG.log(Level.FINE, "Parsing {0} ", fileName); //NOI18N |
| long start = System.currentTimeMillis(); |
| try { |
| boolean tooLargeSnapshot = snapshot.getText().length() > MAX_SNAPSHOT_SIZE; |
| |
| //parse just an empty string in case of an oversize snapshot |
| CharSequence source = tooLargeSnapshot ? "" : snapshot.getText(); |
| |
| ExtCss3Lexer lexer = new ExtCss3Lexer(source, mimeType); |
| TokenStream tokenstream = new ProgressingTokenStream( |
| 10_000_000, |
| new CommonTokenStream(lexer)); |
| NbParseTreeBuilder builder = new NbParseTreeBuilder(source); |
| ExtCss3Parser parser = new ExtCss3Parser(tokenstream, builder, mimeType); |
| |
| if (cancelled.get()) { |
| return; |
| } |
| |
| try { |
| parser.styleSheet(); |
| } catch (ProgressingFailedException pfe) { |
| LOG.log(Level.INFO, "CSS/SASS/LESS document exceeded maximum reads: " + snapshot.getSource().getFileObject()); |
| this.tree = null; |
| this.problems = Arrays.asList(new ProblemDescription( |
| 0, snapshot.getText().length(), "Failed to parse CSS/SASS/LESS document", ProblemDescription.Keys.PARSING.name(), ProblemDescription.Type.FATAL |
| )); |
| return; |
| } |
| |
| if (cancelled.get()) { |
| return; |
| } |
| |
| AbstractParseTreeNode tree_local = builder.getTree(); |
| List<ProblemDescription> problems_local = new ArrayList<>(); |
| //add lexer issues |
| problems_local.addAll(lexer.getProblems()); |
| //add parser issues |
| problems_local.addAll(builder.getProblems()); |
| |
| filterProblemsInVirtualCode(snapshot, problems_local); |
| filterTemplatingProblems(snapshot, problems_local); |
| |
| if(tooLargeSnapshot) { |
| //add a problem description informing the user there's something 'wrong' with the file |
| problems_local.add(new ProblemDescription(0, 0, |
| Bundle.too_large_snapshot(), ProblemDescription.Keys.PARSING.name(), ProblemDescription.Type.WARNING )); |
| } |
| |
| if (cancelled.get()) { |
| return; |
| } |
| |
| this.tree = tree_local; |
| this.problems = problems_local; |
| |
| } catch (RecognitionException ex) { |
| throw new ParseException(String.format("Error parsing %s snapshot.", snapshot), ex); //NOI18N |
| } finally { |
| long end = System.currentTimeMillis(); |
| LOG.log(Level.FINE, "Parsing of {0} took {1} ms.", new Object[]{fileName, (end - start)}); //NOI18N |
| } |
| |
| } |
| |
| @Override |
| public CssParserResult getResult(Task task) throws ParseException { |
| return cancelled.get() || (tree == null) ? null : new CssParserResult(snapshot, tree, problems); |
| } |
| |
| @Override |
| public void cancel(CancelReason reason, SourceModificationEvent event) { |
| if (CancelReason.SOURCE_MODIFICATION_EVENT == reason && event.sourceChanged()) { |
| cancelled.set(true); |
| tree = null; |
| problems = null; |
| snapshot = null; |
| } |
| } |
| |
| @Override |
| public void addChangeListener(ChangeListener changeListener) { |
| //no-op |
| } |
| |
| @Override |
| public void removeChangeListener(ChangeListener changeListener) { |
| //no-op |
| } |
| |
| private static void filterProblemsInVirtualCode(Snapshot snapshot, List<ProblemDescription> problems) { |
| ListIterator<ProblemDescription> listIterator = problems.listIterator(); |
| while (listIterator.hasNext()) { |
| ProblemDescription p = listIterator.next(); |
| int from = p.getFrom(); |
| int to = p.getTo(); |
| if (snapshot.getOriginalOffset(from) == -1 || snapshot.getOriginalOffset(to) == -1) { |
| listIterator.remove(); |
| } |
| } |
| } |
| |
| //filtering out problems caused by templating languages |
| private static void filterTemplatingProblems(Snapshot snapshot, List<ProblemDescription> problems) { |
| MimePath mimePath = snapshot.getMimePath(); |
| if (mimePath.size() > 1) { |
| //once the css code is embedde, we need to assume there might be sg. generated. |
| //now also for plain html code as there are the html extenstions like Angular or Knockout, |
| //which might contain expressions like <div style="width: {{model.getWidth()}}%">...</div> |
| CharSequence text = snapshot.getText(); |
| ListIterator<ProblemDescription> listIterator = problems.listIterator(); |
| while (listIterator.hasNext()) { |
| ProblemDescription p = listIterator.next(); |
| //XXX Idealy the filtering context should be dependent on the enclosing node |
| //sg. like if there's a templating error in an declaration - search the whole |
| //declaration for the templating mark. |
| // |
| //Using some simplification - line context, though some nodes may span multiple |
| //lines and the templating mark may not necessarily be at the line with the error. |
| // |
| //so find line bounds... |
| |
| //the "premature end of file" error has position pointing after the last char (=text.length())! |
| if (p.getFrom() == text.length()) { |
| listIterator.remove(); //consider this as hidden error |
| continue; |
| } |
| |
| int from, to; |
| for (from = p.getFrom(); from > 0; from--) { |
| char c = text.charAt(from); |
| if (c == '\n') { |
| break; |
| } |
| } |
| for (to = p.getTo(); to < text.length(); to++) { |
| char c = text.charAt(to); |
| if (c == '\n') { |
| break; |
| } |
| } |
| //check if there's the templating mark (@@@) in the context |
| CharSequence img = snapshot.getText().subSequence(from, to); |
| if (CharSequences.indexOf(img, Constants.LANGUAGE_SNIPPET_SEPARATOR) != -1) { |
| listIterator.remove(); |
| } |
| } |
| } |
| } |
| |
| } |