Mercurial > repos > shellac > sam_consensus_v3
comparison env/lib/python3.9/site-packages/cwltool/validate_js.py @ 0:4f3585e2f14b draft default tip
"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
| author | shellac |
|---|---|
| date | Mon, 22 Mar 2021 18:12:50 +0000 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:4f3585e2f14b |
|---|---|
| 1 import copy | |
| 2 import itertools | |
| 3 import json | |
| 4 import logging | |
| 5 from collections import namedtuple | |
| 6 from typing import ( | |
| 7 Any, | |
| 8 Dict, | |
| 9 List, | |
| 10 MutableMapping, | |
| 11 MutableSequence, | |
| 12 Optional, | |
| 13 Tuple, | |
| 14 Union, | |
| 15 cast, | |
| 16 ) | |
| 17 | |
| 18 from pkg_resources import resource_stream | |
| 19 from ruamel.yaml.comments import CommentedMap | |
| 20 from schema_salad.avro.schema import ( | |
| 21 ArraySchema, | |
| 22 EnumSchema, | |
| 23 RecordSchema, | |
| 24 Schema, | |
| 25 UnionSchema, | |
| 26 ) | |
| 27 from schema_salad.sourceline import SourceLine | |
| 28 from schema_salad.utils import json_dumps | |
| 29 from schema_salad.validate import validate_ex | |
| 30 | |
| 31 from .errors import WorkflowException | |
| 32 from .expression import SubstitutionError | |
| 33 from .expression import scanner as scan_expression | |
| 34 from .loghandler import _logger | |
| 35 from .sandboxjs import code_fragment_to_js, exec_js_process | |
| 36 | |
| 37 | |
| 38 def is_expression(tool, schema): | |
| 39 # type: (Any, Optional[Schema]) -> bool | |
| 40 return ( | |
| 41 isinstance(schema, EnumSchema) | |
| 42 and schema.name == "Expression" | |
| 43 and isinstance(tool, str) | |
| 44 ) | |
| 45 | |
| 46 | |
| 47 class SuppressLog(logging.Filter): | |
| 48 def __init__(self, name): # type: (str) -> None | |
| 49 """Initialize this log suppressor.""" | |
| 50 name = str(name) | |
| 51 super().__init__(name) | |
| 52 | |
| 53 def filter(self, record): # type: (logging.LogRecord) -> bool | |
| 54 return False | |
| 55 | |
| 56 | |
| 57 _logger_validation_warnings = logging.getLogger("cwltool.validation_warnings") | |
| 58 _logger_validation_warnings.addFilter(SuppressLog("cwltool.validation_warnings")) | |
| 59 | |
| 60 | |
| 61 def get_expressions( | |
| 62 tool: Union[CommentedMap, str], | |
| 63 schema: Optional[Union[Schema, ArraySchema]], | |
| 64 source_line: Optional[SourceLine] = None, | |
| 65 ) -> List[Tuple[str, Optional[SourceLine]]]: | |
| 66 if is_expression(tool, schema): | |
| 67 return [(cast(str, tool), source_line)] | |
| 68 elif isinstance(schema, UnionSchema): | |
| 69 valid_schema = None | |
| 70 | |
| 71 for possible_schema in schema.schemas: | |
| 72 if is_expression(tool, possible_schema): | |
| 73 return [(cast(str, tool), source_line)] | |
| 74 elif validate_ex( | |
| 75 possible_schema, | |
| 76 tool, | |
| 77 raise_ex=False, | |
| 78 logger=_logger_validation_warnings, | |
| 79 ): | |
| 80 valid_schema = possible_schema | |
| 81 | |
| 82 return get_expressions(tool, valid_schema, source_line) | |
| 83 elif isinstance(schema, ArraySchema): | |
| 84 if not isinstance(tool, MutableSequence): | |
| 85 return [] | |
| 86 | |
| 87 return list( | |
| 88 itertools.chain( | |
| 89 *map( | |
| 90 lambda x: get_expressions( | |
| 91 x[1], schema.items, SourceLine(tool, x[0]) # type: ignore | |
| 92 ), | |
| 93 enumerate(tool), | |
| 94 ) | |
| 95 ) | |
| 96 ) | |
| 97 | |
| 98 elif isinstance(schema, RecordSchema): | |
| 99 if not isinstance(tool, MutableMapping): | |
| 100 return [] | |
| 101 | |
| 102 expression_nodes = [] | |
| 103 | |
| 104 for schema_field in schema.fields: | |
| 105 if schema_field.name in tool: | |
| 106 expression_nodes.extend( | |
| 107 get_expressions( | |
| 108 tool[schema_field.name], | |
| 109 schema_field.type, | |
| 110 SourceLine(tool, schema_field.name), | |
| 111 ) | |
| 112 ) | |
| 113 | |
| 114 return expression_nodes | |
| 115 else: | |
| 116 return [] | |
| 117 | |
| 118 | |
| 119 JSHintJSReturn = namedtuple("JSHintJSReturn", ["errors", "globals"]) | |
| 120 | |
| 121 | |
| 122 def jshint_js( | |
| 123 js_text: str, | |
| 124 globals: Optional[List[str]] = None, | |
| 125 options: Optional[Dict[str, Union[List[str], str, int]]] = None, | |
| 126 ) -> JSHintJSReturn: | |
| 127 if globals is None: | |
| 128 globals = [] | |
| 129 if options is None: | |
| 130 options = { | |
| 131 "includewarnings": [ | |
| 132 "W117", # <VARIABLE> not defined | |
| 133 "W104", | |
| 134 "W119", # using ES6 features | |
| 135 ], | |
| 136 "strict": "implied", | |
| 137 "esversion": 5, | |
| 138 } | |
| 139 | |
| 140 with resource_stream(__name__, "jshint/jshint.js") as res: | |
| 141 # NOTE: we need a global variable for lodash (which jshint depends on) | |
| 142 jshint_functions_text = "var global = this;" + res.read().decode("utf-8") | |
| 143 | |
| 144 with resource_stream(__name__, "jshint/jshint_wrapper.js") as res2: | |
| 145 # NOTE: we need to assign to ob, as the expression {validateJS: validateJS} as an expression | |
| 146 # is interpreted as a block with a label `validateJS` | |
| 147 jshint_functions_text += ( | |
| 148 "\n" | |
| 149 + res2.read().decode("utf-8") | |
| 150 + "\nvar ob = {validateJS: validateJS}; ob" | |
| 151 ) | |
| 152 | |
| 153 returncode, stdout, stderr = exec_js_process( | |
| 154 "validateJS(%s)" | |
| 155 % json_dumps({"code": js_text, "options": options, "globals": globals}), | |
| 156 timeout=30, | |
| 157 context=jshint_functions_text, | |
| 158 ) | |
| 159 | |
| 160 def dump_jshint_error(): | |
| 161 # type: () -> None | |
| 162 raise RuntimeError( | |
| 163 'jshint failed to run succesfully\nreturncode: %d\nstdout: "%s"\nstderr: "%s"' | |
| 164 % (returncode, stdout, stderr) | |
| 165 ) | |
| 166 | |
| 167 if returncode == -1: | |
| 168 _logger.warning("jshint process timed out") | |
| 169 | |
| 170 if returncode != 0: | |
| 171 dump_jshint_error() | |
| 172 | |
| 173 try: | |
| 174 jshint_json = json.loads(stdout) | |
| 175 except ValueError: | |
| 176 dump_jshint_error() | |
| 177 | |
| 178 jshint_errors = [] # type: List[str] | |
| 179 | |
| 180 js_text_lines = js_text.split("\n") | |
| 181 | |
| 182 for jshint_error_obj in jshint_json.get("errors", []): | |
| 183 text = "JSHINT: " + js_text_lines[jshint_error_obj["line"] - 1] + "\n" | |
| 184 text += "JSHINT: " + " " * (jshint_error_obj["character"] - 1) + "^\n" | |
| 185 text += "JSHINT: {}: {}".format( | |
| 186 jshint_error_obj["code"], | |
| 187 jshint_error_obj["reason"], | |
| 188 ) | |
| 189 jshint_errors.append(text) | |
| 190 | |
| 191 return JSHintJSReturn(jshint_errors, jshint_json.get("globals", [])) | |
| 192 | |
| 193 | |
| 194 def print_js_hint_messages( | |
| 195 js_hint_messages: List[str], source_line: Optional[SourceLine] | |
| 196 ) -> None: | |
| 197 if source_line is not None: | |
| 198 for js_hint_message in js_hint_messages: | |
| 199 _logger.warning(source_line.makeError(js_hint_message)) | |
| 200 | |
| 201 | |
| 202 def validate_js_expressions( | |
| 203 tool: CommentedMap, | |
| 204 schema: Schema, | |
| 205 jshint_options: Optional[Dict[str, Union[List[str], str, int]]] = None, | |
| 206 ) -> None: | |
| 207 | |
| 208 if tool.get("requirements") is None: | |
| 209 return | |
| 210 | |
| 211 requirements = tool["requirements"] | |
| 212 | |
| 213 default_globals = ["self", "inputs", "runtime", "console"] | |
| 214 | |
| 215 for prop in reversed(requirements): | |
| 216 if prop["class"] == "InlineJavascriptRequirement": | |
| 217 expression_lib = prop.get("expressionLib", []) | |
| 218 break | |
| 219 else: | |
| 220 return | |
| 221 | |
| 222 js_globals = copy.deepcopy(default_globals) | |
| 223 | |
| 224 for i, expression_lib_line in enumerate(expression_lib): | |
| 225 expression_lib_line_errors, expression_lib_line_globals = jshint_js( | |
| 226 expression_lib_line, js_globals, jshint_options | |
| 227 ) | |
| 228 js_globals.extend(expression_lib_line_globals) | |
| 229 print_js_hint_messages( | |
| 230 expression_lib_line_errors, SourceLine(expression_lib, i) | |
| 231 ) | |
| 232 | |
| 233 expressions = get_expressions(tool, schema) | |
| 234 | |
| 235 for expression, source_line in expressions: | |
| 236 unscanned_str = expression.strip() | |
| 237 try: | |
| 238 scan_slice = scan_expression(unscanned_str) | |
| 239 except SubstitutionError as se: | |
| 240 if source_line: | |
| 241 source_line.raise_type = WorkflowException | |
| 242 raise source_line.makeError(str(se)) | |
| 243 else: | |
| 244 raise se | |
| 245 | |
| 246 while scan_slice: | |
| 247 if unscanned_str[scan_slice[0]] == "$": | |
| 248 code_fragment = unscanned_str[scan_slice[0] + 1 : scan_slice[1]] | |
| 249 code_fragment_js = code_fragment_to_js(code_fragment, "") | |
| 250 expression_errors, _ = jshint_js( | |
| 251 code_fragment_js, js_globals, jshint_options | |
| 252 ) | |
| 253 print_js_hint_messages(expression_errors, source_line) | |
| 254 | |
| 255 unscanned_str = unscanned_str[scan_slice[1] :] | |
| 256 scan_slice = scan_expression(unscanned_str) |
