using System; using System.Text.RegularExpressions; namespace WandEnhancer.Core.Js { /// A named function or class method located in a bundle, addressed by delimiter position. internal sealed class JsFunction { private static readonly Regex ReturnKeyword = new Regex(@"(? _source.Substring(BodyOpen + 1, BodyClose - BodyOpen - 1); private JsCursor BodyCursor => _body ?? (_body = new JsCursor(Body)); /// Captures a group from a pattern matched against this body only, not the whole bundle. public string Resolve(string pattern, string group) { var match = Regex.Match(Body, pattern, RegexOptions.Singleline); if (!match.Success || string.IsNullOrEmpty(match.Groups[group].Value)) { throw new Exception($"Could not resolve '{group}' inside {Name}()"); } return match.Groups[group].Value; } /// Rewrites the first match of a pattern scoped to this body; ${group} back-references work. public JsEdit ReplaceInBody(string pattern, string replacement) { var match = Regex.Match(Body, pattern, RegexOptions.Singleline); if (!match.Success) { throw new Exception($"Pattern '{pattern}' not found inside {Name}()"); } int start = BodyOpen + 1 + match.Index; return new JsEdit(start, start + match.Length, match.Result(replacement)); } public JsEdit InsertAtStart(string code) => new JsEdit(BodyOpen + 1, BodyOpen + 1, code); public JsEdit InsertAtEnd(string code) => new JsEdit(BodyClose, BodyClose, code); public JsEdit ReplaceBody(string code) => new JsEdit(BodyOpen + 1, BodyClose, code); /// /// Rewrites the last top-level return X as return WRAPPER, where the wrapper's /// $0 placeholder receives the original expression. /// public JsEdit WrapReturn(string wrapper) { var body = BodyCursor; int keywordEnd = -1; for (var match = ReturnKeyword.Match(body.Text); match.Success; match = match.NextMatch()) { if (body.OpenerStack(match.Index).Count == 0) { keywordEnd = match.Index + match.Length; } } if (keywordEnd < 0) { throw new Exception($"No top-level return statement in {Name}()"); } int expressionStart = body.SkipWhitespaceForward(keywordEnd); int expressionEnd = FindStatementEnd(body, expressionStart); string expression = body.Text.Substring(expressionStart, expressionEnd - expressionStart); return new JsEdit( BodyOpen + 1 + expressionStart, BodyOpen + 1 + expressionEnd, wrapper.Replace("$0", $"({expression})")); } private static int FindStatementEnd(JsCursor body, int start) { for (int cursor = start; cursor < body.Text.Length; cursor++) { if (body.Text[cursor] == ';' && body.OpenerStack(cursor).Count == 0) { return cursor; } } return body.Text.Length; } } /// A splice: replace [Start, End) of the bundle with . internal sealed class JsEdit { public JsEdit(int start, int end, string text) { Start = start; End = end; Text = text; } /// An insertion at , replacing nothing. public JsEdit(int at, string text) : this(at, at, text) { } public int Start { get; } public int End { get; } public string Text { get; } public string ApplyTo(string source) => source.Substring(0, Start) + Text + source.Substring(End); } }