2424import org .sonar .check .Rule ;
2525import org .sonar .plugins .python .api .PythonSubscriptionCheck ;
2626import org .sonar .plugins .python .api .tree .AnnotatedAssignment ;
27+ import org .sonar .plugins .python .api .tree .AssignmentExpression ;
2728import org .sonar .plugins .python .api .tree .AssignmentStatement ;
2829import org .sonar .plugins .python .api .tree .Expression ;
2930import org .sonar .plugins .python .api .tree .ExpressionList ;
@@ -44,6 +45,7 @@ public class HardcodedIPCheck extends PythonSubscriptionCheck {
4445 private static final String IPV6_ALONE = ("(?<ipv6>(" + IPV6_NO_PREFIX_COMPRESSION + "|" + IPV6_PREFIX_COMPRESSION + ")??(:?" + IPV4_ALONE + ")?" + ")" );
4546 private static final String IPV6_URL = "([^\\ d.]*/)?\\ [" + IPV6_ALONE + "]((:\\ d{1,5})?(?!\\ d|\\ .))(/.*)?" ;
4647
48+ private static final Pattern IPV4_ALONE_REGEX = Pattern .compile (IPV4_ALONE );
4749 private static final Pattern IPV4_URL_REGEX = Pattern .compile ("([^\\ d.]*/)?" + IPV4_ALONE + "((:\\ d{1,5})?(?!\\ d|\\ .))(/.*)?" );
4850 private static final List <Pattern > IPV6_REGEX_LIST = Arrays .asList (
4951 Pattern .compile (IPV6_ALONE ),
@@ -72,10 +74,10 @@ public void initialize(Context context) {
7274 return ;
7375 }
7476 StringLiteral stringLiteral = (StringLiteral ) ctx .syntaxNode ();
75- if (isMultilineString (stringLiteral ) || isVersionLiteral (stringLiteral )) {
77+ String content = Expressions .unescape (stringLiteral );
78+ if (isMultilineString (stringLiteral ) || isVersionLiteral (stringLiteral , content )) {
7679 return ;
7780 }
78- String content = Expressions .unescape (stringLiteral );
7981 Matcher matcher = IPV4_URL_REGEX .matcher (content );
8082 if (matcher .matches ()) {
8183 String ip = matcher .group ("ipv4" );
@@ -97,7 +99,10 @@ public void initialize(Context context) {
9799 });
98100 }
99101
100- private static boolean isVersionLiteral (StringLiteral stringLiteral ) {
102+ private static boolean isVersionLiteral (StringLiteral stringLiteral , String content ) {
103+ if (!IPV4_ALONE_REGEX .matcher (content ).matches ()) {
104+ return false ;
105+ }
101106 Expression assignedValue = stringLiteral ;
102107 while (assignedValue .parent () instanceof ParenthesizedExpression parenthesizedExpression ) {
103108 assignedValue = parenthesizedExpression ;
@@ -106,6 +111,9 @@ private static boolean isVersionLiteral(StringLiteral stringLiteral) {
106111 if (parent instanceof AssignmentStatement assignment ) {
107112 return assignment .assignedValue () == assignedValue && hasVersionName (assignment );
108113 }
114+ if (parent instanceof AssignmentExpression assignment ) {
115+ return assignment .expression () == assignedValue && isVersionName (assignment .lhsName ());
116+ }
109117 return parent instanceof AnnotatedAssignment assignment
110118 && assignment .assignedValue () == assignedValue
111119 && isVersionName (assignment .variable ());
@@ -120,7 +128,32 @@ private static boolean hasVersionName(AssignmentStatement assignment) {
120128 }
121129
122130 private static boolean isVersionName (Expression expression ) {
123- return Expressions .removeParentheses (expression ) instanceof Name name && "__version__" .equals (name .name ());
131+ return Expressions .removeParentheses (expression ) instanceof Name name
132+ && containsVersionWord (name .name ());
133+ }
134+
135+ private static boolean containsVersionWord (String identifier ) {
136+ int wordStart = 0 ;
137+ for (int index = 1 ; index < identifier .length (); index ++) {
138+ if (isIdentifierWordBoundary (identifier , index )) {
139+ if ("version" .equalsIgnoreCase (identifier .substring (wordStart , index ))) {
140+ return true ;
141+ }
142+ wordStart = index ;
143+ }
144+ }
145+ return "version" .equalsIgnoreCase (identifier .substring (wordStart ));
146+ }
147+
148+ private static boolean isIdentifierWordBoundary (String identifier , int index ) {
149+ char previous = identifier .charAt (index - 1 );
150+ char current = identifier .charAt (index );
151+ return previous == '_'
152+ || current == '_'
153+ || Character .isDigit (previous ) != Character .isDigit (current )
154+ || (Character .isLowerCase (previous ) && Character .isUpperCase (current ))
155+ || (Character .isUpperCase (previous ) && Character .isUpperCase (current )
156+ && index + 1 < identifier .length () && Character .isLowerCase (identifier .charAt (index + 1 )));
124157 }
125158
126159 private static boolean isMultilineString (StringLiteral pyStringLiteralTree ) {
0 commit comments