|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the qt3to4 porting application of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 #include "tokenreplacements.h" |
|
43 #include "logger.h" |
|
44 #include "portingrules.h" |
|
45 |
|
46 QT_BEGIN_NAMESPACE |
|
47 using namespace TokenEngine; |
|
48 |
|
49 void addLogSourceEntry(const QString &text, const TokenContainer &tokenContainer, const int index) |
|
50 { |
|
51 Logger *logger = Logger::instance(); |
|
52 int line = tokenContainer.line(index); |
|
53 int col = tokenContainer.column(index); |
|
54 SourcePointLogEntry *logEntry = |
|
55 new SourcePointLogEntry(QLatin1String("Info"), QLatin1String("Porting"), |
|
56 logger->globalState.value(QLatin1String("currentFileName")), |
|
57 line, col, text); |
|
58 logger->addEntry(logEntry); |
|
59 } |
|
60 |
|
61 void addLogWarning(const QString &text) |
|
62 { |
|
63 Logger::instance()->addEntry(new PlainLogEntry(QLatin1String("Warning"), QLatin1String("Porting"), text)); |
|
64 } |
|
65 |
|
66 QualifiedNameParser::QualifiedNameParser(const TokenContainer &tokenContainer, const int tokenIndex) |
|
67 :tokenContainer(tokenContainer) |
|
68 ,currentIndex(tokenIndex) |
|
69 { |
|
70 Q_ASSERT(isValidIndex(currentIndex)); |
|
71 } |
|
72 |
|
73 bool QualifiedNameParser::isPartOfQualifiedName() |
|
74 { |
|
75 return ((nextScopeToken(Left) != -1) || (nextScopeToken(Right) != -1)); |
|
76 } |
|
77 |
|
78 |
|
79 bool QualifiedNameParser::isValidIndex(int index) |
|
80 { |
|
81 return (index < tokenContainer.count() && index >= 0); |
|
82 } |
|
83 |
|
84 /* |
|
85 A qualifier is a the leftmost or middle part of a qualified name |
|
86 */ |
|
87 bool QualifiedNameParser::isQualifier() |
|
88 { |
|
89 return (nextScopeToken(Right) != -1); |
|
90 } |
|
91 |
|
92 /* |
|
93 A name is a the rightmost part of a qualified name. |
|
94 */ |
|
95 bool QualifiedNameParser::isName() |
|
96 { |
|
97 return (nextScopeToken(Left) != -1); |
|
98 } |
|
99 |
|
100 /* |
|
101 Peek for a qualifier or name in the given direction |
|
102 */ |
|
103 int QualifiedNameParser::peek(Direction direction) |
|
104 { |
|
105 return nextScopeToken(direction); |
|
106 } |
|
107 |
|
108 /* |
|
109 Look for a qualifier or name in the given direction,update |
|
110 current position if found. |
|
111 */ |
|
112 int QualifiedNameParser::move(Direction direction) |
|
113 { |
|
114 int tokenIndex = nextScopeToken(direction); |
|
115 if(tokenIndex != -1) |
|
116 currentIndex = tokenIndex; |
|
117 return tokenIndex; |
|
118 } |
|
119 |
|
120 /* |
|
121 Looks for "::" starting at currentIndex, returns the token index |
|
122 for it if found. If the first non-whitespace token found is something else, |
|
123 -1 is returned. |
|
124 */ |
|
125 int QualifiedNameParser::findScopeOperator(Direction direction) |
|
126 { |
|
127 int tokenIndex = currentIndex; |
|
128 QByteArray tokenText; |
|
129 //loop until we get a token containg text or we pass the beginning/end of the source |
|
130 tokenIndex += direction; |
|
131 while(tokenText.isEmpty() && isValidIndex(tokenIndex)) { |
|
132 tokenText = tokenContainer.text(tokenIndex).trimmed(); |
|
133 if(tokenText==QByteArray("::")) |
|
134 return tokenIndex; |
|
135 tokenIndex += direction; |
|
136 } |
|
137 return -1; |
|
138 } |
|
139 /* |
|
140 Walks a qualified name. Returns the token index |
|
141 for the next identifer in the qualified name, or -1 if its not found. |
|
142 */ |
|
143 int QualifiedNameParser::nextScopeToken(Direction direction) |
|
144 { |
|
145 int tokenIndex = findScopeOperator(direction); |
|
146 if (tokenIndex == -1) |
|
147 return -1; |
|
148 QByteArray tokenText; |
|
149 //loop until we get a token containg text or we pass the start of the source |
|
150 tokenIndex += direction; |
|
151 while(tokenText.isEmpty() && isValidIndex(tokenIndex)) { |
|
152 tokenText = tokenContainer.text(tokenIndex).trimmed(); |
|
153 tokenIndex += direction; |
|
154 } |
|
155 return tokenIndex - direction; |
|
156 } |
|
157 |
|
158 ///////////////////// |
|
159 GenericTokenReplacement::GenericTokenReplacement(QByteArray oldToken, QByteArray newToken) |
|
160 :oldToken(oldToken) |
|
161 ,newToken(newToken) |
|
162 {} |
|
163 |
|
164 QByteArray GenericTokenReplacement::getReplaceKey() |
|
165 { |
|
166 return QByteArray(oldToken); |
|
167 } |
|
168 |
|
169 bool GenericTokenReplacement::doReplace(const TokenContainer &tokenContainer, |
|
170 int index, TextReplacements &textReplacements) |
|
171 { |
|
172 QByteArray tokenText = tokenContainer.text(index); |
|
173 if(tokenText == oldToken){ |
|
174 addLogSourceEntry(QString::fromLatin1(tokenText + QByteArray(" -> ") + newToken), tokenContainer, index); |
|
175 TokenEngine::Token token = tokenContainer.token(index); |
|
176 textReplacements.insert(newToken, token.start, token.length); |
|
177 return true; |
|
178 } |
|
179 return false; |
|
180 |
|
181 } |
|
182 |
|
183 /////////////////// |
|
184 ClassNameReplacement::ClassNameReplacement(QByteArray oldToken, QByteArray newToken) |
|
185 :oldToken(oldToken) |
|
186 ,newToken(newToken) |
|
187 {} |
|
188 |
|
189 QByteArray ClassNameReplacement::getReplaceKey() |
|
190 { |
|
191 return QByteArray(oldToken); |
|
192 } |
|
193 |
|
194 /* |
|
195 Replace a class name token. If the class name is a scope specifier (a "qualifier") |
|
196 in a qualified name, we check if qualified name will be replaced by a porting rule. |
|
197 If so, we don't do the class name replacement. |
|
198 */ |
|
199 bool ClassNameReplacement::doReplace(const TokenContainer &tokenContainer, int index, TextReplacements &textReplacements) |
|
200 { |
|
201 QByteArray tokenText = tokenContainer.text(index); |
|
202 if(tokenText != oldToken) |
|
203 return false; |
|
204 |
|
205 QualifiedNameParser nameParser(tokenContainer, index); |
|
206 if(nameParser.isPartOfQualifiedName() && |
|
207 nameParser.peek(QualifiedNameParser::Right) != -1) { |
|
208 int nameTokenIndex = nameParser.peek(QualifiedNameParser::Right); |
|
209 QByteArray name = tokenContainer.text(nameTokenIndex); |
|
210 TextReplacements textReplacements; |
|
211 QList<TokenReplacement*> tokenReplacements |
|
212 = PortingRules::instance()->getTokenReplacementRules(); |
|
213 bool changed = false; |
|
214 foreach(TokenReplacement *tokenReplacement, tokenReplacements) { |
|
215 changed = tokenReplacement->doReplace(tokenContainer, nameTokenIndex, textReplacements); |
|
216 if(changed) |
|
217 break; |
|
218 } |
|
219 if(changed) |
|
220 return false; |
|
221 } |
|
222 addLogSourceEntry(QString::fromLatin1(tokenText + QByteArray(" -> ") + newToken), tokenContainer, index); |
|
223 TokenEngine::Token token = tokenContainer.token(index); |
|
224 textReplacements.insert(newToken, token.start, token.length); |
|
225 return true; |
|
226 } |
|
227 |
|
228 /////////////////// |
|
229 |
|
230 ScopedTokenReplacement::ScopedTokenReplacement(const QByteArray &oldToken, |
|
231 const QByteArray &newToken) |
|
232 :newScopedName(newToken) |
|
233 { |
|
234 Q_ASSERT(oldToken.contains(QByteArray("::"))); |
|
235 |
|
236 // Split oldToken into scope and name parts. |
|
237 oldName = oldToken.mid(oldToken.lastIndexOf(':')+1); |
|
238 oldScope = oldToken.mid(0, oldToken.indexOf(':')); |
|
239 |
|
240 // Split newToken into scope and name parts, execept if we have a spcial |
|
241 // case like Qt::WType_Modal -> (Qt::WType_Dialog | Qt::WShowModal) |
|
242 if (newToken.count(QByteArray("::")) != 1 || newToken.contains(QByteArray("("))) { |
|
243 newName = newToken; |
|
244 } else { |
|
245 newName = newToken.mid(newToken.lastIndexOf(':')+1); |
|
246 newScope = newToken.mid(0, newToken.indexOf(':')); |
|
247 } |
|
248 |
|
249 strictMode = Logger::instance()->globalState.contains(QString::fromLatin1("strictMode")); |
|
250 } |
|
251 |
|
252 bool ScopedTokenReplacement::doReplace(const TokenContainer &tokenContainer, int sourceIndex, TextReplacements &textReplacements) |
|
253 { |
|
254 const QByteArray sourceName = tokenContainer.text(sourceIndex); |
|
255 |
|
256 // Check if the token texts matches. |
|
257 if (sourceName != oldName) |
|
258 return false; |
|
259 |
|
260 // Get token attributes. The attributes are created by the the C++ parser/analyzer. |
|
261 const TokenAttributes *attributes = tokenContainer.tokenAttributes(); |
|
262 // If the declaration attribute is set we don't replace. |
|
263 if (!attributes->attribute(sourceIndex, "declaration").isEmpty()) |
|
264 return false; |
|
265 // If the unknown (undeclared) attribute is set we don't replace. |
|
266 if (!attributes->attribute(sourceIndex, "unknown").isEmpty()) |
|
267 return false; |
|
268 // If nameUse is set we test if the nameUse refers to the correct declaration. |
|
269 // This is done by checking the parentScope attriute, wich returns the scope |
|
270 // for the declaration associated with this name use. |
|
271 const bool haveNameUseInfo = !attributes->attribute(sourceIndex, "nameUse").isEmpty(); |
|
272 if (haveNameUseInfo) { |
|
273 if (attributes->attribute(sourceIndex, "parentScope") != oldScope) |
|
274 return false; |
|
275 // If the user has specified -strict, we don't replace tokens when we don't have name use info. |
|
276 } else if (strictMode) { |
|
277 return false; |
|
278 } |
|
279 |
|
280 // The token might have a qualifier, and in that case we need to check if |
|
281 // we should replace the qualifier as well. |
|
282 QualifiedNameParser nameParser(tokenContainer, sourceIndex); |
|
283 |
|
284 // This is a pretty special case, it means that in a qualified |
|
285 // name like aaa::bbb the replacement rule has been triggered for |
|
286 // the aaa part. Since this is not what we'd normally use a |
|
287 // ScopedReplacement for, we just return here. |
|
288 if (nameParser.isQualifier()) |
|
289 return false; |
|
290 |
|
291 // If the token is unqualified, just replace it. |
|
292 if (!nameParser.isPartOfQualifiedName()) { |
|
293 // If we have no name use info we try to avoid replacements of |
|
294 // e.g. Vertical with QSizePolicy::Vertically. Unqualified tokens |
|
295 // can't happen for classes one does not usually inherit from, so |
|
296 // we only let them pass for stuff that people usually inherited from. |
|
297 if (!haveNameUseInfo && newScope != "Qt" && newScope != "QFrame" && newScope != "QValidator") |
|
298 return false; |
|
299 |
|
300 const Token sourceToken = tokenContainer.token(sourceIndex); |
|
301 addLogSourceEntry(QString::fromLatin1(sourceName + QByteArray(" -> ") + newScopedName), tokenContainer, sourceIndex); |
|
302 textReplacements.insert(newScopedName, sourceToken.start, sourceName.size()); |
|
303 return true; |
|
304 } |
|
305 |
|
306 // Peek left for the qualifer token. |
|
307 const int sourceScopeIndex = nameParser.peek(QualifiedNameParser::Left); |
|
308 if (sourceScopeIndex == -1) { |
|
309 return false; |
|
310 } |
|
311 |
|
312 const Token sourceNameToken = tokenContainer.token(sourceIndex); |
|
313 const Token sourceScopeToken = tokenContainer.token(sourceScopeIndex); |
|
314 const QByteArray sourceScope = tokenContainer.text(sourceScopeIndex); |
|
315 |
|
316 // If we have no name use info and the source and old scopes don't match, |
|
317 // we generally dont't do a replace, unless the old scope is Qt and |
|
318 // the source scope inherits Qt. For example, QWidget::ButtonState should |
|
319 // be renamed to Qt::ButtonState. |
|
320 if (!haveNameUseInfo && sourceScope != oldScope) { |
|
321 if (oldScope != "Qt") |
|
322 return false; |
|
323 // Check if sourceScope inherits the Qt class. |
|
324 if (!PortingRules::instance()->getInheritsQt().contains(QString::fromLatin1(sourceScope.constData()))) //TODO optimize: linear search |
|
325 return false; |
|
326 } |
|
327 |
|
328 // Spcecial cases, such as QIODevice::Offset -> Q_LONGLONG |
|
329 // or Qt::WType_Modal -> (Qt::WType_Dialog | Qt::WShowModal). |
|
330 if (newScope.isEmpty()) { |
|
331 addLogSourceEntry(QString::fromLatin1((sourceScope + QByteArray("::") + sourceName + |
|
332 QByteArray(" -> ") + newScopedName).constData()), tokenContainer, sourceIndex); |
|
333 const int qualiferLength = sourceNameToken.start - sourceScopeToken.start; |
|
334 const int length = qualiferLength + sourceNameToken.length; |
|
335 textReplacements.insert(newName, sourceScopeToken.start, length); |
|
336 return true; |
|
337 } |
|
338 |
|
339 // If the old and new scopes are equal, we replace the name part only. |
|
340 if (newScope == sourceScope) { |
|
341 // If the names are equal, there is no need to do anything. |
|
342 if (newName == sourceName) |
|
343 return true; |
|
344 addLogSourceEntry(QString::fromLatin1((sourceName + QByteArray(" -> ") + newName).constData()), tokenContainer, sourceIndex); |
|
345 textReplacements.insert(newName, sourceNameToken.start, sourceNameToken.length); |
|
346 return true; |
|
347 } |
|
348 |
|
349 // If the names are equal, replace scope only. |
|
350 if (newName == sourceName) { |
|
351 addLogSourceEntry(QString::fromLatin1((sourceScope + QByteArray(" -> ") + newScope).constData()), tokenContainer, sourceScopeIndex); |
|
352 textReplacements.insert(newScope, sourceScopeToken.start, sourceScopeToken.length); |
|
353 return true; |
|
354 } |
|
355 |
|
356 // Replace scope and name. |
|
357 addLogSourceEntry(QString::fromLatin1((sourceScope + QByteArray("::") + sourceName + |
|
358 QByteArray(" -> ") + newScopedName).constData()), |
|
359 tokenContainer, sourceScopeIndex); |
|
360 textReplacements.insert(newScope, sourceScopeToken.start, sourceScopeToken.length); |
|
361 textReplacements.insert(newName, sourceNameToken.start, sourceNameToken.length); |
|
362 return true; |
|
363 } |
|
364 |
|
365 QByteArray ScopedTokenReplacement::getReplaceKey() |
|
366 { |
|
367 return oldName; |
|
368 } |
|
369 |
|
370 |
|
371 QT_END_NAMESPACE |