1use std::{
6 fs::File,
7 io,
8 path::{Path, PathBuf},
9};
10
11use serde::Serialize;
12
13use crate::{
14 config::{Config, ContractRules, FunctionConfig, Req, VariableConfig, WithParamsRules},
15 definitions::{Identifier, ItemType, Parent},
16 error::{ErrorKind, Result},
17 interner::INTERNER,
18 natspec::{NatSpec, NatSpecKind},
19 parser::{DocumentId, Parse, ParsedDocument},
20 textindex::TextRange,
21};
22
23#[derive(Debug, Clone, Serialize, thiserror::Error)]
25#[error("Error")]
26pub struct FileDiagnostics {
27 pub path: PathBuf,
29
30 #[serde(skip_serializing)]
34 pub document_id: DocumentId,
35
36 pub items: Vec<ItemDiagnostics>,
38}
39
40#[derive(Debug, Clone, Serialize, bon::Builder)]
42#[non_exhaustive]
43#[builder(on(String, into))]
44pub struct ItemDiagnostics {
45 pub parent: Option<Parent>,
47
48 pub item_type: ItemType,
50
51 pub name: &'static str,
53
54 pub span: TextRange,
56
57 pub diags: Vec<Diagnostic>,
59}
60
61impl ItemDiagnostics {
62 pub fn print_compact(
67 &self,
68 f: &mut impl io::Write,
69 source_name: &str,
70 ) -> std::result::Result<(), io::Error> {
71 writeln!(f, "{source_name}:{}", self.span.start)?;
72 if let Some(parent) = &self.parent {
73 writeln!(f, "{} {}.{}", self.item_type, parent, self.name)?;
74 } else {
75 writeln!(f, "{} {}", self.item_type, self.name)?;
76 }
77 for diag in &self.diags {
78 writeln!(f, " {}", diag.message)?;
79 }
80 writeln!(f)
81 }
82}
83
84#[derive(Debug, Clone, Serialize)]
86pub struct Diagnostic {
87 pub span: TextRange,
92 pub message: String,
93}
94
95pub fn lint(
102 mut parser: impl Parse,
103 path: impl AsRef<Path>,
104 options: &ValidationOptions,
105 keep_contents: bool,
106) -> Result<Option<FileDiagnostics>> {
107 fn inner(
108 path: &Path,
109 document: ParsedDocument,
110 options: &ValidationOptions,
111 ) -> Option<FileDiagnostics> {
112 let mut items: Vec<_> = document
113 .definitions
114 .into_iter()
115 .filter_map(|item| {
116 let mut item_diags = item.validate(options);
117 if item_diags.diags.is_empty() {
118 None
119 } else {
120 item_diags.diags.sort_unstable_by_key(|d| d.span.start);
121 Some(item_diags)
122 }
123 })
124 .collect();
125 if items.is_empty() {
126 return None;
127 }
128 items.sort_unstable_by_key(|i| i.span.start);
129 Some(FileDiagnostics {
130 path: path.to_path_buf(),
131 document_id: document.id,
132 items,
133 })
134 }
135 let file = File::open(&path).map_err(|err| ErrorKind::IOError {
136 path: path.as_ref().to_path_buf(),
137 err,
138 })?;
139 let document = parser.parse_document(file, Some(&path), keep_contents)?;
140 Ok(inner(path.as_ref(), document, options))
141}
142
143#[derive(Debug, Clone, PartialEq, Eq, bon::Builder)]
145#[non_exhaustive]
146pub struct ValidationOptions {
147 #[builder(default = true)]
149 pub inheritdoc: bool,
150
151 #[builder(default = false)]
153 pub inheritdoc_override: bool,
154
155 #[builder(default)]
157 pub notice_or_dev: bool,
158
159 #[builder(default)]
161 pub contracts: ContractRules,
162
163 #[builder(default)]
165 pub interfaces: ContractRules,
166
167 #[builder(default)]
169 pub libraries: ContractRules,
170
171 #[builder(default = WithParamsRules::default_constructor())]
173 pub constructors: WithParamsRules,
174
175 #[builder(default)]
177 pub enums: WithParamsRules,
178
179 #[builder(default = WithParamsRules::required())]
181 pub errors: WithParamsRules,
182
183 #[builder(default = WithParamsRules::required())]
185 pub events: WithParamsRules,
186
187 #[builder(default)]
189 pub functions: FunctionConfig,
190
191 #[builder(default = WithParamsRules::required())]
193 pub modifiers: WithParamsRules,
194
195 #[builder(default)]
197 pub structs: WithParamsRules,
198
199 #[builder(default)]
201 pub variables: VariableConfig,
202}
203
204impl Default for ValidationOptions {
205 fn default() -> Self {
210 Self {
211 inheritdoc: true,
212 inheritdoc_override: false,
213 notice_or_dev: false,
214 contracts: ContractRules::default(),
215 interfaces: ContractRules::default(),
216 libraries: ContractRules::default(),
217 constructors: WithParamsRules::default_constructor(),
218 enums: WithParamsRules::default(),
219 errors: WithParamsRules::required(),
220 events: WithParamsRules::required(),
221 functions: FunctionConfig::default(),
222 modifiers: WithParamsRules::required(),
223 structs: WithParamsRules::default(),
224 variables: VariableConfig::default(),
225 }
226 }
227}
228
229impl From<Config> for ValidationOptions {
231 fn from(value: Config) -> Self {
232 Self {
233 inheritdoc: value.lintspec.inheritdoc,
234 inheritdoc_override: value.lintspec.inheritdoc_override,
235 notice_or_dev: value.lintspec.notice_or_dev,
236 contracts: value.contracts,
237 interfaces: value.interfaces,
238 libraries: value.libraries,
239 constructors: value.constructors,
240 enums: value.enums,
241 errors: value.errors,
242 events: value.events,
243 functions: value.functions,
244 modifiers: value.modifiers,
245 structs: value.structs,
246 variables: value.variables,
247 }
248 }
249}
250
251impl From<&Config> for ValidationOptions {
253 fn from(value: &Config) -> Self {
254 Self {
255 inheritdoc: value.lintspec.inheritdoc,
256 inheritdoc_override: value.lintspec.inheritdoc_override,
257 notice_or_dev: value.lintspec.notice_or_dev,
258 contracts: value.contracts.clone(),
259 interfaces: value.interfaces.clone(),
260 libraries: value.libraries.clone(),
261 constructors: value.constructors.clone(),
262 enums: value.enums.clone(),
263 errors: value.errors.clone(),
264 events: value.events.clone(),
265 functions: value.functions.clone(),
266 modifiers: value.modifiers.clone(),
267 structs: value.structs.clone(),
268 variables: value.variables.clone(),
269 }
270 }
271}
272
273pub trait Validate {
275 fn validate(&self, options: &ValidationOptions) -> ItemDiagnostics;
277}
278
279#[derive(Debug, Clone, bon::Builder)]
281pub struct CheckParams<'a> {
282 natspec: &'a Option<NatSpec>,
284 rule: Req,
286 params: &'a [Identifier],
288 default_span: TextRange,
290}
291
292impl CheckParams<'_> {
293 #[must_use]
299 pub fn check(&self) -> Vec<Diagnostic> {
300 let mut res = Vec::new();
301 self.check_into(&mut res);
302 res.sort_unstable_by_key(|d| d.span.start.utf8);
303 res
304 }
305
306 pub fn check_into(&self, out: &mut Vec<Diagnostic>) {
310 match self.rule {
311 Req::Ignored => {}
312 Req::Required => self.check_required(out),
313 Req::Forbidden => self.check_forbidden(out),
314 }
315 }
316
317 fn check_required(&self, out: &mut Vec<Diagnostic>) {
319 let Some(natspec) = self.natspec else {
320 out.extend(self.missing_diags());
321 return;
322 };
323 out.extend(self.extra_diags());
324 out.extend(self.count_diags(natspec));
325 }
326
327 fn check_forbidden(&self, out: &mut Vec<Diagnostic>) {
329 if let Some(natspec) = self.natspec
330 && natspec.has_param()
331 {
332 out.push(Diagnostic {
333 span: self.default_span.clone(),
334 message: "@param is forbidden".to_string(),
335 });
336 }
337 }
338
339 fn missing_diags(&self) -> impl Iterator<Item = Diagnostic> {
341 self.params.iter().filter_map(|p| {
342 p.name.map(|name| {
343 let name = INTERNER.resolve(name);
344 Diagnostic {
345 span: p.span.clone(),
346 message: format!("@param {name} is missing"),
347 }
348 })
349 })
350 }
351
352 fn extra_diags(&self) -> impl Iterator<Item = Diagnostic> {
354 self.natspec
356 .as_ref()
357 .map(|n| {
358 n.items.iter().filter_map(|item| {
359 let NatSpecKind::Param { name } = item.kind else {
360 return None;
361 };
362 if self
363 .params
364 .iter()
365 .any(|p| matches!(p.name, Some(param_name) if param_name == name))
366 {
367 None
368 } else {
369 let span_start = self.default_span.start + item.span.start;
371 let span_end = self.default_span.start + item.span.end;
372 let name = INTERNER.resolve(name);
373 Some(Diagnostic {
374 span: span_start..span_end,
375 message: format!("extra @param {name}"),
376 })
377 }
378 })
379 })
380 .into_iter()
381 .flatten()
382 }
383
384 fn count_diags(&self, natspec: &NatSpec) -> impl Iterator<Item = Diagnostic> {
386 self.counts(natspec).filter_map(|(param, count)| {
387 let name = param.name.map_or("unnamed_param", |n| INTERNER.resolve(n));
388 match count {
389 0 => Some(Diagnostic {
390 span: param.span.clone(),
391 message: format!("@param {name} is missing"),
392 }),
393 1 => None,
394 2.. => Some(Diagnostic {
395 span: param.span.clone(),
396 message: format!("@param {name} is present more than once"),
397 }),
398 }
399 })
400 }
401
402 fn counts(&self, natspec: &NatSpec) -> impl Iterator<Item = (&Identifier, usize)> {
404 self.params.iter().map(|p: &Identifier| {
406 let Some(param_name) = p.name else {
407 return (p, 0);
408 };
409 (
410 p,
411 natspec
412 .items
413 .iter()
414 .filter(|n| matches!(n.kind, NatSpecKind::Param { name } if name == param_name))
415 .count(),
416 )
417 })
418 }
419}
420
421#[derive(Debug, Clone, bon::Builder)]
423pub struct CheckReturns<'a> {
424 natspec: &'a Option<NatSpec>,
426 rule: Req,
428 returns: &'a [Identifier],
430 default_span: TextRange,
432 is_var: bool,
434}
435
436impl CheckReturns<'_> {
437 #[must_use]
443 pub fn check(&self) -> Vec<Diagnostic> {
444 let mut res = Vec::new();
445 self.check_into(&mut res);
446 res
447 }
448
449 pub fn check_into(&self, out: &mut Vec<Diagnostic>) {
453 match self.rule {
454 Req::Ignored => {}
455 Req::Required => self.check_required(out),
456 Req::Forbidden => self.check_forbidden(out),
457 }
458 }
459
460 fn check_required(&self, out: &mut Vec<Diagnostic>) {
462 let Some(natspec) = self.natspec else {
463 out.extend(self.missing_diags());
464 return;
465 };
466 out.extend(self.return_diags(natspec));
467 out.extend(self.extra_unnamed_diags(natspec));
468 }
469
470 fn check_forbidden(&self, out: &mut Vec<Diagnostic>) {
472 if let Some(natspec) = self.natspec
473 && natspec.has_return()
474 {
475 out.push(Diagnostic {
476 span: self.default_span.clone(),
477 message: "@return is forbidden".to_string(),
478 });
479 }
480 }
481
482 fn missing_diags(&self) -> impl Iterator<Item = Diagnostic> {
484 self.returns.iter().enumerate().map(|(idx, r)| {
485 let message = if let Some(name) = r.name {
486 let name = INTERNER.resolve(name);
487 format!("@return {name} is missing")
488 } else if self.is_var {
489 "@return is missing".to_string()
490 } else {
491 format!("@return missing for unnamed return #{}", idx + 1)
492 };
493 Diagnostic {
494 span: r.span.clone(),
495 message,
496 }
497 })
498 }
499
500 fn named_count_diag(natspec: &NatSpec, ret: &Identifier, name: &str) -> Option<Diagnostic> {
502 match natspec.count_return(ret) {
503 0 => Some(Diagnostic {
504 span: ret.span.clone(),
505 message: format!("@return {name} is missing"),
506 }),
507 1 => None,
508 2.. => Some(Diagnostic {
509 span: ret.span.clone(),
510 message: format!("@return {name} is present more than once"),
511 }),
512 }
513 }
514
515 fn unnamed_diag(
517 &self,
518 returns_count: usize,
519 idx: usize,
520 ret: &Identifier,
521 ) -> Option<Diagnostic> {
522 if idx + 1 > returns_count {
523 let message = if self.is_var {
524 "@return is missing".to_string()
525 } else {
526 format!("@return missing for unnamed return #{}", idx + 1)
527 };
528 Some(Diagnostic {
529 span: ret.span.clone(),
530 message,
531 })
532 } else {
533 None
534 }
535 }
536
537 fn return_diags(&self, natspec: &NatSpec) -> impl Iterator<Item = Diagnostic> {
539 let returns_count = natspec.count_all_returns();
540 self.returns
541 .iter()
542 .enumerate()
543 .filter_map(move |(idx, ret)| {
544 if let Some(name) = ret.name {
545 Self::named_count_diag(natspec, ret, INTERNER.resolve(name))
547 } else {
548 self.unnamed_diag(returns_count, idx, ret)
550 }
551 })
552 }
553
554 fn extra_unnamed_diags(&self, natspec: &NatSpec) -> impl Iterator<Item = Diagnostic> {
556 let unnamed_returns = self.returns.iter().filter(|r| r.name.is_none()).count();
557 if natspec.count_unnamed_returns() > unnamed_returns {
558 Some(Diagnostic {
559 span: self
560 .returns
561 .last()
562 .cloned()
563 .map_or(self.default_span.clone(), |r| r.span),
564 message: "too many unnamed returns".to_string(),
565 })
566 } else {
567 None
568 }
569 .into_iter()
570 }
571}
572
573#[derive(Debug, Clone, bon::Builder)]
575pub struct CheckNotice<'a> {
576 natspec: &'a Option<NatSpec>,
578 rule: Req,
580 span: &'a TextRange,
582}
583
584impl CheckNotice<'_> {
585 #[must_use]
588 pub fn check(&self) -> Option<Diagnostic> {
589 match self.rule {
590 Req::Ignored => None,
591 Req::Required => self.check_required(),
592 Req::Forbidden => self.check_forbidden(),
593 }
594 }
595
596 pub fn check_into(&self, out: &mut Vec<Diagnostic>) {
600 if let Some(diag) = self.check() {
601 out.push(diag);
602 }
603 }
604
605 fn check_required(&self) -> Option<Diagnostic> {
607 if let Some(natspec) = self.natspec
608 && natspec.has_notice()
609 {
610 None
611 } else {
612 Some(Diagnostic {
613 span: self.span.clone(),
614 message: "@notice is missing".to_string(),
615 })
616 }
617 }
618
619 fn check_forbidden(&self) -> Option<Diagnostic> {
621 if let Some(natspec) = self.natspec
622 && natspec.has_notice()
623 {
624 Some(Diagnostic {
625 span: self.span.clone(),
626 message: "@notice is forbidden".to_string(),
627 })
628 } else {
629 None
630 }
631 }
632}
633
634#[derive(Debug, Clone, bon::Builder)]
636pub struct CheckDev<'a> {
637 natspec: &'a Option<NatSpec>,
639 rule: Req,
641 span: &'a TextRange,
643}
644
645impl CheckDev<'_> {
646 #[must_use]
649 pub fn check(&self) -> Option<Diagnostic> {
650 match self.rule {
651 Req::Ignored => None,
652 Req::Required => self.check_required(),
653 Req::Forbidden => self.check_forbidden(),
654 }
655 }
656
657 pub fn check_into(&self, out: &mut Vec<Diagnostic>) {
661 if let Some(diag) = self.check() {
662 out.push(diag);
663 }
664 }
665
666 fn check_required(&self) -> Option<Diagnostic> {
668 if let Some(natspec) = self.natspec
669 && natspec.has_dev()
670 {
671 None
672 } else {
673 Some(Diagnostic {
674 span: self.span.clone(),
675 message: "@dev is missing".to_string(),
676 })
677 }
678 }
679
680 fn check_forbidden(&self) -> Option<Diagnostic> {
682 if let Some(natspec) = self.natspec
683 && natspec.has_dev()
684 {
685 Some(Diagnostic {
686 span: self.span.clone(),
687 message: "@dev is forbidden".to_string(),
688 })
689 } else {
690 None
691 }
692 }
693}
694
695#[derive(Debug, Clone, bon::Builder)]
697pub struct CheckTitle<'a> {
698 natspec: &'a Option<NatSpec>,
700 rule: Req,
702 span: &'a TextRange,
704}
705
706impl CheckTitle<'_> {
707 #[must_use]
710 pub fn check(&self) -> Option<Diagnostic> {
711 match self.rule {
712 Req::Ignored => None,
713 Req::Required => self.check_required(),
714 Req::Forbidden => self.check_forbidden(),
715 }
716 }
717
718 pub fn check_into(&self, out: &mut Vec<Diagnostic>) {
722 if let Some(diag) = self.check() {
723 out.push(diag);
724 }
725 }
726
727 fn check_required(&self) -> Option<Diagnostic> {
729 if let Some(natspec) = self.natspec
730 && natspec.has_title()
731 {
732 None
733 } else {
734 Some(Diagnostic {
735 span: self.span.clone(),
736 message: "@title is missing".to_string(),
737 })
738 }
739 }
740
741 fn check_forbidden(&self) -> Option<Diagnostic> {
743 if let Some(natspec) = self.natspec
744 && natspec.has_title()
745 {
746 Some(Diagnostic {
747 span: self.span.clone(),
748 message: "@title is forbidden".to_string(),
749 })
750 } else {
751 None
752 }
753 }
754}
755
756#[derive(Debug, Clone, bon::Builder)]
758pub struct CheckAuthor<'a> {
759 natspec: &'a Option<NatSpec>,
761 rule: Req,
763 span: &'a TextRange,
765}
766
767impl CheckAuthor<'_> {
768 #[must_use]
771 pub fn check(&self) -> Option<Diagnostic> {
772 match self.rule {
773 Req::Ignored => None,
774 Req::Required => self.check_required(),
775 Req::Forbidden => self.check_forbidden(),
776 }
777 }
778
779 pub fn check_into(&self, out: &mut Vec<Diagnostic>) {
783 if let Some(diag) = self.check() {
784 out.push(diag);
785 }
786 }
787
788 fn check_required(&self) -> Option<Diagnostic> {
790 if let Some(natspec) = self.natspec
791 && natspec.has_author()
792 {
793 None
794 } else {
795 Some(Diagnostic {
796 span: self.span.clone(),
797 message: "@author is missing".to_string(),
798 })
799 }
800 }
801
802 fn check_forbidden(&self) -> Option<Diagnostic> {
804 if let Some(natspec) = self.natspec
805 && natspec.has_author()
806 {
807 Some(Diagnostic {
808 span: self.span.clone(),
809 message: "@author is forbidden".to_string(),
810 })
811 } else {
812 None
813 }
814 }
815}
816
817#[derive(Debug, Clone, bon::Builder)]
819pub struct CheckNoticeAndDev<'a> {
820 natspec: &'a Option<NatSpec>,
822 notice_rule: Req,
824 dev_rule: Req,
826 notice_or_dev: bool,
828 span: &'a TextRange,
830}
831
832impl CheckNoticeAndDev<'_> {
833 #[must_use]
842 pub fn check(&self) -> Vec<Diagnostic> {
843 let mut res = Vec::new();
844 self.check_into(&mut res);
845 res
846 }
847
848 pub fn check_into(&self, out: &mut Vec<Diagnostic>) {
852 match (self.notice_or_dev, self.notice_rule, self.dev_rule) {
853 (true, Req::Required, Req::Ignored | Req::Required)
854 | (true, Req::Ignored, Req::Required) => self.check_notice_or_dev_into(out),
855 (true, Req::Forbidden, _) | (true, _, Req::Forbidden) | (false, _, _) => {
856 self.check_separately(out);
857 }
858 (true, Req::Ignored, Req::Ignored) => {}
859 }
860 }
861
862 fn check_notice_or_dev_into(&self, out: &mut Vec<Diagnostic>) {
864 if let Some(natspec) = self.natspec
865 && (natspec.has_notice() || natspec.has_dev())
866 {
867 } else {
869 out.push(Diagnostic {
870 span: self.span.clone(),
871 message: "@notice or @dev is missing".to_string(),
872 });
873 }
874 }
875
876 fn check_separately(&self, out: &mut Vec<Diagnostic>) {
878 CheckNotice::builder()
879 .natspec(self.natspec)
880 .rule(self.notice_rule)
881 .span(self.span)
882 .build()
883 .check_into(out);
884 CheckDev::builder()
885 .natspec(self.natspec)
886 .rule(self.dev_rule)
887 .span(self.span)
888 .build()
889 .check_into(out);
890 }
891}
892
893#[cfg(test)]
894mod tests {
895 use similar_asserts::assert_eq;
896
897 use crate::config::{BaseConfig, FunctionRules, NoticeDevRules};
898
899 use super::*;
900
901 #[test]
902 fn test_validation_options_default() {
903 assert_eq!(
904 ValidationOptions::default(),
905 ValidationOptions::builder().build()
906 );
907
908 let default_config = Config::default();
909 let options = ValidationOptions::from(&default_config);
910 assert_eq!(ValidationOptions::default(), options);
911 }
912
913 #[test]
914 fn test_validation_options_conversion() {
915 let config = Config::builder().build();
916 let options = ValidationOptions::from(&config);
917 assert_eq!(config.lintspec.inheritdoc, options.inheritdoc);
918 assert_eq!(config.lintspec.notice_or_dev, options.notice_or_dev);
919 assert_eq!(config.contracts, options.contracts);
920 assert_eq!(config.interfaces, options.interfaces);
921 assert_eq!(config.libraries, options.libraries);
922 assert_eq!(config.constructors, options.constructors);
923 assert_eq!(config.enums, options.enums);
924 assert_eq!(config.errors, options.errors);
925 assert_eq!(config.events, options.events);
926 assert_eq!(config.functions, options.functions);
927 assert_eq!(config.modifiers, options.modifiers);
928 assert_eq!(config.structs, options.structs);
929 assert_eq!(config.variables, options.variables);
930
931 let config = Config::builder()
932 .lintspec(
933 BaseConfig::builder()
934 .inheritdoc(false)
935 .notice_or_dev(true)
936 .build(),
937 )
938 .contracts(
939 ContractRules::builder()
940 .title(Req::Required)
941 .author(Req::Required)
942 .dev(Req::Required)
943 .notice(Req::Forbidden)
944 .build(),
945 )
946 .interfaces(
947 ContractRules::builder()
948 .title(Req::Ignored)
949 .author(Req::Forbidden)
950 .dev(Req::Forbidden)
951 .notice(Req::Required)
952 .build(),
953 )
954 .libraries(
955 ContractRules::builder()
956 .title(Req::Forbidden)
957 .author(Req::Ignored)
958 .dev(Req::Required)
959 .notice(Req::Ignored)
960 .build(),
961 )
962 .constructors(WithParamsRules::builder().dev(Req::Required).build())
963 .enums(WithParamsRules::builder().param(Req::Required).build())
964 .errors(WithParamsRules::builder().notice(Req::Forbidden).build())
965 .events(WithParamsRules::builder().param(Req::Forbidden).build())
966 .functions(
967 FunctionConfig::builder()
968 .private(FunctionRules::builder().dev(Req::Required).build())
969 .build(),
970 )
971 .modifiers(WithParamsRules::builder().dev(Req::Forbidden).build())
972 .structs(WithParamsRules::builder().notice(Req::Ignored).build())
973 .variables(
974 VariableConfig::builder()
975 .private(NoticeDevRules::builder().dev(Req::Required).build())
976 .build(),
977 )
978 .build();
979 let options = ValidationOptions::from(&config);
980 assert_eq!(config.lintspec.inheritdoc, options.inheritdoc);
981 assert_eq!(config.lintspec.notice_or_dev, options.notice_or_dev);
982 assert_eq!(config.contracts, options.contracts);
983 assert_eq!(config.interfaces, options.interfaces);
984 assert_eq!(config.libraries, options.libraries);
985 assert_eq!(config.constructors, options.constructors);
986 assert_eq!(config.enums, options.enums);
987 assert_eq!(config.errors, options.errors);
988 assert_eq!(config.events, options.events);
989 assert_eq!(config.functions, options.functions);
990 assert_eq!(config.modifiers, options.modifiers);
991 assert_eq!(config.structs, options.structs);
992 assert_eq!(config.variables, options.variables);
993 }
994}