Merge branch 'master' of https://github.com/metersphere/metersphere
This commit is contained in:
commit
42ef1bcc89
|
@ -363,6 +363,13 @@
|
|||
<version>4.5.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-text</artifactId>
|
||||
<version>1.3</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -14,6 +14,7 @@ import io.metersphere.commons.utils.SessionUtils;
|
|||
import io.metersphere.controller.request.QueryScheduleRequest;
|
||||
import io.metersphere.dto.ScheduleDao;
|
||||
import io.metersphere.service.CheckOwnerService;
|
||||
|
||||
import org.apache.shiro.authz.annotation.Logical;
|
||||
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
@ -21,8 +22,12 @@ import org.springframework.web.multipart.MultipartFile;
|
|||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import static io.metersphere.commons.utils.JsonPathUtils.getListJson;
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = "/api")
|
||||
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR)
|
||||
|
@ -79,7 +84,6 @@ public class APITestController {
|
|||
public void mergeCreate(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "selectIds") List<String> selectIds) {
|
||||
apiTestService.mergeCreate(request, file, selectIds);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/update", consumes = {"multipart/form-data"})
|
||||
public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
|
||||
checkownerService.checkApiTestOwner(request.getId());
|
||||
|
@ -141,4 +145,9 @@ public class APITestController {
|
|||
public List<ScheduleDao> listSchedule(@RequestBody QueryScheduleRequest request) {
|
||||
return apiTestService.listSchedule(request);
|
||||
}
|
||||
|
||||
@PostMapping("/getJsonPaths")
|
||||
public List<HashMap> getJsonPaths(@RequestBody QueryJsonPathRequest request) {
|
||||
return getListJson(request.getJsonPath());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package io.metersphere.api.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class QueryJsonPathRequest implements Serializable {
|
||||
private String jsonPath;
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
package io.metersphere.commons.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson.JSONPath;
|
||||
|
||||
public class JsonPathUtils {
|
||||
|
||||
public static List<HashMap> getListJson(String jsonString) {
|
||||
|
||||
JSONObject jsonObject =JSONObject.parseObject(jsonString);
|
||||
List<HashMap> allJsons =new ArrayList<>();
|
||||
|
||||
// 获取到所有jsonpath后,获取所有的key
|
||||
List<String> jsonPaths = JSONPath.paths(jsonObject).keySet()
|
||||
.stream()
|
||||
.collect(Collectors.toList());
|
||||
//去掉根节点key
|
||||
List<String> parentNode = new ArrayList<>();
|
||||
//根节点key
|
||||
parentNode.add("/");
|
||||
//循环获取父节点key,只保留叶子节点
|
||||
for (int i = 0; i < jsonPaths.size(); i++) {
|
||||
if (jsonPaths.get(i).lastIndexOf("/") > 0) {
|
||||
parentNode.add(jsonPaths.get(i).substring(0, jsonPaths.get(i).lastIndexOf("/")));
|
||||
}
|
||||
}
|
||||
|
||||
//remove父节点key
|
||||
for (String parentNodeJsonPath : parentNode) {
|
||||
jsonPaths.remove(parentNodeJsonPath);
|
||||
}
|
||||
|
||||
List<String> jsonPathList = new ArrayList<>();
|
||||
Iterator<String> jsonPath = jsonPaths.iterator();
|
||||
//将/替换为点.
|
||||
while (jsonPath.hasNext()) {
|
||||
Map<String,String> item = new HashMap<>();
|
||||
|
||||
|
||||
String o_json_path = "$" + jsonPath.next().replaceAll("/", ".");
|
||||
String value = JSONPath.eval(jsonObject,o_json_path).toString();
|
||||
|
||||
if(o_json_path.toLowerCase().contains("id")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if(value.equals("") || value.equals("[]") || o_json_path.equals("")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String json_path = formatJson(o_json_path);
|
||||
|
||||
|
||||
|
||||
//System.out.println(json_path);
|
||||
|
||||
|
||||
|
||||
item.put("json_path", json_path);
|
||||
item.put("json_value", addEscapeForString(value));
|
||||
allJsons.add((HashMap)item);
|
||||
|
||||
jsonPathList.add(json_path);
|
||||
}
|
||||
//排序
|
||||
Collections.sort(jsonPathList);
|
||||
return allJsons;
|
||||
}
|
||||
|
||||
private static String formatJson(String json_path){
|
||||
|
||||
String ret="";
|
||||
// 正则表达式
|
||||
String reg = ".(\\d{1,3}).{0,1}";
|
||||
|
||||
Boolean change_flag = false;
|
||||
Matcher m1 = Pattern.compile(reg).matcher(json_path);
|
||||
|
||||
|
||||
String newStr="";
|
||||
int rest = 0;
|
||||
String tail = "";
|
||||
while (m1.find()) {
|
||||
|
||||
int start = m1.start();
|
||||
int end = m1.end() - 1;
|
||||
if(json_path.charAt(start) != '.' || json_path.charAt(end) != '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
newStr += json_path.substring(rest,m1.start()) +"[*]." ;
|
||||
|
||||
rest = m1.end();
|
||||
tail = json_path.substring(m1.end());
|
||||
change_flag = true;
|
||||
}
|
||||
|
||||
|
||||
if(change_flag) {
|
||||
ret = newStr + tail;
|
||||
} else {
|
||||
ret = json_path;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return ret;
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static String addEscapeForString(String input) {
|
||||
|
||||
String ret="";
|
||||
|
||||
|
||||
String reg = "[?*/]";
|
||||
|
||||
Boolean change_flag = false;
|
||||
Matcher m1 = Pattern.compile(reg).matcher(input);
|
||||
|
||||
|
||||
String newStr="";
|
||||
int rest = 0;
|
||||
String tail = "";
|
||||
while (m1.find()) {
|
||||
|
||||
int start = m1.start();
|
||||
int end = m1.end() - 1;
|
||||
|
||||
|
||||
|
||||
newStr += input.substring(rest,m1.start()) + "\\" + m1.group(0) ;
|
||||
|
||||
rest = m1.end();
|
||||
tail = input.substring(m1.end());
|
||||
change_flag = true;
|
||||
|
||||
}
|
||||
if(change_flag) {
|
||||
ret = newStr + tail;
|
||||
} else {
|
||||
ret = input;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -22,6 +22,30 @@
|
|||
</el-row>
|
||||
</div>
|
||||
|
||||
<div >
|
||||
|
||||
<el-row :gutter="10" style="text-align: right;">
|
||||
|
||||
<el-button
|
||||
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="suggestJson"
|
||||
>推荐JSONPath断言</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="danger"
|
||||
@click="clearJson"
|
||||
>清空JSONPath断言</el-button>
|
||||
|
||||
</el-row>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<ms-api-assertions-edit :is-read-only="isReadOnly" :assertions="assertions"/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -30,7 +54,7 @@
|
|||
import MsApiAssertionText from "./ApiAssertionText";
|
||||
import MsApiAssertionRegex from "./ApiAssertionRegex";
|
||||
import MsApiAssertionDuration from "./ApiAssertionDuration";
|
||||
import {ASSERTION_TYPE, Assertions} from "../../model/ScenarioModel";
|
||||
import {ASSERTION_TYPE, Assertions, JSONPath} from "../../model/ScenarioModel";
|
||||
import MsApiAssertionsEdit from "./ApiAssertionsEdit";
|
||||
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
|
||||
|
||||
|
@ -43,6 +67,7 @@
|
|||
|
||||
props: {
|
||||
assertions: Assertions,
|
||||
jsonPathList: Array,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
@ -60,6 +85,24 @@
|
|||
methods: {
|
||||
after() {
|
||||
this.type = "";
|
||||
},
|
||||
suggestJson() {
|
||||
console.log("This is suggestJson")
|
||||
// console.log(this.jsonPathList);
|
||||
this.jsonPathList.forEach((item) => {
|
||||
let jsonItem = new JSONPath();
|
||||
jsonItem.expression=item.json_path;
|
||||
jsonItem.expect=item.json_value;
|
||||
jsonItem.setJSONPathDescription();
|
||||
this.assertions.jsonPath.push(jsonItem);
|
||||
});
|
||||
|
||||
},
|
||||
clearJson() {
|
||||
console.log("This is suggestJson")
|
||||
// console.log(this.jsonPathList);
|
||||
this.assertions.jsonPath = [];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,4 +120,22 @@
|
|||
margin: 5px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.bg-purple-dark {
|
||||
background: #99a9bf;
|
||||
}
|
||||
.bg-purple {
|
||||
background: #d3dce6;
|
||||
}
|
||||
.bg-purple-light {
|
||||
background: #e5e9f2;
|
||||
}
|
||||
.grid-content {
|
||||
border-radius: 4px;
|
||||
min-height: 36px;
|
||||
}
|
||||
.row-bg {
|
||||
padding: 10px 0;
|
||||
background-color: #f9fafc;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<template>
|
||||
<template xmlns:v-slot="http://www.w3.org/1999/XSL/Transform">
|
||||
<el-form :model="request" :rules="rules" ref="request" label-width="100px" :disabled="isReadOnly">
|
||||
|
||||
<el-form-item :label="$t('api_test.request.name')" prop="name">
|
||||
|
@ -66,7 +66,7 @@
|
|||
:environment="scenario.environment"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.request.assertions.label')" name="assertions">
|
||||
<ms-api-assertions :is-read-only="isReadOnly" :assertions="request.assertions"/>
|
||||
<ms-api-assertions :jsonPathList="jsonPathList" :is-read-only="isReadOnly" :assertions="request.assertions"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.request.extract.label')" name="extract">
|
||||
<ms-api-extract :is-read-only="isReadOnly" :extract="request.extract"/>
|
||||
|
@ -104,6 +104,7 @@ export default {
|
|||
MsApiVariable, ApiRequestMethodSelect, MsApiExtract, MsApiAssertions, MsApiBody, MsApiKeyValue},
|
||||
props: {
|
||||
request: HttpRequest,
|
||||
jsonPathList: Array,
|
||||
scenario: Scenario,
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
|
|
|
@ -1,33 +1,24 @@
|
|||
<template>
|
||||
<div class="request-form">
|
||||
<component @runDebug="runDebug" :is="component" :is-read-only="isReadOnly" :request="request" :scenario="scenario"/>
|
||||
<component @runDebug="runDebug" :is="component" :jsonPathList="jsonPathList" :is-read-only="isReadOnly" :request="request" :scenario="scenario"/>
|
||||
<el-divider v-if="isCompleted"></el-divider>
|
||||
<ms-request-result-tail v-loading="debugReportLoading" v-if="isCompleted"
|
||||
:request="request.debugRequestResult ? request.debugRequestResult : {responseResult: {}, subRequestResults: []}"
|
||||
:scenario-name="request.debugScenario ? request.debugScenario.name : ''"
|
||||
ref="msDebugResult"/>
|
||||
<ms-request-result-tail v-loading="debugReportLoading" v-if="isCompleted" :request="request.debugRequestResult ? request.debugRequestResult : {responseResult: {}, subRequestResults: []}"
|
||||
:scenario-name="request.debugScenario ? request.debugScenario.name : ''" ref="msDebugResult"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {JSR223Processor, Request, RequestFactory, Scenario} from "../../model/ScenarioModel";
|
||||
import MsApiHttpRequestForm from "./ApiHttpRequestForm";
|
||||
import MsApiTcpRequestForm from "./ApiTcpRequestForm";
|
||||
import MsApiDubboRequestForm from "./ApiDubboRequestForm";
|
||||
import MsScenarioResults from "../../../report/components/ScenarioResults";
|
||||
import MsRequestResultTail from "../../../report/components/RequestResultTail";
|
||||
import MsApiSqlRequestForm from "./ApiSqlRequestForm";
|
||||
import {JSR223Processor, Request, RequestFactory, Scenario} from "../../model/ScenarioModel";
|
||||
import MsApiHttpRequestForm from "./ApiHttpRequestForm";
|
||||
import MsApiTcpRequestForm from "./ApiTcpRequestForm";
|
||||
import MsApiDubboRequestForm from "./ApiDubboRequestForm";
|
||||
import MsScenarioResults from "../../../report/components/ScenarioResults";
|
||||
import MsRequestResultTail from "../../../report/components/RequestResultTail";
|
||||
import MsApiSqlRequestForm from "./ApiSqlRequestForm";
|
||||
|
||||
export default {
|
||||
name: "MsApiRequestForm",
|
||||
components: {
|
||||
MsApiSqlRequestForm,
|
||||
MsRequestResultTail,
|
||||
MsScenarioResults,
|
||||
MsApiDubboRequestForm,
|
||||
MsApiHttpRequestForm,
|
||||
MsApiTcpRequestForm
|
||||
},
|
||||
components: {MsApiSqlRequestForm, MsRequestResultTail, MsScenarioResults, MsApiDubboRequestForm, MsApiHttpRequestForm},
|
||||
props: {
|
||||
scenario: Scenario,
|
||||
request: Request,
|
||||
|
@ -37,107 +28,140 @@ export default {
|
|||
},
|
||||
debugReportId: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
reportId: "",
|
||||
content: {scenarios: []},
|
||||
debugReportLoading: false,
|
||||
showDebugReport: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
component({request: {type}}) {
|
||||
let name;
|
||||
switch (type) {
|
||||
case RequestFactory.TYPES.DUBBO:
|
||||
name = "MsApiDubboRequestForm";
|
||||
break;
|
||||
case RequestFactory.TYPES.SQL:
|
||||
name = "MsApiSqlRequestForm";
|
||||
break;
|
||||
data() {
|
||||
return {
|
||||
reportId: "",
|
||||
content: {scenarios:[]},
|
||||
debugReportLoading: false,
|
||||
showDebugReport: false,
|
||||
jsonPathList:[]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
component({request: {type}}) {
|
||||
let name;
|
||||
switch (type) {
|
||||
case RequestFactory.TYPES.DUBBO:
|
||||
name = "MsApiDubboRequestForm";
|
||||
break;
|
||||
case RequestFactory.TYPES.SQL:
|
||||
name = "MsApiSqlRequestForm";
|
||||
break;
|
||||
case RequestFactory.TYPES.TCP:
|
||||
name = "MsApiTcpRequestForm";
|
||||
break;
|
||||
default:
|
||||
name = "MsApiHttpRequestForm";
|
||||
default:
|
||||
name = "MsApiHttpRequestForm";
|
||||
}
|
||||
return name;
|
||||
},
|
||||
isCompleted() {
|
||||
return !!this.request.debugReport;
|
||||
}
|
||||
return name;
|
||||
},
|
||||
isCompleted() {
|
||||
return !!this.request.debugReport;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
debugReportId() {
|
||||
this.getReport();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
//兼容旧版本 beanshell
|
||||
if (!this.request.jsr223PreProcessor.script && this.request.beanShellPreProcessor) {
|
||||
this.request.jsr223PreProcessor = new JSR223Processor(this.request.beanShellPreProcessor);
|
||||
}
|
||||
if (!this.request.jsr223PostProcessor.script && this.request.beanShellPostProcessor) {
|
||||
this.request.jsr223PostProcessor = new JSR223Processor(this.request.beanShellPostProcessor);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getReport() {
|
||||
if (this.debugReportId) {
|
||||
this.debugReportLoading = true;
|
||||
this.showDebugReport = true;
|
||||
this.request.debugReport = {};
|
||||
let url = "/api/report/get/" + this.debugReportId;
|
||||
this.$get(url, response => {
|
||||
let report = response.data || {};
|
||||
let res = {};
|
||||
if (response.data) {
|
||||
try {
|
||||
res = JSON.parse(report.content);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
if (res) {
|
||||
this.debugReportLoading = false;
|
||||
this.request.debugReport = res;
|
||||
if (res.scenarios && res.scenarios.length > 0) {
|
||||
this.request.debugScenario = res.scenarios[0];
|
||||
this.request.debugRequestResult = this.request.debugScenario.requestResults[0];
|
||||
this.deleteReport(this.debugReportId);
|
||||
} else {
|
||||
this.request.debugScenario = new Scenario();
|
||||
this.request.debugRequestResult = {responseResult: {}, subRequestResults: []};
|
||||
watch: {
|
||||
debugReportId() {
|
||||
this.getReport();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
//兼容旧版本 beanshell
|
||||
if (!this.request.jsr223PreProcessor.script && this.request.beanShellPreProcessor) {
|
||||
this.request.jsr223PreProcessor = new JSR223Processor(this.request.beanShellPreProcessor);
|
||||
}
|
||||
if (!this.request.jsr223PostProcessor.script && this.request.beanShellPostProcessor) {
|
||||
this.request.jsr223PostProcessor = new JSR223Processor(this.request.beanShellPostProcessor);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getReport() {
|
||||
if (this.debugReportId) {
|
||||
this.debugReportLoading = true;
|
||||
this.showDebugReport = true;
|
||||
this.request.debugReport = {};
|
||||
let url = "/api/report/get/" + this.debugReportId;
|
||||
this.$get(url, response => {
|
||||
let report = response.data || {};
|
||||
let res = {};
|
||||
|
||||
if (response.data) {
|
||||
try {
|
||||
res = JSON.parse(report.content);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
if (res) {
|
||||
this.debugReportLoading = false;
|
||||
this.request.debugReport = res;
|
||||
if (res.scenarios && res.scenarios.length > 0) {
|
||||
this.request.debugScenario = res.scenarios[0];
|
||||
this.request.debugRequestResult = this.request.debugScenario.requestResults[0];
|
||||
|
||||
//add by Cuipeng
|
||||
this.debugResultDetails=this.request.debugRequestResult.responseResult.body;
|
||||
// console.log(this.debugResultDetails);
|
||||
try {
|
||||
|
||||
let param = {
|
||||
jsonPath: this.debugResultDetails
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
this.$post("/api/getJsonPaths", param).then(response1 => {
|
||||
|
||||
this.jsonPathList = response1.data.data;
|
||||
// console.log(this.jsonPathList);
|
||||
|
||||
}).catch(() => {
|
||||
|
||||
this.$warning("获取推荐jsonpath列表失败");
|
||||
});
|
||||
|
||||
|
||||
|
||||
} catch (e) {
|
||||
alert("调试结果的返回不是一个json");
|
||||
throw e;
|
||||
}
|
||||
//add by Cuipeng
|
||||
|
||||
this.deleteReport(this.debugReportId);
|
||||
} else {
|
||||
this.request.debugScenario = new Scenario();
|
||||
this.request.debugRequestResult = {responseResult: {}, subRequestResults: []};
|
||||
}
|
||||
this.$refs.msDebugResult.reload();
|
||||
} else {
|
||||
setTimeout(this.getReport, 2000)
|
||||
}
|
||||
this.$refs.msDebugResult.reload();
|
||||
} else {
|
||||
setTimeout(this.getReport, 2000)
|
||||
this.debugReportLoading = false;
|
||||
}
|
||||
} else {
|
||||
this.debugReportLoading = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
deleteReport(reportId) {
|
||||
this.$post('/api/report/delete', {id: reportId});
|
||||
},
|
||||
runDebug() {
|
||||
this.$emit('runDebug', this.request);
|
||||
}
|
||||
},
|
||||
deleteReport(reportId) {
|
||||
this.$post('/api/report/delete', {id: reportId});
|
||||
},
|
||||
runDebug() {
|
||||
this.$emit('runDebug', this.request);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.scenario-results {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.scenario-results {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.request-form >>> .debug-button {
|
||||
margin-left: auto;
|
||||
display: block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.request-form >>> .debug-button {
|
||||
margin-left: auto;
|
||||
display: block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -797,6 +797,10 @@ export class JSONPath extends AssertionType {
|
|||
this.set(options);
|
||||
}
|
||||
|
||||
setJSONPathDescription() {
|
||||
this.description = this.expression + " expect: " + (this.expect ? this.expect : '');
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return !!this.expression;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue