/* * Copyright (C) 2012-2016 NS Solutions Corporation * * 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.htmlhifive.tools.jslint.parse; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.script.ScriptException; import org.apache.commons.lang.StringUtils; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceVisitor; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.Status; import com.htmlhifive.tools.jslint.JSLintPlugin; import com.htmlhifive.tools.jslint.JSLintPluginConstant; import com.htmlhifive.tools.jslint.configure.ConfigBean; import com.htmlhifive.tools.jslint.configure.FilterBean; import com.htmlhifive.tools.jslint.configure.FilterBean.FilterLevel; import com.htmlhifive.tools.jslint.configure.JSLintConfigManager; import com.htmlhifive.tools.jslint.engine.JSChecker; import com.htmlhifive.tools.jslint.engine.JSCheckerErrorBean; import com.htmlhifive.tools.jslint.engine.JSCheckerFactory; import com.htmlhifive.tools.jslint.engine.JSCheckerResult; import com.htmlhifive.tools.jslint.engine.option.CheckOption; import com.htmlhifive.tools.jslint.engine.option.CheckOptionFileWrapper; import com.htmlhifive.tools.jslint.engine.option.CheckOptionFileWrapperFactory; import com.htmlhifive.tools.jslint.engine.option.Engine; import com.htmlhifive.tools.jslint.engine.option.JSHintDefaultOptions; import com.htmlhifive.tools.jslint.logger.JSLintPluginLogger; import com.htmlhifive.tools.jslint.logger.JSLintPluginLoggerFactory; import com.htmlhifive.tools.jslint.messages.Messages; import com.htmlhifive.tools.jslint.util.ConfigBeanUtil; import com.htmlhifive.tools.jslint.util.PluginResourceUtils; /** * JSlintを使用し、選択されたオブジェクトをパースするクラス. * * @author NS Solutions Corporation * */ public class JsParser implements Parser { /** * ロガー. */ private static JSLintPluginLogger logger = JSLintPluginLoggerFactory.getLogger(JsParser.class); /** * jsファイルを解析するタスク量. */ private static final int TASK_PARSE_JS = 1000; /** * ビューにマークするタスク量. */ private static final int TASK_MARK_VIEW = 600; /** * JSファイルを取得するタスク量. */ private static final int TASK_SERCH_JS = 50; /** * JSLint.jsおよびオプションファイルを読み込むタスク量. */ private static final int TASK_LOAD_JSLINT = 50; /** * 全てのタスク量. */ private static final int TASK_ALL; static { int allTask = 0; allTask += TASK_PARSE_JS; allTask += TASK_MARK_VIEW; allTask += TASK_SERCH_JS; allTask += TASK_LOAD_JSLINT; TASK_ALL = allTask; } /** * 実行中のパーサ. */ private IProgressMonitor monitor = null; /** * 解析されるリソース(選択されたリソース). */ private IResource resource; /** * 解析に利用されるコンフィグビーン. */ private ConfigBean bean; /** * オプションで設定した最大エラー数. */ private int maxerr = 50; /** * コンストラクタ. * * @param resource 解析されるリソース. * @param monitor */ public JsParser(IResource resource) { this.resource = resource; IProject project = resource.getProject(); ConfigBean configBean = JSLintConfigManager.getConfigBean(project); if (configBean.isUseOtherProject()) { bean = JSLintConfigManager.getConfigBean((IProject) PluginResourceUtils.pathToContainer(configBean .getOtherProjectPath())); bean.setExternalLibPathList(configBean.getExternalLibPathList()); bean.setInternalLibPathList(configBean.getInternalLibPathList()); } else { bean = configBean; } logger.debug("use project path : " + bean.getOtherProjectPath()); } @Override public synchronized void parse(IProgressMonitor monitor) throws CoreException, InterruptedException { try { ParserManager.replaceCurrentParser(this); this.monitor = monitor; // 入力チェック String[] errorMessages = ConfigBeanUtil.checkProperty(bean); if (errorMessages.length > 0) { throwCoreException(IStatus.WARNING, errorMessages); } checkCancel(); monitor.beginTask(Messages.T0000.getText(), TASK_ALL); monitor.subTask(Messages.T0001.getText()); monitor.worked(TASK_SERCH_JS); monitor.subTask(Messages.T0002.getText()); logger.debug("prop file is " + bean.getOptionFilePath()); checkCancel(); // エンジンファイルの取得,エンジンオブジェクト生成. checkCancel(); IFile jslintFile = (IFile) ResourcesPlugin.getWorkspace().getRoot().findMember(bean.getJsLintPath()); long parseStart = System.currentTimeMillis(); // チェッカエンジンの指定. checkCancel(); Engine engine = getEngine(jslintFile); // プロパティファイルの取得 CheckOptionFileWrapper option = null; CheckOption[] newOptions = null; if (StringUtils.isNotEmpty(bean.getOptionFilePath())) { IFile propFile = (IFile) ResourcesPlugin.getWorkspace().getRoot().findMember(bean.getOptionFilePath()); option = CheckOptionFileWrapperFactory.createCheckOptionFileWrapper(propFile); if (option.getOptions(engine).length != 0) { // 最大エラー数を取得. String maxerrStr = option.getOption("maxerr", engine.getKey()).getValue(); if (maxerrStr != null) { maxerr = Integer.parseInt(maxerrStr); } CheckOption[] options = option.getEnableOptions(engine); newOptions = handleMaxerr(options); } } logger.debug("jslint file is " + bean.getJsLintPath()); checkCancel(); JSChecker jsLint = JSCheckerFactory.createJSChecker(jslintFile, newOptions); logger.debug("create checker time" + String.valueOf(System.currentTimeMillis() - parseStart)); monitor.worked(TASK_LOAD_JSLINT); monitor.setTaskName(Messages.T0003.getText()); long getLibStart = System.currentTimeMillis(); // ライブラリの取得. JsFileInfo libStr = getLibrary(); logger.debug("get lib time " + String.valueOf(System.currentTimeMillis() - getLibStart)); checkCancel(); // ソースファイルの取得. IFile[] jsFiles = getJsFile(); beforeCheck(); // ソースファイルのチェック. List<JsMarkingSupport> markList = new ArrayList<JsMarkingSupport>(); for (IFile iFile : jsFiles) { checkCancel(); monitor.subTask(Messages.T0006.format(iFile.getName())); logger.debug("targetFile : " + iFile.getName()); JsFileInfo target = null; JsFileInfo fileInfo = new JsFileInfo(iFile); JSCheckerResult result = null; if (libStr != null) { target = libStr.clone(); target.append(fileInfo); } else { target = fileInfo.clone(); } beforeCheckAtFile(iFile); checkCancel(); long parseAtFileStart = System.currentTimeMillis(); result = jsLint.lint(target.getSourceStr()); logger.debug("parse at file time" + String.valueOf(System.currentTimeMillis() - parseAtFileStart)); addMakingList(markList, iFile, result.getErrors(), libStr != null ? libStr.getLineCount() : 0); // 1ファイルあたりのタスク量を進める monitor.worked(TASK_PARSE_JS / jsFiles.length); } monitor.setTaskName(Messages.T0007.getText()); mark(markList); logger.debug("parse time : " + String.valueOf(System.currentTimeMillis() - parseStart)); } catch (IOException e) { logger.put(Messages.EM0100, e); throwCoreException(IStatus.ERROR, e); } catch (ScriptException e) { throwCoreException(IStatus.WARNING, Messages.EM0003.format(e.getFileName())); } monitor.subTask(Messages.T0004.getText()); monitor.done(); ParserManager.clearCurrentParser(); } /** * キャンセルしたかどうかをチェックする. * * @throws InterruptedException キャンセル例外. */ private void checkCancel() throws InterruptedException { if (monitor.isCanceled()) { ParserManager.clearCurrentParser(); throw new InterruptedException(); } } /** * 引数のoptionsのmaxerrを差し替える.<br> * オプションの設定値をそのままJSLintに適用してしまうと,<br> * ライブラリと合わせると数がずれてしまうため、maxerrのオプションの値を変える必要がある。 * * @param options オプション. * @return 変換後のオプション. */ private CheckOption[] handleMaxerr(CheckOption[] options) { List<CheckOption> newOptionList = new ArrayList<CheckOption>(); boolean containMaxerr = false; for (CheckOption checkOption : options) { logger.debug("option : " + checkOption.toString()); if ("maxerr".equals(checkOption.getKey())) { checkOption.setValue(String.valueOf(Integer.MAX_VALUE)); containMaxerr = true; } newOptionList.add(checkOption); } if (containMaxerr) { return (CheckOption[]) newOptionList.toArray(new CheckOption[newOptionList.size()]); } // なかった場合はmaxerrのオプションを加える. CheckOption maxerrOption = JSHintDefaultOptions.MAXERR.convertToOption(); newOptionList.add(maxerrOption); return (CheckOption[]) newOptionList.toArray(new CheckOption[newOptionList.size()]); } /** * エンジンファイルからEngineオブジェクトを取得する. * * @param jslintFile エンジンファイル. * @return Engineオブジェクト. */ private Engine getEngine(IFile jslintFile) { if (JSLintPluginConstant.JS_LINT_NAME.equals(jslintFile.getName())) { return Engine.JSLINT; } else { return Engine.JSHINT; } } /** * parse前の初期化処理を行う. * * @throws CoreException 解析例外. */ void beforeCheck() throws CoreException { } /** * 対象ファイルの解析前の処理. * * @param file 解析ファイル. * @throws CoreException 解析例外. */ void beforeCheckAtFile(IFile file) throws CoreException { file.deleteMarkers(JSLintPluginConstant.JS_TYPE_MARKER, true, IResource.DEPTH_INFINITE); } /** * ライブラリの設定を取得する. * * @return ライブラリ情報. * @throws CoreException 解析例外. */ protected JsFileInfo getLibrary() throws CoreException { return null; } /** * 検査対象のJsファイルを取得する. * * @param jsFileList * @return 検査対象のJsファイル * @throws CoreException 解析例外. */ protected IFile[] getJsFile() throws CoreException { final List<IFile> jsFileList = new ArrayList<IFile>(); if (resource instanceof IContainer) { IContainer container = (IContainer) resource; // コンテナ内のjsファイルを取得. container.accept(new IResourceVisitor() { @Override public boolean visit(IResource resource) throws CoreException { if (JSLintPluginConstant.EXTENTION_JS.equals(resource.getFileExtension())) { jsFileList.add((IFile) resource); } return true; } }); } else if (JSLintPluginConstant.EXTENTION_JS.equals(resource.getFileExtension())) { jsFileList.add((IFile) resource); } else { // 恐らく来ない throw new AssertionError(); } return (IFile[]) jsFileList.toArray(new IFile[jsFileList.size()]); } /** * CoreExceptinをスローする. * * @param severity エラーレベル. * @param e 例外. * @throws CoreException CoreException例外 */ private void throwCoreException(int severity, Exception e) throws CoreException { throw new CoreException(new Status(severity, JSLintPlugin.PLUGIN_ID, null, e)); } /** * CoreExceptinをスローする. * * @param severity エラーレベル. * @param messages エラーメッセージ. * @throws CoreException CoreException例外 */ private void throwCoreException(int severity, String... messages) throws CoreException { MultiStatus multiStatus = new MultiStatus(JSLintPlugin.PLUGIN_ID, IStatus.OK, Messages.EM0001.getText(), null); for (String string : messages) { IStatus iStatus = new Status(severity, JSLintPlugin.PLUGIN_ID, string, null); multiStatus.add(iStatus); } throw new CoreException(multiStatus); } /** * 引数のリストにマーキング情報を追加する. * * @param list マーキング情報リスト. * @param iFile 対象のjsファイル. * @param errors エラー情報. * @param startPosition スタート位置. * @throws CoreException 例外. */ private void addMakingList(List<JsMarkingSupport> list, IFile iFile, JSCheckerErrorBean[] errors, int startPosition) throws CoreException { int i = 0; for (JSCheckerErrorBean jsLintError : errors) { if (jsLintError.getLine() > startPosition) { // フィルタレベル判定用. FilterLevel revel = matchExcludeFilter(jsLintError.getReason()); if (i < maxerr && !FilterLevel.IGNORE.equals(revel)) { JsMarkingSupport support = new JsMarkingSupport( iFile.createMarker(JSLintPluginConstant.JS_TYPE_MARKER)); support.putMessageAttribute(jsLintError.getReason()); if (FilterLevel.ERROR.equals(revel)) { support.putSeverityAttribute(IMarker.SEVERITY_ERROR); } else { support.putSeverityAttribute(IMarker.SEVERITY_WARNING); } support.putLocationAttribute(Messages.VM0000.format(jsLintError.getLine().intValue() - startPosition)); support.putLineNumAttribute(jsLintError.getLine().intValue() - startPosition); list.add(support); i++; } } } } /** * 引数のリストのマーク情報を全てビューに反映する. * * @param list 対象のマーク情報. */ private void mark(List<JsMarkingSupport> list) { int i = 0; int atFile = list.size() / 400; if (atFile == 0) { atFile = 1; } for (JsMarkingSupport jsMarkingSupport : list) { jsMarkingSupport.marking(); i++; if (i % atFile == 0) { monitor.subTask(Messages.T0008.format(i + 1, list.size())); this.monitor.worked(TASK_MARK_VIEW * atFile / list.size()); } } } /** * エラー理由がフィルターににマッチするかどうかを判定する. * * @param reason エラー理由. * @return フィルターにマッチすれば該当するフィルターレベル,しなければnull. */ private FilterLevel matchExcludeFilter(String reason) { FilterBean[] filterBeans = bean.getFilterBeans(); FilterLevel revel = null; for (FilterBean filterBean : filterBeans) { if (!filterBean.isState()) { continue; } Pattern pattern = Pattern.compile(filterBean.getRegex()); Matcher matcher = pattern.matcher(reason); // エラーと無視が被った場合無視が優先 if (matcher.matches()) { revel = filterBean.getRevel(); if (FilterLevel.IGNORE.equals(revel)) { // レベルが無視だったら返す. return revel; } } } return revel; } /** * 解析されるリソース(選択されたリソース)を取得する. * * @return 解析されるリソース(選択されたリソース) */ protected IResource getResource() { return resource; } @Override public void cansel() { monitor.setCanceled(true); } /** * 設定ファイルビーンを取得する. * * @return 設定ファイルビーン. */ protected ConfigBean getBean() { return bean; } }