mirror of
https://github.com/denoland/deno.git
synced 2025-01-08 23:28:18 -05:00
Update to TypeScript 3.6.3 (#2969)
This commit is contained in:
parent
e55e4a2838
commit
60a2b5a8d0
8 changed files with 146 additions and 193 deletions
|
@ -80,7 +80,7 @@ pub struct DiagnosticItem {
|
||||||
|
|
||||||
/// A chain of messages, code, and categories of messages which indicate the
|
/// A chain of messages, code, and categories of messages which indicate the
|
||||||
/// full diagnostic information.
|
/// full diagnostic information.
|
||||||
pub message_chain: Option<Box<DiagnosticMessageChain>>,
|
pub message_chain: Option<DiagnosticMessageChain>,
|
||||||
|
|
||||||
/// Other diagnostic items that are related to the diagnostic, usually these
|
/// Other diagnostic items that are related to the diagnostic, usually these
|
||||||
/// are suggestions of why an error occurred.
|
/// are suggestions of why an error occurred.
|
||||||
|
@ -198,21 +198,12 @@ impl DisplayFormatter for DiagnosticItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_message(&self, level: usize) -> String {
|
fn format_message(&self, level: usize) -> String {
|
||||||
|
debug!("format_message");
|
||||||
if self.message_chain.is_none() {
|
if self.message_chain.is_none() {
|
||||||
return format!("{:indent$}{}", "", self.message, indent = level);
|
return format!("{:indent$}{}", "", self.message, indent = level);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut s = String::new();
|
let mut s = self.message_chain.clone().unwrap().format_message(level);
|
||||||
let mut i = level / 2;
|
|
||||||
let mut item_o = self.message_chain.clone();
|
|
||||||
while item_o.is_some() {
|
|
||||||
let item = item_o.unwrap();
|
|
||||||
s.push_str(&std::iter::repeat(" ").take(i * 2).collect::<String>());
|
|
||||||
s.push_str(&item.message);
|
|
||||||
s.push('\n');
|
|
||||||
item_o = item.next.clone();
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
s.pop();
|
s.pop();
|
||||||
|
|
||||||
s
|
s
|
||||||
|
@ -280,15 +271,11 @@ pub struct DiagnosticMessageChain {
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub code: i64,
|
pub code: i64,
|
||||||
pub category: DiagnosticCategory,
|
pub category: DiagnosticCategory,
|
||||||
pub next: Option<Box<DiagnosticMessageChain>>,
|
pub next: Option<Vec<DiagnosticMessageChain>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiagnosticMessageChain {
|
impl DiagnosticMessageChain {
|
||||||
pub fn from_json_value(v: &serde_json::Value) -> Option<Box<Self>> {
|
fn from_value(v: &serde_json::Value) -> Self {
|
||||||
if !v.is_object() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let obj = v.as_object().unwrap();
|
let obj = v.as_object().unwrap();
|
||||||
let message = obj
|
let message = obj
|
||||||
.get("message")
|
.get("message")
|
||||||
|
@ -301,16 +288,55 @@ impl DiagnosticMessageChain {
|
||||||
|
|
||||||
let next_v = obj.get("next");
|
let next_v = obj.get("next");
|
||||||
let next = match next_v {
|
let next = match next_v {
|
||||||
Some(n) => DiagnosticMessageChain::from_json_value(n),
|
Some(n) => DiagnosticMessageChain::from_next_array(n),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(Box::new(Self {
|
Self {
|
||||||
message,
|
message,
|
||||||
code,
|
code,
|
||||||
category,
|
category,
|
||||||
next,
|
next,
|
||||||
}))
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_next_array(v: &serde_json::Value) -> Option<Vec<Self>> {
|
||||||
|
if !v.is_array() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let vec = v
|
||||||
|
.as_array()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|item| Self::from_value(&item))
|
||||||
|
.collect::<Vec<Self>>();
|
||||||
|
|
||||||
|
Some(vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_json_value(v: &serde_json::Value) -> Option<Self> {
|
||||||
|
if !v.is_object() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Self::from_value(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_message(&self, level: usize) -> String {
|
||||||
|
let mut s = String::new();
|
||||||
|
|
||||||
|
s.push_str(&std::iter::repeat(" ").take(level * 2).collect::<String>());
|
||||||
|
s.push_str(&self.message);
|
||||||
|
s.push('\n');
|
||||||
|
if self.next.is_some() {
|
||||||
|
let arr = self.next.clone().unwrap();
|
||||||
|
for dm in arr {
|
||||||
|
s.push_str(&dm.format_message(level + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,22 +374,22 @@ mod tests {
|
||||||
items: vec![
|
items: vec![
|
||||||
DiagnosticItem {
|
DiagnosticItem {
|
||||||
message: "Type '(o: T) => { v: any; f: (x: B) => string; }[]' is not assignable to type '(r: B) => Value<B>[]'.".to_string(),
|
message: "Type '(o: T) => { v: any; f: (x: B) => string; }[]' is not assignable to type '(r: B) => Value<B>[]'.".to_string(),
|
||||||
message_chain: Some(Box::new(DiagnosticMessageChain {
|
message_chain: Some(DiagnosticMessageChain {
|
||||||
message: "Type '(o: T) => { v: any; f: (x: B) => string; }[]' is not assignable to type '(r: B) => Value<B>[]'.".to_string(),
|
message: "Type '(o: T) => { v: any; f: (x: B) => string; }[]' is not assignable to type '(r: B) => Value<B>[]'.".to_string(),
|
||||||
code: 2322,
|
code: 2322,
|
||||||
category: DiagnosticCategory::Error,
|
category: DiagnosticCategory::Error,
|
||||||
next: Some(Box::new(DiagnosticMessageChain {
|
next: Some(vec![DiagnosticMessageChain {
|
||||||
message: "Types of parameters 'o' and 'r' are incompatible.".to_string(),
|
message: "Types of parameters 'o' and 'r' are incompatible.".to_string(),
|
||||||
code: 2328,
|
code: 2328,
|
||||||
category: DiagnosticCategory::Error,
|
category: DiagnosticCategory::Error,
|
||||||
next: Some(Box::new(DiagnosticMessageChain {
|
next: Some(vec![DiagnosticMessageChain {
|
||||||
message: "Type 'B' is not assignable to type 'T'.".to_string(),
|
message: "Type 'B' is not assignable to type 'T'.".to_string(),
|
||||||
code: 2322,
|
code: 2322,
|
||||||
category: DiagnosticCategory::Error,
|
category: DiagnosticCategory::Error,
|
||||||
next: None,
|
next: None,
|
||||||
})),
|
}]),
|
||||||
})),
|
}]),
|
||||||
})),
|
}),
|
||||||
code: 2322,
|
code: 2322,
|
||||||
category: DiagnosticCategory::Error,
|
category: DiagnosticCategory::Error,
|
||||||
start_position: Some(267),
|
start_position: Some(267),
|
||||||
|
@ -437,124 +463,67 @@ mod tests {
|
||||||
&r#"{
|
&r#"{
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"message": "Type '(o: T) => { v: any; f: (x: B) => string; }[]' is not assignable to type '(r: B) => Value<B>[]'.",
|
"message": "Type '{ a(): { b: number; }; }' is not assignable to type '{ a(): { b: string; }; }'.",
|
||||||
"messageChain": {
|
"messageChain": {
|
||||||
"message": "Type '(o: T) => { v: any; f: (x: B) => string; }[]' is not assignable to type '(r: B) => Value<B>[]'.",
|
"message": "Type '{ a(): { b: number; }; }' is not assignable to type '{ a(): { b: string; }; }'.",
|
||||||
"code": 2322,
|
"code": 2322,
|
||||||
"category": 3,
|
"category": 3,
|
||||||
"next": {
|
"next": [
|
||||||
"message": "Types of parameters 'o' and 'r' are incompatible.",
|
{
|
||||||
"code": 2328,
|
"message": "Types of property 'a' are incompatible.",
|
||||||
"category": 3,
|
"code": 2326,
|
||||||
"next": {
|
|
||||||
"message": "Type 'B' is not assignable to type 'T'.",
|
|
||||||
"code": 2322,
|
|
||||||
"category": 3
|
"category": 3
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
},
|
},
|
||||||
"code": 2322,
|
"code": 2322,
|
||||||
"category": 3,
|
"category": 3,
|
||||||
"startPosition": 235,
|
"startPosition": 352,
|
||||||
"endPosition": 241,
|
"endPosition": 353,
|
||||||
"sourceLine": " values: o => [",
|
"sourceLine": "x = y;",
|
||||||
"lineNumber": 18,
|
"lineNumber": 29,
|
||||||
"scriptResourceName": "/deno/tests/complex_diagnostics.ts",
|
"scriptResourceName": "/deno/tests/error_003_typescript.ts",
|
||||||
"startColumn": 2,
|
"startColumn": 0,
|
||||||
"endColumn": 8,
|
"endColumn": 1
|
||||||
"relatedInformation": [
|
|
||||||
{
|
|
||||||
"message": "The expected type comes from property 'values' which is declared here on type 'C<B>'",
|
|
||||||
"code": 6500,
|
|
||||||
"category": 2,
|
|
||||||
"startPosition": 78,
|
|
||||||
"endPosition": 84,
|
|
||||||
"sourceLine": " values?: (r: T) => Array<Value<T>>;",
|
|
||||||
"lineNumber": 6,
|
|
||||||
"scriptResourceName": "/deno/tests/complex_diagnostics.ts",
|
|
||||||
"startColumn": 2,
|
|
||||||
"endColumn": 8
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"message": "Property 't' does not exist on type 'T'.",
|
|
||||||
"code": 2339,
|
|
||||||
"category": 3,
|
|
||||||
"startPosition": 267,
|
|
||||||
"endPosition": 268,
|
|
||||||
"sourceLine": " v: o.t,",
|
|
||||||
"lineNumber": 20,
|
|
||||||
"scriptResourceName": "/deno/tests/complex_diagnostics.ts",
|
|
||||||
"startColumn": 11,
|
|
||||||
"endColumn": 12
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}"#,
|
}"#,
|
||||||
).unwrap();
|
).unwrap();
|
||||||
let r = Diagnostic::from_json_value(&v);
|
let r = Diagnostic::from_json_value(&v);
|
||||||
let expected = Some(Diagnostic {
|
let expected = Some(
|
||||||
items: vec![
|
Diagnostic {
|
||||||
DiagnosticItem {
|
items: vec![
|
||||||
message: "Type '(o: T) => { v: any; f: (x: B) => string; }[]' is not assignable to type '(r: B) => Value<B>[]'.".to_string(),
|
DiagnosticItem {
|
||||||
message_chain: Some(Box::new(DiagnosticMessageChain {
|
message: "Type \'{ a(): { b: number; }; }\' is not assignable to type \'{ a(): { b: string; }; }\'.".to_string(),
|
||||||
message: "Type '(o: T) => { v: any; f: (x: B) => string; }[]' is not assignable to type '(r: B) => Value<B>[]'.".to_string(),
|
message_chain: Some(
|
||||||
code: 2322,
|
DiagnosticMessageChain {
|
||||||
category: DiagnosticCategory::Error,
|
message: "Type \'{ a(): { b: number; }; }\' is not assignable to type \'{ a(): { b: string; }; }\'.".to_string(),
|
||||||
next: Some(Box::new(DiagnosticMessageChain {
|
|
||||||
message: "Types of parameters 'o' and 'r' are incompatible.".to_string(),
|
|
||||||
code: 2328,
|
|
||||||
category: DiagnosticCategory::Error,
|
|
||||||
next: Some(Box::new(DiagnosticMessageChain {
|
|
||||||
message: "Type 'B' is not assignable to type 'T'.".to_string(),
|
|
||||||
code: 2322,
|
code: 2322,
|
||||||
category: DiagnosticCategory::Error,
|
category: DiagnosticCategory::Error,
|
||||||
next: None,
|
next: Some(vec![
|
||||||
})),
|
DiagnosticMessageChain {
|
||||||
})),
|
message: "Types of property \'a\' are incompatible.".to_string(),
|
||||||
})),
|
code: 2326,
|
||||||
related_information: Some(vec![
|
category: DiagnosticCategory::Error,
|
||||||
DiagnosticItem {
|
next: None,
|
||||||
message: "The expected type comes from property 'values' which is declared here on type 'C<B>'".to_string(),
|
}
|
||||||
message_chain: None,
|
])
|
||||||
related_information: None,
|
}
|
||||||
source_line: Some(" values?: (r: T) => Array<Value<T>>;".to_string()),
|
),
|
||||||
line_number: Some(6),
|
related_information: None,
|
||||||
script_resource_name: Some("/deno/tests/complex_diagnostics.ts".to_string()),
|
source_line: Some("x = y;".to_string()),
|
||||||
start_position: Some(78),
|
line_number: Some(29),
|
||||||
end_position: Some(84),
|
script_resource_name: Some("/deno/tests/error_003_typescript.ts".to_string()),
|
||||||
category: DiagnosticCategory::Info,
|
start_position: Some(352),
|
||||||
code: 6500,
|
end_position: Some(353),
|
||||||
start_column: Some(2),
|
category: DiagnosticCategory::Error,
|
||||||
end_column: Some(8),
|
code: 2322,
|
||||||
}
|
start_column: Some(0),
|
||||||
]),
|
end_column: Some(1)
|
||||||
source_line: Some(" values: o => [".to_string()),
|
}
|
||||||
line_number: Some(18),
|
]
|
||||||
script_resource_name: Some("/deno/tests/complex_diagnostics.ts".to_string()),
|
}
|
||||||
start_position: Some(235),
|
);
|
||||||
end_position: Some(241),
|
|
||||||
category: DiagnosticCategory::Error,
|
|
||||||
code: 2322,
|
|
||||||
start_column: Some(2),
|
|
||||||
end_column: Some(8),
|
|
||||||
},
|
|
||||||
DiagnosticItem {
|
|
||||||
message: "Property 't' does not exist on type 'T'.".to_string(),
|
|
||||||
message_chain: None,
|
|
||||||
related_information: None,
|
|
||||||
source_line: Some(" v: o.t,".to_string()),
|
|
||||||
line_number: Some(20),
|
|
||||||
script_resource_name: Some("/deno/tests/complex_diagnostics.ts".to_string()),
|
|
||||||
start_position: Some(267),
|
|
||||||
end_position: Some(268),
|
|
||||||
category: DiagnosticCategory::Error,
|
|
||||||
code: 2339,
|
|
||||||
start_column: Some(11),
|
|
||||||
end_column: Some(12),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
assert_eq!(expected, r);
|
assert_eq!(expected, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,20 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
interface Value<T> {
|
let x = {
|
||||||
f?: (r: T) => any;
|
a: {
|
||||||
v?: string;
|
b: {
|
||||||
}
|
c() {
|
||||||
|
return { d: "hello" };
|
||||||
interface C<T> {
|
}
|
||||||
values?: (r: T) => Array<Value<T>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
class A<T> {
|
|
||||||
constructor(private e?: T, public s?: C<T>) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class B {
|
|
||||||
t = "foo";
|
|
||||||
}
|
|
||||||
|
|
||||||
var a = new A(new B(), {
|
|
||||||
values: o => [
|
|
||||||
{
|
|
||||||
v: o.t,
|
|
||||||
f: x => "bar"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
});
|
};
|
||||||
|
let y = {
|
||||||
|
a: {
|
||||||
|
b: {
|
||||||
|
c() {
|
||||||
|
return { d: 1234 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
x = y;
|
||||||
|
|
|
@ -1,28 +1,16 @@
|
||||||
[WILDCARD]error TS2322: Type '(o: T) => { v: any; f: (x: B) => string; }[]' is not assignable to type '(r: B) => Value<B>[]'.
|
[WILDCARD]error TS2322: Type '{ a: { b: { c(): { d: number; }; }; }; }' is not assignable to type '{ a: { b: { c(): { d: string; }; }; }; }'.
|
||||||
Types of parameters 'o' and 'r' are incompatible.
|
Types of property 'a' are incompatible.
|
||||||
Type 'B' is not assignable to type 'T'.
|
Type '{ b: { c(): { d: number; }; }; }' is not assignable to type '{ b: { c(): { d: string; }; }; }'.
|
||||||
'B' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.
|
Types of property 'b' are incompatible.
|
||||||
|
Type '{ c(): { d: number; }; }' is not assignable to type '{ c(): { d: string; }; }'.
|
||||||
|
Types of property 'c' are incompatible.
|
||||||
|
Type '() => { d: number; }' is not assignable to type '() => { d: string; }'.
|
||||||
|
Type '{ d: number; }' is not assignable to type '{ d: string; }'.
|
||||||
|
Types of property 'd' are incompatible.
|
||||||
|
Type 'number' is not assignable to type 'string'.
|
||||||
|
|
||||||
[WILDCARD]tests/error_003_typescript.ts:20:3
|
[WILDCARD]/tests/error_003_typescript.ts:20:1
|
||||||
|
|
||||||
20 values: o => [
|
20 x = y;
|
||||||
~~~~~~
|
^
|
||||||
|
|
||||||
The expected type comes from property 'values' which is declared here on type 'C<B>'
|
|
||||||
|
|
||||||
[WILDCARD]tests/error_003_typescript.ts:8:3
|
|
||||||
|
|
||||||
8 values?: (r: T) => Array<Value<T>>;
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
|
|
||||||
error TS2339: Property 't' does not exist on type 'T'.
|
|
||||||
|
|
||||||
[WILDCARD]tests/error_003_typescript.ts:22:12
|
|
||||||
|
|
||||||
22 v: o.t,
|
|
||||||
^
|
|
||||||
|
|
||||||
|
|
||||||
Found 2 errors.
|
|
||||||
|
|
||||||
|
|
|
@ -245,6 +245,7 @@ pub fn get_asset(name: &str) -> Option<&'static str> {
|
||||||
"lib.es2017.string.d.ts" => inc!("lib.es2017.string.d.ts"),
|
"lib.es2017.string.d.ts" => inc!("lib.es2017.string.d.ts"),
|
||||||
"lib.es2017.intl.d.ts" => inc!("lib.es2017.intl.d.ts"),
|
"lib.es2017.intl.d.ts" => inc!("lib.es2017.intl.d.ts"),
|
||||||
"lib.es2017.typedarrays.d.ts" => inc!("lib.es2017.typedarrays.d.ts"),
|
"lib.es2017.typedarrays.d.ts" => inc!("lib.es2017.typedarrays.d.ts"),
|
||||||
|
"lib.es2018.asyncgenerator.d.ts" => inc!("lib.es2018.asyncgenerator.d.ts"),
|
||||||
"lib.es2018.asynciterable.d.ts" => inc!("lib.es2018.asynciterable.d.ts"),
|
"lib.es2018.asynciterable.d.ts" => inc!("lib.es2018.asynciterable.d.ts"),
|
||||||
"lib.es2018.promise.d.ts" => inc!("lib.es2018.promise.d.ts"),
|
"lib.es2018.promise.d.ts" => inc!("lib.es2018.promise.d.ts"),
|
||||||
"lib.es2018.regexp.d.ts" => inc!("lib.es2018.regexp.d.ts"),
|
"lib.es2018.regexp.d.ts" => inc!("lib.es2018.regexp.d.ts"),
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit cf7b2d4ae91c4f27ba9ae7137ddf9a407815e590
|
Subproject commit 26655db1dd04d93217002ac87f950aba812c53eb
|
|
@ -18,7 +18,7 @@ export interface DiagnosticMessageChain {
|
||||||
message: string;
|
message: string;
|
||||||
category: DiagnosticCategory;
|
category: DiagnosticCategory;
|
||||||
code: number;
|
code: number;
|
||||||
next?: DiagnosticMessageChain;
|
next?: DiagnosticMessageChain[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DiagnosticItem {
|
export interface DiagnosticItem {
|
||||||
|
@ -130,19 +130,20 @@ function getSourceInformation(
|
||||||
|
|
||||||
/** Converts a TypeScript diagnostic message chain to a Deno one. */
|
/** Converts a TypeScript diagnostic message chain to a Deno one. */
|
||||||
function fromDiagnosticMessageChain(
|
function fromDiagnosticMessageChain(
|
||||||
messageChain: ts.DiagnosticMessageChain | undefined
|
messageChain: ts.DiagnosticMessageChain[] | undefined
|
||||||
): DiagnosticMessageChain | undefined {
|
): DiagnosticMessageChain[] | undefined {
|
||||||
if (!messageChain) {
|
if (!messageChain) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { messageText: message, code, category, next } = messageChain;
|
return messageChain.map(({ messageText: message, code, category, next }) => {
|
||||||
return {
|
return {
|
||||||
message,
|
message,
|
||||||
code,
|
code,
|
||||||
category: fromDiagnosticCategory(category),
|
category: fromDiagnosticCategory(category),
|
||||||
next: fromDiagnosticMessageChain(next)
|
next: fromDiagnosticMessageChain(next)
|
||||||
};
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Parse out information from a TypeScript diagnostic structure. */
|
/** Parse out information from a TypeScript diagnostic structure. */
|
||||||
|
@ -171,7 +172,7 @@ function parseDiagnostic(
|
||||||
message = messageText;
|
message = messageText;
|
||||||
} else {
|
} else {
|
||||||
message = messageText.messageText;
|
message = messageText.messageText;
|
||||||
messageChain = fromDiagnosticMessageChain(messageText);
|
messageChain = fromDiagnosticMessageChain([messageText])![0];
|
||||||
}
|
}
|
||||||
|
|
||||||
const base = {
|
const base = {
|
||||||
|
|
|
@ -8,6 +8,6 @@
|
||||||
"eslint-config-prettier": "4.1.0",
|
"eslint-config-prettier": "4.1.0",
|
||||||
"magic-string": "0.25.2",
|
"magic-string": "0.25.2",
|
||||||
"prettier": "1.17.1",
|
"prettier": "1.17.1",
|
||||||
"typescript": "3.5.1"
|
"typescript": "3.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 1915f1bb617bec93f480a8f63100f721c8dda28b
|
Subproject commit 32c4689ee8f3364544c902b80f19ab227d3e1117
|
Loading…
Reference in a new issue