| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed 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 com.intellij.psi.css; |
| |
| import com.intellij.lang.ParserDefinition; |
| import com.intellij.lexer.Lexer; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.tree.TokenSet; |
| import org.jetbrains.annotations.NotNull; |
| |
| public class MinifiedFilesUtil { |
| |
| private MinifiedFilesUtil() { |
| } |
| |
| private static final int MAX_OFFSET = 2048; // this is how far we look through the file |
| |
| private static final int MIN_SIZE = 150; // file should be large enough to be considered as minified (only non-comment text counts) |
| |
| private static final int MIN_LINE_LENGTH = 100; // if there's compact line that is long enough, file is considered to be minified |
| |
| private static final double MAX_UNNEEDED_OFFSET_PERCENTAGE = 0.01; |
| |
| private static final int COUNT_OF_CONSIDERING_CHARACTERS_FROM_END_OF_FILE = 400; |
| |
| /** |
| * Finds out whether the file minified by using common (not language-specific) heuristics. |
| * Can be used for checking of css/less/scss/sass and js files. |
| * |
| * @param fileContent target file content |
| * @param parserDefinition Parser definition of target language |
| * @param noWSRequireAfterTokenSet TokenSet of types that doesn't require whitespaces after them. |
| */ |
| public static boolean isMinified(@NotNull CharSequence fileContent, |
| @NotNull ParserDefinition parserDefinition, |
| @NotNull TokenSet noWSRequireAfterTokenSet) { |
| Lexer lexer = parserDefinition.createLexer(null); |
| lexer.start(fileContent); |
| if (!isMinified(lexer, parserDefinition, noWSRequireAfterTokenSet)) { |
| return false; |
| } |
| else if (lexer.getTokenType() == null) { |
| // whole file had been considered |
| return true; |
| } |
| |
| int startOffset = fileContent.length() - COUNT_OF_CONSIDERING_CHARACTERS_FROM_END_OF_FILE; |
| if (startOffset <= 0) { |
| return true; |
| } |
| lexer.start(fileContent, startOffset, fileContent.length()); |
| return isMinified(lexer, parserDefinition, noWSRequireAfterTokenSet); |
| } |
| |
| protected static boolean isMinified(Lexer lexer, ParserDefinition parserDefinition, TokenSet noWSRequireAfterTokenSet) { |
| int offsetIgnoringComments = 0; |
| int offsetIgnoringCommentsAndStrings = 0; |
| int lineLength = 0; |
| int unneededWhitespaceCount = 0; |
| IElementType lastTokenType = null; |
| TokenSet whitespaceTokens = parserDefinition.getWhitespaceTokens(); |
| TokenSet stringLiteralElements = parserDefinition.getStringLiteralElements(); |
| TokenSet commentTokens = parserDefinition.getCommentTokens(); |
| for (IElementType tokenType = lexer.getTokenType(); tokenType != null; lexer.advance(), tokenType = lexer.getTokenType()) { |
| if (commentTokens.contains(tokenType)) { |
| lastTokenType = tokenType; |
| continue; |
| } |
| |
| int tokenLength = lexer.getTokenEnd() - lexer.getTokenStart(); |
| offsetIgnoringComments += tokenLength; |
| if (stringLiteralElements.contains(tokenType)) { |
| lineLength += tokenLength; |
| lastTokenType = tokenType; |
| continue; |
| } |
| offsetIgnoringCommentsAndStrings += tokenLength; |
| |
| if (whitespaceTokens.contains(tokenType)) { |
| if (!commentTokens.contains(lastTokenType) && tokenLength > 1) { |
| lexer.advance(); |
| if (lexer.getTokenType() == null) { |
| // it was last token |
| break; |
| } else { |
| return false; |
| } |
| } |
| |
| if (lexer.getTokenText().contains("\n")) { |
| if (lineLength > MIN_LINE_LENGTH) { |
| break; |
| } |
| lineLength = 0; |
| } |
| |
| if (" ".equals(lexer.getTokenText()) && noWSRequireAfterTokenSet.contains(lastTokenType)) { |
| unneededWhitespaceCount++; |
| } |
| } |
| else { |
| lineLength += tokenLength; |
| } |
| |
| if (offsetIgnoringComments >= MAX_OFFSET) { |
| break; |
| } |
| lastTokenType = tokenType; |
| } |
| |
| return offsetIgnoringComments >= MIN_SIZE && |
| (unneededWhitespaceCount + 0.0d) / offsetIgnoringCommentsAndStrings < MAX_UNNEEDED_OFFSET_PERCENTAGE; |
| } |
| |
| public static boolean isMinified(@NotNull CharSequence fileContent, @NotNull ParserDefinition parserDefinition) { |
| return isMinified(fileContent, parserDefinition, TokenSet.EMPTY); |
| } |
| } |